Joshua Potter 403a5a641e | ||
---|---|---|
.githooks | ||
docs | ||
include | ||
specs | ||
src | ||
test | ||
.clang-format | ||
.envrc | ||
.gitignore | ||
CMakeLists.txt | ||
README.md | ||
compile_commands.json | ||
default.nix | ||
flake.lock | ||
flake.nix | ||
main.c |
README.md
bootstrap
CLI utility for defining custom project initialization scripts.
Overview
bootstrap
is a tool for quickly defining your own init-like scripts. If you
are familiar with tools like
npm init
nix flake init
django-admin startproject
mix phx.new
- etc.
this project will feel at home. Ultimately the goal is to create (optionally) interactive scripts like those mentioned in the above list to quickly scaffold your new projects in a consistent way.
We start with an example. Consider the following spec, which we'll name
touch
(this example exists as a pre-packaged spec):
{
"filename": {
"type": "line",
"prompt": "What file should I create for you? "
}
}
and its associated runner:
#!/usr/bin/env bash
echo "Creating $FILENAME"
touch "$OUT/$FILENAME"
Running bootstrap
with these two files configured will invoke the following
interactive script:
$ bootstrap touch
What file should I create for you? hello-world.txt
Creating hello-world.txt
$ ls
... hello-world.txt ...
You should now see a new hello-world.txt
file in your current working
directory.
Installation
Nix
It is recommended you use Nix to install bootstrap
. If using a flake, specify
bootstrap
as an inputs
attribute the normal way. Otherwise, if you have a
new enough version of nix, import the executable like so:
(builtins.getFlake "github:jrpotter/bootstrap/${version}").packages.${system}.default;
If flakes is not enabled or your nix version does not support
builtins.getFlake
, you can instead use:
(import (pkgs.fetchFromGitHub {
owner = "jrpotter";
repo = "bootstrap";
rev = "${version}";
sha256 = "${sha256}";
})).packages.${system}.default;
Usage
Runners
A spec refers to any directory containing a file named runner
. The only
requirement enforced by bootstrap
is for this file to be an executable (e.g.
chmod +x
), but typically the runner
is a shell script:
#!/usr/bin/env bash
...
The runner
is invoked with its current working directory set to that of the
directory containing it. For instance, if we have a runner
script living in
directory ~/Documents/specs/example
with contents:
#!/usr/bin/env bash
echo "$PWD"
the output of bootstrap example
will always be e.g.
> bootstrap example
/home/jrpotter/Documents/specs/example
regardless of where we call the bootstrap
command.
Exit Code
bootstrap
always invokes the runner
using the system sh
command:
> /bin/sh sh -c ./runner
The exit code emitted by bootstrap
will mirror that returned by the runner
executable.
Specs
If interested in making the runner
more flexible, you can provide different
environment variables in the form of a spec.json
file. This file must live
in the same spec as the runner
. When invoking bootstrap
, the file is used to
determine what prompts should be displayed to the user before executing the
runner
file. The user's responses are then included as environment variables
to the runner
process.
The file contents should consist of a top-level JSON object and any number of
child objects called fields. A typical spec.json
file looks like:
{
"fieldname": {
"type": "line",
"prompt": "Prompt for field> "
},
...
}
In this example, the first field is called "fieldname"
. bootstrap
sees this
field and writes the prompt "Prompt for field> "
to stdout
. Since
"fieldname"
has type "line"
, bootstrap
will wait for the user to input
a string (submitted with a newline).
If the user were to enter fieldvalue
in response to the prompt, the runner
script would then have access to an environment variable FIELDNAME
set to
fieldvalue
. Field names should respect the POSIX standard
on environment variable naming. That is, field names must consist solely of
alphanumeric characters or underscores and are not permitted to start with a
digit.
Types
The value of type
determines how a field is prompted for. Note the value of
type
is case insenstive. The currently supported list of types are:
line
- Takes in a free-form response submitted after encountering a newline (
\n
). The resulting environment variable has leading and trailing whitespace trimmed. - If
required
, whitespace-only strings are re-prompted.
- Takes in a free-form response submitted after encountering a newline (
yes
- Takes in any of
"yes"
,"y"
,"no"
, and"n"
. Answers are case insensitive. - Even if not
required
, any answer that does not match one of these patterns is re-prompted. - A value of
"yes"
has an environment variable with value1
passed to the runner. A value of"no"
has an environment variable with a null value (i.e. an empty string) passed to the runner.
- Takes in any of
Options
Additional options can be included in a field definition.
required
- A value must be specified. How this option is interpreted depends on
type
. - Defaults to
true
.
- A value must be specified. How this option is interpreted depends on
Root Directory
All specs should exist in the same root directory. As an example of what this
directory might look like, refer to specs
at the top-level of this project.
When invoking bootstrap <name>
, <name>
is expected to correspond to some
spec found within the root directory.
To tell bootstrap
where your specs are located, you can provide the path to
the root directory using the -d
option like so:
> bootstrap -d ~/Documents/specs example
...
If no option is set, bootstrap
will fallback to using the value of the
BOOTSTRAP_ROOT_DIR
environment variable. If this also isn't set, bootstrap
will abort with an appropriate error message.
Other Environment Variables
By default, the runner
command will have the following environment variables
defined. Defining these fields in a spec.json
file will override the default
values:
OUT
- The directory
bootstrap
was invoked from. Named since this is usually where you want to initialize new files of your project in.
- The directory
Supplied Specs
A number of specs are provided out of the box. If you installed bootstrap
using nix
, the BOOTSTRAP_ROOT_DIR
will automatically be set to the location
of these specs. Keep in mind this list is very opinionated - they
reflect my personal needs for projects. Feel free to specify a different root
directory if these do not fit your needs.
As a suggestion, use nix
from within your runner
scripts for maximum
reproducibility. Refer to the provided specs for inspiration on how you can do
this.
Development
This tool was originally written for personal usage and, as such, any functionality (or lack thereof) reflects my own needs as I have come across them. If interested in adding more capabilities, please send a PR or just fork the project for your own purposes.
Building
We use CMake (version 3.27.7) to build the project. If a
build/
directory does not already exist, run the following:
$ mkdir -p build/{Debug,Release}
$ pushd build/Debug && cmake -DCMAKE_BUILD_TYPE=Debug ../.. && popd
$ pushd build/Release && cmake -DCMAKE_BUILD_TYPE=Release ../.. && popd
These commands will create a CMake cache file in each subdirectory with the
build types set. Now you can build a Debug
or Release
variant by navigating
to the corresponding subdirectory and running:
$ cmake --build .
The clangd LSP (version 14.0.6) is included in this flake. The codelldb VSCode plugin is also included to interface with the LSP. Note this plugin, despite its name, is compatible with other editors (e.g. neovim). To configure, refer to your editor's documentation.
To use the LSP across files, a
compilation database
must be generated. The CMakeLists.txt
file already enables this in the Debug
configuration type. A top-level compile_commands.json
symbolic link already
exists and points to this generated database.
Testing
We use CTest (version
3.27.7) for unit testing. To run the tests, navigate to build/Debug
and type
the following:
$ cmake --build .
$ make test
Documentation
Documentation is generated using Doxygen (version 1.9.7) through CMake. (Re)generate documentation by navigating to the desired build configuration directory and running:
$ cmake --build .
$ make docs
Formatting
We use clang-format
to ensure consistent formatting. A pre-commit
file is
included in .githooks
to enforce usage. Run the following to configure git
to use it:
git config --local core.hooksPath .githooks/
If running direnv, this is done automatically upon entering the project directory.