Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for script dependencies #14

Merged
merged 3 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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