Skip to content

Commit

Permalink
moar docs
Browse files Browse the repository at this point in the history
  • Loading branch information
unRob committed Jan 18, 2023
1 parent 709e2e4 commit 05d09ca
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 91 deletions.
194 changes: 119 additions & 75 deletions .milpa/docs/milpa/command/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,56 @@ related-docs: [milpa/command/environment, milpa/repo/index, milpa/repo/docs]
related-commands: ["itself create", "itself docs generate"]
index: Command specs
---
Command specs go along with your scripts and help inform `milpa` of what its input should look like. Based on it, `milpa` will produce help pages and autocompletions, and may validate the arguments to your command.
Command specs go along with your scripts and help inform `milpa` of what its input should look like. Based on it, `milpa` will produce help pages and word completions, and may validate the arguments to your command.

Specs must be written in YAML, have a `yaml` extension, and be named exactly like your command (minus the extension, if any). For example, given a command at `.milpa/commands/my-command.sh` the corresponding spec file would be `.milpa/commands/my-command.yaml`.

## Example

If we wanted to have a command written in bash at `milpa release`, we'll need to create file at `.milpa/commands/release.sh` and its corresponding spec at `.milpa/commands/release.yaml`. Let's take a look at what such a spec might look like:

```yaml
summary: Create a github release
description: |
This command shows you a changelog and waits for approval before generating and pushing a new tag, creating a github release, and opening the browser at the new release.
## Schemes
You may choose between [semver](https://semver.org) and [calver](https://calver.org). Their composition is as follows:
- **semver**: `{major}.{minor}.{patch}`, i.e. `3.14.1592`
- **calver**: `{date}.{minor}.{micro}`, where date is derived from the `prefix` option; for example `16.18.339`.
arguments:
- name: increment
descrption: the increment to apply to the last git version
default: patch
required: true
values:
static: [micro, patch, minor, major]
options:
scheme:
description: Determines the format of the tags for this repo.
short-name: s
default: semver
values:
static: [semver, calver]
prefix:
description: |
An optional prefix to prepend to release identifiers. If `calver` is chosen as `scheme`, you may specify a combination of `YY`, `YYYY`, `MM`, and `DD` to be replaced with the corresponding values of the local date. The default in that case is `YY`.
default: ""
ask:
description: Prompt for confirmation before creating a github release
type: bool
default: false
```
## The Basics
```yaml
# a summary is required. It shows up during autocomplete and command listings
summary: Create a github release
# a description is required as well. Add a longer description of how your command does its magic here
# a description is required as well, and describes how your command does its magic
# it may be formatted with markdown
description: |
This command shows you a changelog and waits for approval before generating and pushing a new tag, creating a github release, and opening the browser at the new release.
Expand All @@ -24,104 +62,110 @@ description: |
- **semver**: `{major}.{minor}.{patch}`, i.e. `3.14.1592`
- **calver**: `{date}.{minor}.{micro}`, where date is derived from the `prefix` option; for example `16.18.339`.
# a list of arguments expected by your command, if any
# as well as a map of options
# see below for more details on options and arguments
arguments: []
options: {}
```
## Arguments
The `arguments` list describes the positional arguments that may be passed to a command. Arguments require a `name` and a `description`. The `name` of the argument will become available to commands through the environment variable named `MILPA_ARG_$NAME` where `$NAME` means the uppercased value of the `name` property. For example, an argument with `name: increment` will be available as your command's environment variable `MILPA_ARG_INCREMENT`. Arguments are passed as positional arguments to your command, i.e. `$1`, `$2`, and so on. Dashes (`-`) will be turned into underscores `_` (`name: my-argument` turns into `MILPA_ARG_MY_ARGUMENT`).

> ⚠️ Argument names need to be valid as environment variable names, and characters `!$\/%^@#?:'"` are not allowed.

