Flags solves the "how the heck do I parse command line args" problem.
What makes it different from the other 10000 solutions?
flags
parses, validates, and generates help messages for all your arguments and flags from a completely declarative configuration.- You can compile your bash scripts and plugins so your users can enjoy them without ANY required dependencies!
Some other bonuses:
- Supports Sub-commands (think 'git log', 'git rebase', etc.)
- Configuration is entirely declarative, no fiddling with args yourself!
- Supports both short and long flags (at the same time)
- Allows passing multiple of the same flag
- Yaml config
- Automatically generates the proper 'help' messages.
- Support '--' passthrough flag
- Default flags
- Required vs optional arguments
- Npm install
- Default args
- Supports custom shebang:
#!/usr/local/bin/flags shebang
- Init command
- Validate argument types (string, number, file, dir, path)
- Single top-level command
Upcoming features:
- Tab-complete
How do we achieve all this ✨magic✨ you ask?
Easy! Flags just generates bash for you!
During development of your script or plugin you'll run the flags
program locally,
it parses your configuration and helps you run your program.
When you're ready to ship your script or plugin, simply run flags build
to activate compiler mode.
It'll bundle generated bash with your plugin or script into a dependency-free bash script you can distribute to your users.
Let's say we're writing a todo app, as all programmers are legally obligated to do every 3 months.
First off, here's what it looks like to use the app:
$ todo add "Microwave Pizza Pops™"
$ todo add "Finish writing the README"
$ todo list
1 Finish writing the README
2 Microwave Pizza Pops™
# List todos in reverse using an optional '-r' short-flag
$ todo list -r
2 Microwave Pizza Pops™
1 Finish writing the README
# Search todos using a 'query' long-flag
$ todo list --query Pizza
2 Microwave Pizza Pops™
# Print help/usage info
$ todo help
Usage:
todo <command>
More info:
todo [command] --help
Commands:
todo add [todo...]
todo list [-r|--reverse] [-q|--query=<query>]
We can see we've got three sub commands; add
, list
, and help
(which is generated for you).
We've also got some flag option for list
. One called query
which takes an argument, one called reverse
with a -r short-flag
Here's the whole source:
#!/usr/local/bin/flags shebang
LIST_LOCATION="$HOME/.todos"
# Define our 'add' sub command
# Arguments will be provided as expected in "$@"
function add {
for todo in "$@" ; do
echo "$todo" >> "$LIST_LOCATION"
done
}
# Define our 'list' sub command
# Our flags for this sub-command will be parsed and provided as environment variables
function list {
if [[ -n "$reverse" ]]; then
reverser="tac"
else
reverser="cat"
fi
if [[ -n "$query" ]]; then
filterer="grep $query"
else
filterer="cat"
fi
cat -n "$LIST_LOCATION" | \
$filterer | \
$reverser
}
# We don't need to call any of these functions, 'flags' will pick the right command and run it for us.
And we've got a config file at flags.yaml
which looks like this:
- name: add
description: "Add a todo to the list"
args:
- name: todo
description: "The todos you'd like to add"
multiple: true
- name: "list"
description: "List out your existing TODOs"
flags:
- name: "reverse"
description: "Reverse the TODO list"
- name: "query"
description: "List only TODOs containing this text"
arg:
type: string
required: false
That's it!
There are a few main commands in flags
: run
, build
, init
, and shebang
flags build
is used to compile flags
' argument handling logic into a simple bash script.
This is useful either when distributing your script to others where flags
may not be installed. You may also wish to to do this for your scripts locally to gain a teensy bonus on startup time for your script (though flags
is generally pretty fast).
To compile our todo
cli we'd run this:
flags build todo.sh > todo-cli
Where todo-cli
is the location you'd like to write the resulting bash script. You can also optionally provide a -o todo-cli
if you prefer to write directly rather than redirecting stdout.
The resulting script will include your logic from the original script, as well as all of the argument and flag parsing logic, and will patch the shebang to be #!/bin/bash
.
flags run
is a command for when you're working on your script.
Pass it a script and some args and flags
will parse the arguments and run the script.
For our todo-list example it looks like this:
flags run todo.sh -- list -r
2 Microwave Pizza Pops™
1 Finish writing the README
flags run
looks for a flags.yaml config in the same directory as the script, but you can specify a config with -f
if needed.
flags init
will create a helpful sample flags.yaml
in the current directory.
You can embed flags shebang
into your script as a shebang by add this as your script's first line:
#!/usr/local/bin/flags shebang
Where /usr/local/bin/flags
is replaced by the result of running which flags
on your system.
Note that due to limitations of using a shebang unfortunately you can't specify any configuration options when using the shebang style.
You can configure your script using flags.yaml
.
name: command-name
# This description is printed in the help message
description: "This is a command"
# Argument configuration
args:
# The name of a positional argument
- name: positional-argument
# This description is printed in the help message
description: "A positional argument"
# (default: false) Whether multiple values can be provided for this argument
multiple: false
# (default: true) Whether the argument is required or optional
required: true
# (default: null) A default value for optional arguments
default: null
flags:
# (default: first char of long-name)
- shortName: "f"
# (required) Both the name of the flag, and the name of the environment variable which it will be bound to.
# dashes will be replaced with underscores in variable names
name: "flag"
# This description is printed in the help message
description: "A flag option"
# (default: false) Whether the flag can be provided multiple times
multiple: false
# (default: null) The configuration for the flag's argument if it taks one
arg:
# (default: null) A default value for optional arguments
default: null
# (default: false) Whether the argument is required or optional
required: false
# (default: string) The type of validations to run on the argument.
# Options include: [string, number, file, dir, path]
type: string
If the top level of your yaml file is a list of commands they'll be treated like subcommands.
- name: sub-command-1
description: "This is a command"
args:
- name: positional-argument
description: "A positional argument"
multiple: false
required: true
default: null
flags:
- shortName: "f"
name: "flag"
description: "A flag option"
multiple: false
arg:
default: null
required: false
type: string
- name: sub-command-2
description: "This is a command"
args:
- name: positional-argument
description: "A positional argument"
multiple: false
required: true
default: null
flags:
- shortName: "f"
name: "flag"
description: "A flag option"
multiple: false
arg:
default: null
required: false
type: string