diff --git a/.gitignore b/.gitignore index 23c35a4..650003f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ docs/_build # Ignore the ahoy binary when it's built ahoy +.idea diff --git a/README.md b/README.md index 230f534..2bcbde4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Test Status: master [![CircleCI](https://circleci.com/gh/ahoy-cli/ahoy/tree/master.svg?style=svg)](https://circleci.com/gh/ahoy-cli/ahoy/tree/master) -Ahoy is command line tool that gives each of your projects their own CLI app with with zero code and dependencies. +Ahoy is command line tool that gives each of your projects their own CLI app with with zero code and dependencies. Simply write your commands in a yaml file and ahoy gives you lots of features like: * a command listing @@ -29,7 +29,6 @@ With ahoy, you can turn this into - Consitent - Commands always run relative to the .ahoy.yml file, but can be called from any subfolder. - Visual - See a list of all of your commands in one place, along with helpful descriptions. - Flexible - Commands are specific to a single folder tree, so each repo/workspace can have its own commands -- Command Templates - Args can be dropped into your commands using `{{args}}` - Fully interactive - your shells (like mysql) and prompts still work. - Self-Documenting - Commands and help declared in .ahoy.yml show up as ahoy command help and bash completion of commands (see below) @@ -122,24 +121,41 @@ GLOBAL OPTIONS: ## Version 2 -All new features are being added to the v2 (master) branch of ahoy which is still in alpha and will have breaking changes with v1 ahoy files, so to use ahoy v2, you'll need to do the following: +All new features are being added to the v2 (master) branch of ahoy which is still in alpha and will have breaking changes with v1 ahoy files, so to use ahoy v2, you'll need to do the following: - Upgrade to the ahoy v2 binary which currently needs to be compiled from source. If you are using homebrew, you can use that to upgrade to v2 using the following: ``` - brew uninstall ahoy # Required or you'll get errors + brew uninstall ahoy # Required or you'll get errors brew upgrade # Updates the tap brew install ahoy --HEAD # Installs ahoy by compiling the latest from the master branch ahoy # You should see full version that you're using. ``` - Change your `ahoyapi: v1` lines to `ahoyapi: v2` +- Change your `{{args}}` items to the default bash symbol `"$@"` ### New Features in v2 - Implements a new feature to import mulitple config files using the "imports" field. - Uses the "last in wins" rule to deal with duplicate commands amongst the config files. - -``` +- Better handling of quotes by no longer using `{{args}}`. Use regular bash syntax like `"$@"` for all arguments, or `$1` for the first argument. +- You can now use a different entrypoint (the thing that runs your commands) instead of bash. Ex. using php, nodejs, python, etc. +- Plugins are now possible by overriding the entrypoint. + +###Example of new yaml setup in v2 + +```Yaml +# All files must have v2 set or you'll get an error +ahoyapi: v2 + +# You can now override the entrypoint. This is the default if you don't override it. +# {{cmd}} is replaced with your command and {{name}} is the name of the command that was run (available as $0) +entrypoint: + - bash + - "-c" + - '{{cmd}}' + - '{{name}}' commands: list: usage: List the commands from the imported config files. + # These commands will be aggregated together with later files overriding earlier ones if they exist. imports: - ./confirmation.ahoy.yml - ./docker.ahoy.yml @@ -147,11 +163,10 @@ commands: ``` ### Planned v2 features -- Provide "drivers" or "plugins" for bash, docker-compose, kubernetes (these systems still work now, this would just make it easier) -- Do specific arg replacement like {{arg1}} and enable specifying specific arguments and flags in the ahoy file itself to cut down on parsing arguments in scripts. +- Enable specifying specific arguments and flags in the ahoy file itself to cut down on parsing arguments in scripts. - Support for more built-in commands or a "verify" yaml option that would create a yes / no prompt for potentially destructive commands. (Are you sure you want to delete all your containers?) - Pipe tab completion to another command (allows you to get tab completion) - +- Support for configuration ## Previewing the Read the Docs documentation locally. diff --git a/ahoy.go b/ahoy.go index fe64aed..debe95a 100644 --- a/ahoy.go +++ b/ahoy.go @@ -19,9 +19,10 @@ import ( // Config handles the overall configuration in an ahoy.yml file // with one Config per file. type Config struct { - Usage string - AhoyAPI string - Commands map[string]Command + Usage string + AhoyAPI string + Commands map[string]Command + Entrypoint []string } // Command is an ahoy command detailed in ahoy.yml files. Multiple @@ -116,6 +117,10 @@ func getConfig(file string) (Config, error) { return config, err } + if config.Entrypoint == nil { + config.Entrypoint = []string{"bash", "-c", "{{cmd}}", "{{name}}"} + } + return config, err } @@ -166,7 +171,6 @@ func getCommands(config Config) []cli.Command { for _, name := range keys { cmd := config.Commands[name] - cmdName := name newCmd := cli.Command{ Name: name, @@ -180,8 +184,42 @@ func getCommands(config Config) []cli.Command { if cmd.Cmd != "" { newCmd.Action = func(c *cli.Context) { - args = c.Args() - runCommand(cmdName, cmd.Cmd) + // For some unclear reason, if we don't add an item at the end here, + // the first argument is skipped... actually it's not! + // 'bash -c' says that arguments will be passed starting with $0, which also means that + // $@ skips the first item. See http://stackoverflow.com/questions/41043163/xargs-sh-c-skipping-the-first-argument + var cmdItems []string + var cmdArgs []string + var cmdEntrypoint []string + + // c.Args() is not a slice apparently. + for _, arg := range c.Args() { + cmdArgs = append(cmdArgs, arg) + } + + // Replace the entry point placeholders. + cmdEntrypoint = config.Entrypoint[:] + for i := range cmdEntrypoint { + if cmdEntrypoint[i] == "{{cmd}}" { + cmdEntrypoint[i] = cmd.Cmd + } else if cmdEntrypoint[i] == "{{name}}" { + cmdEntrypoint[i] = c.Command.Name + } + } + cmdItems = append(cmdEntrypoint, cmdArgs...) + + if verbose { + log.Println("===> AHOY", name, "from", sourcefile, ":", cmdItems) + } + command := exec.Command(cmdItems[0], cmdItems[1:]...) + command.Dir = AhoyConf.srcDir + command.Stdout = os.Stdout + command.Stdin = os.Stdin + command.Stderr = os.Stderr + if err := command.Run(); err != nil { + fmt.Fprintln(os.Stderr) + os.Exit(1) + } } } @@ -197,24 +235,6 @@ func getCommands(config Config) []cli.Command { return exportCmds } -func runCommand(name string, c string) { - - cReplace := strings.Replace(c, "{{args}}", strings.Join(args, " "), -1) - - if verbose { - log.Println("===> AHOY", name, "from", sourcefile, ":", cReplace) - } - cmd := exec.Command("bash", "-c", cReplace) - cmd.Dir = AhoyConf.srcDir - cmd.Stdout = os.Stdout - cmd.Stdin = os.Stdin - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - fmt.Fprintln(os.Stderr) - os.Exit(1) - } -} - func addDefaultCommands(commands []cli.Command) []cli.Command { defaultInitCmd := cli.Command{ diff --git a/examples/confirmation.ahoy.yml b/examples/confirmation.ahoy.yml index 758aa43..5bd6728 100644 --- a/examples/confirmation.ahoy.yml +++ b/examples/confirmation.ahoy.yml @@ -2,7 +2,7 @@ ahoyapi: v2 commands: confirm: cmd: | - read -r -p "{{args}} [y/N] " response + read -r -p "$@ [y/N] " response if [ $response = y ] then true diff --git a/examples/docker.ahoy.yml b/examples/docker.ahoy.yml index d408f95..8c48204 100644 --- a/examples/docker.ahoy.yml +++ b/examples/docker.ahoy.yml @@ -19,7 +19,7 @@ commands: cmd: "docker-compose stop && docker-compose rm && ahoy up" usage: Start the docker compose-containers. exec: - cmd: docker exec -it $(docker-compose ps -q cli) bash -c '{{args}}' + cmd: docker exec -it $(docker-compose ps -q cli) bash -c "$@" usage: run a command in the docker-compose cli service container. mysql: cmd: "docker exec -it $(docker-compose ps -q cli) bash -c 'mysql -u$DB_ENV_MYSQL_USER -p$DB_ENV_MYSQL_PASSWORD -h$DB_PORT_3306_TCP_ADDR $DB_ENV_MYSQL_DATABASE'" diff --git a/examples/examples.ahoy.yml b/examples/examples.ahoy.yml index 01d78b1..0efabdf 100644 --- a/examples/examples.ahoy.yml +++ b/examples/examples.ahoy.yml @@ -9,27 +9,27 @@ commands: usage: Stop the vagrant box if one exists. ps: - cmd: "docker-compose ps {{args}}" + cmd: docker-compose ps "$@" usage: List the running docker-compose containers. start: - cmd: "docker-compose start {{args}}" + cmd: docker-compose start "$@" usage: Start the docker compose-containers. stop: - cmd: "docker-compose stop {{args}}" + cmd: docker-compose stop "$@" usage: Stop the docker-compose containers. restart: - cmd: "docker-compose restart {{args}}" + cmd: docker-compose restart "$@" usage: Restart the docker-compose containers. drush: - cmd: "docker-compose run cli drush --root=docroot {{args}}" + cmd: docker-compose run cli drush --root=docroot "$@" usage: Run drush commands in the cli service container. bash: - cmd: "docker-compose run {{args}} bash" + cmd: docker-compose run "$1" bash usage: Start a shell in the container (like ssh without actual ssh). sqlc: @@ -37,7 +37,7 @@ commands: usage: Connect to the default mysql database. Supports piping of data into the command. behat: - cmd: 'docker-compose run cli bash -c "cd docroot/test && composer install --prefer-source --no-interaction && bin/behat -p docker {{args}}"' + cmd: docker-compose run cli bash -c "cd docroot/test && composer install --prefer-source --no-interaction && bin/behat -p docker $@" usage: Run the behat tests within the container. behat-init: diff --git a/testdata/config.sh b/testdata/config.sh new file mode 100644 index 0000000..f04df33 --- /dev/null +++ b/testdata/config.sh @@ -0,0 +1 @@ +export TEST1=test1 diff --git a/testdata/docker.ahoy.yml b/testdata/docker.ahoy.yml index d408f95..8c48204 100644 --- a/testdata/docker.ahoy.yml +++ b/testdata/docker.ahoy.yml @@ -19,7 +19,7 @@ commands: cmd: "docker-compose stop && docker-compose rm && ahoy up" usage: Start the docker compose-containers. exec: - cmd: docker exec -it $(docker-compose ps -q cli) bash -c '{{args}}' + cmd: docker exec -it $(docker-compose ps -q cli) bash -c "$@" usage: run a command in the docker-compose cli service container. mysql: cmd: "docker exec -it $(docker-compose ps -q cli) bash -c 'mysql -u$DB_ENV_MYSQL_USER -p$DB_ENV_MYSQL_PASSWORD -h$DB_PORT_3306_TCP_ADDR $DB_ENV_MYSQL_DATABASE'" diff --git a/testdata/entrypoint-bash.ahoy.yml b/testdata/entrypoint-bash.ahoy.yml new file mode 100644 index 0000000..d93c3d4 --- /dev/null +++ b/testdata/entrypoint-bash.ahoy.yml @@ -0,0 +1,12 @@ +ahoyapi: v2 +entrypoint: + - bash + - "-x" + - "-c" + - '{{cmd}}' + - '{{name}}' + +commands: + echo: + cmd: | + echo "$@" diff --git a/testdata/entrypoint-php.ahoy.yml b/testdata/entrypoint-php.ahoy.yml new file mode 100644 index 0000000..185815e --- /dev/null +++ b/testdata/entrypoint-php.ahoy.yml @@ -0,0 +1,7 @@ +ahoyapi: v2 +entrypoint: [php, "-r", '{{cmd}}'] +commands: + echo: + cmd: | + array_shift($argv); + print(implode(" ", $argv) . "\n"); diff --git a/testdata/entrypoint-prefix.ahoy.yml b/testdata/entrypoint-prefix.ahoy.yml new file mode 100644 index 0000000..853028f --- /dev/null +++ b/testdata/entrypoint-prefix.ahoy.yml @@ -0,0 +1,15 @@ +ahoyapi: v2 +entrypoint: + - "bash" + - "-c" + - | + source ./config.sh + bash -c "$0" "$@" + - '{{cmd}}' + - '{{name}}' + +commands: + echo-test: + cmd: | + ls "$@" + echo $TEST1 diff --git a/testdata/simple.ahoy.yml b/testdata/simple.ahoy.yml index 2b28927..26c038e 100644 --- a/testdata/simple.ahoy.yml +++ b/testdata/simple.ahoy.yml @@ -1,4 +1,4 @@ ahoyapi: v2 commands: echo: - cmd: echo {{args}} + cmd: echo "$@" diff --git a/tests/entrypoint.bats b/tests/entrypoint.bats new file mode 100644 index 0000000..e48f667 --- /dev/null +++ b/tests/entrypoint.bats @@ -0,0 +1,19 @@ +#!/usr/bin/env bats + +@test "override bash entrypoint to add additional flags" { + run ./ahoy -f testdata/entrypoint-bash.ahoy.yml echo something + [ $status -eq 0 ] + echo "$output" + [ "${#lines[@]}" -eq 2 ] + [ "${lines[0]}" == "+ echo something" ] + [ "${lines[1]}" == "something" ] + +} + +@test "override bash entrypoint to use php instead" { + run ./ahoy -f testdata/entrypoint-php.ahoy.yml echo something + [ $status -eq 0 ] + echo "$output" + [ "${#lines[@]}" -eq 1 ] + [ "${lines[0]}" == "something" ] +}