```yaml
# arguments is an ordered list of to arguments expected by your command
arguments:
# each argument gets a name, your script will find these in the environment
# so in this example, increment becomes $MILPA_ARG_INCREMENT
# but it's also available as the first argument (i.e. `$1` in bash)
# `!$\/%^@#?:'"` are all disallowed characters in argument names.
# available in to the command as the MILPA_ARG_INCREMENT environment variable
- name: increment
# arguments require a description
# this description shows up during auto-completion and in the command's help page
descrption: the increment to apply to the last git version
# arguments can have a default
# a default may be specified, it'll be passed to your command if none is provided
default: patch
# or be required, but not have a default and be required at the same time.
# 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
required: true
# arguments may be variadic, that is, be all the arguments passed by the user
# starting at the position of this argument forwards, in this case, since
# there's only one argument, it would mean all arguments after the command name
# (not including options)
# 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
# the command name (not including options)
# should an argument be variadic, it's `default:` then must also be a list!
variadic: true
# argument values can come from many sources:
# dirs, files, milpa, script or static can be used for any argument
# if specified, arguments will be validated by default before running the command
values:
# autocompletes directory names only, if a prefix is passed
# then it'll be used as a prefix to search from
dirs: "prefix"
# autocompletes files with the given extensions, if any
files: [yaml, json, hcl]
# milpa runs the subcommand and offers an option for every line of stdout
# Options and arguments may be used within go templates. For example,
# the following would execute `milpa itself increments --scheme semver`
# {{ Current }} will insert the argument's current value durint autocompletion
milpa: itself increments {{ Opt "scheme" }}
# script runs the provided command with `bash -c "$script"` and offers
# each line of stdout as an option during autocomplete
# go templates can be used in `script` as well
script: git tag -l {{ Opt "prefix" }}
# arguments can be validated to be part of a static set of values
static: [micro, patch, minor, major]
# when using script or milpa values, wait at most this
# amount of seconds before giving up during autocomplete or validation
timeout: 10
# only suggest these as autocompletions but don't validate them before running
suggest-only: true
# if enabled, will not add a space after a suggestion
suggest-raw: false
# the `values` property specifies how to provide completions and perform validation on
# the values provided at the command line
values: {}
```
## Options
The `options` map describes the named options that may be passed to a command. Options require a `name` and a `description`. The `name` of the option will become available to commands through the environment variable named `MILPA_OPT_$NAME` where `$NAME` means the uppercased value of key for an option. For example, an option at the key `scheme` will be available as your command's environment variable `MILPA_OPT_SCHEME`.

> ⚠️ Options are not available as positional arguments to your command. The same character restrictions as arguments apply to options.

```yaml
# options, also known as flags, are specified as a map
options:
# this sets the --scheme option
# it will be available to your script as the $MILPA_OPT_SCHEME environment variable
# your users will be able to use `--scheme "semver"` or `--scheme=semver` for example
# and may be specified on the command line as either `--scheme "semver"` or `--scheme=semver`.
scheme:
# options require a description
# 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`
short-name: s
# and they may have defaults
# a default value may be passed to the command if none is provided by the user
default: semver
# or be required, but not have a default and be required at the same time.
required: true
# as well as arguments, flags can be specified multiple times and be passed as a list to your script
repeated: false
# option values can come from/be validated against many sources as well!
# the values provided at the command line
# flags can be boolean. Since environment variables can only be strings,
# false values (the default) will be passed as an empty string "", while
# true values will be passed as the string "true"
type: bool # or `string`
# the `values` property specifies how to provide completions and perform validation on
values: {}
```
## Value completion and validation
A `values` property may be specified for both arguments and options; `milpa` will provide completion and validation from the following sources:

- directories (with a matching prefix),
- files (with given extensions),
- stdout of any `milpa` sub-commands,
- stdout of any given bash script, and
- a static list of pre-defined values.

```yaml
# if specified for any `argument` or `option`
#
# `dirs`, `files`, `milpa`, `script`, or `static`
values:
# autocompletes directory names only, if a prefix is passed
# then it'll be used as a prefix to search from
dirs: "prefix"
# autocompletes directory names only. If a prefix is set, it'll be used as a
# prefix to filter matches. In this example, it would offer directories with a name
# that begins with `config` as completion values.
# Validation of provided values is never performed
dirs: "config"
# autocompletes files with the given extensions, if any
# Validation of provided values is never performed
files: [yaml, json, hcl]
# milpa runs the subcommand and returns an option for every line of stdout
milpa: "itself repo list"
# script runs the provided command with `bash -c "$script"` and returns an
# option for every line of stdout
script: "git tag -l"
# arguments can be validated to be part of a static set of values
# `milpa` runs the named milpa sub-command, offering a completion for every line of stdout
# Options and arguments to sub-commands can be provided as go templates. For example,
# the following would run `milpa itself increments --scheme semver`
# {{ Current }} will insert the argument's current value durint completion
milpa: itself increments {{ Opt "scheme" }}
# script runs the provided command with `bash -c "$script"` and offers
# each line of stdout as an option during autocomplete
# go templates can be used in `script` as well
script: git tag -l {{ Opt "prefix" }}
# values can be validated to be part of a given set of strings
static: [micro, patch, minor, major]
# when using script or milpa values, wait at most this
# amount of seconds before giving up during autocomplete or validation
# when using `script` or `milpa` values, wait at most this amount of seconds
# before erroring out during autocomplete or validation
timeout: 10
# only suggest these as autocompletions but don't validate them before running
# only suggest values as completions but don't validate them before running
# has no effect for `dirs` or `files` as these are always suggestions and never validated
suggest-only: true
# if enabled, will not add a space after a suggestion
# if enabled, will not add a space after suggestions during autocomplete
suggest-raw: false

