{"id":16615770,"url":"https://github.com/juanibiapina/sub","last_synced_at":"2025-03-21T14:31:27.017Z","repository":{"id":54792087,"uuid":"173811786","full_name":"juanibiapina/sub","owner":"juanibiapina","description":"Shell scripts with superpowers 🦸‍♂️","archived":false,"fork":false,"pushed_at":"2025-02-27T11:42:30.000Z","size":292,"stargazers_count":18,"open_issues_count":1,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-19T21:26:18.902Z","etag":null,"topics":["bash","cli","linux","macos","shell","terminal","zsh"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/juanibiapina.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-03-04T19:55:52.000Z","updated_at":"2025-02-27T11:42:34.000Z","dependencies_parsed_at":"2024-05-27T22:57:44.455Z","dependency_job_id":"63836c3a-c5d4-450d-a737-2477200942ab","html_url":"https://github.com/juanibiapina/sub","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juanibiapina%2Fsub","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juanibiapina%2Fsub/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juanibiapina%2Fsub/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juanibiapina%2Fsub/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/juanibiapina","download_url":"https://codeload.github.com/juanibiapina/sub/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244815188,"owners_count":20514911,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bash","cli","linux","macos","shell","terminal","zsh"],"created_at":"2024-10-12T02:10:34.789Z","updated_at":"2025-03-21T14:31:26.633Z","avatar_url":"https://github.com/juanibiapina.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Sub\n\n![GitHub top language](https://img.shields.io/github/languages/top/juanibiapina/sub)\n![GitHub branch check runs](https://img.shields.io/github/check-runs/juanibiapina/sub/master)\n![GitHub Release](https://img.shields.io/github/v/release/juanibiapina/sub)\n![GitHub License](https://img.shields.io/github/license/juanibiapina/sub)\n\nScripts with superpowers.\n\n`sub` is a tool for organizing scripts into a unified command-line interface.\nIt allows the dynamic creation of a CLI from a directory (and subdirectories)\nof scripts with support for documentation, argument validation, and\ncompletions.\n\n## Table of Contents\n\n\u003c!-- vim-markdown-toc GFM --\u003e\n\n* [Key features](#key-features)\n* [Demo](#demo)\n* [Installation](#installation)\n* [Setup](#setup)\n  * [Examples](#examples)\n  * [As an alias](#as-an-alias)\n  * [As an executable](#as-an-executable)\n* [Usage](#usage)\n* [Documenting commands](#documenting-commands)\n* [Validating arguments](#validating-arguments)\n* [Parsing arguments](#parsing-arguments)\n* [Completions](#completions)\n* [Nested subcommands](#nested-subcommands)\n* [Aliases](#aliases)\n* [Sharing code between scripts](#sharing-code-between-scripts)\n* [Caching](#caching)\n* [Migrating to Sub 2.x](#migrating-to-sub-2x)\n  * [change --bin to --executable](#change---bin-to---executable)\n  * [Usage comments](#usage-comments)\n  * [Help, commands and completions](#help-commands-and-completions)\n* [Inspiration](#inspiration)\n\n\u003c!-- vim-markdown-toc --\u003e\n\n## Key features\n\n- **Display help:** Display usage and documentation for scripts.\n- **Validate arguments:** Validate arguments to scripts based on documentation.\n- **Parse arguments:** Automatically parse arguments to scripts so `getopts` is not needed.\n- **Nested subcommands:** Supports nested directories for hierarchical command structures.\n- **Aliases:** Supports aliases for subcommands.\n- **Completions:** Supports auto completion of subcommands.\n- **Cross-platform:** Works on Linux and macOS.\n\n## Demo\n\n[![asciicast](https://asciinema.org/a/664235.svg)](https://asciinema.org/a/664235)\n\n## Installation\n\n\u003cdetails\u003e\n\u003csummary\u003eHomebrew\u003c/summary\u003e\n\n```sh\nbrew install juanibiapina/tap/sub\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eNix with Flakes (install the `sub` binary)\u003c/summary\u003e\n\nAdd sub to your flake inputs:\n\n```nix\n{\n  inputs = {\n    sub = {\n      url = \"github:juanibiapina/sub\";\n      inputs.nixpkgs.follows = \"nixpkgs\"; # Optional\n    };\n  };\n\n  # ...\n}\n```\n\nThen add it to your packages:\n\n```nix\n{\n  environment.systemPackages = with pkgs; [\n    inputs.sub.packages.\"${pkgs.system}\".sub\n    # ...\n  ];\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eNix with Flakes (including setup)\u003c/summary\u003e\n\nThis repository is a flake that exports a function\n`lib.${system}.mkSubDerivation`. This function creates a package for your cli\nthat uses `sub` as the entry point and already includes the [Setup](#setup).\n\nFor an example on how to use it, check out https://github.com/ggazzi/dev-cli-utils\n\nThanks [@ggazzi](https://github.com/ggazzi) for writing this module.\n\u003c/details\u003e\n\n## Setup\n\nThis section explains how to set up a CLI called `hat` using `sub`.\n\n### Examples\n\nFor a simple example, check out a [hello\nworld](https://github.com/juanibiapina/sub/tree/master/examples/hello) project.\n\nFor a complete example with lots of features, check out the\n[complete](https://github.com/juanibiapina/sub/tree/master/examples/complete)\nexample project.\n\n### As an alias\n\nThe quickest way to get started with `sub` is to define an alias for your CLI\nin your shell:\n\n```sh\nalias hat='sub --name hat --absolute /path/to/cli/root --'\n```\n\nWhere `/path/to/cli/root` contains a `libexec` directory with executable\nscripts, for example:\n\n```\n.\n└── libexec\n    ├── user-script1\n    ├── user-script2\n    └── user-script3\n```\n\n### As an executable\n\nA more reliable way is to use an executable script as the entry point. Given\nthe following directory structure:\n\n```\n.\n├── bin\n│   └── hat\n└── libexec\n    ├── user-script1\n    ├── user-script2\n    └── user-script3\n```\n\nThe entry point in `bin/hat` is then:\n\n```sh\n#!/usr/bin/env bash\n\nsub --name hat --executable \"${BASH_SOURCE[0]}\" --relative \"..\" -- \"$@\"\n```\n\nThe `--executable` argument tells `sub` where the CLI entry point is located.\nThis will almost always be `${BASH_SOURCE[0]}`. The `--relative` argument tells\n`sub` how to find the root of the CLI starting from the CLI entry point. In the\nline above, just replace `hat` with the name of your CLI.\n\n## Usage\n\nOnce you have set up your CLI (we called it `hat`), you can get help by running:\n\n```sh\n$ hat --help\n```\n```\nUsage: hat [OPTIONS] [commands_with_args]...\n\nArguments:\n  [commands_with_args]...\n\nOptions:\n      --usage                  Print usage\n  -h, --help                   Print help\n      --completions            Print completions\n      --commands               Print subcommands\n      --extension \u003cextension\u003e  Filter subcommands by extension\n\nAvailable subcommands:\n    user-script1\n    user-script2\n    user-script3\n```\n\nTo invoke a subcommand, use:\n\n```\n$ hat user-script1\n```\n\nTo get help for a command, use the built in `--help` flag:\n\n```sh\nhat --help \u003ccommandname\u003e\n```\nor\n```sh\nhat \u003ccommandname\u003e --help\n```\n\n## Documenting commands\n\nIn order to display help information, `sub` looks for special comments in the\ncorresponding script. A fully documented `hello` script could look like this:\n\n```sh\n#!/usr/bin/env bash\n#\n# Summary: Say hello\n#\n# Usage: {cmd} \u003cname\u003e [--spanish]\n#\n# Say hello to a user by name.\n#\n# With the --spanish flag, the greeting will be in Spanish.\n\nset -e\n\ndeclare -A args=\"($_HAT_ARGS)\"\n\nif [[ \"${args[spanish]}\" == \"true\" ]]; then\n  echo \"¡Hola, ${args[name]}!\"\nelse\n  echo \"Hello, ${args[name]}!\"\nfi\n```\n\n`sub` looks for special comments in a comment block in the beginning of the\nfile. The special comments are:\n\n- `Summary:` A short description of the script.\n- `Usage:` A description of the arguments the script accepts. Note that the\n  Usage comment, when present, has specific syntactic rules and is used to\n  parse command line arguments. See [Validating arguments](#validating-arguments)\n  and [Parsing arguments](#parsing-arguments) for more information.\n- `Options:` A description of the options the script accepts. This is used to\n  display help information and generate completions. See\n  [Completions](#completions) for more details.\n- Extended documentation: Any other comment lines in this initial block will be\n  considered part of the extended documentation.\n\n## Validating arguments\n\n`sub` automatically validates arguments to scripts based on the `Usage`\ncomment when it is present. The syntax for the `Usage` comment is:\n\n```\n# Usage: {cmd} \u003cpositional\u003e [optional] [-u] [--long] [--value=VALUE] [--exclusive]! [rest]...\n```\n\n- `{cmd}`: This special token represents the name of the command and is always required.\n- `\u003cpositional\u003e`: A required positional argument.\n- `[optional]`: An optional positional argument.\n- `[-u]`: An optional short flag.\n- `[--long]`: An optional long flag.\n- `[--value=VALUE]`: An optional long flag that takes a value.\n- `[--exclusive]!`: An optional long flag that cannot be used with other flags.\n- `[rest]...`: A rest argument that consumes all remaining arguments.\n\nShort and long flags can also be made required by omitting the brackets.\n\nWhen invoking a script with invalid arguments, `sub` will display an error. For\nexample, invoking the `hello` script from the previous section with invalid\narguments:\n\n```sh\n$ hat hello\n```\n\n```\nerror: the following required arguments were not provided:\n  \u003cname\u003e\n\nUsage: hat hello --spanish \u003cname\u003e\n\nFor more information, try '--help'.\n```\n\n## Parsing arguments\n\nWhen arguments to a script are valid, `sub` sets an environment variable called\n`_HAT_ARGS` (where `HAT` is the capitalized name of your CLI). This variable\nholds the parsed arguments as a list of key value pairs. The value of this\nvariable is a string that can be evaluated to an associative array in bash\nscripts:\n\n```sh\ndeclare -A args=\"($_HAT_ARGS)\"\n```\n\nWhich can then be used to access argument values:\n\n```sh\necho \"${args[positional]}\"\n\nif [[ \"${args[long]}\" == \"true\" ]]; then\n  # ...\nfi\n```\n\n## Completions\n\nsub automatically provides completions for subcommand names.\n\nTo enable completions for positional arguments in the `Usage` comment, add an\n`Options:` comment with a list of arguments. An option must have the format:\n`name (completion_type): description`. Completion type is optional.\nThe following completion types are supported:\n\n- `` `command` ``: Runs a command to generate completions. The command should print\n  completions to stdout:\n\n```sh\n# Usage: {cmd} \u003cfile\u003e\n# Options:\n#   file (`ls -1`): File or directory\n\n# script logic\n# ...\n```\n\n- `script`: Invokes the current script to generate completions. This allows for\n  more complex completions:\n\n```sh\n# Usage: {cmd} \u003cname\u003e\n# Options:\n#   name (script): A name\n\n# check if we're being requested completions\nif [[ \"$_HAT_COMPLETE\" == \"true\" ]]; then\n  if [[ \"$_HAT_COMPLETE_ARG\" == \"name\" ]]; then\n    echo \"Alice\"\n    echo \"Bob\"\n    echo \"Charlie\"\n    # note that you can have any logic here to generate completions\n  fi\n\n  # make sure to exit when generating completions to prevent the script from running\n  exit 0\nfi\n\n# read arguments\ndeclare -A args=\"($_HAT_ARGS)\"\n\n# say hello\necho \"Hello, ${args[name]}!\"\n```\n\n\n## Nested subcommands\n\n`sub` supports nested directories for hierarchical command structures. For\nexample, given the following directory structure:\n\n```\n.\n└── libexec\n    └── nested\n        ├── README\n        └── user-script2\n```\n\n`user-script2` can be invoked with:\n\n```sh\n$ hat nested user-script2\n```\n\nDirectories can be nested arbitrarily deep.\n\nA `README` file can be placed in a directory to provide a description of the\nsubcommands in that directory. The `README` file should be formatted like a\nscript, with a special comment block at the beginning:\n\n```sh\n# Summary: A collection of user scripts\n#\n# This directory contains scripts that do magic.\n# This help can be as long as you want.\n# The Usage comment is ignored in README files.\n```\n\n## Aliases\n\nTo define an alias, simply create a symlink. For example, in the `libexec`\ndirectory:\n\n```sh\nln -s user-script1 us1\n```\n\nAliases can also point to scripts in subdirectories:\n```sh\nln -s nested/user-script2 us2\n```\n\nThe full power of symlinks can be used to create complex command structures.\n\n## Sharing code between scripts\n\nWhen invoking subcommands, `sub` sets an environment variable called\n`_HAT_ROOT` (where `HAT` is the capitalized name of your CLI. This variable\nholds the path to the root of your CLI. It can be used, for instance, for\nsourcing shared scripts from a `lib` directory next to `libexec`:\n\n```sh\nsource \"$_CLINAME_ROOT/lib/shared.sh\"\n```\n\n## Caching\n\nWhen invoking subcommands, `sub` sets an environment variable called\n`_HAT_CACHE` (where `HAT` is the capitalized name of your CLI. This variable\npoints to an XDG compliant cache directory that can be used for storing\ntemporary files shared between subcommands.\n\n## Migrating to Sub 2.x\n\n### change --bin to --executable\n\nThe `--bin` argument was renamed to `--executable` to better reflect its purpose.\n\n### Usage comments\n\nSub 2.x introduces automatic validation and parsing of command line arguments\nbased on special Usage comments in scripts. If you previously used arbitrary\nUsage comments in sub 1.x for the purpose of documenting, you can run `sub`\nwith the `--validate` flag to check if your scripts are compatible with the new\nversion.\n\nExample:\n```\n$ sub --name hat --absolute /path/to/cli/root -- --validate\n```\n\n### Help, commands and completions\n\nIf you used the `help`, `commands` or `completions` subcommands, they are now\n`--help`, `--commands` and `--completions` flags respectively.\n\n## Inspiration\n\n- [sub from basecamp](https://github.com/basecamp/sub)\n- [sd](https://github.com/cv/sd)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuanibiapina%2Fsub","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjuanibiapina%2Fsub","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuanibiapina%2Fsub/lists"}