From 05b8b6339c4b00b0e898c8456677be2883c8a072 Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Tue, 23 Mar 2021 15:37:31 -0600 Subject: read arguments --- src/args.rs | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 8 ++- 2 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 src/args.rs (limited to 'src') diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..1fe6731 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,189 @@ +use std::env; +use std::ffi::OsString; +use std::path::PathBuf; + +use structopt::StructOpt; + +#[derive(StructOpt, Debug, PartialEq, Eq)] +#[structopt(author, about)] +pub struct Args { + /// Cause environment variables, including those with null values, to override macro + /// assignments within makefiles. + #[structopt(short, long)] + environment_overrides: bool, + + /// Specify a different makefile (or '-' for standard input). + /// + /// The argument makefile is a pathname of a description file, which is also referred + /// to as the makefile. A pathname of '-' shall denote the standard input. There can + /// be multiple instances of this option, and they shall be processed in the order + /// specified. The effect of specifying the same option-argument more than once is + /// unspecified. + #[structopt(short = "f", long = "file", visible_alias = "makefile", number_of_values = 1, parse(from_os_str))] + makefile: Vec, + + /// Ignore error codes returned by invoked commands. + /// + /// This mode is the same as if the special target .IGNORE were specified without + /// prerequisites. + #[structopt(short, long)] + ignore_errors: bool, + + /// Continue to update other targets that do not depend on the current target if a + /// non-ignored error occurs while executing the commands to bring a target + /// up-to-date. + #[structopt(short, long)] + keep_going: bool, + + /// Write commands that would be executed on standard output, but do not execute them + /// (but execute lines starting with '+'). + /// + /// However, lines with a ( '+' ) prefix shall be executed. In this mode, + /// lines with an at-sign ( '@' ) character prefix shall be written to standard + /// output. + #[structopt(short = "n", long, visible_alias = "just-print", visible_alias = "recon")] + dry_run: bool, + + /// Write to standard output the complete set of macro definitions and target + /// descriptions. + /// + /// The output format is unspecified. + #[structopt(short, long, visible_alias = "print-data-base")] + print_everything: bool, + + /// Return a zero exit value if the target file is up-to-date; otherwise, return an + /// exit value of 1. + /// + /// Targets shall not be updated if this option is specified. However, a makefile + /// command line (associated with the targets) with a ( '+' ) prefix + /// shall be executed. + #[structopt(short, long)] + question: bool, + + /// Clear the suffix list and do not use the built-in rules. + #[structopt(short = "r", long)] + no_builtin_rules: bool, + + /// Terminate make if an error occurs while executing the commands to bring a target + /// up-to-date (default behavior, required by POSIX to be also a flag for some + /// reason). + /// + /// This shall be the default and the opposite of -k. + #[structopt(short = "S", long, visible_alias = "stop", hidden = true, overrides_with="keep-going")] + no_keep_going: bool, + + /// Do not write makefile command lines or touch messages to standard output before + /// executing. + /// + /// This mode shall be the same as if the special target .SILENT were specified + /// without prerequisites. + #[structopt(short, long, visible_alias = "quiet")] + silent: bool, + + /// Update the modification time of each target as though a touch target had been + /// executed. + /// + /// Targets that have prerequisites but no commands, or that are already up-to-date, + /// shall not be touched in this manner. Write messages to standard output for each + /// target file indicating the name of the file and that it was touched. Normally, + /// the makefile command lines associated with each target are not executed. However, + /// a command line with a ( '+' ) prefix shall be executed. + #[structopt(short, long)] + touch: bool, + + /// Target names or macro definitions. + /// + /// If no target is specified, while make is processing the makefiles, the first + /// target that make encounters that is not a special target or an inference rule + /// shall be used. + targets_or_macros: Vec, +} + +impl Args { + pub fn from_env_and_args() -> Args { + // POSIX spec says "Any options specified in the MAKEFLAGS environment variable + // shall be evaluated before any options specified on the make utility command + // line." + // TODO make sure only flags show up in MAKEFLAGS (i.e. not -f or other stuff) + // TODO accept "option letters without the leading characters" + let env_makeflags = env::var("MAKEFLAGS").unwrap_or_default(); + let env_makeflags = env_makeflags.split_whitespace() + .map(|x| OsString::from(x)); + let mut args = env::args_os(); + // per the structopt docs, the first argument will be used as the binary name, + // so we need to make sure it goes in before MAKEFLAGS + let arg0 = args.next().unwrap_or_else(|| env!("CARGO_PKG_NAME").into()); + + let args = ::std::iter::once(arg0) + .chain(env_makeflags.into_iter()) + .chain(args); + Args::from_iter(args) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn no_args() { + let args: Vec = vec!["makers".into()]; + let args = Args::from_iter(args.into_iter()); + assert_eq!(args, Args { + environment_overrides: false, + makefile: vec![], + ignore_errors: false, + keep_going: false, + dry_run: false, + print_everything: false, + question: false, + no_builtin_rules: false, + no_keep_going: false, + silent: false, + touch: false, + targets_or_macros: vec![], + }); + } + + #[test] + fn kitchen_sink_args() { + let args = "makers -eiknpqrstf foo -f bruh bar baz=yeet"; + let args = Args::from_iter(args.split_whitespace()); + assert_eq!(args, Args { + environment_overrides: true, + makefile: vec!["foo".into(), "bruh".into()], + ignore_errors: true, + keep_going: true, + dry_run: true, + print_everything: true, + question: true, + no_builtin_rules: true, + no_keep_going: false, + silent: true, + touch: true, + targets_or_macros: vec!["bar".into(), "baz=yeet".into()], + }); + } + + #[test] + fn keep_going_wrestling() { + let args = "makers -kSkSkSSSkSkkSk -k -S -k -k -S -S -k"; + let args = Args::from_iter(args.split_whitespace()); + assert_eq!(args, Args { + environment_overrides: false, + makefile: vec![], + ignore_errors: false, + keep_going: true, + dry_run: false, + print_everything: false, + question: false, + no_builtin_rules: false, + no_keep_going: false, + silent: false, + touch: false, + targets_or_macros: vec![], + }); + } + + // TODO test MAKEFLAGS +} diff --git a/src/main.rs b/src/main.rs index e7a11a9..2316546 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,9 @@ + +mod args; + +use args::Args; + fn main() { - println!("Hello, world!"); + let args = Args::from_env_and_args(); + dbg!(args); } -- cgit v1.2.3