/* * This needs, of course, to be built with -A nonstandard_style, * because the Rust people think it appropriate to elevate their * preferred style from guide status (which are what coding style * rules should be) to straitjacket status - and wire them into the * compiler(!!). * * Pushing the religion of Unicode, and UTF-8 in particular, is * apparently more important to Rust than being useful. String (and * its derivatives, like &str) are UTF-8 strings, whether you like it * or not. Using std::env::args panics(!) if an argument contains * non-UTF-8 octet sequences; apparently providing an arglist * interface that lets anyone crash your program by the simple * expedient of providing a non-UTF-8 argument is considered * acceptable. They mention this, but do so dismissively, as if it * were nothing to be concerned about(!!). std::env::args_os, which * deals in OsStrings instead of Strings, is there, but they couldn't * be bothered to add proper support for OsString; the Rust doc says * that OsString values are "more complex to work with than String * values". Which they are; for example, OsString doesn't even * implement Display, so we can't simply print one(!!!). * * This UTF-8 bigotry also shows itself in the complete lack (as far as * I've been able to find) of any way to do anything like formatted * I/O in anything but UTF-8. It also is why, for example, * parse_args_top uses the opt_* variables, which in turn forces the * use of cascaded ifs instead of a match. I would rather use a * match, but haven't found any way to do so that doesn't depend on * all the arglist options being UTF-8. It doesn't work to * match a { * opt_m => mode = FORCEMODE::FM_MOVE, * opt_u => mode = FORCEMODE::FM_UNMOVE, * ... * } * which I (think I) understand; it's treating opt_m as an irrefutable * pattern which binds (or sets? not sure) opt_m, not as a value for * the control value to be compared against. I've been unable to find * any way to specify a variable's value is to be used as a * compare-against value. * * I also have found no way to avoid having to write the FORCEMODE:: * prefix on all the FORCEMOVE values. I found one webpage which * seemed to imply "use crate::FORCEMODE;" should help; that just * complains about FORCEMODE being defined twice. (See also below.) * * Also, Rust is incredibly hostile to global ("static", it calls them) * variables. Apparently this is because they're unsafe in threaded * programs, but the Rust people appear to have failed to notice the * facts, which I would have thought would have been obvious, that * there is nothing inherently unsafe about global variables in * nonthreaded programs and not all programs are threaded. Maybe * they've got more bigotry against nonthreaded programs? * * OFWN exists to work around a bug in...I'm not sure what. If OPTS.f * is just fn(&mut OPTS, OsString), something goes wrong with * lifetimes, breaking #[derive(Debug)]. So we manually carry the * name around along with the function pointer in OFWN, for which we * manually implement Debug (and Display). (To see the error, * uncomment g in OPTS and the initialization of it in main().) * https://doc.rust-lang.org/rust-by-example/hello/print/print_debug.html * says that *all* types can derive a Debug implementation; that, * well, I'll be charitable and call it a mistake rather than a lie. * * It's annoying to have the list the options in two forms in * parse_args_top. Needing this is a consequence of Rust's UTF-8 * bigotry. * * Also in parse_args_top, apparently io::stderr.write_all is marked * must-use for some incomprehensible reason. There is NOTHING wrong * with ignoring the return value of a write of an error message to * stderr! (What are you going to do if it fails? Print a complaint * to stderr??) So we have to uglify the code with _ =, unless and * until someone comes up with a better approach. It's bad enough to * be unable to use the formatted I/O support (because of the UTF-8 * bigotry), but now this?! * * parse_args_first exists because I have been unable to figure out any * way to loop over only the second and later items. Whatever it is * that env::args_os() returns, it doesn't support [1..] - or, more * precisely, it supports it in that it's accepted, but it doesn't * actually _work_; I still get argv[0] in the loop. This may be * fixable, but it's sure not clear to me how. * * Things like the apparently-compulsory FORCEMODE:: prefix and the * duplicated code in the OFWN settings REALLY make me wish for a * textual pass along the lines of C's preprocessor. (Rust macros * are not a suitable substitute, as far as I can tell; it appears to * be impossible for a Rust macro to exist without a trailing ! and * at least a pair of argument delimiters...or always existing inside * #[]. I could maybe live with the former, but between the two of * them, no, Rust's macros are insufficient.) I'm almost to the point * of building additional tooling to use cpp or m4 or something of the * sort to provide a textual macro-expansion pass. */ use std::io; use std::env; use std::fmt; use std::io::Write; use std::ffi::OsString; use std::os::unix::ffi::OsStrExt; #[derive(Debug)] enum FORCEMODE { FM_NONE, FM_MOVE, FM_UNMOVE, } struct OFWN<'a> { f: fn(&mut OPTS, OsString), n: &'a str, } #[derive(Debug)] struct OPTS<'a> { mode: FORCEMODE, iarg: Option, karg: Option, Sarg: Vec, Narg: Option, Marg: Option, qopt: bool, vopt: bool, nopt: bool, Dopt: bool, errs: bool, f: OFWN<'a>, // g: fn(&mut OPTS, OsString), } impl fmt::Display for FORCEMODE { fn fmt(&self, f:&mut fmt::Formatter) -> fmt::Result { match self { FORCEMODE::FM_NONE => write!(f,"NONE"), FORCEMODE::FM_MOVE => write!(f,"MOVE"), FORCEMODE::FM_UNMOVE => write!(f,"UNMOVE"), } } } impl fmt::Display for OFWN<'_> { fn fmt(&self, f:&mut fmt::Formatter) -> fmt::Result { f.write_str(self.n) } } impl fmt::Debug for OFWN<'_> { fn fmt(&self, f:&mut fmt::Formatter) -> fmt::Result { f.write_str(self.n) } } fn parse_args_first(o: &mut OPTS, _: OsString) { o.f = OFWN { f: parse_args_top, n: "parse_args_top" }; } fn parse_args_top(o: &mut OPTS, a: OsString) { let opt_m: OsString = "-m".into(); let opt_u: OsString = "-u".into(); let opt_i: OsString = "-i".into(); let opt_k: OsString = "-k".into(); let opt_S: OsString = "-S".into(); let opt_N: OsString = "-N".into(); let opt_M: OsString = "-M".into(); let opt_q: OsString = "-q".into(); let opt_v: OsString = "-v".into(); let opt_n: OsString = "-n".into(); let opt_D: OsString = "-D".into(); if *a == opt_m { o.mode = FORCEMODE::FM_MOVE; } else if *a == opt_u { o.mode = FORCEMODE::FM_UNMOVE; } else if *a == opt_i { o.f = OFWN { f: parse_args_i, n: "parse_args_i" }; } else if *a == opt_k { o.f = OFWN { f: parse_args_k, n: "parse_args_k" }; } else if *a == opt_S { o.f = OFWN { f: parse_args_S, n: "parse_args_S" }; } else if *a == opt_N { o.f = OFWN { f: parse_args_N, n: "parse_args_N" }; } else if *a == opt_M { o.f = OFWN { f: parse_args_M, n: "parse_args_M" }; } else if *a == opt_q { o.qopt = true; } else if *a == opt_v { o.vopt = true; } else if *a == opt_n { o.nopt = true; } else if *a == opt_D { o.Dopt = true; } else if a.as_bytes()[0] == b'-' { let mut w = io::stderr(); _ = w.write_all("Unrecognized option ".as_bytes()); _ = w.write_all(a.as_bytes()); _ = w.write_all("\n".as_bytes()); _ = w.flush(); o.errs = true; } else { let mut w = io::stderr(); _ = w.write_all("Stray argument ".as_bytes()); _ = w.write_all(a.as_bytes()); _ = w.write_all("\n".as_bytes()); _ = w.flush(); o.errs = true; } } fn parse_args_i(o: &mut OPTS, s: OsString) { o.iarg = Some(s); o.f = OFWN { f: parse_args_top, n: "parse_args_top" }; } fn parse_args_k(o: &mut OPTS, s: OsString) { o.karg = Some(s); o.f = OFWN { f: parse_args_top, n: "parse_args_top" }; } fn parse_args_S(o: &mut OPTS, s: OsString) { if s.is_empty() { o.Sarg.truncate(0); } else { o.Sarg.push(s); } o.f = OFWN { f: parse_args_top, n: "parse_args_top" }; } fn parse_args_N(o: &mut OPTS, s: OsString) { o.Narg = Some(s); o.f = OFWN { f: parse_args_top, n: "parse_args_top" }; } fn parse_args_M(o: &mut OPTS, s: OsString) { o.Marg = Some(s); o.f = OFWN { f: parse_args_top, n: "parse_args_top" }; } fn parse_args(o: &mut OPTS) { for a in env::args_os() { (o.f.f)(o,a); } } fn main() { let mut o = OPTS { mode: FORCEMODE::FM_NONE, iarg: None, karg: None, Sarg: Vec::new(), Narg: None, Marg: None, qopt: false, vopt: false, nopt: false, Dopt: false, errs: false, f: OFWN { f: parse_args_first, n: "parse_args_first" }, // g: parse_args_first, }; parse_args(&mut o); println!("o is {:?}",o); }