From 6e3b26c0fd810fb349be173bf542327561bb7404 Mon Sep 17 00:00:00 2001 From: Aaron McHale Date: Sun, 19 Apr 2026 16:02:57 +0100 Subject: [PATCH 1/4] Add CLIROOT and CMDROOT --- README.md | 1 + cli.sh | 26 +++++++++++++++++--------- commands/hello-world.sh | 5 +++++ docs/CLI.md | 3 +++ 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 229ef15..ba86a55 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ So this could be useful for any project which wants to ship a single entrypoint ## Docs [Read the CLI docs for more information on how commands works.](docs/CLI.md) + [Read the Tests docs for more information on how to write tests.](docs/Tests.md) ## Using this template in your project diff --git a/cli.sh b/cli.sh index 26cbe15..3d90a6c 100755 --- a/cli.sh +++ b/cli.sh @@ -3,9 +3,16 @@ set -u # Simple CLI wrapper that discovers scripts in the commands/ directory -# Cd to the directory of this script, so it can be run from anywhere +# Get the directory of this script, so it can be run from anywhere realpath="$(realpath "$0")" || { echo "Failed to get realpath"; exit 1; } -cd "${realpath%/*}" || { echo "Failed to cd to script directory"; exit 1; } + +# `CLIROOT` is the directory of the cli script. +# Commands are run from the directory the user is in, +# use `$CLIROOT` to cd to the directory of the cli script. +CLIROOT="${realpath%/*}" + +# `CMDROOT` absolute path to commands directory. +CMDROOT="$CLIROOT/commands" #################### # Helper functions # @@ -58,8 +65,8 @@ BLUE="\e[34m" # Build commands list from commands/*.sh (lowercased, without .sh) commands=() -if [ -d "commands" ]; then - for f in commands/*.sh; do +if [ -d "$CMDROOT" ]; then + for f in "$CMDROOT"/*.sh; do [ -f "$f" ] || continue name=$(basename "$f" .sh) name_lc=${name,,} @@ -90,7 +97,7 @@ if [ "$CMD" = "list" ]; then for c in "${commands[@]}"; do desc="" - helpfile="commands/${c}.help.txt" + helpfile="$CMDROOT/${c}.help.txt" if [ -f "$helpfile" ]; then IFS= read -r desc < "$helpfile" || true fi @@ -100,14 +107,15 @@ if [ "$CMD" = "list" ]; then fi if [ "$CMD" = "help" ] || [ "$CMD" = "--help" ]; then - if [ -z "$1" ]; then + if [ "$#" -eq 0 ]; then echo "Display help for a command." echo "Usage: help [command]" exit 0 fi target=${1,,} - if command_exists "$target" && [ -f "commands/${target}.help.txt" ]; then - cat "commands/${target}.help.txt" + helpfile="$CMDROOT/${target}.help.txt" + if command_exists "$target" && [ -f "$helpfile" ]; then + cat "$helpfile" exit 0 fi echo "No help available for $target" @@ -122,4 +130,4 @@ fi # Source the script matching CMD # shellcheck source=/dev/null -. "commands/${CMD}.sh" +source -p "$CMDROOT" "$CMD.sh" diff --git a/commands/hello-world.sh b/commands/hello-world.sh index 26e55d9..beca668 100644 --- a/commands/hello-world.sh +++ b/commands/hello-world.sh @@ -3,6 +3,11 @@ echo "Hello world!" # Examples of using the helper functions to print messages. +# You can cd to the directory of the cli script using `$CLIROOT` +cd "$CLIROOT" +# You can also cd to the commands directory using `$CMDROOT` +cd "$CMDROOT" + # You can check if a command exists and do something command_exists "hello-world" && echo "hello world exists!" diff --git a/docs/CLI.md b/docs/CLI.md index 31aaf6a..d830411 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -10,6 +10,8 @@ Running `cli.sh` or `cli.sh list` will then show the newly created command. When running `cli.sh` followed by the name of the command, the shell script for the command will be sourced as is. This means that commands do not need to use any specific functions to run. +Note that `cli.sh` does not change the directory, running `pwd` from a command script will show the directory the user is in. Variables are provided to get the directory of the CLI script and commands. + ## Writing the command script The command script in the `commands` directory will be sourced by the top-level `cli.sh` script when the command is run, this means that the script should be written procedurally. @@ -17,6 +19,7 @@ The command script in the `commands` directory will be sourced by the top-level Scripts should be written following these best practices: * Start with `#!/usr/bin/env bash` * Do not write any usage or help functions within the script, instead use a `.help.txt` file, see the next section for more details. +* Commands are ran from the user's current directory, use `$CLIROOT` to get the directory of the cli script, and `$CMDROOT` to get the directory containing the command script. For example, use `cd "$CLIROOT"` to change to the directory of the cli script. * Use `$CMD` to get the name of the command being run. All positional arguments have been shifted, so `$1` is not the name of the command, instead it is the first argument passed after the name of the command. Checking if `$#` is `0` is a quick way to know if the user provided any additional arguments. * To check if a command exists, use the `command_exists` function, for example `command_exists "hello-world"` will return `0` if `hello-world` is a command, otherwise `1`. * To print error, warning and success messages, use the functions `error`, `warning` and `success`, all arguments passed to these will be printed. From 513b22715d4a6f7ad7c09f27fa7a51b828c121c9 Mon Sep 17 00:00:00 2001 From: Aaron McHale Date: Sun, 19 Apr 2026 16:07:42 +0100 Subject: [PATCH 2/4] Some shellcheck stuff --- commands/hello-world.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/commands/hello-world.sh b/commands/hello-world.sh index beca668..6228527 100644 --- a/commands/hello-world.sh +++ b/commands/hello-world.sh @@ -4,9 +4,10 @@ echo "Hello world!" # Examples of using the helper functions to print messages. # You can cd to the directory of the cli script using `$CLIROOT` -cd "$CLIROOT" +cd "$CLIROOT" || error "Failed to cd to CLIROOT: $CLIROOT" # You can also cd to the commands directory using `$CMDROOT` -cd "$CMDROOT" +cd "$CMDROOT" || error "Failed to cd to CMDROOT: $CMDROOT" +# However, these are optional, commands (and helper functions) will work without needing to change directory. # You can check if a command exists and do something command_exists "hello-world" && echo "hello world exists!" From 29c1ce94edad1f943f204de6e58d41ba5c86530f Mon Sep 17 00:00:00 2001 From: Aaron McHale Date: Sun, 19 Apr 2026 16:09:43 +0100 Subject: [PATCH 3/4] Older bash doesn't support source -p --- cli.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cli.sh b/cli.sh index 3d90a6c..0378c2f 100755 --- a/cli.sh +++ b/cli.sh @@ -129,5 +129,4 @@ if ! command_exists "$CMD"; then fi # Source the script matching CMD -# shellcheck source=/dev/null -source -p "$CMDROOT" "$CMD.sh" +source "$CMDROOT/$CMD.sh" From e57d8cd3403824463614324dae4d1ac8e378172d Mon Sep 17 00:00:00 2001 From: Aaron McHale Date: Sun, 19 Apr 2026 16:10:38 +0100 Subject: [PATCH 4/4] It's fine shellcheck --- cli.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/cli.sh b/cli.sh index 0378c2f..a0f3754 100755 --- a/cli.sh +++ b/cli.sh @@ -129,4 +129,5 @@ if ! command_exists "$CMD"; then fi # Source the script matching CMD +# shellcheck disable=SC1090 source "$CMDROOT/$CMD.sh"