Mastering KWT: Harnessing YAML to Define Powerful CLI Actions (Part 2)
In part 2 of our series on kwt, we'll explore how YAMLs are used to configure this CLI utility. If you haven't already, please install kwt by following the guide and read part 1 for context.
What are Menus, Actions, and Params?
The core structures of kwt are defined in menu.go. While the code references JSON for struct tags, the actual config files use YAML format. Let's break down these structures:
// Param struct: a parameter for an 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 in kwt is an action, which represents a command. It consists of:
- Name: A unique key for this action.
- Help: An optional help message for this action.
- Template: A Go template for this action, renderable with input parameters.
- Params: An optional list of input parameter definitions.
A param defines an input parameter for an action and includes 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, which kwt can then execute.
Let's look at some examples:
find-vim
From developer-demo.yaml:
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's one param called target
. The placeholder {{.target}}
in the template is filled at run-time to produce an executable command. If target
is Apple.java
, the template renders to:
vim $(find . -name 'Apple.java' | head -1)
If no target
is provided, it uses the default value README.md
:
vim $(find . -name 'README.md' | head -1)
Kwt executes the rendered command, which opens vim with the specified file.
uuid
From python-demo.yaml:
name: uuid
help: Generate a UUID
template: python -c 'import uuid; print(str(uuid.uuid4()))'
An action doesn't require params. If none are defined, the template renders to itself and is executed. The benefit here is that the action is a predefined command with a descriptive name.
forex-rate
Also from python-demo.yaml:
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
Templates can be multi-line. As long as the rendered content can be executed as a bash or Windows BAT script, it should work. The cmd.go file actually writes the content to a file then calls the shell to execute it. You can write complex Python scripts, but be cautious with using "
as it's already used to quote the script. Go templating also allows for advanced features like if-else statements and ranges.
Menus
A menu is a YAML file representing a collection of actions that are managed and distributed together. A menu includes:
- A list of actions
- A name
- A help message (similar to actions)
- An optional version to convey the evolution of a menu
- A hash (checksum) defined in code for other purposes
Examples of menus can be found in the kwt-menus repository.
Working with Menus
How do I use menus?
To use a menu, ingest it into your system with the ingest
command from kwt
. It accepts either a local file or an 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
For more information, read and follow the help, or refer to the quick start guide:
kwt -h
Where are the YAMLs stored?
Kwt ingests menus to and reads menus from $HOME/.kwt/menus/
. It attempts to load every file in this directory as a menu YAML. Files that fail to load are ignored without warnings (which should probably be added in the future). This can happen when menus have syntax errors. The environment variable KUT_HOME
can be used to override the directory path to $KUT_HOME/menus
.
How to develop menus?
To create new menus:
- Copy an existing menu to a new file under
$HOME/.kwt/menus/
. - Ensure the
name
defined inside the YAML matches the new file name to avoid confusion. - Modify the new menu and verify it appears in
kwt -h
. - If your menu doesn't show up in kwt, it likely has a YAML syntax error.
- Continue refining your menu as needed.
- Optionally, give the menu a version number.
How to distribute menus?
As kwt's user base grows, menu distribution will need more consideration. Currently, the recommended approach is for authors to publish menus as publicly available text files (e.g., raw GitHub files) and instruct users to ingest them with kwt i
. If menu distribution becomes more prevalent, a system similar to Homebrew taps might be implemented.
Conclusion
Understanding how to configure kwt using YAMLs is crucial for maximizing its potential. By creating and distributing menus with well-defined actions and params, you can streamline your command-line workflows and share them with others. As kwt evolves, expect to see more robust features for menu management and distribution.