You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

422 lines
16 KiB

use super::*;
struct Opt(&'static [&'static str], Option<&'static str>, &'static str, usize, Option<&'static str>, Option<&'static [&'static str]>);
impl Opt
{
const fn with_def(self, vl: &'static str) -> Self
{
Self(self.0, self.1, self.2, self.3, Some(vl), self.5)
}
const fn with_slice(self, vl: &'static [&'static str]) -> Self
{
Self(self.0, self.1, self.2, self.3, self.4, Some(vl))
}
}
/// Macro for printing the options
///
/// # Usage
/// Any combination of the following:
/// ```
/// opt!("--long", "--other"; "Description"); // --long, --other: Description
/// opt!("--long", "--other"; * 2 "Description"); // --long, --other: Description
/// opt!("--long", "--other" => "value name"; "Description"); // --long, --other <value name>: Description
/// opt!("--long"; "Description", ""); // --long, --other: Description (default)
/// opt!("--long"; "Description", "Value"); // --long, --other: Description (default: `Value`)
/// opt!("--long"; "Description", ["OTHER", "OTHER 2"], "value"); // --long, --other: Description (see `OTHER`, `OTHER 2`) (default: `value`)
/// ```
#[macro_export] macro_rules! opt {
(where $desc:literal $(, [$($see:literal),*])?) => {
{
let o = Opt(&[], None, $desc, 2, None, None);
$(let o = o.with_slice(&[$($see),*]);)?
o
}
};
(in $supersection:literal) => {
Opt(&[], None, $supersection, 1, None, None)
};
($section:literal) => {
Opt(&[], None, $section, 0, None, None)
};
() => {
Opt(&[], None, "", 0, None, None)
};
($($name:literal),* => $num:literal; $desc:literal $(, [$($see:literal),*])? $(, $def:literal)?) => {
{
let o = Opt(&[$($name),*], Some($num), $desc, 1, None, None);
$(let o = o.with_def($def);)?
$(let o = o.with_slice(&[$($see),*]);)?
o
}
};
($($name:literal),* => $num:literal; * $tabs:literal $desc:literal $(, [$($see:literal),*])? $(, $def:literal)?) => {
{
let o = Opt(&[$($name),*], Some($num), $desc, $tabs, None, None);
$(let o = o.with_def($def);)?
$(let o = o.with_slice(&[$($see),*]);)?
o
}
};
($($name:literal),*; $desc:literal $(, [$($see:literal),*])? $(, $def:literal)?) => {
{
let o = Opt(&[$($name),*], None, $desc, 1, None, None);
$(let o = o.with_def($def);)?
$(let o = o.with_slice(&[$($see),*]);)?
o
}
};
($($name:literal),*; * $tabs:literal $desc:literal $(, [$($see:literal),*])? $(, $def:literal)?) => {
{
let o = Opt(&[$($name),*], None, $desc, $tabs, None, None);
$(let o = o.with_def($def);)?
$(let o = o.with_slice(&[$($see),*]);)?
o
}
};
}
impl fmt::Display for Opt
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
{
use recolored::Colorize;
match (self.0, self.2, self.3) {
(&[], "", _) => return Ok(()),
(&[], desc, 2) => {
for line in desc.split('\n') {
writeln!(f, "> {}", line)?;
}
if let Some(strings) = self.5 {
writeln!(f, "> (see {})", strings.iter().map(|x| iter!["`".clear(), x.underline(), "`".clear()]).flat_join(", "))?;
}
return Ok(());
},
(&[], desc, 1) => return write!(f, "[{}]:", desc.bold().bright_blue()),
(&[], desc, _) if desc.starts_with("\n") => return write!(f, "\n>>> {}:", &desc[1..].bright_cyan()),
(&[], desc, _) => return write!(f, ">>> {}:", desc.bright_cyan()),
_ => (),
};
let wt = self.0.iter().join(", ");
if wt.starts_with("-") {
write!(f, " {}", wt)?;
} else {
write!(f, " {}", wt.bright_blue())?;
}
}
if let Some(mx) = self.1 {
use recolored::Colorize;
write!(f, " <{}>", mx.italic())?;
}
write!(f, ":")?;
for ch in std::iter::repeat('\t').take(self.3)
{
use std::fmt::Write;
f.write_char(ch)?;
}
write!(f,"{}", self.2)?;
{
use recolored::Colorize;
match self.5 {
Some(&[]) => (),
Some(strings) => {
write!(f, " (see {})", strings.iter().map(|x| iter!["`".clear(), x.underline(), "`".clear()]).flat_join(", "))?;
}
_ => (),
}
match self.4 {
Some("") => {
write!(f, " ({})", "default".bold().bright_red())?;
},
Some(string) => {
write!(f, " ({}: `{}`)", "default".bold().bright_red(), string.bright_blue())?;
},
None => (),
}
}
Ok(())
}
}
// ---
#[inline] fn filespec() -> impl Iterator<Item = Opt>
{
iter![
opt!(in "TASKS"),
opt!(where "There are various ways to add processes to the task list"),
opt!("--execute", "-e" => "process, args..."; "Add this process to the task list with this arguments. It should be in the format 'program arg1 arg2 ...', with each sperated by whitespace and in a single argument. (this will usually require escaping the whitespace)."),
opt!("--shell-execute", "-es" => "expression"; "Add this shell expression to the task list. The default shell can be specified", ["ENVIRONMENT"]),
opt!("--+execute", "-E"; * 3 "Interpret the rest of the arguments as if they were passed to `--execute`. The same format should be used."),
opt!("--+shell-execute", "-Es"; * 3 "Interpret the rest of the arguments as if they were passed to `--shell-execute`. The same format should be used"),
opt!("--"; * 5 "Read inputs from stdin and interpret them as if they were passed by `--execute`. There should be one task per line. The process will keep reading lines from stdin until an explicit termination or error occurs, or the stream is closed."),
opt!("--s"; * 5 "The same as `--` but for `--shell-execute` instead."),
opt!(),
opt!("Explicit tasks"),
opt!(where "You can apply very specific rules to individually provided tasks.\nFull explanations are below", ["EXPLICITY"]),
opt!("--execute-spec:<opts>", "-ex:<opts>"; * 2 "Explicit `--execute`"),
opt!("--shell-execute-spec:<opts>", "-esx:<opts>"; "Explicit `--execute`"),
opt!("--+execute-spec:<opts>", "-Ex:<opts>"; * 2 "Explicit `--+execute`"),
opt!("--+shell-execute-spec:<opts>", "-Esx:<opts>"; "Explicit `--+shell-execute`"),
opt!("--x:<opts>"; * 5 "Explicit `--`"),
opt!("--sx:<opts>"; * 5 "Explicit `--s`"),
opt!()
]
}
/// The `options` section
#[inline] fn options() -> impl Iterator<Item = Opt>
{
iter![
opt!(in "OPTIONS"),
opt!("--limit", "-l" => "max children"; "Limit the number of child processes running at once."),
opt!("--auto-limit", "-m"; * 2 "Limit to max number of CPUs + 1", ""),
opt!("--unlimit", "-U"; * 3 "No limit to number of processes spawned at once"),
opt!("--completion", "-c" => "action"; "Set the default action on completion", ["ACTIONS", "COMPLETION"], "ignore"),
opt!("--silent", "-s"; * 3 "Output no messages other than that of child processes"),
opt!("--stfu", "-S"; * 3 "Output no messages at all"),
opt!("--path:override", "-P" => "path"; * 1 "Override the path to search for programs in for execution. They should be in the same format as the Unix `PATH` environment variable."),
opt!("--path", "-p" => "path"; * 2 "Append these semi-colon seperated entries to the search path for programs."),
opt!("--path:none", "-N"; * 2 "Do not use any look-up paths for finding programs for execution, assume each entry resolves to a path itself"),
opt!("\nChild I/O"),
opt!(where "Defaults for I/O redirections of children\nThese can be overwritten individually with explicit inputs."),
opt!("--stdout" => "where"; * 2 "Set default redirect option for childrens' `stdout`", ["ACTIONS"] ,"inherit"),
opt!("--stderr" => "where"; * 2 "Set default redirect option for childrens' `stderr`", ["ACTIONS"], "inherit"),
opt!("--stdin" => "where"; * 2 "Set default redirect option for childrens' `stdin`", ["ACTIONS"], "close"),
opt!()
]
}
/// The `actions` sections
#[inline] fn actions() -> impl Iterator<Item = Opt>
{
iter![
opt!(in "ACTIONS"),
opt!(where "Certain options have specific actions"),
opt!("I/O"),
opt!(where "Child I/O related operations"),
opt!("inherit"; * 3 "Inherit the stream from parent (this) process"),
opt!("close"; * 4 "Close the stream"),
opt!("ignore"; * 3 "Don't close the stream, but don't do anything with it"),
opt!("file" => "filename"; * 2 "Use this file as input / output.\n\t\t\t\tFor input, if the file is not readable or nonexistant, it causes a panic.\n\t\t\t\tFor output, it appends to the file or uses a new one for output.\n\t\t\t\tIf this is not possible, it causes a panic.\n\t\t\t\tTo overwrite the files instead of appending, use file:clobber"),
opt!("file:clobber" => "filename"; "Only valid for outputs, overwrite the file and write to it, instead of appending"),
opt!("stderr"; * 3 "Redirect stdout to the same location as stderr.\n\t\t\t\tIf there is a cycle detected here, or it is used for anything other that stdout, there is a panic"),
opt!("stdout"; * 3 "Redirect stderr to the same location as stdout.\n\t\t\t\tIf there is a cycle detected here, or it is used for anything other that stdout, there is a panic"),
opt!(),
opt!("Control"),
opt!(where "Controlling how processes are spawned\nThese can be overridden individually with explicit inputs"),
opt!("--uid" => "uid"; "Run as user specified by `uid`. Panics if `uid` is not a valid user id\n\t\t(note: Process must be ran as root to use this option)"),
opt!("--user" => "user"; "Same as `--uid`, except specified user is by name instead of uid"),
opt!("--gid" => "gid"; "Same as `--uid`, except runs as specific group instead of user"),
opt!("--group" => "group"; "Same as `--gid`, except specified group is by name instead of gid"),
opt!()
]
}
/// The `process completion` section.
#[inline] fn completion() -> impl Iterator<Item = Opt>
{
iter![
opt!(in "COMPLETION"),
opt!(where "Directives for how to handle abnormal process completion (i.e. non-zero exit codes)\nThis does not apply to `detached` processes", ["DETACHING"]),
opt!("ignore"; * 3 "Ignore process return code", ""),
opt!("supercede"; * 3 "If return code is a non-zero code, use it as the return code for the parent (this) process.\n\t\t\t\tIf there are multiple non-zero return codes, it is not specified which is returned"),
opt!("terminate"; * 3 "If return code is a non-zero code, abort all running children and return the code as this process' return code immediately"),
opt!("wait"; * 4 "Same as `terminate`, except waits for currently active processes to complete before aborting"),
opt!("log"; * 4 "Write the code to this process' `stderr` if non-zero"),
opt!("file" => "filename"; * 2 "If non-zero, append the code to a file if non-zero. If unable to write to the file, it is ignored"),
opt!("file:clobber" => "filename"; "If non-zero, (over)write the code to a file if non-zero. If unable to write to the file, it is ignored"),
opt!("retry" => "number"; * 2 "If non-zero, restart the process up to `number` times.\n\t\t\t\tIf `number` if `inf`, retry indefinately (this can cause an infinite loop if program never completes with zero exit code)"),
opt!()
]
}
#[inline] fn environment() -> impl Iterator<Item = Opt>
{
iter![
opt!(in "ENVIRONMENT"),
opt!(where "Some environment variables can control the behaviour of the program also"),
opt!("RUST_LOG"),
opt!(where "Controls the logging level of the parent process\nThese can be surpressed entirely by `--silent` or `--stfu`\nIf the var is not set, the default level is used."),
opt!("trace"; * 2 "The most verbose logging"),
opt!("debug"; * 2 "Include debug messages"),
opt!("info"; * 2 "Include information messages"),
opt!("warn"; * 2 "Only warnings and errors"),
opt!("error"; * 2 "Only errors"),
opt!("none"; * 2 "No diagnostic messages", ""),
opt!(),
opt!("PATH"),
opt!(where "Programs are looked up through the set path. This behaviour can be changed", ["OPTIONS"]),
opt!("SHELL"),
opt!(where "The path to the default shell used for shell execution tasks, if it is unset, this will default to `/bin/sh`"),
opt!()
]
}
/// Section for explaining the options for explicit task dispatch
fn explicity() -> impl Iterator<Item = Opt>
{
iter![
opt!(in "EXPLICITY"),
opt!(where "Using the `-*x` / `--*-spec` family of arguments, the options for explicit dispatch are described here\nThe options are provided in the form `<opt>=<value>:<opt2>=<value2>...`. `value`\nThe opt-value pairs are described below. Some can be specified multiple times if said so."),
opt!("Common"),
opt!(where "For all execution modes"),
opt!("r" => "number"; "Repeat execution `number` times. If `number` is `inf`, repeat indefinately."),
opt!(),
opt!("c" => "action"; * 3 "Action when process completes with non-zero return code.", ["COMPLETION"]),
opt!("timeout" => "[signal,]unit,value"; "Timeout before sending `signal` (default `SIGKILL`) to process. Unit can be ns, ms, s, m, or h."),
opt!("detach"; * 4 "Detach this process from the parent. This nullifies the behaviour of some other options", ["DETACHING"]),
opt!(),
opt!("uid" => "user id"; * 2 "Run as this uid (must be root to use this option)", ["ACTIONS, Control"]),
opt!("user" => "user name"; "Run as this user (must be root to use this option)", ["ACTIONS, Control"]),
opt!("gid" => "group id"; "Run as this gid (must be root to use this option)", ["ACTIONS, Control"]),
opt!("group" => "group name"; "Run as this group (must be root to use this option)", ["ACTIONS, Control"]),
opt!(),
opt!("stdin" => "action"; "How to handle process' stdin stream", ["ACTIONS, I/O"]),
opt!("stdout" => "action"; "How to handle process' stdout stream", ["ACTIONS, I/O"]),
opt!("stderr" => "action"; "How to handle process' stderr stream", ["ACTIONS, I/O"]),
opt!(),
opt!("Normal execute"),
opt!(where "For non-shell execution"),
opt!("p" => "program"; "Specify the process name or path"),
opt!("a" => "args"; "Arguments for the process, comma seperated. Multiple of these options can be used to append arguments one by one"),
opt!(),
opt!("Shell execute"),
opt!(where "For shell execution only"),
opt!("sh" => "shell location"; "Specify the shell to use for execution."),
opt!("ex" => "expression"; "The shell expression to use for execution"),
opt!(),
opt!("Stdin / trailing execution"),
opt!(where "For options that consume the rest of arguments or parent's stdin, a trailing option must be specified which determines which option the trailing arguments are to specify.\nEach argument is substituted for the trailing option, producing individual tasks with that option changed.\nThe format of trailing options is `:<opt>` at the end of the argument, with no value.")
]
}
fn detaching() -> impl Iterator<Item = Opt>
{
iter![
opt!(in "DETACHING"),
opt!(where "The behaviour of processes with the `detach` option set (or global `--detach` flag) is different than that of normal child processes.\nThe following flags / explicit options do not work"),
opt!("--completion", ":c"; "Cannot wait on a detached process"),
opt!("--timeout", ":timeout"; "Cannot wait on a detached process"),
opt!(),
opt!("--stdin", "--stdout", "--stderr", ":stdin", ":stdout", ":stderr"; "I/O redirection directives `inherit` and `ignore` do not work on detached processes. If `ignore` (default) is specified, the stream is closed instead."),
opt!()
]
}
/// Get the sections as display iterators.
/// Each item in the iterator is intended to be written as a single line.
pub fn sections() -> impl Iterator<Item = impl fmt::Display>
{
filespec()
.chain(options())
.chain(explicity())
.chain(actions())
.chain(completion())
.chain(detaching())
.chain(environment())
}
#[derive(Debug, Clone, Copy, Default)]
struct Feature
{
name: &'static str,
desc: &'static str,
enabled: bool,
default: Option<bool>, // if `None`, this will only be printed at all if `enabled` is true.
}
macro_rules! feature {
($single:tt => $desc:literal) => {
{
Feature {
name: stringify!($single),
desc: $desc,
enabled: {
cfg_if!{
if #[cfg($single)] {
true
} else {
false
}
}
},
default: None,
}
}
};
() => {
Feature::default()
};
}
impl fmt::Display for Feature
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
use recolored::Colorize;
if self.name == "" {
return Ok(());
}
match self.default {
None if self.enabled => {
write!(f, " +{}\t", self.name.bright_red().bold())?;
write!(f, "{}", self.desc)?;
Ok(())
},
Some(default) => {
todo!()
}
_ => Ok(()),
}
}
}
/// Get each enabled compilation feature
pub fn features() -> impl Iterator<Item = impl fmt::Display>
{
iter![
feature!(nightly => "Compiled with Rust nightly features")
]
}