Run commands easily with kwt part 2: defining menus, actions, and params with YAML
In part 2, I will explain how YAMLs are used to configure kwt. Please install kwt by following the guide and read part-1 if interested.
What are menus, actions, and params?
The structures are defined in menu.go. Note the actual config files are YAML even though the code references JSON.
// Param struct: a parameter for a action
type Param struct {
Name string `json:"name" binding:"required"`
Help string `json:"help"`
Value string `json:"value"`
}
// Action struct: a thing a user can do
type Action struct {
Name string `json:"name" binding:"required"`
Help string `json:"help"`
Template string `json:"template" binding:"required"`
Params []Param `json:"params"`
}
// Menu struct: a list of actions
type Menu struct {
Name string `json:"name" binding:"required"`
Version string `json:"version"`
Help string `json:"help"`
Actions []Action `json:"actions" binding:"required"`
Hash string `json:"hash"`
}
Actions & params
The core concept is an action, which represents an command. It consists of
- Name: the unique key for this action.
- Help: the help message for this action (optional).
- Template: the Go template for this action, renderable with input parameters.
- Params: list of input parameter definitions (optional).
A param defines a input parameter for an action, and is relatively simple. It consists of a name, a help message, and an optional default value. A fully defined action allows a user to render a templated command by applying parameter values, so that kwt can execute it.
find-vim
name: find-vim
help: Find the first file with given name in directory and edit it with vim
template: vim $(find . -name '{{.target}}' | head -1)
params:
- name: target
help: Target file name / directory
value: README.md
In this example, there is one param called target
, and with it the placeholder {{.target}}
in template can be filled at run-time (after kwt is called and before the rendered command executes) to produce an executable command. If target
is Apple.java
, the template renders to.
vim $(find . -name 'Apple.java' | head -1)
The default target
is README.md
, so if no target
is provided, the template renders to.
vim $(find . -name 'README.md' | head -1)
Kwt executes the rendered command, which will consequently open a vim
.
uuid
name: uuid
help: Generate a UUID
template: python -c 'import uuid; print(str(uuid.uuid4()))'
An action is not required to have params, if no params are defined, the template just renders to itself and gets executed. The benefit is the action is a predefined command and has a name.
forex-rate
name: forex-rate
help: Print forex rates
template: |
python3 -u -c "
import urllib.request
import urllib.parse
import json
url = 'https://api.exchangeratesapi.io/latest?base={{.base}}&symbols={{.symbols}}'
r = json.load(urllib.request.urlopen(url))
date = r['date']
rates = r['rates']
print(f'On {date}')
print('\n'.join(
f'1 {{.base}} can buy {rates[symbol]:.2f} {symbol}'
for symbol in rates
))
"
params:
- name: base
help: Base currency
value: USD
- name: symbols
help: Comma separated currency symbol list
value: CAD,GBP
The template can be multi-line. As long as the rendered can be executed as a bash or Windows BAT script, it should work (cmd.go will actually write the content to a file then call shell to execute it). I tried going crazy and wrote long Python scripts, it generally works, unless you need to use "
, which is already used to quote the script. One can also go crazy with Go templating with if-else, range, etc.
Menus
A menu is a YAML file, it represents a collection of actions that are managed and distributed together. A menu has a list of actions, and a name and a help much like an action. It also has an optional version which the author can use to convey the evolution of a menu. A hash (checksum) is defined in code for other purposes, and kwt does not expect the YAML to carry its own checksum. Examples are in kwt-menus.
How do I use menus?
To use a menu, ingest it into your system with ingest
command from kwt
. It takes either a local file or a HTTP link, and overrides the existing menu with the same name.
kwt i -s ~/Download/python-demo.yaml
kwt i -s https://raw.githubusercontent.com/bettercallshao/kwt-menus/master/python-demo.yaml
Read and follow help, or read guide.
kwt -h
Where are the YAMLs?
Kwt ingests menus to and reads menus from $HOME/.kwt/menus/
. It tries to load every file in this directory as a menu YAML. If a file fails to load, it is ignored with no warnings (should probably add some), which unfortunately happens when menus have bad syntax. Environment variable KUT_HOME
can be used to override the directory path to $KUT_HOME/menus
.
How to develop menus?
To write new menus.
- Copy an existing menu to a new file under
$HOME/.kwt/menus/
. - Make sure the
name
defined inside the YAML matches the new file name. Otherwise it's confusing and untested. - Modify the new menu and you should see it in
kwt -h
. - If you can't find your menu in kwt, it probably has a YAML syntax error.
- Hack away!
- Optionally give the menu a version.
How to distribute menus?
Since I have been the only user, this has not been thought through. The current guide is that the author should publish a menu as a publicly available text file (e.g. raw github file) and ask the user to ingest it with kwt i
. If menu distribution becomes a thing, we should probably do something similar to Homebrew taps.