There are tools used to shorten the length of every-day commands us programmers need to run. I have tried some of these tools, I specially liked how doskey allowed to pass extra arguments, and the structure of cargo-make, but they still didn't meet all my requirements.
In short, this tool brings more powerful argument parsing, team oriented configuration, and hopefully more features in the future.
The easiest way to install is with cargo.
bash
cargo install --force yamis
TODO: Add binaries
project.yamis.toml
should be added at the root of a project.
Here is an example of how it could look like:
```toml
[env] # global env variables
DEBUG = "TRUE"
[tasks.say_hi] script = "echo Hello {name}" # name can be passed as --name=world, -name=world, or name="big world"
[tasks.say_hi.env] # can add env variables per task DEBUG = "FALSE"
[tasks.folder_content] # Default for linux and macos, can be individually specified like for windows. script = "ls {path?}" # path is an optional argument
[tasks.folder_content.windows] # Task version for windows systems script = "dir {path?}"
[tasks.project_content] wd = "" # Uses the dir where the config file appears as working dir script = "ls"
[tasks.project_content.windows] wd = "" script = "dir"
[tasks.python] program = "python" args = ["{( -c )1?}"] # Runs either the python interpreter, or an inline program if given ```
After having a config file, you can run a task by calling yamis
, the name of the task, and any arguments, i.e.
yamis say_hello name="big world"
. Passing the same argument multiple times will also add it multiple times, i.e.
yamis say_hello name="person 1" --name="person 2"
is equivalent to echo Hello person 1 person 2
The program will look at the directory where it was invoked and its parents until a project.yamis.toml
is
discovered or the root folder is reached. Valid filenames are the following:
- local.yamis.toml
: First one to look at for tasks. This one should hold private tasks and should not
be committed to the repository.
- yamis.toml
: Second one to look at for tasks. Should be used in sub-folders of a project for tasks specific
to that folder and sub-folders.
- project.yamis.toml
: Last one to look at for tasks. The file discovery stops when this one is found.
Note that you can have multiple local.yamis.toml
and yamis.toml
files in a project.
The script
value inside a task will be executed in the command line (defaults to cmd in Windows
and bash in Unix). Scripts can spawn multiple lines, and contain shell built-ins and programs. When
passing multiple arguments, they will be expanded by default, the common example would be the "{*}"
tag which expands to all the passed arguments.
By default, all passed arguments are quoted (with double quotes).
This can be changed at the task or file level by specifying the
quote
param, which can be either:
- always
: Always quote arguments (default)
- spaces
: Quote arguments if they contain spaces
- never
: Never quote arguments
Although quoting prevents common errors like things breaking because a space, it might fail in certain cases. This might be fixed in the future.
The program
value inside a task will be executed as a separate process, with the arguments passed
on args
. Note that each argument can contain at most one tag, that is, {1}{2}
is not valid. When
passing multiple values, they are unpacked into the program arguments, i.e. "{*}"
will result in
all arguments passed down to the program. Note also that if you add an argument like -f={*}.txt
will
also be unpacked as expected, with the argument surrounded by the suffix and prefix.
One obvious option to run tasks one after the other is to create a script, i.e. with the following:
yamis say_hi
yamis say_bye
The other option is to use serial
, which should take a list of tasks to run in order, i.e.:
toml
[tasks.greet]
serial = ["say_hi", "say_bye"]
Note that any argument passed will be passed to both tasks equally.
Because escaping arguments properly can get really complex quickly, scripts are prone to fail if certain arguments are passed. To prevent classic errors, arguments are quoted by default (see Auto quoting), but this is not completely safe. Also, each time a script runs, a temporal batch or cmd file is created. Not a big deal, but an extra step nevertheless.
On the other hand, programs run in their own process with arguments passed directly to it, so there is no
need to escape them. The downside however, is that we cannot execute builtin shell commands such as echo
,
and we need to define the arguments as a list.
When calling a task, you can pass args to insert into the scripts or the argument of programs. These arguments,
or argument tags can have different forms:
- positional: passed by position, i.e. {1}
, {2}
, etc.
- named: case-sensitive and passed by name, i.e. {out}
, {file}
, etc. Note that any dash before the argument
is removed, i.e. if --file=out.txt
is passed, {file}
will accept it. Also note that the named argument passed
to the task will need to be in the form <key>=<value>
, i.e. -o out.txt
is not recognized as a named argument,
this is to prevent ambiguities as the parsing of arguments can change from application to application.
- all: defined by {*}
, all arguments will be passed as they are.
Named argument tasks must start with a letter, and be followed by any number of letters, digits, -
or _
.
Argument tags are mandatory by default, but they can be made optional by adding ?
, i.e. {*?}
does not raise an error if no arguments are given.
Argument tags can also include a prefix or suffix, which will be only added if the argument was passed,
i.e. {(--f=)file?(.txt)}
will result in --file=out.txt
of a file parameter is passed. Note that
{(--f=)file(.txt)}
, even though file
is mandatory, it is useful if we want to unpack it (see next section).
Also, you can include anything inside the prefix and suffix except newlines or brackets. Note that
parenthesis can be included in the prefix or suffix, only the surrounding ones will be excluded, i.e.
{(()sample())}
will result in (hello)
if sample=hello
is passed.
When the same named argument it passed multiple times, the program or script will include them multiple time. For example, given the following tasks:
```toml [tasks.say-hi] script = "echo hello {person}"
[tasks.something] program = "imaginary-program" args = ["{(-o )f}"] ```
If we call yamis hello person=John1 person=John2
, it will run echo hello "John1" "John2"
.
Similarly, yamis something --f=out1.txt out2.txt
will call imaginary-program
with
["-o out1.txt", "-o out2.txt""]
You can have a different OS version for each task. If a task for the current OS is not found, it will fall back to the non os-specific task if it exists. I.e. ```toml [task.ls] # Runs if not in windows script = "ls {*?}"
[task.ls.windows] # Other options are linux and macos script = "dir {*?}" ```
#### Environment variables Environment variables can be defined at the task level. These two forms are equivalent: ```toml [tasks.echo] env = {"DEBUG" = "TRUE"}
[tasks.echo.env]
DEBUG = "TRUE"
They can also be passed globally
toml
[env]
DEBUG = "TRUE"
```
##### Working directory
By default, the working directory of the task is one where it was executed. This can be changed at the task level
or root level, with wd
. The path can be relative or absolute, with relative paths being resolved against the
configuration file and not the directory where the task was executed, this means ""
can be used to make the
working directory the same one as the directory for the configuration file.
Feel free to create issues to report bugs, ask questions or request new features.
Code contributions are welcome and can be in the form of, but not limited to, fixes, more tests, or new features. You can fork the repository and make a pull request, just make sure the code is well tested. Signed commits are preferred.