use clap::{self, crate_authors, crate_version, App, AppSettings};
type Arg = clap::Arg<'static, 'static>;
pub struct CustomArg {
clap_arg: Arg,
pub name: &'static str,
pub doc_short: &'static str,
pub doc_long: &'static str,
pub hidden: bool,
pub kind: CustomArgKind,
}
#[derive(Clone)]
pub enum CustomArgKind {
Positional {
value_name: &'static str,
multiple: bool,
},
Switch {
long: &'static str,
short: Option<&'static str>,
multiple: bool,
},
Flag {
long: &'static str,
short: Option<&'static str>,
value_name: &'static str,
multiple: bool,
possible_values: Vec<&'static str>,
},
}
impl CustomArg {
fn positional(name: &'static str, value_name: &'static str) -> Self {
Self {
clap_arg: Arg::with_name(name).value_name(value_name),
name,
doc_short: "",
doc_long: "",
hidden: false,
kind: CustomArgKind::Positional {
value_name,
multiple: false,
},
}
}
fn switch(long_name: &'static str) -> Self {
let clap_arg = Arg::with_name(long_name).long(long_name);
Self {
clap_arg,
name: long_name,
doc_short: "",
doc_long: "",
hidden: false,
kind: CustomArgKind::Switch {
long: long_name,
short: None,
multiple: false,
},
}
}
fn flag(long_name: &'static str, value_name: &'static str) -> Self {
let clap_arg = Arg::with_name(long_name)
.long(long_name)
.value_name(value_name)
.takes_value(true)
.number_of_values(1);
Self {
clap_arg,
name: long_name,
doc_short: "",
doc_long: "",
hidden: false,
kind: CustomArgKind::Flag {
long: long_name,
short: None,
value_name,
multiple: false,
possible_values: vec![],
},
}
}
fn short(mut self, name: &'static str) -> Self {
match self.kind {
CustomArgKind::Positional { .. } => panic!("expected switch or flag"),
CustomArgKind::Switch { ref mut short, .. } => {
*short = Some(name);
}
CustomArgKind::Flag { ref mut short, .. } => {
*short = Some(name);
}
}
self.clap_arg = self.clap_arg.short(name);
self
}
fn help(mut self, text: &'static str) -> Self {
self.doc_short = text;
self.clap_arg = self.clap_arg.help(text);
self
}
fn long_help(mut self, text: &'static str) -> Self {
self.doc_long = text;
self.clap_arg = self.clap_arg.long_help(text);
self
}
fn multiple(mut self) -> Self {
match self.kind {
CustomArgKind::Positional {
ref mut multiple, ..
} => {
*multiple = true;
}
CustomArgKind::Switch {
ref mut multiple, ..
} => {
*multiple = true;
}
CustomArgKind::Flag {
ref mut multiple, ..
} => {
*multiple = true;
}
}
self.clap_arg = self.clap_arg.multiple(true);
self
}
fn possible_values(mut self, values: &[&'static str]) -> Self {
match self.kind {
CustomArgKind::Positional { .. } => panic!("expected flag"),
CustomArgKind::Switch { .. } => panic!("expected flag"),
CustomArgKind::Flag {
ref mut possible_values,
..
} => {
*possible_values = values.to_vec();
self.clap_arg = self
.clap_arg
.possible_values(values)
.hide_possible_values(true);
}
}
self
}
#[allow(dead_code)]
fn required_unless(mut self, names: &[&'static str]) -> Self {
self.clap_arg = self.clap_arg.required_unless_one(names);
self
}
#[allow(dead_code)]
fn hidden(mut self) -> Self {
self.hidden = true;
self.clap_arg = self.clap_arg.hidden(true);
self
}
#[allow(dead_code)]
fn alias(mut self, name: &'static str) -> Self {
self.clap_arg = self.clap_arg.alias(name);
self
}
#[allow(dead_code)]
fn allow_leading_hyphen(mut self) -> Self {
match self.kind {
CustomArgKind::Positional { .. } => panic!("expected flag"),
CustomArgKind::Switch { .. } => panic!("expected flag"),
CustomArgKind::Flag { .. } => {
self.clap_arg = self.clap_arg.allow_hyphen_values(true);
}
}
self
}
fn conflicts(mut self, names: &[&'static str]) -> Self {
self.clap_arg = self.clap_arg.conflicts_with_all(names);
self
}
#[allow(dead_code)]
fn overrides(mut self, name: &'static str) -> Self {
self.clap_arg = self.clap_arg.overrides_with(name);
self
}
#[allow(dead_code)]
fn default_value(mut self, value: &'static str) -> Self {
self.clap_arg = self.clap_arg.default_value(value);
self
}
#[allow(dead_code)]
fn default_value_if(mut self, value: &'static str, arg_name: &'static str) -> Self {
self.clap_arg = self.clap_arg.default_value_if(arg_name, None, value);
self
}
#[allow(dead_code)]
fn number(mut self) -> Self {
self.clap_arg = self.clap_arg.validator(|val| {
val.parse::<usize>()
.map(|_| ())
.map_err(|err| err.to_string())
});
self
}
}
macro_rules! long {
($lit:expr) => {
concat!($lit, " ")
};
}
const ABOUT: &str = "
printr is meant to be a drop in replacement for the everyday shell command - echo. Though
coloring is supported by echo, it is un-intuitive and often not cross-platform. Enter printr,
a cross platform command that does all that echo does, and a bit more.
It supports coloring as well as styling your output via simple command line flags. Most of
the time, however, supplying these flags is not required since printr is smart enough to
figure out the input STRING's sentiment via some naive sentiment analysis.
Project home page: https://github.com/IgnisDa/printr
";
pub fn app() -> App<'static, 'static> {
let mut app = App::new("printr")
.author(crate_authors!())
.version(crate_version!())
.about(ABOUT)
.max_term_width(100)
.setting(AppSettings::UnifiedHelpMessage)
.setting(AppSettings::AllArgsOverrideSelf)
.help_message("Prints help information. Use --help for more details.");
for arg in all_args_and_flags() {
app = app.arg(arg.clap_arg);
}
app
}
pub fn all_args_and_flags() -> Vec<CustomArg> {
let mut args = vec![];
arg_string(&mut args);
switch_newline(&mut args);
switch_spaces(&mut args);
switch_enable_interpretation(&mut args);
switch_disable_interpretation(&mut args);
switch_plain(&mut args);
switch_error(&mut args);
flag_input_file(&mut args);
flag_color(&mut args);
flag_formatting(&mut args);
args
}
fn arg_string(args: &mut Vec<CustomArg>) {
const SHORT: &str = "The string to print";
const LONG: &str = long!(
"\
A string that will be printed to stdout (or stderr using --error flag).
This can be left empty, in which case, a newline will be printed
(assuming the -n/--newline flag is not supplied).
Example:
`printr Hello` will output \"Hello\"
`printr Hello World` will output \"Hello World\"
"
);
let arg = CustomArg::positional("STRING", "STRING")
.multiple()
.help(SHORT)
.long_help(LONG)
.conflicts(&["input-file"]);
args.push(arg);
}
fn switch_newline(args: &mut Vec<CustomArg>) {
const SHORT: &str = "Do not output a newline at the end";
const LONG: &str = long!(
"\
When this switch is specified, the output does not have a newline
(that is, a '\\n') appended to its end.
"
);
let arg = CustomArg::switch("newline")
.short("n")
.help(SHORT)
.long_help(LONG);
args.push(arg);
}
fn switch_spaces(args: &mut Vec<CustomArg>) {
const SHORT: &str = "Do not separate arguments with spaces";
const LONG: &str = long!(
"\
When this switch is specified, input STRING arguments are not
separated by spaces.
Example:
`printr -s Hello World` will output \"HelloWorld\"
This switch has no effect on the output if an input file
(via the --input-file option) is supplied.
"
);
let arg = CustomArg::switch("spaces")
.short("s")
.help(SHORT)
.long_help(LONG);
args.push(arg);
}
fn switch_disable_interpretation(args: &mut Vec<CustomArg>) {
const SHORT: &str = "Disable interpretation of backslash escapes (default)";
const LONG: &str = long!(
"\
This is the default behavior.
"
);
let arg = CustomArg::switch("disable_interpretation")
.short("E")
.help(SHORT)
.long_help(LONG);
args.push(arg);
}
fn switch_enable_interpretation(args: &mut Vec<CustomArg>) {
const SHORT: &str = "Enable interpretation of backslash escapes";
const LONG: &str = long!(
"\
If -e is used, the following sequences are recognized:
• \\ backslash
• \\a alert (BEL)
• \\b backspace
• \\c produce no further output
• \\e escape
• \\f form feed
• \\n new line
• \\r carriage return
• \\t horizontal tab
• \\v vertical tab
• \\0NNN byte with octal value NNN (1 to 3 digits)
• \\xHH byte with hexadecimal value HH (1 to 2 digits)
"
);
let arg = CustomArg::switch("enable_interpretation")
.short("e")
.help(SHORT)
.long_help(LONG);
args.push(arg);
}
fn switch_plain(args: &mut Vec<CustomArg>) {
const SHORT: &str = "Print the input without any color";
const LONG: &str = long!(
"\
This option prints the output \"plainly\", that is, no colors are applied.
It is helpful when piping the output of printr to another file, so that
color escape codes do not mangle up the resulting file.
"
);
let arg = CustomArg::switch("plain")
.short("p")
.help(SHORT)
.long_help(LONG);
args.push(arg);
}
fn switch_error(args: &mut Vec<CustomArg>) {
const SHORT: &str = "Print to stderr instead of stdout";
const LONG: &str = long!(
"\
If this switch is supplied, the output will be printed to stdout.
This can then be piped as required.
Example:
printr --error \"Error string input\" 2> errors.txt
"
);
let arg = CustomArg::switch("error").help(SHORT).long_help(LONG);
args.push(arg);
}
fn flag_input_file(args: &mut Vec<CustomArg>) {
const SHORT: &str = "The file to read the input STRING from";
const LONG: &str = long!(
"\
If this is supplied, the input STRING is read from the file
contents.
"
);
let arg = CustomArg::flag("input-file", "input-file")
.short("i")
.help(SHORT)
.long_help(LONG)
.conflicts(&["STRING"]);
args.push(arg);
}
fn flag_color(args: &mut Vec<CustomArg>) {
const SHORT: &str = "The color of the output";
const LONG: &str = long!(
"\
If not supplied, the color is guessed from the contents of the input
using some naive sentiment analysis.
[possible values: red, blue, green, yellow, cyan]
"
);
let arg = CustomArg::flag("color", "color")
.short("c")
.help(SHORT)
.long_help(LONG)
.conflicts(&["plain"])
.possible_values(&["red", "blue", "green", "yellow", "cyan"]);
args.push(arg);
}
fn flag_formatting(args: &mut Vec<CustomArg>) {
const SHORT: &str = "The style to apply to the text, defaults to `None`";
const LONG: &str = long!(
"\
Some terminal emulators do not support some of the formatting options.
You might have to experiment to find which ones work for you.
[possible values: bold, underline, strikethrough, dimmed]
"
);
let arg = CustomArg::flag("formatting", "formatting")
.short("f")
.help(SHORT)
.long_help(LONG)
.possible_values(&["bold", "underline", "strikethrough", "dimmed"]);
args.push(arg);
}