Skip to content

Commit

Permalink
Merge branch 'main' of github.com:shikaan/shmux
Browse files Browse the repository at this point in the history
  • Loading branch information
shikaan committed Dec 22, 2023
2 parents e8b4172 + 6f95f34 commit a4e59a8
Show file tree
Hide file tree
Showing 15 changed files with 516 additions and 295 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
- uses: actions/setup-go@v4
with:
go-version: '1.21'
- run: go test ./...
15 changes: 15 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${fileDirname}/../main.go"
}
]
}
68 changes: 49 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ Head to the [releases](https://github.com/shikaan/shmux/releases) page and downl

### Usage

A common use case for `shmux` is running simple scripts in a standardized and language-agnostic way. These scripts are to be found in the _configuration_ file, also known as _shmuxfile_.
A common use case for `shmux` is running simple scripts in a standardized and *language-agnostic* way (see [Other Languages](#other-languages)).
These scripts are to be found in the _configuration_ file, also known as _shmuxfile_.

For example, a `shmuxfile.sh` for a Go project might look like:

Expand All @@ -47,6 +48,26 @@ build:

greet:
echo "Hello $1, my old friend"

echo:
echo "$@"
```

Last two recipes in JavaScript that could looke like:

```js
// shmuxfile.js

greet:
#!/usr/bin/env node

const friend = "$1"
console.log(`Hello ${friend}, my old friend`)

echo:
#!/usr/bin/env node

console.log(`$@`)
```

Which can then be utilized as
Expand All @@ -63,39 +84,48 @@ $ shmux greet -- "darkness"
# => Hello darkness, my old friend
```

### More Usage

What if we wanted to write the scripts in JavaScript? Well, you then just need a `shmuxfile.js` with a [shebang](https://en.wikipedia.org/wiki/Shebang_(Unix)) defining the interpreter to be used and you're set.
Recipes can have dependencies:

```js
greet:
#!/usr/bin/env node
```sh
test:
go test ./...

const friend = "$1"
const author = "$@"
const message = friend === "darkness"
? "Hello darkness, my old friend"
: `Hello ${friend}, from ${author}`

console.log(message)
build: test
go build
```

and run it like

```bash
$ shmux greet -- "Manuel"
# => Hello Manuel, from greet
$ shmux build
```
Running `shmux build` will execute `test` before `build`.

## 📄 Documentation

More detailed documentation can be found [here](./docs/docs.md).

## ❓ FAQs

* _Isn't this similar to a Makefile?_

`shmux` draws inspiration from `make` but stands out as a script runner, not a build system. This distinction eliminates common build system constraintsl ike the presumption that outputs are files. Moreover, it offers:

* Command line arguments support.
* Compatibility with various scripting languages.
* Pre-runtime issue detection.
* Execution capability from any subdirectory.
* Native support on MacOS and Windows, no extra dependencies required.

* _Which languages are supported?_

`shmux` makes no assumptions about the underlying scripting language to utilize, because it always requires you to specify the shell. Any language whose syntax is compatible with shmuxfiles' requirements is supported.
`shmux` makes no assumptions about the underlying scripting language to utilize, because it always requires you to specify the shell (either via flag or shebang).

To this day, `shmux` is known to be working with:

* sh and derviatives (bash, dash, fish, zsh...)
* JavaScript / TypeScript (with ts-node)
* Perl
* Python
* Ruby

* _Does it have editor support?_

Expand Down
8 changes: 8 additions & 0 deletions docs/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
The scripts run by `shmux` live in files called _shmuxfiles_. These configuration files follow the following pattern:

* lines starting with a non-white space and ending with a `:` will be interpreted as a _script definition_
* if the colon is followed by space-separated words, they are treated as _script dependencies_ (i.e., scripts running before the invoked one)
* non-empty lines prepended with whitespaces are considered _script lines_
* the other lines are ignored

Expand All @@ -20,6 +21,13 @@ In a nutshell, `shmux` is not opinionated about which languages the script are w

For example, a shmuxfile with bash scripts can be called `shmuxfile.bash`. If it was with JavaScript scripts, it can be called `shmuxfile.js`. This will yield pretty decent syntax highlighting.

At this point, `shmux` is known to be working with:
* sh and derviatives (bash, dash, fish, zsh...)
* JavaScript / TypeScript (with ts-node)
* Perl
* Python
* Ruby

If you need more sophisticated tooling, please [open an Issue](https://github.com/shikaan/shmux/issues).

[^1]: Namely, permits intendations and presence of the `script:` labels.
Expand Down
19 changes: 13 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,36 @@ package main

import (
"fmt"
"io"
"os"

"github.com/shikaan/shmux/pkg/arguments"
"github.com/shikaan/shmux/pkg/exceptions"
"github.com/shikaan/shmux/pkg/scripts"
)

const MAX_FILE_SIZE = 1<<20; // 1MB

func main() {
shell, config, scriptName, args, err := arguments.Parse()
exceptions.HandleException(err)
exceptions.HandleException(err, 1)

file, err := os.Open(config)
exceptions.HandleException(err)
exceptions.HandleException(err, 1)
defer file.Close()

if scriptName == arguments.HELP_SCRIPT {
fmt.Print(scripts.MakeHelp(file))
return
}

script, err := scripts.ReadScript(scriptName, shell, file)
exceptions.HandleException(err)
limitedReader := io.LimitReader(file, MAX_FILE_SIZE)
content, err := io.ReadAll(limitedReader)
exceptions.HandleException(err, 1)

script, err := scripts.ReadScript(scriptName, shell, content, 0)
exceptions.HandleException(err, 1)

err = scripts.RunScript(script, args)
exceptions.HandleScriptError(scriptName, err)
status, err := scripts.RunScript(script, args)
exceptions.HandleException(err, status)
}
15 changes: 1 addition & 14 deletions pkg/exceptions/exceptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,11 @@ package exceptions
import (
"fmt"
"os"
"os/exec"
)

func HandleException(e error) {
func HandleException(e error, status int) {
if e != nil {
os.Stderr.WriteString(fmt.Sprintf("shmux: %s\n", e.Error()))
os.Exit(1)
}
}

func HandleScriptError(scriptName string, e error) {
if e != nil {
status := 1
if exitError, ok := e.(*exec.ExitError); ok {
status = exitError.ExitCode()
}

os.Stderr.WriteString(fmt.Sprintf("shmux: script \"%s\" exited with code %d\n", scriptName, status))
os.Exit(status)
}
}
28 changes: 28 additions & 0 deletions pkg/scripts/help.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package scripts

import (
"bufio"
"fmt"
"io"
"strings"
)

func MakeHelp(file io.Reader) string {
availableScripts := []string{}
scanner := bufio.NewScanner(file)

for scanner.Scan() {
line := scanner.Text()
isScriptLine, match, _ := readScript(line)

if isScriptLine {
availableScripts = append(availableScripts, match)
}
}

return fmt.Sprintf(`usage: shmux [-config <path>] [-shell <path>] <script> -- [arguments ...]
Available scripts: %s
Run 'shmux -h' for details.
`, strings.Join(availableScripts, ", "))
}
27 changes: 27 additions & 0 deletions pkg/scripts/help_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package scripts

import (
"io"
"strings"
"testing"
)

func TestMakeHelp(t *testing.T) {
type args struct {
file io.Reader
}
tests := []struct {
name string
args args
includes string
}{
{"returns the correct text", args{strings.NewReader("script:\n\tscript1\n\nanother:\n\tscript2")}, "script1, script2"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := MakeHelp(tt.args.file); strings.Contains(got, tt.includes) {
t.Errorf("MakeHelp() = %v, does not include %v", got, tt.includes)
}
})
}
}
Loading

0 comments on commit a4e59a8

Please sign in to comment.