prefix: # becomes MILPA_OPT_PREFIX
description: |
An optional prefix to prepend to release identifiers. If `calver` is chosen as `scheme`, you may specify a combination of `YY`, `YYYY`, `MM`, and `DD` to be replaced with the corresponding values of the local date. The default in that case is `YY`.
# if desired, defaults can be set to empty strings to be explicit
default: ""
ask: # becomes MILPA_OPT_ASK
description: Prompt for confirmation before creating a github release
# flags can be boolean. Since environment variables can only be strings,
# these false values (or defaults) will be set as an empty string, while
# true values will be set as "true"
type: bool
default: false

```
2 changes: 1 addition & 1 deletion .milpa/docs/milpa/quick-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ weight: 1
Getting started with `milpa` is a matter of following these steps:

0. Installing it,
1. installing milpa's autocompletion script to your shell, possibly restarting your session,
1. installing milpa's autocomplete script to your shell, possibly restarting your session,
2. creating a new command, and editing it's spec
3. running the command to test it out

Expand Down
12 changes: 7 additions & 5 deletions .milpa/docs/milpa/use-case.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,29 @@ Here's some examples of how I've used used `milpa` so far:

- for **managing homelab services** (i.e. [unRob/nidito](https://github.com/unRob/nidito/tree/master/.milpa)): building and deploying them, looking at their status and logs. From my personal device or CI.
- **bootstrapping engineering laptops** (i.e. [unRob/dotfiles](https://github.com/unRob/dotfiles/tree/master/.milpa/commands/computar)): no need to follow a README, you get the right development environment for your os/arch, your credentials setup and the code ready for you to dive in.
- **every-day dev workflow** (i.e. [unrRob/milpa](https://github.com/unRob/milpa/tree/main/repos/internal/commands/)): lint and test a codebase, connect to vpn, get credentials to resources, maybe `--connect` to them, abstract away APIs (internal, cloud provider and SaaS) and CLIs, toggle feature gates, build reports and update google sheets with the results.
- **every-day dev workflow** (i.e. [unRob/milpa](https://github.com/unRob/milpa/tree/main/repos/internal/commands/)): lint and test a codebase, connect to vpn, get credentials to resources, maybe `--connect` to them, abstract away APIs (internal, cloud provider and SaaS) and CLIs, toggle feature gates, build reports and update google sheets with the results.
- as a way to organize all those odd, one-off-but-not-really commands: found a nice home for [shell scripts](https://github.com/unRob/dotfiles/blob/master/.milpa/commands/code/todo.sh), quick ruby scripts, perl hacks and [jq monstrosities](https://github.com/unRob/dotfiles/blob/master/.milpa/commands/creds.sh) that used to live in my `~/.zsh_history`.

I found `milpa` useful for these particular problems, since it only requires Bash and a supported OS/arch. `milpa` helps setup the computers I use (directly or remotely) and makes working with whatever languages my employer prefers (today and beyond) a breeze to integrate into a cohesive set of scripts. `milpa` does the work of parsing arguments, validating them, offering help when things go sideways and so on, so these small scripts remain just so: small scripts.
I found `milpa` useful for these particular problems, since it only requires Bash and a supported OS/arch. `milpa` helps setup the computers I use (directly or remotely) and makes working with whatever languages my employer prefers (today and beyond) a breeze to integrate into a cohesive set of scripts. `milpa` takes care of parsing, validation, offering help when things go sideways, and so on, so these small scripts remain just so: _small scripts_.

## Where `milpa` won't shine

I haven't tested the performance beyond dozens of repos with dozens of commands, and that being said, I can't see myself using `milpa` for anything more complex than that. I'd usually reach for another language to build a domain-specific CLI, specially if working with a team that is not very comfortable with shell scripting.

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). That being said, it can be done and it works fine: I've used `milpa` to orchestrate and operate a raspberry pi running an art installation.
`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).

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.


## Alternatives to `milpa`

### Regular scripts in the filesystem

These are great, as long as you know the path to your script, it doesn't consume many arguments/options, rarely changes, and/or is used only by the same few folks.
These are great, as long as: a) you know the path to your script, b) it doesn't consume many arguments/options, c) rarely changes, and/or d) is used only by the same few folks.

`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 auto completion 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.
`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

Expand Down
Loading

0 comments on commit 05d09ca

Please sign in to comment.