From c5c4536ae42315c88805af202fd536d1fee6a284 Mon Sep 17 00:00:00 2001 From: Roberto Hidalgo Date: Tue, 28 Mar 2023 00:38:04 -0600 Subject: [PATCH] continue working on docs (#92) * prevent horrible colors on terminal.app * more browser doc tweaks * remove version from global options * even out description punctuation --- .ackrc | 4 - .milpa/commands/itself/command-tree.yaml | 8 +- .milpa/commands/itself/create.yaml | 4 +- .milpa/commands/itself/repo/install.yaml | 4 +- .milpa/commands/itself/repo/list.yaml | 8 +- .milpa/commands/itself/repo/uninstall.yaml | 2 +- .milpa/docs/milpa/command/index.md | 28 +++---- .milpa/docs/milpa/command/spec.md | 27 ++++++- .milpa/docs/milpa/environment.md | 6 +- .milpa/docs/milpa/use-case.md | 19 ++--- .milpa/docs/milpa/util/user-input.md | 2 +- .milpa/util/log.sh | 3 +- README.md | 4 +- compa.go | 9 ++- go.mod | 4 +- go.sum | 8 +- internal/actions/command_tree.go | 1 + internal/actions/docs.go | 74 ++++++++++++------- internal/actions/docs_test.go | 6 +- internal/command/command.go | 4 +- internal/constants/constants.go | 6 +- internal/docs/colors.go | 65 ++++++++++++++++ internal/docs/html.go | 50 +++---------- internal/docs/static/css/index.css | 57 +++++++++++++- internal/errors/errors.go | 10 ++- repos/internal/commands/dev/test.sh | 3 + .../commands/dev/test/coverage-report.sh | 1 + repos/internal/commands/release/build.yaml | 10 +-- repos/internal/commands/release/create.yaml | 2 +- test/_helpers/milpa/load.bash | 2 +- test/commands/help/docs.bats | 24 +++--- test/compa.bats | 6 ++ test/fixtures/docs.html | 33 ++++++--- test/fixtures/environment.html | 6 +- test/fixtures/environment.txt | 6 +- test/fixtures/index.html | 4 +- test/fixtures/readme.html | 4 +- test/fixtures/readme.txt | 4 +- test/milpa.bats | 13 +++- 39 files changed, 356 insertions(+), 175 deletions(-) delete mode 100644 .ackrc create mode 100644 internal/docs/colors.go diff --git a/.ackrc b/.ackrc deleted file mode 100644 index cbf9866..0000000 --- a/.ackrc +++ /dev/null @@ -1,4 +0,0 @@ ---ignore-dir=dist ---ignore-dir=test/_helpers/bats-assert ---ignore-dir=test/_helpers/bats-file ---ignore-dir=test/_helpers/bats-support diff --git a/.milpa/commands/itself/command-tree.yaml b/.milpa/commands/itself/command-tree.yaml index 9193cb8..a8ca82d 100644 --- a/.milpa/commands/itself/command-tree.yaml +++ b/.milpa/commands/itself/command-tree.yaml @@ -1,6 +1,6 @@ summary: Prints a tree of known commands description: | - Prints out command names and descriptions, or optionally all properties as `json` or `yaml`. + Prints out command names and descriptions, or optionally a nested representation of all properties of commands, serialized as `json` or `yaml`. Custom textual representations of commands can be obtained by using the `--template` option and specifying a [go-template](https://pkg.go.dev/text/template#hdr-Actions) to be applied to every command. See [chinampa/pkg.Command](https://pkg.go.dev/git.rob.mx/nidito/chinampa@v0.0.0-20230324015136-6ec1c56f0fc5/pkg/command#Command) and [milpa/internal/command.Meta](https://pkg.go.dev/github.com/unrob/milpa@v0.0.0-20230321064607-ee1825c67af7/internal/command#Meta) for references on the structs available during template rendering. ## Examples @@ -11,14 +11,14 @@ description: | # print a tree of milpa itself sub-commands milpa itself command-tree itself - # print out all commands, skipping groups - milpa itself command-tree --template '{{ if (not (eq .Meta.Kind "virtual")) }}{{ .FullName }}'$'\n''{{ end }}' - # get all commands as a json tree milpa itself command-tree --output json # same, but as the yaml representation of this command itself milpa itself command-tree --output json itself command-tree + + # print out all commands, skipping groups + milpa itself command-tree --template '{{ if (not (eq .Command.Meta.Kind "virtual")) }}{{ .Command.FullName }}'$'\n''{{ end }}' ``` arguments: - name: prefix diff --git a/.milpa/commands/itself/create.yaml b/.milpa/commands/itself/create.yaml index a99ea9e..80ca965 100644 --- a/.milpa/commands/itself/create.yaml +++ b/.milpa/commands/itself/create.yaml @@ -21,10 +21,10 @@ options: description: Open the script file in your current $EDITOR after creation executable: type: bool - description: Create an empty, executable command. Useful when you'd like using something other than bash. + description: Create an empty, executable command. Useful when you'd like using something other than bash repo: type: string - description: a path to the milpa repo to create this command in. By default, the nearest .milpa directory from `pwd` and up. + description: a path to the milpa repo to create this command in. By default, the nearest .milpa directory from `pwd` and up values: suggest-only: true script: find . -type d -maxdepth 1 && milpa itself repo list --paths-only diff --git a/.milpa/commands/itself/repo/install.yaml b/.milpa/commands/itself/repo/install.yaml index dc1ace7..2f420ee 100644 --- a/.milpa/commands/itself/repo/install.yaml +++ b/.milpa/commands/itself/repo/install.yaml @@ -11,8 +11,8 @@ description: | ### Examples - `github.com/unRob/milpa` would download the `.milpa` folder from the `unRob/milpa` github repo, using **https**, and prompting for credentials for private repos. - - `git::ssh://git@github.com/unRob/milpa.git//internal` would download the `internal/.milpa` folder from the `unRob/milpa` github repo using **ssh credentials**, useful for skipping credential prompts for private repos - - `~/.dotfiles` would symlink `$HOME/.dotfiles/.milpa` to the target. An alternative to this is to set `MILPA_PATH` to `$HOME/.dotfiles` (see [`milpa help docs milpa environment`](/.milpa/docs/milpa/environment.md)) + - `git::ssh://git@github.com/unRob/milpa.git//internal` would download the `internal/.milpa` folder from the `unRob/milpa` github repo using **ssh credentials**, useful for skipping credential prompts for private repos. + - `~/.dotfiles` would symlink `$HOME/.dotfiles/.milpa` to the target. An alternative to this is to set `MILPA_PATH` to `$HOME/.dotfiles` (see [`milpa help docs milpa environment`](/.milpa/docs/milpa/environment.md)). arguments: - name: source diff --git a/.milpa/commands/itself/repo/list.yaml b/.milpa/commands/itself/repo/list.yaml index be2217c..f01609c 100644 --- a/.milpa/commands/itself/repo/list.yaml +++ b/.milpa/commands/itself/repo/list.yaml @@ -1,11 +1,11 @@ summary: Lists installed repos description: | - Milpa can install repositories from remote locations at two different locations, depending on how it was instaled. + `milpa` installs repositories from remote locations at two different locations: - - In the `$MILPA_ROOT` (by default, /usr/local/lib/milpa) - - In `$XDG_DATA_HOME/milpa` folder + - machine-wide repositories are installed to `$MILPA_ROOT`, usually at `/usr/local/lib/milpa`, + - user-specific repositories are installed to `$XDG_DATA_HOME/milpa` folder, usually `$HOME/.local/share/milpa/` - For more information on installing packages, see [`milpa itself repo install --help`](/.milpa/commands/itself/repo/install.md) + For more information on installing packages, see [`milpa itself repo install --help`](/.milpa/commands/itself/repo/install.md). options: paths-only: description: Only output the paths to repos with no decoration diff --git a/.milpa/commands/itself/repo/uninstall.yaml b/.milpa/commands/itself/repo/uninstall.yaml index ab4d3ad..44bc52d 100644 --- a/.milpa/commands/itself/repo/uninstall.yaml +++ b/.milpa/commands/itself/repo/uninstall.yaml @@ -1,6 +1,6 @@ summary: Removes an installed milpa repo description: | - Uninstalls a milpa repo by PATH. See [`milpa itself repo list --help`](/.milpa/commands/itself/repo/list.md) for a list of available repos + Uninstalls a milpa repo by PATH. See [`milpa itself repo list --help`](/.milpa/commands/itself/repo/list.md) for a list of available repos. arguments: - name: path description: The repo path to uninstall diff --git a/.milpa/docs/milpa/command/index.md b/.milpa/docs/milpa/command/index.md index 6da3b07..b5b583a 100644 --- a/.milpa/docs/milpa/command/index.md +++ b/.milpa/docs/milpa/command/index.md @@ -5,12 +5,12 @@ weight: 15 description: Commands overview --- -`milpa` has two modes for running your scripts: +`milpa` runs your scripts or executables, by matching passed arguments to files in `.milpa/commands` directories. It looks at a few of these, starting with the one where `milpa` was called from. Check out `MILPA_PATH` of [`milpa help docs milpa environment`](/.milpa/docs/environment.md#MILPA_PATH) to understand where `milpa` looks for **commands**. A collection of commands under a single `.milpa` folder is called a **repo**, and you can learn more reading [`milpa help docs milpa repo`](/.milpa/docs/milpa/repo/index.md) -0. bash scripts, with an `.sh` extension, or -1. executable files without an extension, written in whatever language you want. +Your **script** or executable plus its corresponding **spec** is what we call a `milpa` **command**. Scripts can be: -Your **script** or executable plus its corresponding **spec** is what we call a `milpa` **command**. If your command does not have an extension, remember to set the executable bit on (`chmod +x .milpa/commands/your-command`)! +0. bash scripts, with an `.sh` extension, or +1. executable files without an extension, written in whatever language you want. If your command does not have an extension, remember to set on the executable bit (`chmod +x .milpa/commands/your-command`)! ## Spec @@ -55,24 +55,26 @@ echo "$MILPA_OPT_GREETING $(title_case "$name")" ## Arguments, Options and Environment -The **arguments** and **options** passed in the command line will be parsed and validated according to your spec. If valid, your script will receive numbered arguments as usual (`$1` and so on), without known options. Valid arguments and options will be available to your script as **environment variables** as well. +The **arguments** and **options** passed in the command line will be parsed and validated according to your spec. If valid, your script will receive numbered arguments as usual (`$1` and so on), excluding any supplied options (i.e. without `--option=value` "arguments"). Valid arguments and options will be available to your script as **environment variables** as well. + +> ⚠️ Unknown arguments and options will cause an error before your script executes! The environment available to a **script** is composed of four groups: -- `MILPA_COMMAND_*` variables have information about the **command** called by the user, -- `MILPA_ARG_*` variables hold values for every **argument** of the spec, -- `MILPA_OPT_*` variables hold values for every **option** defined, and -- [global environment variables](/.milpa/docs/milpa/environment.md) that affect `milpa`'s overall behavior. +0. `MILPA_COMMAND_*` variables have information about the **command** called by the user, +1. `MILPA_ARG_*` variables hold values for every **argument** of the spec, +2. `MILPA_OPT_*` variables hold values for every **option** defined, and +3. [global environment variables](/.milpa/docs/milpa/environment.md) that affect `milpa`'s overall behavior. -### Command: `MILPA_COMMAND_*` +### Command metadata: `MILPA_COMMAND_*` Your script has access to the following variables set by `milpa` after parsing arguments and running validations: - `MILPA_COMMAND_NAME`: the space delimited name of your command, i.e. `db connect`; - `MILPA_COMMAND_KIND`: either `source` for `.sh` scripts, or `exec` for executables; - `MILPA_COMMAND_REPO`: the path to the repo containing this command, i.e. `/home/you/project/.milpa`; and -- `MILPA_COMMAND_PATH`: the full path to the executable being called +- `MILPA_COMMAND_PATH`: the full path to the executable being called. ### Arguments: `MILPA_ARG_*` @@ -93,7 +95,7 @@ milpa greet elmer homero ### Options: `MILPA_OPT_*` -**Options** show up on the environment with the `MILPA_OPT_` prefix followed by the name in your spec. Names will be all uppercase, and dashes will be turned into underscores. In the [command spec](/.milpa/docs/milpa/command/spec.md) you'll find more details on how to set **options**. +**Options** show up on the environment with the `MILPA_OPT_` prefix followed by the name in your spec. Names will be all uppercase, and dashes will be turned into underscores. In the [command spec](/.milpa/docs/milpa/command/spec.md) you'll find more details on how to define **options**. ```sh # when ran like this: @@ -122,7 +124,7 @@ options: if [[ "$MILPA_OPT_SHOUT" ]]; then echo "$MILPA_OPT_GREETING $(title_case "$name")!" | awk '{print toupper($0)}' else - echo "$MILPA_ARG_GREETING $(title_case "${MILPA_ARG_FULL_NAME[*]}")" + echo "$MILPA_OPT_GREETING $(title_case "$name")" fi ``` diff --git a/.milpa/docs/milpa/command/spec.md b/.milpa/docs/milpa/command/spec.md index 1493f1b..89c98fc 100644 --- a/.milpa/docs/milpa/command/spec.md +++ b/.milpa/docs/milpa/command/spec.md @@ -85,7 +85,7 @@ arguments: # a default may be specified, it'll be passed to your command if none is provided default: patch # if marked as required, the command won't run unless this argument is provided - # An error will result if the argument is bothr required and has a default set + # An error will result if the argument is both required and has a default set required: true # arguments may be variadic, that is, all remaining arguments starting at this position # in this case, since there's only one argument, it would mean all arguments after @@ -106,11 +106,12 @@ The `options` map describes the named options that may be passed to a command. O ```yaml # options, also known as flags, are specified as a map options: - # this sets the --scheme option + # this creates the --scheme option # it will be available to your script as the $MILPA_OPT_SCHEME environment variable # and may be specified on the command line as either `--scheme "semver"` or `--scheme=semver`. scheme: - # options require a description, this will show during completions and on the command's help page + # options require a description, this will show during completions + # and on the command's help page description: Determines the format of the tags for this repo. # Sometimes, very commonly used flags might benefit from setting a short name # in this case, users would be able to use `-s calver` @@ -169,3 +170,23 @@ A `values` property may be specified for both arguments and options; `milpa` wil # if enabled, will not add a space after suggestions during autocomplete suggest-raw: false ``` + +### Value completion script interpolation + +[go-template](https://pkg.go.dev/text/template#hdr-Actions) tags may be used within `milpa` and `script` value completions to interpolate already supplied values. The following tags are available: + +- `{{ Arg "name" }}`: the value (or default) for the named argument. A map of argument names to their string values is also available at `{{ index Args "name" }}`. +- `{{ Opt "name" }}`: the value (or default) for the named option. A map of argument names to their string values is also available at `{{ index Opts "name" }}`. +- `{{ Current }}`: the value currently being auto-completed. On a command line like `milpa song play joão⇥`, `{{ Current }}` would return `joão`. + +Additionally, the following Go functions are available: + +- `{{ contains "team" "ea" }}`: [`strings.Contains`](https://pkg.go.dev/strings#Contains) +- `{{ hasSuffix "content" "ent" }}`: [`strings.HasSuffix`](https://pkg.go.dev/strings#HasSuffix) +- `{{ hasPrefix "content" "con" }}`: [`strings.HasPrefix`](https://pkg.go.dev/strings#HasPrefix) +- `{{ replace "file.yaml" ".yaml" ".sh" }}`: [`strings.ReplaceAll`](https://pkg.go.dev/strings#ReplaceAll) +- `{{ toUpper "shout" }}`: [`strings.ToUpper`](https://pkg.go.dev/strings#ToUpper) +- `{{ toLower "whisper" }}`: [`strings.ToLower`](https://pkg.go.dev/strings#ToLower) +- `{{ trim " padded " }}`: [`strings.Trim`](https://pkg.go.dev/strings#Trim) +- `{{ trimSuffix "content" "con" }}`: [`strings.TrimSuffix`](https://pkg.go.dev/strings#TrimSuffix) +- `{{ trimPrefix "content" "ent" }}`: [`strings.TrimPrefix`](https://pkg.go.dev/strings#TrimPrefix) diff --git a/.milpa/docs/milpa/environment.md b/.milpa/docs/milpa/environment.md index ab049bf..a8c53dc 100644 --- a/.milpa/docs/milpa/environment.md +++ b/.milpa/docs/milpa/environment.md @@ -57,11 +57,11 @@ By default, when stdout is a TTY, `milpa` will output color escape characters. I If either `--no-color` or `--color` options are provided, these will override both `COLOR` and `NO_COLOR` environment variables. -### `MILPA_HELP_STYLE` / `MILPA_PLAIN_HELP` +If `COLORTERM` is set to `truecolor`, 24-bit colors will be used instead of the default 256-color palette. -`MILPA_HELP_STYLE` can be one of `auto`, `dark`, and `light` and specifies the theme to use when rendering help pages. This option has no effect if `--no-color` or `NO_COLOR=1` is specified. +### `MILPA_HELP_STYLE` -`MILPA_PLAIN_HELP` will not colorize help output at all, formatting help as plain markdown text instead. +`MILPA_HELP_STYLE` controls the theme to use when rendering help pages, and must be one of `auto`, `dark`, `light`, and `markdown`. --- diff --git a/.milpa/docs/milpa/use-case.md b/.milpa/docs/milpa/use-case.md index 63ba922..3b18c50 100644 --- a/.milpa/docs/milpa/use-case.md +++ b/.milpa/docs/milpa/use-case.md @@ -6,7 +6,7 @@ weight: 2 I built `milpa` with a few use cases in mind: -- To **share groups of scripts** used by a group of folks in an engineering team (i.e. setup development environments, work with secrets/credentials) +- To **share groups of scripts** like those needed by folks in an engineering team (i.e. setup development environments, work with secrets/credentials) - to **share context and code**, without having to ask folks to run stuff off READMEs (i.e. pull service logs, forward ports), and - to **quickly build scripts** that will be documented sufficiently well for my forgetful future-self. @@ -15,9 +15,9 @@ My goal with `milpa` is to make following the [Command Line Interface Guidelines ## `milpa` is great for -- working with small programs meant to be used as either building blocks for other programs, or user-facing collections of these blocks -- sharing related commands using your team's source control versioning system, allowing anyone to mess with them and test them often -- when writing shell scripts is the way to go. Maybe you're interacting with binaries in the system, or operating on files and directories and using your programming language of choice is overkill. +- working with small programs meant to be used as either building blocks for other programs, or user-facing collections of these blocks; +- sharing related commands using your team's source control versioning system, allowing anyone to mess with them and test them often; +- when writing shell scripts is the way to go. Maybe you're interacting with binaries in the system, or operating on files and directories and using your programming language of choice is overkill; and - turning runbooks into actionable, runnable scripts. Up-to-date remediation one `git pull && milpa runbook ...` away. Tasks like bootstrapping development environments are usually left out for the user to accomplish by following through a README or wiki with likely outdated links; `milpa` is great when these tasks can be accomplished by prompting for information, querying identity providers and then running configuration commands or modifying the filesystem directly. @@ -41,7 +41,7 @@ I haven't tested the performance beyond dozens of repos with dozens of commands, When there's a need to distribute stand-alone CLI programs, `milpa` won't be the best method to package and distribute CLIs. While facilities to work with `milpa` repos exists (see [`milpa itself repo install`](/.milpa/commands/itself/repo/install)), it may be less than ideal since there's a dependency in `milpa`. -`milpa` could be the wrong tool when the primary runner of commands is not gonna be a human. Sorry robot friends! `milpa`'s features are oriented primarily towards improving the experience of maintaining and running scripts by humans, and while there's nothing wrong with having an automated system (say CI, for example) run `milpa` commands, there is an overhead to consider by invoking `milpa` (both cognitive overhead and in terms of resource usage). +`milpa` could be the wrong tool when the primary runner of commands is not gonna be a human. Sorry robot friends! `milpa`'s features are oriented primarily towards improving the experience of maintaining and running scripts by humans, and while there's nothing wrong with having an automated system (say CI, for example) run `milpa` commands, there is an overhead to consider (both cognitive, and in terms of resource usage). That being said, it can be done and it works fine (or, if you like writing bash scripts as much as I do, _beautifully_); check out [unRob/smoked-by-the-house](https://github.com/unRob/smoked-by-the-house) where `milpa` orchestrated and operated a Raspberry Pi running an art installation for three months of 2022 at the Anahuacalli Museum in Mexico City. @@ -54,7 +54,7 @@ These are great, as long as: a) you know the path to your script, b) it doesn't `milpa` provides usability advantages over this approach which will come handy when any of the constraints listed before are not met. With `milpa`, scripts that change often get updated docs and autocomplete for free, and may be appreciated by new users and non-regulars alike. Coming back to rarely used commands is one `--help` away when these scripts are part of a `milpa` repo. -### Bash tools +### Other command launchers There's some amazing tools out there, such as: @@ -63,8 +63,9 @@ There's some amazing tools out there, such as: - [Rerun](http://rerun.github.io/rerun/) - [Sub](https://github.com/basecamp/sub) - [Basher package manager](https://github.com/basherpm/basher) +- [Criteo's command-launcher](https://github.com/criteo/command-launcher) -These all serve different purposes, and there is some feature overlap with `milpa` and each of these. +These all serve different purposes, and there is some feature overlap between `milpa` and each of these. `milpa` aims to provide the same level of support to non-bash scripts (hello applescript) without the need for another runtime to be installed. These projects are great for distributing and packaging software that may be used beyond your organization, while `milpa` aims to help smaller user bases, such as engineering teams, keep their shared scripts organized and up-to-date. @@ -78,6 +79,6 @@ That being said, organizing makefiles and dealing with arguments is not somethin Building your own CLI is usually what ends up happening given a team with enough time and direction to invest in building a proper CLI with whatever language is already at use. Some teams use more than one language, which may complicate this approach. In the microservices world, many codebases come with their own CLIs that may follow slightly different conventions, are seldomly documented and often just end up calling other binaries through `exec`. -In my limited experience with engineering teams of less than 300 folks, many of the bootstrapping tasks will involve operations that can easily (and more succintly) be expressed with a shell scripting language. `milpa` could also be a useful intermediate step, that could help teams avoid the dread of jira runbooks until it's a good time to build your own CLI. +In my limited experience with engineering teams of less than 300 folks, many of the bootstrapping tasks will involve operations that can easily (and more succinctly) be expressed with a shell scripting language. `milpa` could also be a useful intermediate step, that could help teams avoid the dread of confluence/notion/google-docs runbooks until it's a good time to build your own CLI. -Building a CLI is something that happens to me somewhat often, and when golang is a good choice, I get most of `milpa`'s niceties by building my CLI on [chinampa](https://git.rob.mx/nidito/chinampa). `joao`, a configuration manager, is an exmaple of something that started as a [milpa repo](https://github.com/unRob/nidito/tree/0812e0caf6d81dd06b740701c3e95a2aeabd86de/.milpa/commands/nidito/config) that later became [it's own CLI](https://git.rob.mx/nidito/joao); getting it to work with `milpa` first was fundamental in figuring out _what_ it needed to do, and it only took a few hours. +Building a CLI is something that happens to me somewhat often, and when golang is a good choice, I get most of `milpa`'s niceties by building my CLI on [chinampa](https://git.rob.mx/nidito/chinampa). `joao`, a configuration manager, is an example of something that started as a [milpa repo](https://github.com/unRob/nidito/tree/0812e0caf6d81dd06b740701c3e95a2aeabd86de/.milpa/commands/nidito/config) that later became [it's own CLI](https://git.rob.mx/nidito/joao); getting it to work with `milpa` first was fundamental in figuring out _what_ it needed to do, and it only took a few hours. diff --git a/.milpa/docs/milpa/util/user-input.md b/.milpa/docs/milpa/util/user-input.md index d4567be..8e29a9c 100644 --- a/.milpa/docs/milpa/util/user-input.md +++ b/.milpa/docs/milpa/util/user-input.md @@ -10,7 +10,7 @@ The `user-input` util contains shell functions related to the creation and clean `@milpa.ask PROMPT [DEFAULT]` -Promts the user to enter a value. It will show the prompt, followed by the default value with no additional formatting. It outputs the entered value on success and asks again if no value was entered (and no default is set). +Prompts the user to enter a value. It will show the prompt, followed by the default value with no additional formatting. It outputs the entered value on success and asks again if no value was entered (and no default is set). ```sh #!/usr/bin/env bash diff --git a/.milpa/util/log.sh b/.milpa/util/log.sh index 63f31f6..489401c 100755 --- a/.milpa/util/log.sh +++ b/.milpa/util/log.sh @@ -18,6 +18,7 @@ _FMT_RESET=$'\e[0m' _FMT_FG_DEFAULT=$'\e[39m' _FMT_FG_RED=$'\e[31m' _FMT_FG_GREEN=$'\e[32m' +_FMT_FG_WHITE=$'\e[225m' _FMT_FG_YELLOW=$'\e[33m' _FMT_FG_GRAY=$'\e[37m' _FMT_BG_DEFAULT=$'\e[49m' @@ -58,7 +59,7 @@ function _print_message () { elif [[ "$level" == "error" ]]; then prefix="ERROR: " if @milpa.is_color_enabled; then - prefix="${_FMT_BG_RED}${_FMT_BOLD} ERROR ${_FMT_RESET} " + prefix="${_FMT_BG_RED}${_FMT_FG_WHITE}${_FMT_BOLD} ERROR ${_FMT_RESET} " fi fi diff --git a/README.md b/README.md index 00bf1bd..885c811 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,12 @@ curl -L https://milpa.dev/install.sh | bash - You and your team write scripts and a little spec for each of them—use bash, or any other language—, and `milpa` provides: -- argument and option **completions** from static sources, other `milpa` scripts or even other programs; +- argument and option **completions** from static sources, files and directories, other `milpa` scripts or even other programs; - **nested sub-commands** as simple as the filesystem, organize them in folders and `milpa` does the rest; - **parsing** and **validation** for arguments and options, writing little to no code; and - **help** and **documentation** on the terminal and browser. -There's [a few reasons why](/.milpa/docs/milpa/use-case.md) you and your team might wanna use `milpa`, but in summary, it's goal is to provide all those nice features above while making it easier to follow the [Command Line Interface Guidelines](https://clig.dev). +There's [a few reasons why](/.milpa/docs/milpa/use-case.md) you and your team might wanna use `milpa`, but in summary, its goal is to provide all those nice features above while making it easier to follow the [Command Line Interface Guidelines](https://clig.dev). `milpa` is licensed under the Apache License 2.0, and its code is available at [github.com/unRob/milpa](https://github.com/unRob/milpa). diff --git a/compa.go b/compa.go index 498156d..adfb9b7 100644 --- a/compa.go +++ b/compa.go @@ -37,18 +37,19 @@ func main() { err := bootstrap.Run() if err != nil { - logger.Fatal(err) + errors.HandleExit(nil, err) // nolint: errcheck + return } cfg := chinampa.Config{ Name: "milpa", Version: version, Summary: "Runs commands found in " + _c.RepoRoot + " folders", - Description: `﹅milpa﹅ is a command-line tool to care for one's own garden of scripts, its name comes from an agricultural method that combines multiple crops in close proximity. You and your team write scripts and a little spec for each command -use bash, or any other language-, and ﹅milpa﹅ provides autocomplete, sub-commands, argument parsing and validation so you can skip the toil and focus on your scripts. + Description: `﹅milpa﹅ is a command-line tool to care for one's own garden of scripts, providing autocomplete, sub-commands, argument parsing and validation, along beautiful documentation—just add a simple YAML spec and a bash (or your language of choice) script. -See [﹅milpa help docs milpa﹅](/.milpa/docs/milpa/index.md) for more information about ﹅milpa﹅`, +See [﹅milpa help docs milpa﹅](/.milpa/docs/milpa/index.md) for more information about ﹅milpa﹅.`, } - chinampa.SetErrorHandler(errors.HandleCobraExit) + chinampa.SetErrorHandler(errors.HandleExit) chinampa.SetVersionCommandName("__version") chinampa.Register(actions.Doctor) diff --git a/go.mod b/go.mod index 3451454..9e3c11a 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/unrob/milpa go 1.20 require ( - git.rob.mx/nidito/chinampa v0.0.0-20230321062825-4c0d40c631cc + git.rob.mx/nidito/chinampa v0.0.0-20230324064447-fef0a55288ea github.com/alecthomas/chroma/v2 v2.5.0 github.com/alessio/shellescape v1.4.1 github.com/bmatcuk/doublestar/v4 v4.6.0 @@ -48,7 +48,7 @@ require ( github.com/leodido/go-urn v1.2.2 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/microcosm-cc/bluemonday v1.0.23 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect diff --git a/go.sum b/go.sum index 93ddc32..0d80f46 100644 --- a/go.sum +++ b/go.sum @@ -179,8 +179,8 @@ cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuW cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -git.rob.mx/nidito/chinampa v0.0.0-20230321062825-4c0d40c631cc h1:nTiIKFnHJ4SIyXGbBk56FJ2ZUzrJ9eA6Y/usXJoQ0gw= -git.rob.mx/nidito/chinampa v0.0.0-20230321062825-4c0d40c631cc/go.mod h1:ImvF16HDuvzSgb1VYOlrw6v1Hy/QNNNr2drVetpEvsk= +git.rob.mx/nidito/chinampa v0.0.0-20230324064447-fef0a55288ea h1:h5/LtKqw3nNwh0di7JJho4r9Ey0MfUKvSDm5A0OFeYA= +git.rob.mx/nidito/chinampa v0.0.0-20230324064447-fef0a55288ea/go.mod h1:ImvF16HDuvzSgb1VYOlrw6v1Hy/QNNNr2drVetpEvsk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -390,8 +390,8 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= diff --git a/internal/actions/command_tree.go b/internal/actions/command_tree.go index 6a0337a..e143a1c 100644 --- a/internal/actions/command_tree.go +++ b/internal/actions/command_tree.go @@ -128,6 +128,7 @@ var CommandTree = &command.Command{ tpl := template.Must(template.New("treeItem").Funcs(render.TemplateFuncs).Parse(outputTpl)) serializationFn = func(t interface{}) ([]byte, error) { tree := t.(*tree.CommandTree) + addMetaToTree(tree) var output bytes.Buffer err := tree.Traverse(func(cmd *command.Command) error { return tpl.Execute(&output, cmd) }) return output.Bytes(), err diff --git a/internal/actions/docs.go b/internal/actions/docs.go index a1ea4c8..a825113 100644 --- a/internal/actions/docs.go +++ b/internal/actions/docs.go @@ -32,7 +32,9 @@ var dlog = logger.Sub("action:docs") var AfterHelp = os.Exit func startServer(listen, address string) error { - os.Setenv(env.HelpUnstyled, "true") + os.Setenv(env.HelpStyle, "markdown") + // Replace with DevelopmentStaticResourceHandler to use locally available + // static resources during development http.Handle("/static/", docs.EmbeddedStaticResourceHandler()) http.HandleFunc("/", docs.RenderHandler(address)) @@ -50,17 +52,53 @@ func startServer(listen, address string) error { return server.ListenAndServe() } -var Docs = &command.Command{ - Path: []string{"help", "docs"}, - Summary: "Displays docs on TOPIC", - Description: "Shows markdown-formatted documentation from milpa repos. See `" + _c.Milpa + " " + _c.HelpCommandName + " docs milpa repo docs` for more information on how to write your own." + ` +func init() { + // This convoluted piece of code will ensure `help docs` render + // the correct address in its own help + // as well as rendering available docs topics + Docs.HelpFunc = func(printLinks bool) string { + base := Docs.Options["base"].ToString() + listen := Docs.Options["listen"] + defaultListen := listen.Default.(string) + base = strings.ReplaceAll(base, defaultListen, listen.ToString()) + dlog.Debug("showing docs help") + topics, err := lookup.Docs([]string{}, "", false) + if err != nil { + return "" + } + topicList := []string{} + for _, topic := range topics { + if printLinks { + topic = fmt.Sprintf("[%s](%s)", topic, topic) + } + topicList = append(topicList, "- "+topic) + } + + return `### Server mode An HTTP server to browse documentation can be started by running: ﹅﹅﹅sh milpa help docs --server +# then head to http://localhost:4242 ﹅﹅﹅ -`, + +Command and docs are available at their names, replacing spaces with forward slashes ﹅/﹅, for example: + +- ` + base + `/help/docs will show this help page. +- ` + base + `/itself/doctor shows documentation for the ﹅milpa itself doctor﹅ command. +- ` + base + `/help/docs/milpa renders the file at ﹅.milpa/docs/milpa.md﹅ (or ﹅.milpa/docs/milpa/index.md﹅). + +## Available topics + +` + strings.Join(topicList, "\n") + } +} + +var Docs = &command.Command{ + Path: []string{"help", "docs"}, + Summary: "Displays docs on TOPIC", + Description: "Shows markdown-formatted documentation from milpa repos. See `" + _c.Milpa + " " + _c.HelpCommandName + " docs milpa repo docs` for more information on how to write your own.", Arguments: command.Arguments{ &command.Argument{ Name: "topic", @@ -74,7 +112,8 @@ milpa help docs --server dlog.Debugf("looking for docs given %v and %s", args, currentValue) cv := "" - if len(args) > 1 { + if len(args) > 1 && args[len(args)-1] == "" { + // remove last argument from docs path lookup base cv = args[len(args)-1] args = args[0 : len(args)-1] } @@ -112,29 +151,14 @@ milpa help docs --server Repo: os.Getenv(_c.EnvVarMilpaRoot), Kind: "docs", }, - HelpFunc: func(printLinks bool) string { - dlog.Debug("showing docs help") - topics, err := lookup.Docs([]string{}, "", false) - if err != nil { - return "" - } - topicList := []string{} - for _, topic := range topics { - if printLinks { - topic = fmt.Sprintf("[%s](%s)", topic, topic) - } - topicList = append(topicList, "- "+topic) - } - - return "## Available topics:\n\n" + strings.Join(topicList, "\n") - }, Action: func(cmd *command.Command) error { - dlog.Debug("Rendering docs") args := cmd.Arguments[0].ToValue().([]string) if len(args) == 0 { if cmd.Options["server"].ToValue().(bool) { listen := cmd.Options["listen"].ToString() - address := cmd.Options["base"].ToString() + base := cmd.Options["base"] + defaultListen := cmd.Options["listen"].Default.(string) + address := strings.ReplaceAll(base.ToString(), defaultListen, listen) dlog.Infof("Starting docs server at http://%s, press CTRL-C to stop...", listen) return startServer(listen, address) } diff --git a/internal/actions/docs_test.go b/internal/actions/docs_test.go index b301c4a..d4250e0 100644 --- a/internal/actions/docs_test.go +++ b/internal/actions/docs_test.go @@ -38,7 +38,7 @@ func TestRenderDocsHelpMain(t *testing.T) { out := bytes.Buffer{} stderr := bytes.Buffer{} cmd := &cobra.Command{Use: "asdf"} - os.Setenv(env.HelpUnstyled, "1") + os.Setenv(env.HelpStyle, "markdown") cmd.SetHelpFunc(Docs.HelpRenderer(command.Root.Options)) cmd.SetOut(&out) cmd.SetErr(&stderr) @@ -50,7 +50,7 @@ func TestRenderDocsHelpMain(t *testing.T) { } expected := ` -## Available topics: +## Available topics - [milpa](milpa) ` @@ -68,7 +68,7 @@ func TestRenderDocsRender(t *testing.T) { out := bytes.Buffer{} stderr := bytes.Buffer{} cmd := &cobra.Command{Use: "asdf"} - os.Setenv(env.HelpUnstyled, "1") + os.Setenv(env.HelpStyle, "markdown") cmd.SetHelpFunc(Docs.HelpRenderer(command.Root.Options)) cmd.SetOut(&out) cmd.SetErr(&stderr) diff --git a/internal/command/command.go b/internal/command/command.go index 3e1a226..e71b3d7 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -69,7 +69,9 @@ func canRun(cmd *command.Command) error { issues = append(issues, i.Error()) } - return fmt.Errorf("refusing to run <%s>: %s", cmd.FullName(), strings.Join(issues, "\n")) + return errors.ConfigError{ + Err: fmt.Errorf("cannot run command <%s>: %s", cmd.FullName(), strings.Join(issues, "\n")), + } } return nil } diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 1015a41..a3adc9b 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -12,7 +12,6 @@ const Milpa = "milpa" const HelpCommandName = "help" func init() { - env.HelpUnstyled = "MILPA_PLAIN_HELP" env.HelpStyle = "MILPA_HELP_STYLE" env.Verbose = "MILPA_VERBOSE" env.Silent = "MILPA_SILENT" @@ -20,6 +19,11 @@ func init() { } // Environment Variables. + +// EnvVarColorBitDepth is annoying because it's not set over ssh. +// It's also the least annoying way to find out if truecolor support is available +// see https://github.com/termstandard/colors#querying-the-terminal +const EnvVarColorBitDepth = "COLORTERM" const EnvVarMilpaPath = "MILPA_PATH" const EnvVarMilpaPathParsed = "MILPA_PATH_PARSED" const EnvVarMilpaRoot = "MILPA_ROOT" diff --git a/internal/docs/colors.go b/internal/docs/colors.go new file mode 100644 index 0000000..3deffac --- /dev/null +++ b/internal/docs/colors.go @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright © 2021 Roberto Hidalgo +package docs + +import ( + "os" + + "github.com/charmbracelet/glamour" + "github.com/unrob/milpa/internal/constants" +) + +func stringptr(str string) *string { + return &str +} + +func uintptr(number uint) *uint { + return &number +} + +type StyleName string + +const ( + StyleDark StyleName = "dark" + StyleLight StyleName = "light" + StylePlain StyleName = "plain" +) + +func init() { + var zero uint + glamour.NoTTYStyleConfig.Document.Margin = &zero + glamour.NoTTYStyleConfig.Document.StylePrimitive.Color = nil + glamour.DarkStyleConfig.Document.Margin = &zero + glamour.DarkStyleConfig.Document.StylePrimitive.Color = nil + glamour.LightStyleConfig.Document.Margin = &zero + glamour.LightStyleConfig.Document.StylePrimitive.Color = nil + glamour.DarkStyleConfig.List.Margin = uintptr(2) + + if os.Getenv(constants.EnvVarColorBitDepth) != "truecolor" { + // Apple's Terminal.app does not support "true color", which is sad. + glamour.DarkStyleConfig.H1.StylePrimitive.Color = stringptr("193") + glamour.DarkStyleConfig.H1.StylePrimitive.BackgroundColor = stringptr("22") + glamour.DarkStyleConfig.Heading.StylePrimitive.Color = stringptr("193") + glamour.DarkStyleConfig.Code.StylePrimitive.Color = stringptr("230") + glamour.DarkStyleConfig.Code.StylePrimitive.BackgroundColor = stringptr("22") + + glamour.LightStyleConfig.H1.StylePrimitive.Color = stringptr("193") + glamour.LightStyleConfig.H1.StylePrimitive.BackgroundColor = stringptr("22") + glamour.LightStyleConfig.Heading.StylePrimitive.Color = stringptr("28") + glamour.LightStyleConfig.Code.StylePrimitive.Color = stringptr("22") + glamour.LightStyleConfig.Code.StylePrimitive.BackgroundColor = stringptr("194") + return + } + + glamour.DarkStyleConfig.H1.StylePrimitive.Color = stringptr("#cefcd3") + glamour.DarkStyleConfig.H1.StylePrimitive.BackgroundColor = stringptr("#2b3c2d") + glamour.DarkStyleConfig.Heading.StylePrimitive.Color = stringptr("#c0e394") + glamour.DarkStyleConfig.Code.StylePrimitive.Color = stringptr("#96b452") + glamour.DarkStyleConfig.Code.StylePrimitive.BackgroundColor = stringptr("#132b17") + + glamour.LightStyleConfig.H1.StylePrimitive.Color = stringptr("#cefcd3") + glamour.LightStyleConfig.H1.StylePrimitive.BackgroundColor = stringptr("#2b3c2d") + glamour.LightStyleConfig.Heading.StylePrimitive.Color = stringptr("#12731D") + glamour.LightStyleConfig.Code.StylePrimitive.Color = stringptr("#12731D") + glamour.LightStyleConfig.Code.StylePrimitive.BackgroundColor = stringptr("#cee3c4") +} diff --git a/internal/docs/html.go b/internal/docs/html.go index 2576d6c..2fea8d3 100644 --- a/internal/docs/html.go +++ b/internal/docs/html.go @@ -14,7 +14,6 @@ import ( "git.rob.mx/nidito/chinampa/pkg/command" "git.rob.mx/nidito/chinampa/pkg/render" chromahtml "github.com/alecthomas/chroma/v2/formatters/html" - "github.com/charmbracelet/glamour" "github.com/spf13/cobra" _c "github.com/unrob/milpa/internal/constants" "github.com/yuin/goldmark" @@ -23,32 +22,6 @@ import ( "github.com/yuin/goldmark/extension" ) -func stringptr(str string) *string { - return &str -} - -func init() { - var zero uint - glamour.NoTTYStyleConfig.Document.Margin = &zero - glamour.NoTTYStyleConfig.Document.StylePrimitive.Color = nil - - glamour.DarkStyleConfig.Document.Margin = &zero - glamour.DarkStyleConfig.Document.StylePrimitive.Color = nil - glamour.DarkStyleConfig.H1.StylePrimitive.Color = stringptr("#cefcd3") - glamour.DarkStyleConfig.H1.StylePrimitive.BackgroundColor = stringptr("#2b3c2d") - glamour.DarkStyleConfig.Heading.StylePrimitive.Color = stringptr("#c0e394") - glamour.DarkStyleConfig.Code.StylePrimitive.Color = stringptr("#96b452") - glamour.DarkStyleConfig.Code.StylePrimitive.BackgroundColor = stringptr("#132b17") - - glamour.LightStyleConfig.Document.Margin = &zero - glamour.LightStyleConfig.Document.StylePrimitive.Color = nil - glamour.LightStyleConfig.H1.StylePrimitive.Color = stringptr("#cefcd3") - glamour.LightStyleConfig.H1.StylePrimitive.BackgroundColor = stringptr("#2b3c2d") - glamour.LightStyleConfig.Heading.StylePrimitive.Color = stringptr("#12731D") - glamour.LightStyleConfig.Code.StylePrimitive.Color = stringptr("#12731D") - glamour.LightStyleConfig.Code.StylePrimitive.BackgroundColor = stringptr("#cee3c4") -} - //go:embed template.html var LayoutTemplate []byte @@ -70,14 +43,18 @@ type TemplateContents struct { func FixLinks(contents []byte) []byte { fixedLinks := bytes.ReplaceAll(contents, []byte("(/"+_c.RepoDocs), []byte("(/help/docs")) fixedLinks = bytes.ReplaceAll(fixedLinks, []byte("(/"+_c.RepoCommands+"/"), []byte("(/")) - fixedLinks = bytes.ReplaceAll(fixedLinks, []byte("index.md"), []byte("")) - return bytes.ReplaceAll(fixedLinks, []byte(".md"), []byte("/")) + fixedLinks = bytes.ReplaceAll(fixedLinks, []byte("index.md)"), []byte(")")) + fixedLinks = bytes.ReplaceAll(fixedLinks, []byte("index.md#"), []byte("#")) + fixedLinks = bytes.ReplaceAll(fixedLinks, []byte(".md)"), []byte("/)")) + return bytes.ReplaceAll(fixedLinks, []byte(".md#"), []byte("/#")) } func getHTMLLayout() (*template.Template, error) { return template.New("html-help").Funcs(render.TemplateFuncs).Parse(string(LayoutTemplate)) } +var notFoundContents = []byte("# Not found\n\nThat is weird, if you have a second and a github account, [let me know](https://github.com/unRob/milpa/issues/new?labels=docs&title=Page+not+found&template=docs-page-not-found.yml).\n") + func contentsForRequest(comps []string) ([]byte, string, error) { var cmd *cobra.Command var args []string @@ -93,9 +70,8 @@ func contentsForRequest(comps []string) ([]byte, string, error) { var helpMD bytes.Buffer if err != nil || (cmd == root && len(args) > 0) { log.Warnf("returning 404: %s, cmd: %s", comps, cmd.Name()) - contents := []byte("# Not found\n\nThat is weird, if you have a second and a github account, [let me know](https://github.com/unRob/milpa/issues/new?labels=docs&title=Page+not+found&template=docs-page-not-found.yml).\n") desc := "sub-command not found" - return contents, desc, fmt.Errorf("not found: %s", comps) + return notFoundContents, desc, fmt.Errorf("not found: %s", comps) } isDocsCommand := len(args) == 2 && (args[0] == "help" && args[1] == "docs") @@ -112,7 +88,8 @@ func contentsForRequest(comps []string) ([]byte, string, error) { log.Tracef("Rendering docs topic for %s", args) data, err := FromQuery(args) if err != nil { - return nil, "", fmt.Errorf("error: %s", err) + desc := "documentation topic not found" + return notFoundContents, desc, fmt.Errorf("docs topic not found: %s", comps) } helpMD.Write(data) } @@ -166,7 +143,7 @@ func EmbeddedStaticResourceHandler() http.Handler { func DevelopmentStaticResourceHandler() http.Handler { path := os.Getenv("MILPA_DOCS_STATIC_RESOURCES") if path == "" { - path = os.Getenv("MILPA_ROOT") + "/internal/docs/static" + path = os.Getenv("MILPA_ROOT") + "/internal/docs" } log.Warnf("Using static resources from %s", path) return http.FileServer(http.Dir(path)) @@ -189,18 +166,13 @@ func RenderHandler(serverAddr string) func(http.ResponseWriter, *http.Request) { contents, desc, err := contentsForRequest(comps) if err != nil { - if !strings.HasPrefix(err.Error(), "not found:") { - log.Errorf("could not get contents: %s", err) - w.WriteHeader(http.StatusInternalServerError) - return - } log.Errorf("404: %s", comps) w.WriteHeader(http.StatusNotFound) } md, toc, err := mdToHTML(contents) if err != nil { - log.Errorf("could convert to html: %s", err) + log.Errorf("could not convert to html: %s", err) w.WriteHeader(http.StatusInternalServerError) return } diff --git a/internal/docs/static/css/index.css b/internal/docs/static/css/index.css index 0dcda14..82d86b1 100644 --- a/internal/docs/static/css/index.css +++ b/internal/docs/static/css/index.css @@ -82,6 +82,16 @@ header .emoji-maiz:hover::before { mix-blend-mode: normal; } + +::selection { + background-color: #CEFCD3; +} + +header ::selection { + background-color: #5D7260; +} + + #command-selector { font-size: 32px; margin: 0; @@ -143,16 +153,16 @@ header .emoji-maiz:hover::before { #sidebar { position: fixed; - background: #fff; width: 200px; padding: 10px; top: 53px; height: calc(100vh - 73px); overflow-y: auto; z-index: 10; + background: #fff; } -#sidebar nav { +#table-of-contents { margin-bottom: 1.5em; } @@ -371,18 +381,59 @@ hr { z-index: 1000; } - @media screen and (prefers-color-scheme: dark) { body { color: #96b452; background-color: #2B3C2D; } + html { + scrollbar-color: #96b452 rgba(0,0,0,.1); + scrollbar-gutter: both-edges; + } + + ::selection { + background-color: #5D7260; + } + ::-moz-selection { + background-color: rgba(0,0,0,.6); + } + + header ::-moz-selection{ + background-color: #132b17; + } + #sidebar { background-color: #5D7260; color: #CEFCD3; } + ::-webkit-scrollbar { + width: 10px; + } + + ::-webkit-scrollbar-track-piece { + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); + border-radius: 10px; + } + + ::-webkit-scrollbar-track-piece { + background-color: ; + border-radius: none; + } + + ::-webkit-scrollbar-thumb { + background-color: #96b452; + border-radius: 5px; + width: 50%; + transition: background-color .5s ease-in-out; + } + ::-webkit-scrollbar-thumb:hover { + background-color: #c0e394; + border-radius: 5px; + width: 50%; + } + #sidebar .heading { color: #132b17; } diff --git a/internal/errors/errors.go b/internal/errors/errors.go index 7b92ed4..2db6085 100644 --- a/internal/errors/errors.go +++ b/internal/errors/errors.go @@ -23,7 +23,11 @@ type EnvironmentError struct { } func (err ConfigError) Error() string { - return fmt.Sprintf("Invalid configuration %s: %v", err.Config, err.Err) + if err.Config != "" { + return fmt.Sprintf("Invalid configuration %s: %v", err.Config, err.Err) + } + + return fmt.Sprintf("Invalid configuration: %v", err.Err) } func (err EnvironmentError) Error() string { @@ -39,7 +43,7 @@ func showHelp(cmd *cobra.Command) { } } -func HandleCobraExit(cmd *cobra.Command, err error) error { +func HandleExit(cmd *cobra.Command, err error) error { if err == nil { ok, err := cmd.Flags().GetBool("help") if cmd.Name() == "help" || err == nil && ok { @@ -59,7 +63,7 @@ func HandleCobraExit(cmd *cobra.Command, err error) error { logrus.Error(err) os.Exit(statuscode.NotFound) case ConfigError: - showHelp(cmd) + logrus.Info("run `milpa itself doctor` to diagnose your command") logrus.Error(err) os.Exit(statuscode.ConfigError) case EnvironmentError: diff --git a/repos/internal/commands/dev/test.sh b/repos/internal/commands/dev/test.sh index 68bf0c0..447550d 100644 --- a/repos/internal/commands/dev/test.sh +++ b/repos/internal/commands/dev/test.sh @@ -8,9 +8,12 @@ if [[ "$MILPA_OPT_COVERAGE" ]]; then milpa dev build --coverage milpa dev test unit --coverage milpa dev test integration --coverage + mkdir -p "$MILPA_ROOT/test/coverage/doctor" + GOCOVERDIR="$MILPA_ROOT/test/coverage/doctor" milpa itself doctor milpa dev test coverage-report milpa dev build else milpa dev test unit milpa dev test integration + milpa itself doctor fi diff --git a/repos/internal/commands/dev/test/coverage-report.sh b/repos/internal/commands/dev/test/coverage-report.sh index 158f2ad..b8f2f8d 100644 --- a/repos/internal/commands/dev/test/coverage-report.sh +++ b/repos/internal/commands/dev/test/coverage-report.sh @@ -15,3 +15,4 @@ go tool cover -html=test/coverage.cov -o test/coverage.html || @milpa.fail "coul @milpa.log complete "Coverage report built" go tool covdata percent -i="$packages" +go tool cover -func=test/coverage.cov | tail -n 1 diff --git a/repos/internal/commands/release/build.yaml b/repos/internal/commands/release/build.yaml index 5b9168a..faea7ec 100644 --- a/repos/internal/commands/release/build.yaml +++ b/repos/internal/commands/release/build.yaml @@ -2,16 +2,16 @@ summary: Packages and releases milpa and friends description: | Creates a folder, by default `dist` with: - - packages/milpa-$os-$arch.tgz: the packaged milpa executable, compa binary, for each supported arch - - packages/compa-$os-$arch.shasum: the shasum binary for each supported arch - - milpa.dev: html docs for commands, utils, and usage + - `packages/milpa-$os-$arch.tgz`: the packaged milpa kernel + - `packages/compa-$os-$arch.shasum`: the shasum signature for each supported arch + - `milpa.dev`: html docs for commands, utils, and usage The packaged milpa kernel contains: - the `milpa` executable, - the `compa` binary, - - milpa libraries, - - readme, license, and changelog + - the base command repository, + - readme, license, documentation, and changelog arguments: - name: version description: The semver number to build diff --git a/repos/internal/commands/release/create.yaml b/repos/internal/commands/release/create.yaml index 8403c10..431006e 100644 --- a/repos/internal/commands/release/create.yaml +++ b/repos/internal/commands/release/create.yaml @@ -1,6 +1,6 @@ summary: Creates a new tag and updates the changelog description: | - Automation might trigger a release if github is in a good mood + Figures out the next version by looking at pending changelog notes, then prompts to create a new tag. Automation might trigger a release if github is in a good mood. options: pre: values: diff --git a/test/_helpers/milpa/load.bash b/test/_helpers/milpa/load.bash index d3f5b4b..bc7e629 100644 --- a/test/_helpers/milpa/load.bash +++ b/test/_helpers/milpa/load.bash @@ -20,7 +20,7 @@ function _suite_setup() { export MILPA_ROOT="$XDG_DATA_HOME/var/lib/milpa" export PATH="$PROJECT_ROOT:$PATH" export NO_COLOR=1 - export MILPA_PLAIN_HELP=enabled + export MILPA_HELP_STYLE="markdown" export milpa="$MILPA_ROOT/milpa" mkdir -p "$XDG_DATA_HOME" [[ -f "$XDG_DATA_HOME/setup-complete" ]] && return 0 diff --git a/test/commands/help/docs.bats b/test/commands/help/docs.bats index 032d4af..9cf9a89 100644 --- a/test/commands/help/docs.bats +++ b/test/commands/help/docs.bats @@ -12,13 +12,13 @@ setup() { } @test "itself docs" { - run milpa help docs milpa + # regenerate with + # MILPA_HELP_STYLE=markdown MILPA_ROOT="$(pwd)" MILPA_PATH="$(pwd)/.milpa" MILPA_PATH_PARSED=true milpa help docs milpa | tee test/fixtures/readme.txt + run diff -u -L "live" <(milpa help docs milpa) -L "fixture" <(cat "$(fixture readme.txt)") assert_success - assert_output "$(cat "$(fixture readme.txt)")" - run milpa help docs milpa environment + run diff -u -L "live" <(milpa help docs milpa environment) -L "fixture" <(cat "$(fixture environment.txt)") assert_success - assert_output "$(cat "$(fixture environment.txt)")" run milpa __complete help docs "" assert_success @@ -37,6 +37,12 @@ Completion ended with directive: ShellCompDirectiveNoFileComp" @test "itself docs --server" { + # regenerate with + # MILPA_PATH="$(pwd)/.milpa:$(pwd)/test/.milpa" MILPA_PATH_PARSED=true milpa help docs --server + # curl http://localhost:4242/ > test/fixtures/index.html + # curl http://localhost:4242/help/docs/ > test/fixtures/docs.html + # curl http://localhost:4242/help/docs/milpa/ > test/fixtures/readme.html + # curl http://localhost:4242/help/docs/milpa/environment/ > test/fixtures/environment.html milpa help docs --server & server="$!" tries=0 @@ -47,7 +53,7 @@ Completion ended with directive: ShellCompDirectiveNoFileComp" fi echo "waiting for server to come up" - sleep 1 + sleep .1 done curl --max-time 2 --fail --silent --show-error http://localhost:4242/help/docs/ > "docs.html" curl --max-time 2 --fail --silent --show-error http://localhost:4242/help/docs/milpa/ > "readme.html" @@ -56,15 +62,15 @@ Completion ended with directive: ShellCompDirectiveNoFileComp" kill -SIGINT "$server" - run diff "index.html" "$(fixture index.html)" + run diff -u -L fixture "$(fixture index.html)" "index.html" assert_success - run diff "readme.html" "$(fixture readme.html)" + run diff -u -L fixture "$(fixture readme.html)" "readme.html" assert_success - run diff "environment.html" "$(fixture environment.html)" + run diff -u -L fixture "$(fixture environment.html)" "environment.html" assert_success - run diff "docs.html" "$(fixture docs.html)" + run diff -u -L fixture "$(fixture docs.html)" "docs.html" assert_success } diff --git a/test/compa.bats b/test/compa.bats index 3fefa85..30b61ae 100644 --- a/test/compa.bats +++ b/test/compa.bats @@ -20,6 +20,12 @@ setup () { assert_equal "$output" "" } +@test "compa exits correctly on bad MILPA_ROOT" { + MILPA_ROOT="$BATS_TEST_FILENAME" + run -78 compa +} + + @test "compa exits correctly on bad commands" { run -127 --separate-stderr compa bad-command assert_equal "$output" "" diff --git a/test/fixtures/docs.html b/test/fixtures/docs.html index dc06541..c91f41c 100644 --- a/test/fixtures/docs.html +++ b/test/fixtures/docs.html @@ -84,13 +84,17 @@

m In this page
  • - description: Displays docs on TOPIC -
  • Usage -
  • - Available topics:
  • Description + +
  • + Available topics
  • Arguments
  • @@ -196,16 +200,22 @@

    m

    milpa help docs

    -
    -

    Displays docs on TOPIC

    +

    Displays docs on TOPIC

    Usage

    milpa help docs [options] [TOPIC...]

    -

    Available topics:

    Description

    Shows markdown-formatted documentation from milpa repos. See milpa help docs milpa repo docs for more information on how to write your own.

    -

    An HTTP server to browse documentation can be started by running:

    +

    Server mode

    An HTTP server to browse documentation can be started by running:

    milpa help docs --server
    -

    Arguments

      +# then head to http://localhost:4242 +

      Command and docs are available at their names, replacing spaces with forward slashes /, for example:

      +
        +
      • http://localhost:4242/help/docs will show this help page.
      • +
      • http://localhost:4242/itself/doctor shows documentation for the milpa itself doctor command.
      • +
      • http://localhost:4242/help/docs/milpa renders the file at .milpa/docs/milpa.md (or .milpa/docs/milpa/index.md).
      • +
      +

      Available topics

      +

      Arguments

      • TOPIC... - The topic to show docs for

      Options

        @@ -220,7 +230,6 @@

        milpa help docs

      • --silent (bool): Silence non-error logging.
      • --skip-validation (bool): Do not validate any arguments or options.
      • --verbose (bool): Log verbose output to stderr.
      • -
      • --version (bool): Display program version and exit.
    diff --git a/test/fixtures/environment.html b/test/fixtures/environment.html index cafe2ea..04d5ee9 100644 --- a/test/fixtures/environment.html +++ b/test/fixtures/environment.html @@ -110,7 +110,7 @@

    m NO_COLOR / COLOR

  • - MILPA_HELP_STYLE / MILPA_PLAIN_HELP + MILPA_HELP_STYLE
@@ -257,8 +257,8 @@

milpa help docs milpa environment

MILPA_SILENT

Enabled by the --silent option, to hide @milpa.log messages completely. If DEBUG or MILPA_VERBOSE are enabled, these will override MILPA_SILENT.

NO_COLOR / COLOR

By default, when stdout is a TTY, milpa will output color escape characters. If NO_COLOR is set to any non-empty string, then no color will be output. If not connected to a TTY, and COLOR=always is set, then color escape characters will be printed.

If either --no-color or --color options are provided, these will override both COLOR and NO_COLOR environment variables.

-

MILPA_HELP_STYLE / MILPA_PLAIN_HELP

MILPA_HELP_STYLE can be one of auto, dark, and light and specifies the theme to use when rendering help pages. This option has no effect if --no-color or NO_COLOR=1 is specified.

-

MILPA_PLAIN_HELP will not colorize help output at all, formatting help as plain markdown text instead.

+

If COLORTERM is set to truecolor, 24-bit colors will be used instead of the default 256-color palette.

+

MILPA_HELP_STYLE

MILPA_HELP_STYLE controls the theme to use when rendering help pages, and must be one of auto, dark, light, and markdown.


Input

MILPA_SKIP_VALIDATION

If enabled, validation will be skipped for arguments and options. Also enabled with --skip-validation. Skipping validation may be unsafe, but may be useful when validation depends on unavailable data or services.


diff --git a/test/fixtures/environment.txt b/test/fixtures/environment.txt index 831c2dc..01355cf 100644 --- a/test/fixtures/environment.txt +++ b/test/fixtures/environment.txt @@ -54,11 +54,11 @@ By default, when stdout is a TTY, `milpa` will output color escape characters. I If either `--no-color` or `--color` options are provided, these will override both `COLOR` and `NO_COLOR` environment variables. -### `MILPA_HELP_STYLE` / `MILPA_PLAIN_HELP` +If `COLORTERM` is set to `truecolor`, 24-bit colors will be used instead of the default 256-color palette. -`MILPA_HELP_STYLE` can be one of `auto`, `dark`, and `light` and specifies the theme to use when rendering help pages. This option has no effect if `--no-color` or `NO_COLOR=1` is specified. +### `MILPA_HELP_STYLE` -`MILPA_PLAIN_HELP` will not colorize help output at all, formatting help as plain markdown text instead. +`MILPA_HELP_STYLE` controls the theme to use when rendering help pages, and must be one of `auto`, `dark`, `light`, and `markdown`. --- diff --git a/test/fixtures/index.html b/test/fixtures/index.html index 1ddfa45..ced163d 100644 --- a/test/fixtures/index.html +++ b/test/fixtures/index.html @@ -192,8 +192,8 @@

milpa

milpa

Runs commands found in .milpa folders

Usage

milpa [--silent|-v|--verbose] [--[no-]color] [-h|--help] [--version] SUBCOMMAND

-

Description

milpa is a command-line tool to care for one's own garden of scripts, its name comes from an agricultural method that combines multiple crops in close proximity. You and your team write scripts and a little spec for each command -use bash, or any other language-, and milpa provides autocomplete, sub-commands, argument parsing and validation so you can skip the toil and focus on your scripts.

-

See milpa help docs milpa for more information about milpa

+

Description

milpa is a command-line tool to care for one's own garden of scripts, providing autocomplete, sub-commands, argument parsing and validation, along beautiful documentation—just add a simple YAML spec and a bash (or your language of choice) script.

+

See milpa help docs milpa for more information about milpa.

Subcommands