├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE.txt ├── examples ├── basic.rs ├── lines.rs ├── monty.lapp ├── range.rs ├── simple-test.rs ├── simple.lapp ├── test.lapp └── test.lapp.inc ├── readme.md └── src ├── bin └── lapp-gen.rs ├── flag.rs ├── lib.rs ├── strutil.rs └── types.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | scratch/ 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | matrix: 7 | allow_failures: 8 | - rust: nightly 9 | 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lapp" 3 | version = "0.4.0" 4 | authors = ["steve donovan "] 5 | 6 | description = "simple command-line argument parser driven by usage text" 7 | 8 | documentation = "https://docs.rs/lapp" 9 | repository = "https://github.com/stevedonovan/lapp.git" 10 | 11 | readme = "readme.md" 12 | 13 | license="MIT" 14 | 15 | keywords = ["command","command-line","parser","arguments"] 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Steve Donovan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/basic.rs: -------------------------------------------------------------------------------- 1 | extern crate lapp; 2 | use std::io; 3 | use std::io::prelude::*; 4 | use std::error::Error; 5 | 6 | fn run() -> Result<(),Box> { 7 | 8 | let args = lapp::parse_args(" 9 | File input and output 10 | (default stdin) 11 | (default stdout) 12 | "); 13 | 14 | let inf = args.get_infile("in"); 15 | let mut outf = args.get_outfile("out"); 16 | 17 | let rdr = io::BufReader::new(inf); 18 | for line in rdr.lines() { 19 | let line = line?; 20 | write!(outf,"{}\n",line)?; 21 | } 22 | Ok(()) 23 | 24 | } 25 | 26 | fn main() { 27 | run().expect("blew up"); 28 | } 29 | -------------------------------------------------------------------------------- /examples/lines.rs: -------------------------------------------------------------------------------- 1 | extern crate lapp; 2 | include!("test.lapp.inc"); 3 | 4 | fn main() { 5 | let (values,args) = Args::new(); 6 | if values.lines < 1 { 7 | args.quit("lines must be greater than zero"); 8 | } 9 | println!("{:#?}",values); 10 | } 11 | -------------------------------------------------------------------------------- /examples/monty.lapp: -------------------------------------------------------------------------------- 1 | This is the Full Monty, all Lapp possibilities 2 | in one spec file 3 | 4 | -v, --verbose short and long bool flag, default false 5 | --extra-stuff just a long bool flag, ditto 6 | -S just a short bool flag 7 | -n, --lines (default 10) short and log integer flag, explicit default 8 | -p, --ports (integer...) array of integer flag (e.g. --ports '1002 1003') 9 | -s, --scale (default 1.0) float flag, explicit default 10 | -m (float) _required_ float flag (because no default) 11 | --libdir (default '.') string flag - use quotes for correct type! 12 | -I, --include... (string) multiple string flags (like -I. -Ilib) 13 | 14 | Other lines are ignored: 15 | 16 | (string...) rest of args as an array of strings 17 | -------------------------------------------------------------------------------- /examples/range.rs: -------------------------------------------------------------------------------- 1 | extern crate lapp; 2 | 3 | fn main() { 4 | 5 | let args = lapp::parse_args(" 6 | Integer range 7 | (1..10) a number! 8 | "); 9 | 10 | let inf = args.get_integer("in"); 11 | println!("{}",inf); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /examples/simple-test.rs: -------------------------------------------------------------------------------- 1 | extern crate lapp; 2 | 3 | macro_rules! dbg ( 4 | ($x:expr) => { 5 | println!("{}:{} {} = {:?}",file!(),line!(),stringify!($x),$x); 6 | } 7 | ); 8 | 9 | const USAGE: &'static str = " 10 | Testing Lapp 11 | -v, --verbose verbose flag 12 | -k arb flag 13 | -o, --output (string) 14 | -p (integer...) 15 | -h, --help help 16 | (string...) 17 | "; 18 | 19 | fn main() { 20 | let args = lapp::parse_args(USAGE); 21 | 22 | let verbose = args.get_bool("verbose"); 23 | let k = args.get_bool("k"); 24 | let output = args.get_string("output"); 25 | let p = args.get_integers("p"); 26 | let out = args.get_strings("out"); 27 | 28 | dbg!(verbose); 29 | dbg!(k); 30 | dbg!(output); 31 | dbg!(p); 32 | dbg!(out); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /examples/simple.lapp: -------------------------------------------------------------------------------- 1 | Testing Lapp 2 | -v, --verbose verbose flag 3 | -k arb flag 4 | -o, --output (default stdout) 5 | -p (default 42) 6 | --ports (integer...) 7 | -f (default 'none') 8 | -I, --include... (string) 9 | (string) 10 | (string...) 11 | -------------------------------------------------------------------------------- /examples/test.lapp: -------------------------------------------------------------------------------- 1 | Prints out first n lines of a file 2 | -n, --lines (default 10) number of lines 3 | -v, --verbose 4 | (string) input file name 5 | 6 | -------------------------------------------------------------------------------- /examples/test.lapp.inc: -------------------------------------------------------------------------------- 1 | const USAGE: &'static str = " 2 | Prints out first n lines of a file 3 | -n, --lines (default 10) number of lines 4 | -v, --verbose 5 | (string) input file name 6 | 7 | "; 8 | #[derive(Debug)] 9 | struct Args { 10 | lines: i32, 11 | verbose: bool, 12 | file: String, 13 | help: bool, 14 | } 15 | 16 | impl Args { 17 | fn new() -> (Args,lapp::Args<'static>) { 18 | let args = lapp::parse_args(USAGE); 19 | (Args{ 20 | lines: args.get_integer("lines"), 21 | verbose: args.get_bool("verbose"), 22 | file: args.get_string("file"), 23 | help: args.get_bool("help"), 24 | },args) 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Command-line parsing is essential for any program that needs to be run by 2 | other people, and is a messy task with many corner cases: this is one wheel that should not be 3 | reinvented for a project. It should not to be too ugly to use, either, like 4 | the `getopt_long` POSIX interface for C programs. 5 | 6 | This crate is a Rust implementation of the Lua library 7 | [lapp](http://stevedonovan.github.io/Penlight/api/manual/08-additional.md.html#Command_line_Programs_with_Lapp). 8 | Like [docopt](http://docopt.org/), it starts from the fact that you must output usage text anyway, 9 | so why not extract flag names and types from that text? This is one of those ideas 10 | that tends to happen multiple times - my first implementation was in 2009 and it is 11 | now part of the Penlight Lua libraries; docopt came somewhat later in about 2011. 12 | 13 | Given that there is a [Rust implementation](https://github.com/docopt/docopt.rs) of 14 | docopt, what is the justification for Lapp in Rust? It is a good deal simpler to 15 | use and understand, and fulfills the need for basic command-line interfaces that 16 | don't involve subcommands, etc. The philosophy is "fail early and hard" - the 17 | program quits if there are any errors and returns a non-zero code. 18 | 19 | Consider a 'head' program that needs to be given a file and the number of lines 20 | to echo to stdout: 21 | 22 | ```rust 23 | // head.rs 24 | extern crate lapp; 25 | 26 | fn main() { 27 | let args = lapp::parse_args(" 28 | Prints out first n lines of a file 29 | -n, --lines (default 10) number of lines 30 | -v, --verbose 31 | (string) input file name 32 | "); 33 | 34 | let n = args.get_integer("lines"); 35 | let verbose = args.get_bool("verbose"); 36 | let file = args.get_string("file"); 37 | // your magic goes here 38 | } 39 | ``` 40 | 41 | There are _flags_ (both short and long forms) like `lines`, `verbose` and _positional arguments_ 42 | like `file`. Flags have an associated type - for `lines` this is deduced from the 43 | default value, and for `file` it is specified explicitly. Flags or arguments without 44 | defaults must be specified - except for simple boolean flags, which default to false. 45 | 46 | This does a fair amount of work for you, given that you had to write the usage 47 | text anyway: 48 | 49 | - the usage 'mini-language' is fairly simple 50 | - command-line arguments are processed GNU-style. You may say `--lines 20` 51 | or `-n 20`; short flags can be combined `-vn20`. `--` indicates end of 52 | command-line processing 53 | - not providing positional arguments or required flags is an error 54 | - the `lines` flag value must be a valid integer and will be converted 55 | 56 | So the idea is something that is straightforward for the programmer to use and 57 | self-documenting enough for the user. 58 | 59 | ## Lapp mini-language 60 | 61 | A significant line in a Lapp specification starts either with '-' (flags) or 62 | '<' (positional arguments). Flags may be '-s, --long','--long' or '-s'. Any other 63 | lines are ignored. Short flags may only be letters or numbers; long flags are 64 | alphanumeric, plus '_' and '-'. 65 | 66 | These significant lines may be followed by a type-default specifier in parens. It 67 | is either a type, like '(string)' or a default value, like '(default 10)'. If not 68 | present then the flag is a simple boolean flag, default false. The currently 69 | supported types are: 70 | 71 | - string 72 | - integer (`i32`) 73 | - float (`f32`) 74 | - boolean 75 | - infile (`Box`) (can have "stdin" as default) 76 | - outfile (`Box`) (can have "stdout" as default) 77 | - path (`PathBuf`) (default will be tilde-expanded) 78 | 79 | '(default )' then the type is deduced from the value - either an integer or a 80 | float if numerical, string otherwise. It is always possible to quote default 81 | string values in single quotes, which you should do if the default value is not a 82 | word. When in doubt, quote. 83 | 84 | With version 0.3.0, it's also possible to specify both the type and a default, 85 | e.g. "(integer default 0)" or "(path default ~/.bonzo)". 86 | 87 | If there is no default value (except for simple flags) then that flag or argument 88 | _must_ be specified on the command-line - they are _required_. 89 | 90 | In addition, flags may be _multiple_ or _arrays_. Both are reprsented by a vector 91 | of one of the base types, but are used differently. For example, 92 | 93 | ``` 94 | -I, --include... (string) flag may appear multiple times 95 | -p, --ports (integer...) the flag value itself is an array 96 | (string...) 97 | ... 98 | ./exe -I. --include lib 99 | ./exe --ports '9000 9100 9200' 100 | ./exe one two three 101 | ``` 102 | 103 | Array flags are lists separated _either_ with spaces _or_ with commas. (But if 104 | you use commas, extra space will be trimmed.) 105 | 106 | Multiple flags have '...' after the flag, array flags have '...' after the type. 107 | The exception is positional flags, which are always multiple. This syntax does 108 | not support default values, since the default value is well defined - an empty 109 | vector. 110 | 111 | _ranges_ are supported. "(1..10)" means an integer between 112 | 1 and 10 (inclusive!), and "(0.0..5.0)" means a floating point number 113 | between 0.0 and 5.0. 114 | 115 | Two convenient file types are provided, "infile" and "outfile". `get_infile()` 116 | will return a `Box` and `get_outfile()` will return a `Box`. If the 117 | argument is not a file that can be opened for either reading or writing, then 118 | the program will quit. A default can be specified, so "(default stdin)" will 119 | wrap up `io.stdin()` for you if the flag is not provided. (This is why we return 120 | boxed trait objects rather than actual `File` objects - to handle this case.) 121 | 122 | "infile" and "outfile" also act like "path" values and the path given on the 123 | command line can be retrieved with `.get_path()` or `.get_path_result()` as with any 124 | other path. 125 | 126 | By default, the accessor functions exit the program on error. But for every method 127 | like `args.get_string("flag")` there is an error-returning `args.get_string_result("flag")`. 128 | 129 | ## More Code Examples 130 | 131 | Array-valued flags (multiple or array) are accessed with `args.get_strings("flag")`, 132 | `args.get_integers("flag")`, etc. 133 | 134 | If you'd like something other than the standard numeric types (`i32` or `f32`) 135 | you can specify the type: `args.get::("flag")`. It will then be an error to specify 136 | integers outside 0..255. Simularly, `args.get_array::("flag")` will get an 137 | integer-valued array flag as the desired type. 138 | 139 | In fact, any type that implements the [FromStr](https://doc.rust-lang.org/1.15.1/std/str/trait.FromStr.html) trait will work. 140 | In this example, we want to let the user enter integer values as hexadecimal. 141 | It's necessary to specify any user types upfront, because otherwise **lapp** will complain about 142 | unrecognized types. 143 | 144 | ```rust 145 | extern crate lapp; 146 | use std::str::FromStr; 147 | use std::num::ParseIntError; 148 | 149 | struct Hex { 150 | value: u64 151 | } 152 | 153 | impl FromStr for Hex { 154 | type Err = ParseIntError; 155 | 156 | fn from_str(s: &str) -> Result { 157 | let value = u64::from_str_radix(s,16)?; 158 | Ok(Hex{value: value}) 159 | } 160 | } 161 | 162 | let mut args = lapp::Args::new(" 163 | --hex (hex default FF) 164 | "); 165 | args.user_types(&["hex"]); 166 | args.parse(); 167 | 168 | let res: Hex = args.get("hex"); 169 | println!("value was {}", res.value); 170 | ``` 171 | 172 | 173 | ## Codegen 174 | 175 | A criticism of this approach is that it isn't very strongly typed; it is 176 | up to the programmer to use the correct `get_` accessor for the flag, and 177 | spelling mistakes are fatal at run-time. To get the boilerplate correct, there 178 | is a tool in the 'src/bin' folder called `lapp-gen`. In the examples folder 179 | there is a `test.lapp` file: 180 | 181 | ``` 182 | Prints out first n lines of a file 183 | -n, --lines (default 10) number of lines 184 | -v, --verbose 185 | (string) input file name 186 | 187 | ``` 188 | 189 | This is passed to `lapp-gen` as an environment variable (since we don't want to 190 | confuse the command-line parameters here) 191 | 192 | ``` 193 | ~/rust/lapp/examples$ LAPP_GEN='test.lapp vars' lapp-gen 194 | let lines = args.get_integer("lines"); 195 | let verbose = args.get_bool("verbose"); 196 | let file = args.get_string("file"); 197 | let help = args.get_bool("help"); 198 | ``` 199 | 200 | Lapp creates variable names out of flag names using a few simple rules; any '-' 201 | is converted to '_'; if the flag name starts with a number or '_', then the name 202 | is prepended with 'c_'. 203 | 204 | You may test your spec by specifying just the file, and any command-line arguments: 205 | 206 | ``` 207 | ~/rust/lapp/examples$ LAPP_GEN='test.lapp' lapp-gen 208 | flag 'lines' value Int(10) 209 | flag 'verbose' value Bool(false) 210 | flag 'file' value Error("required flag file") 211 | flag 'help' value Bool(false) 212 | ~/rust/lapp/examples$ LAPP_GEN='test.lapp' lapp-gen hello -v 213 | flag 'lines' value Int(10) 214 | flag 'verbose' value Bool(true) 215 | flag 'file' value Str("hello") 216 | flag 'help' value Bool(false) 217 | ~/rust/lapp/examples$ LAPP_GEN='test.lapp' lapp-gen hello -v --lines 30 218 | flag 'lines' value Int(30) 219 | flag 'verbose' value Bool(true) 220 | flag 'file' value Str("hello") 221 | flag 'help' value Bool(false) 222 | ~/rust/lapp/examples$ LAPP_GEN='test.lapp' lapp-gen hello -vn 40 223 | flag 'lines' value Int(40) 224 | flag 'verbose' value Bool(true) 225 | flag 'file' value Str("hello") 226 | flag 'help' value Bool(false) 227 | 228 | ``` 229 | 230 | The `monty.lapp` test file in `examples` gives all the permutations possible 231 | with this version of Lapp. 232 | 233 | The real labour saving codegen option is to generate a struct which is initialized 234 | from lapp command-lines: 235 | 236 | ```rust 237 | ~/rust/lapp/examples$ LAPP_GEN='test.lapp struct:Args' lapp-gen 238 | ~/rust/lapp/examples$ cat test.lapp.inc 239 | const USAGE: &'static str = " 240 | Prints out first n lines of a file 241 | -n, --lines (default 10) number of lines 242 | -v, --verbose 243 | (string) input file name 244 | 245 | "; 246 | #[derive(Debug)] 247 | struct Args { 248 | lines: i32, 249 | verbose: bool, 250 | file: String, 251 | help: bool, 252 | } 253 | 254 | impl Args { 255 | fn new() -> (Args,lapp::Args<'static>) { 256 | let args = lapp::parse_args(USAGE); 257 | (Args{ 258 | lines: args.get_integer("lines"), 259 | verbose: args.get_bool("verbose"), 260 | file: args.get_string("file"), 261 | help: args.get_bool("help"), 262 | },args) 263 | } 264 | } 265 | ``` 266 | 267 | And our program now looks like this, including the output `test.lapp.inc`. 268 | 269 | ```rust 270 | // lines.rs 271 | extern crate lapp; 272 | include!("test.lapp.inc"); 273 | 274 | fn main() { 275 | let (values,args) = Args::new(); 276 | if values.lines < 1 { 277 | args.quit("lines must be greater than zero"); 278 | } 279 | println!("{:#?}",values); 280 | } 281 | 282 | ``` 283 | 284 | (It would probably be more elegant to create a submodule, but then this would not 285 | work in the examples folder except with subdirectories.) 286 | 287 | ## Limitations 288 | 289 | In the last example it was necessary to explicitly _validate_ the arguments and quit 290 | with an appropriate message. But most validation involves checking 291 | more than one argument, and the more general solution is probably to have a `validate` 292 | method stub in the generated code, where you can put your constraints. 293 | 294 | Generally, however, I feel it's important to get a straightforward set of features right, 295 | even if they are limited. There are more general options for handling more complicated 296 | command-line programs (for example, that support commands like 'cargo build' or 'git status') 297 | and I intend to keep `lapp` as simple as possible, without extra dependencies. 298 | 299 | ## The Payoff 300 | 301 | I'll contrast and compare two little programs; the first uses the popular `structopt` crate: 302 | 303 | ```rust 304 | // structopt.rs 305 | use structopt::StructOpt; 306 | 307 | #[derive(StructOpt)] 308 | /// Give the name and the age 309 | struct Args { 310 | #[structopt(short,long)] 311 | /// name of person 312 | name: String, 313 | 314 | #[structopt(short,long)] 315 | /// age of person 316 | age_of_person: u16, 317 | 318 | } 319 | 320 | fn main() { 321 | let args = Args::from_args(); 322 | println!("name {} age {}", args.name, args.age_of_person); 323 | } 324 | ``` 325 | 326 | And here's the equivalent `lapp` program: 327 | 328 | ```rust 329 | // lapp.rs 330 | const USAGE: &str = "\ 331 | Give the name and the age 332 | -n, --name (string) name of person 333 | -a, --age-of-person (integer) age of person 334 | "; 335 | 336 | fn main() { 337 | let args = lapp::parse_args(USAGE); 338 | let name: String = args.get("name"); 339 | let age_of_person: u16 = args.get("age-of-person"); 340 | println!("name {} age {}", name,age_of_person); 341 | } 342 | ``` 343 | 344 | It's hard to beat the ergonomic convenience of `structopt` (especially since version 0.3), and the `lapp` 345 | version does involve more repetition. 346 | 347 | Comparing the stripped release builds: 348 | - `structopt` is 727K (15+ dependencies) 349 | - `lapp` is 359K (1 dependency) 350 | 351 | i.e. twice as large and slower to build. 352 | 353 | If you are building larger programs with many dependencies, the flexibility of `structopt` wins out, 354 | (plus people are more fussy about the output of command-line programs these days.) 355 | 356 | But for dinky little programs? It's useful to have options. 357 | 358 | 359 | -------------------------------------------------------------------------------- /src/bin/lapp-gen.rs: -------------------------------------------------------------------------------- 1 | extern crate lapp; 2 | use std::env; 3 | use std::fs::File; 4 | use std::io; 5 | use std::io::prelude::*; 6 | 7 | const USAGE: &'static str = " 8 | lapp-gen, generate Rust code from lapp specification files 9 | 10 | ABOUT 11 | lapp-gen verifies lapp specifications and then allows testing and code generation based 12 | on those specifications. 13 | 14 | USAGE 15 | lapp-gen's behavior is specified using the environment variable `LAPP_GEN`; for instance: 16 | 17 | LAPP_GEN=my_spec.lapp lapp-gen 18 | 19 | LAPP_GEN can contain an additional field, seperated by a space. This is the code 20 | generation mode, which defaults to 'validate'. 21 | 22 | If lapp_gen is in 'validate' mode, any command-line arguments passed are parsed and the 23 | results displayed. This allows you to prototype a command-line interface rapidly. 24 | 25 | If the extra field is 'vars', it prints out a set of declarations that access the flags. 26 | If 'struct', it prints out a suitable struct declaration for accessing the flags, which 27 | is meant to be brought into your program using 'include!'. 28 | "; 29 | 30 | enum Mode { 31 | Validate, 32 | Vars, 33 | Struct, 34 | } 35 | 36 | fn main() { 37 | // Get the user's instructions for what to do. 38 | let lapp_file_spec = env::var("LAPP_GEN").unwrap_or_else(|_| { 39 | eprint!("{}", USAGE); 40 | ::std::process::exit(1); 41 | }); 42 | let parts: Vec<_> = lapp_file_spec.split_whitespace().collect(); 43 | 44 | let mode = match if parts.len() > 1 { 45 | parts[1] 46 | } else { 47 | "validate" 48 | } { 49 | "validate" => Mode::Validate, 50 | "vars" => Mode::Vars, 51 | "struct" => Mode::Struct, 52 | _ => { 53 | panic!("mode must be blank or one of 'validate', 'vars', or 'struct'"); 54 | } 55 | }; 56 | 57 | // First part of the spec is the file to process. 58 | let lapp_file = parts[0]; 59 | let mut f = File::open(lapp_file).expect(&format!("Unable to open {}. Error", lapp_file)); 60 | let mut txt = String::new(); 61 | f.read_to_string(&mut txt) 62 | .expect(&format!("Unable to read UTF-8 from {}. Error", lapp_file)); 63 | 64 | let mut args = lapp::Args::new(&txt); 65 | 66 | match mode { 67 | Mode::Vars => { 68 | io::stdout() 69 | .write_all(&args.declarations("").into_bytes()) 70 | .expect("Could not write to stdout. Error"); 71 | } 72 | Mode::Struct => { 73 | io::stdout() 74 | .write_all(&args.declarations("Args").into_bytes()) 75 | .expect("Could not write to stdout. Error"); 76 | } 77 | Mode::Validate => { 78 | args.dump(); 79 | } 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /src/flag.rs: -------------------------------------------------------------------------------- 1 | // Flag struct 2 | 3 | use super::types::*; 4 | 5 | #[derive(Default)] 6 | pub struct Flag { 7 | pub long: String, 8 | pub short: char, 9 | pub vtype: Type, 10 | pub value: Value, 11 | pub defval: Value, 12 | pub is_set: bool, 13 | pub is_multiple: bool, 14 | pub pos: usize, 15 | pub help: String, 16 | pub constraint: Option Result >>, 17 | pub strings: Vec, 18 | pub defstr: String, 19 | pub overriden: bool, 20 | } 21 | 22 | impl Flag { 23 | pub fn set_value_from_string(&mut self, arg: &str) -> Result<()> { 24 | let mut v = self.vtype.parse_string(arg)?; 25 | // there may be a constrait on this flag value 26 | if let Some(ref constraint) = self.constraint { 27 | v = constraint(v)?; 28 | } 29 | self.strings.push(arg.to_string()); 30 | self.set_value(v)?; 31 | Ok(()) 32 | } 33 | 34 | pub fn set_default_from_string(&mut self, arg: &str, infer: bool) -> Result<()> { 35 | self.defstr = arg.into(); 36 | if infer { // (default ) 37 | self.defval = Value::from_value(arg,&Type::None)?; 38 | self.vtype = self.defval.type_of(); 39 | } else { // ( default ) 40 | // type has already been set - coerce value. 41 | self.defval = Value::from_value(arg,&self.vtype)?; 42 | } 43 | Ok(()) 44 | } 45 | 46 | pub fn set_range_constraint(&mut self, b1: &str, b2: &str) -> Result<()> { 47 | let b1 = Value::from_value(b1,&Type::None)?; 48 | let b2 = Value::from_value(b2,&Type::None)?; 49 | if b1.type_of() != b2.type_of() { 50 | return error("range values must be same type"); 51 | } 52 | let tn = b1.type_of().short_name(); 53 | if ! (tn == "integer" || tn == "float") { 54 | return error("range values must be integer or float"); 55 | } 56 | self.vtype = b1.type_of(); 57 | 58 | if tn == "integer" { 59 | let i1 = b1.as_int().unwrap(); 60 | let i2 = b2.as_int().unwrap(); 61 | let msg = format!("flag '{}' out of range {}..{}",self.long,i1,i2); 62 | self.constraint = Some(Box::new( 63 | move |v| { 64 | let i = v.as_int().unwrap(); 65 | if i < i1 || i > i2 { 66 | return error(&msg); 67 | } 68 | Ok(Value::Int(i)) 69 | } 70 | )); 71 | } else { 72 | let x1 = b1.as_float().unwrap(); 73 | let x2 = b2.as_float().unwrap(); 74 | let msg = format!("flag '{}' out of range {}..{}",self.long,x1,x2); 75 | self.constraint = Some(Box::new( 76 | move |v| { 77 | let x = v.as_float().unwrap(); 78 | if x < x1 || x > x2 { 79 | return error(&msg); 80 | } 81 | Ok(Value::Float(x)) 82 | } 83 | )); 84 | } 85 | Ok(()) 86 | } 87 | 88 | pub fn set_value(&mut self, v: Value) -> Result<()> { 89 | if ! self.overriden && self.is_set && ! self.is_multiple { 90 | return error(format!("flag already specified {}",self.long)); 91 | } 92 | self.is_set = true; 93 | if ! self.is_multiple { 94 | self.value = v; 95 | } else { 96 | if let Value::Arr(ref mut arr) = self.value { 97 | arr.push(Box::new(v)); 98 | } 99 | } 100 | Ok(()) 101 | } 102 | 103 | pub fn position(&self) -> Option { 104 | if self.pos > 0 {Some(self.pos)} else {None} 105 | } 106 | 107 | // When checking any missing flags after scanning args, insist 108 | // that they have default values - otherwise they are 'required'. 109 | // (Array values may be empty tho) 110 | pub fn check(&mut self) -> Result<()> { 111 | if ! self.is_set { 112 | if let Value::None = self.defval { 113 | if let Type::Arr(_) = self.vtype { 114 | } else 115 | if ! self.is_multiple { 116 | //~ return error(format!("required flag {}",self.long)); 117 | } 118 | } else { 119 | self.value = self.defval.clone(); 120 | self.strings.push(self.defstr.clone()); 121 | } 122 | } 123 | Ok(()) 124 | } 125 | 126 | pub fn uncheck(&mut self) { 127 | self.overriden = true; 128 | self.strings.clear(); 129 | } 130 | 131 | pub fn clear(&mut self) { 132 | self.is_set = false; 133 | self.strings.clear(); 134 | self.value = Value::None; 135 | } 136 | 137 | pub fn rust_name(&self) -> String { 138 | // long name may need massaging to become a Rust variable name 139 | // The result must be snake_case to keep compiler happy! 140 | let mut name = self.long.replace('-',"_").to_lowercase().to_string(); 141 | let firstc = name.chars().nth(0).unwrap(); 142 | if firstc.is_digit(10) || firstc == '_' { 143 | name = format!("c_{}",name); 144 | } 145 | name 146 | } 147 | 148 | pub fn rust_type(&self) -> String { 149 | self.vtype.rust_name(self.is_multiple) 150 | } 151 | 152 | pub fn getter_name(&self) -> String { 153 | let mut tname = self.vtype.short_name(); 154 | // Is this an array flag? Two possibilities - the type is an array, 155 | // or our multiple flag is set. 156 | let maybe_array = self.vtype.array_type(); 157 | if maybe_array.is_some() { 158 | tname = maybe_array.unwrap().short_name() + "s"; 159 | } else 160 | if self.is_multiple { 161 | tname.push('s'); 162 | } 163 | format!("args.get_{}(\"{}\")",tname,self.long) 164 | } 165 | 166 | 167 | 168 | } 169 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `lapp` provides a straightforward way to parse command-line 2 | //! arguments, using the _usage text_ as a pattern. 3 | //! 4 | //! # Example 5 | //! ``` 6 | //! extern crate lapp; 7 | //! 8 | //! let args = lapp::parse_args(" 9 | //! A test program 10 | //! -v,--verbose verbose output 11 | //! -k (default 10) 12 | //! -s, --save (default 'out.txt') 13 | //! (default 'stdout') 14 | //! "); 15 | //! assert_eq!(args.get_bool("verbose"),false); 16 | //! assert_eq!(args.get_integer("k"),10); 17 | //! assert_eq!(args.get_string("save"),"out.txt"); 18 | //! assert_eq!(args.get_string("out"),"stdout"); 19 | //! ``` 20 | //! 21 | //! The usage text or _specification_ follows these simple rules: 22 | //! line begining with one of '-short, --long', '--long' or '-short' (flags) 23 | //! or begining with (positional arguments). 24 | //! These may be followed by a type/default specifier () - otherwise considered a bool flag 25 | //! with default `false`. This specifier can be a type (like '(integer)') or a default, 26 | //! like '(default 10)`. If there's a default, the type is infered from the value - can always 27 | //! use single quotes to insist that the flag value is a string. Otherwise this flag is 28 | //! _required_ and must be present! You can also use a type with default, e.g. "(path default ~/.boo). 29 | //! 30 | //! The currently supported types are 'string','integer','bool','float','infile','outfile' and 'path'. 31 | //! There are corresponding access methods like `get_string("flag")` and so forth. 32 | //! Access methods like `get_string_result("flag")` will _not_ exit the program on error 33 | //! and instead return an error. 34 | //! 35 | //! The flag may be followed by '...' (e.g '-I... ()') and it is then a _multiple_ 36 | //! flag; its value will be a vector. This vector may be empty (flag is not required). 37 | //! If the '...' appears inside the type specifier (e.g. '-p (integer...)') then 38 | //! the flag is expecting several space-separated values (like -p '10 20 30'); it is also 39 | //! represented by a vector. 40 | //! 41 | //! Rest of line (or any other kind of line) is ignored. 42 | //! 43 | //! lapp scans command-line arguments using GNU-style short and long flags. 44 | //! Short flags may be combined, and may immediately followed by a value, e.g '-vk5'. 45 | //! As an extension, you can say '--flag=value' or '-f:value'. 46 | 47 | use std::process; 48 | use std::env; 49 | use std::io; 50 | use std::io::{Write,Read}; 51 | use std::error::Error; 52 | use std::str::FromStr; 53 | use std::fmt::Display; 54 | use std::path::PathBuf; 55 | 56 | mod strutil; 57 | mod types; 58 | mod flag; 59 | use types::*; 60 | pub type Result = types::Result; 61 | use flag::Flag; 62 | 63 | pub struct Args<'a> { 64 | flags: Vec, 65 | pos: usize, 66 | text: &'a str, 67 | varargs: bool, 68 | user_types: Vec, 69 | istart: usize, 70 | } 71 | 72 | impl <'a> Args<'a> { 73 | /// provide a _usage string_ from which we extract flag definitions 74 | pub fn new(text: &'a str) -> Args { 75 | Args{flags: Vec::new(), pos: 0, text: text, varargs: false, user_types: Vec::new(), istart: 1} 76 | } 77 | 78 | /// start offset in program command-line arguments. 79 | /// This defaults to 1, but e.g. for Cargo subcommands 80 | /// it would be 2 81 | pub fn start(mut self, istart: usize) -> Self { 82 | self.istart = istart; 83 | self 84 | } 85 | 86 | /// declare any user-defined types to be used in the spec. 87 | /// (They will need to implement FromStr) 88 | pub fn user_types(&mut self, types: &[&str]) { 89 | let v: Vec = types.iter().map(|s| s.to_string()).collect(); 90 | self.user_types = v; 91 | } 92 | 93 | /// bail out of program with non-zero return code. 94 | /// May force this to panic instead with the 95 | /// LAPP_PANIC environment variable. 96 | pub fn quit(&self, msg: &str) -> ! { 97 | let path = env::current_exe().unwrap(); 98 | let exe = path.file_name().unwrap().to_string_lossy(); 99 | let text = format!("{} error: {}\nType {} --help for more information",exe,msg,exe); 100 | if env::var("LAPP_PANIC").is_ok() { 101 | panic!(text); 102 | } else { 103 | writeln!(&mut io::stderr(),"{}",text).unwrap(); 104 | process::exit(1); 105 | } 106 | } 107 | 108 | /// create suggested variable or struct declarations for accessing the flags... 109 | pub fn declarations(&mut self, struct_name: &str) -> String { 110 | if let Err(e) = self.parse_spec() { 111 | self.quit(e.description()); 112 | } 113 | let mut res = String::new(); 114 | if struct_name.len() > 0 { 115 | res += &format!("const USAGE: &'static str = \"\n{}\";\n",self.text); 116 | res += &format!("#[derive(Debug)]\nstruct {} {{\n",struct_name); 117 | for f in &self.flags { 118 | res += &format!("\t{}: {},\n",f.rust_name(),f.rust_type()); 119 | } 120 | res += &format!( 121 | "}}\n\nimpl {} {{\n\tfn new() -> ({},lapp::Args<'static>) {{\n", 122 | struct_name,struct_name); 123 | res += &format!( 124 | "\t\tlet args = lapp::parse_args(USAGE);\n\t\t({}{{\n",struct_name); 125 | for f in &self.flags { 126 | res += &format!("\t\t\t{}: {},\n",f.rust_name(),f.getter_name()); 127 | } 128 | res += &format!("\t\t}},args)\n\t}}\n}}\n\n"); 129 | } else { 130 | for f in &self.flags { 131 | res += &format!(" let {} = {};\n", 132 | f.rust_name(),f.getter_name()); 133 | } 134 | } 135 | res 136 | } 137 | 138 | pub fn dump(&mut self) { 139 | self.parse(); 140 | for f in &self.flags { 141 | println!("flag '{}' value {:?}",f.long,f.value); 142 | } 143 | } 144 | 145 | pub fn parse_env_args(&mut self) -> Result<()> { 146 | let v: Vec = env::args().skip(self.istart).collect(); 147 | self.parse_command_line(v) 148 | } 149 | 150 | /// parse the spec and the command-line 151 | pub fn parse_result(&mut self) -> Result<()> { 152 | self.parse_spec()?; 153 | self.parse_env_args() 154 | } 155 | 156 | 157 | /// parse the spec and the command-line, quitting on error. 158 | pub fn parse(&mut self) { 159 | if let Err(e) = self.parse_result() { 160 | self.quit(e.description()); 161 | } 162 | } 163 | 164 | /// parse the spec and create the flags. 165 | pub fn parse_spec(&mut self) -> Result<()> { 166 | for line in self.text.lines() { 167 | if let Err(e) = self.parse_spec_line(line) { 168 | return error(format!("{}\nat line: '{}'",e.description(),line)); 169 | } 170 | } 171 | if let Err(_) = self.flags_by_long("help") { 172 | self.parse_spec_line(" -h,--help this help").unwrap(); 173 | } 174 | Ok(()) 175 | } 176 | 177 | 178 | fn parse_spec_line(&mut self, mut slice: &str) -> Result<()> { 179 | use strutil::*; 180 | fn flag_error (flag: &Flag,msg: &str) -> Result<()> { 181 | error(format!("{}: flag '{}'",msg,flag.long)) 182 | } 183 | 184 | if let Some(idx) = slice.find(|c: char| ! c.is_whitespace()) { 185 | let mut flag: Flag = Default::default(); 186 | let mut is_positional = false; 187 | slice = &slice[idx..]; 188 | let is_flag = starts_with(&mut slice,"-"); 189 | let mut long_flag = starts_with(&mut slice,"-"); 190 | if is_flag && ! long_flag { // short flag 191 | flag.short = (&slice[0..1]).chars().next().unwrap(); 192 | flag.long = flag.short.to_string(); 193 | if ! flag.short.is_alphanumeric() { 194 | return flag_error(&flag,"not allowed: only letters or digits in short flags"); 195 | } 196 | slice = &slice[1..]; 197 | if let Some(0) = slice.find(|c: char| c.is_alphanumeric()) { 198 | return flag_error(&flag,"short flags should have one character"); 199 | } 200 | if starts_with(&mut slice,",") { 201 | slice = skipws(slice); 202 | long_flag = starts_with(&mut slice,"--"); 203 | if ! long_flag { 204 | return flag_error(&flag,"expecting long flag after short flag"); 205 | } 206 | } 207 | } 208 | if long_flag { 209 | let idx = slice.find(|c: char| ! (c.is_alphanumeric() || c == '_' || c == '-')) 210 | .unwrap_or(slice.len()); 211 | let parts = slice.split_at(idx); 212 | flag.long = parts.0.to_string(); 213 | slice = parts.1; 214 | if slice.len() > 0 && ! (slice.starts_with(" ") || slice.starts_with(".")) { 215 | return flag_error(&flag,"long flags can only contain letters, numbers, '_' or '-'"); 216 | } 217 | } else 218 | if starts_with(&mut slice, "<") { // positional argument 219 | flag.long = grab_upto(&mut slice, ">")?; 220 | self.pos = self.pos + 1; 221 | flag.pos = self.pos; 222 | is_positional = true; 223 | } 224 | if flag.long == "" && flag.short == '\0' { 225 | // not a significant line, ignore! 226 | return Ok(()); 227 | } 228 | if flag.long == "" { // just a short flag 229 | flag.long = flag.short.to_string(); 230 | } 231 | slice = skipws(slice); 232 | if starts_with(&mut slice,"...") { 233 | flag.is_multiple = true; 234 | slice = skipws(slice); 235 | } 236 | if starts_with(&mut slice,"(") { 237 | let r = grab_upto(&mut slice, ")")?; 238 | let mut rest = r.as_str().trim(); 239 | let multable = ends_with(&mut rest,"..."); 240 | if let Some((b1,b2)) = split_with(rest,"..") { 241 | // bounds on a number type 242 | flag.set_range_constraint(b1,b2)?; 243 | } else { 244 | // default VALUE or TYPE 245 | if rest.len() == 0 { 246 | return flag_error(&flag,"nothing inside type specifier"); 247 | } 248 | if starts_with(&mut rest,"default ") { 249 | rest = skipws(rest); 250 | // flag type will be deduced 251 | flag.set_default_from_string(rest,true)?; 252 | } else { 253 | let name = grab_word(&mut rest); 254 | // custom types are _internally_ stored as string types, 255 | // but we must verify that it is a known type! 256 | flag.vtype = if self.user_types.iter().any(|s| s == name.as_str()) { 257 | Type::Str 258 | } else { 259 | Type::from_name(&name)? 260 | }; 261 | if starts_with(&mut rest,"default ") { 262 | rest = skipws(rest); 263 | // flag already has a definite type 264 | flag.set_default_from_string(rest,false)?; 265 | } 266 | } 267 | } 268 | // if type is followed by '...' then the flag is also represented 269 | // by a vector (e.g. --ports '8080 8081 8082'). 270 | // UNLESS it is a positional argument, 271 | // where it is considered multiple! 272 | if multable { 273 | flag.defval = Value::empty_array(); 274 | if is_positional { 275 | flag.is_multiple = true; 276 | if self.varargs { 277 | return flag_error(&flag,"only last argument can occur multiple times"); 278 | } 279 | self.varargs = true; 280 | } else { // i.e the flag type is an array of a basic scalar type 281 | flag.vtype = flag.vtype.create_empty_array(); 282 | } 283 | } 284 | if flag.is_multiple { 285 | flag.value = Value::empty_array(); 286 | } 287 | } else { 288 | flag.defval = Value::Bool(false); 289 | flag.vtype = Type::Bool; 290 | } 291 | if slice.len() > 0 { 292 | flag.help = skipws(slice).to_string(); 293 | } 294 | 295 | // it is an error to specify a flag twice... 296 | if self.flags_by_long_ref(&flag.long).is_ok() { 297 | return flag_error(&flag,"already defined"); 298 | } 299 | self.flags.push(flag); 300 | } 301 | Ok(()) 302 | 303 | } 304 | 305 | fn flags_by_long(&mut self, s: &str) -> Result<&mut Flag> { 306 | self.flags.iter_mut() 307 | .filter(|&ref f| f.long == s) 308 | .next().ok_or(LappError(format!("no long flag '{}'",s))) 309 | } 310 | 311 | fn flags_by_long_ref(&self, s: &str) -> Result<&Flag> { 312 | self.flags.iter() 313 | .filter(|&f| f.long == s) 314 | .next().ok_or(LappError(format!("no long flag '{}'",s))) 315 | } 316 | 317 | fn flags_by_short(&mut self, ch: char) -> Result<&mut Flag> { 318 | self.flags.iter_mut() 319 | .filter(|&ref f| f.short == ch) 320 | .next().ok_or(LappError(format!("no short flag '{}'",ch))) 321 | } 322 | 323 | fn flags_by_pos(&mut self, pos: usize) -> Result<&mut Flag> { 324 | self.flags.iter_mut() 325 | .filter(|&ref f| f.pos == pos) 326 | .next().ok_or(LappError(format!("no arg #{}",pos))) 327 | } 328 | 329 | pub fn parse_command_line(&mut self, v: Vec) -> Result<()> { 330 | use strutil::*; 331 | let mut iter = v.into_iter(); 332 | 333 | fn nextarg(name: &str, ms: Option) -> Result { 334 | if ms.is_none() {return error(format!("no value for flag '{}'",name));} 335 | Ok(ms.unwrap()) 336 | }; 337 | 338 | // flags _may_ have the value after a = or : delimiter 339 | fn extract_flag_value(s: &mut &str) -> String { 340 | if let Some(idx) = s.find(|c: char| c == '=' || c == ':') { 341 | let rest = (&s[idx+1..]).to_string(); 342 | *s = &s[0..idx]; 343 | rest 344 | } else { 345 | "".to_string() 346 | } 347 | } 348 | 349 | let mut parsing = true; 350 | let mut k = 1; 351 | while let Some(arg) = iter.next() { 352 | let mut s = arg.as_str(); 353 | if parsing && starts_with(&mut s, "--") { // long flag 354 | if s.is_empty() { // plain '--' means 'stop arg processing' 355 | parsing = false; 356 | } else { 357 | let mut rest = extract_flag_value(&mut s); 358 | let flag = self.flags_by_long(s)?; 359 | if flag.vtype != Type::Bool { // then it needs a value.... 360 | if rest == "" { // try grab the next arg 361 | rest = nextarg(s,iter.next())?; 362 | } 363 | flag.set_value_from_string(&rest)?; 364 | } else { 365 | flag.set_value(Value::Bool(true))?; 366 | } 367 | } 368 | } else 369 | if parsing && starts_with(&mut s,"-") { // short flag 370 | // there can be multiple short flags 371 | // although only the last one can take a value 372 | let mut chars = s.chars(); 373 | while let Some(ch) = chars.next() { 374 | let flag = self.flags_by_short(ch)?; 375 | if flag.vtype != Type::Bool { 376 | let mut rest: String = chars.collect(); 377 | if rest == "" { 378 | rest = nextarg(&flag.long,iter.next())?; 379 | } 380 | flag.set_value_from_string(&rest)?; 381 | break; 382 | } else { 383 | flag.set_value(Value::Bool(true))?; 384 | } 385 | } 386 | } else { // positional argument 387 | let flag = self.flags_by_pos(k)?; 388 | flag.set_value_from_string(s)?; 389 | // multiple arguments are added to the vector value 390 | if ! flag.is_multiple { 391 | k += 1; 392 | } 393 | 394 | } 395 | } 396 | 397 | 398 | // display usage if help is requested 399 | if let Ok(ref flag) = self.flags_by_long_ref("help") { 400 | if flag.is_set { 401 | let text = strutil::dedent(self.text); 402 | println!("{}",text); 403 | process::exit(0); 404 | } 405 | } 406 | 407 | // fill in defaults. If a default isn't available it's 408 | // a required flag. If not specified the flag value is set to an error 409 | for flag in &mut self.flags { 410 | flag.check()?; 411 | } 412 | Ok(()) 413 | } 414 | 415 | /// clear used flag state 416 | pub fn clear_used(&mut self) { 417 | for flag in &mut self.flags { 418 | flag.uncheck(); 419 | } 420 | } 421 | 422 | /// clear all the flags - ready to parse a new command line. 423 | pub fn clear(&mut self) { 424 | for flag in &mut self.flags { 425 | flag.clear(); 426 | } 427 | } 428 | 429 | fn error_msg(&self, tname: &str, msg: &str, pos: Option) -> String { 430 | if let Some(idx) = pos { 431 | format!("argument #{} '{}': {}",idx,tname,msg) 432 | } else { 433 | format!("flag '{}': {}",tname,msg) 434 | } 435 | } 436 | 437 | fn bad_flag (&self, tname: &str, msg: &str) -> Result { 438 | let pos = if let Ok(ref flag) = self.flags_by_long_ref(tname) { 439 | flag.position() 440 | } else { 441 | None 442 | }; 443 | error(&self.error_msg(tname,msg,pos)) 444 | } 445 | 446 | fn unwrap(&self, res: Result) -> T { 447 | match res { 448 | Ok(v) => v, 449 | Err(e) => self.quit(e.description()) 450 | } 451 | } 452 | 453 | // there are three bad scenarios here. First, the flag wasn't found. 454 | // Second, the flag's value was not set. Third, the flag's value was an error. 455 | fn result_flag_flag (&self, name: &str) -> Result<&Flag> { 456 | if let Ok(ref flag) = self.flags_by_long_ref(name) { 457 | if flag.value.is_none() { 458 | self.bad_flag(name,"is required") 459 | } else { 460 | if let Value::Error(ref s) = flag.value { 461 | self.bad_flag(name,s) 462 | } else { 463 | Ok(flag) 464 | } 465 | } 466 | } else { 467 | self.bad_flag(name,"is unknown") 468 | } 469 | } 470 | 471 | fn result_flag_value (&self, name: &str) -> Result<&Value> { 472 | Ok(&(self.result_flag_flag(name)?.value)) 473 | } 474 | 475 | // this extracts the desired value from the Value struct using a closure. 476 | // This operation may fail, e.g. args.get_float("foo") is an error if the 477 | // flag type is integer. 478 | fn result_flag Result> (&self, name: &str, extract: F) -> Result { 479 | match self.result_flag_value(name) { 480 | Ok(value) => { 481 | match extract(value) { 482 | Ok(v) => Ok(v), 483 | Err(e) => self.bad_flag(name,e.description()) 484 | } 485 | }, 486 | Err(e) => Err(e) 487 | } 488 | } 489 | 490 | /// has this flag been set? Quits if it's an unknown flag 491 | pub fn flag_present(&self, name: &str) -> bool { 492 | if let Ok(ref flag) = self.flags_by_long_ref(name) { 493 | if flag.value.is_none() { 494 | false 495 | } else { 496 | true 497 | } 498 | } else { 499 | self.quit(&format!("'{}' is not a flag",name)); 500 | } 501 | } 502 | 503 | 504 | /// get flag as a string 505 | pub fn get_string_result(&self, name: &str) -> Result { 506 | self.result_flag(name,|v| v.as_string()) 507 | } 508 | 509 | /// get flag as an integer 510 | pub fn get_integer_result(&self, name: &str) -> Result { 511 | self.result_flag(name,|v| v.as_int()) 512 | } 513 | 514 | /// get flag as a float 515 | pub fn get_float_result(&self, name: &str) -> Result { 516 | self.result_flag(name,|v| v.as_float()) 517 | } 518 | 519 | /// get flag as boolean 520 | pub fn get_bool_result(&self, name: &str) -> Result { 521 | self.result_flag(name,|v| v.as_bool()) 522 | } 523 | 524 | /// get flag as a file for reading 525 | pub fn get_infile_result(&self, name: &str) -> Result> { 526 | self.result_flag(name,|v| v.as_infile()) 527 | } 528 | 529 | /// get flag as a file for writing 530 | pub fn get_outfile_result(&self, name: &str) -> Result> { 531 | self.result_flag(name,|v| v.as_outfile()) 532 | } 533 | 534 | /// get flag as a path 535 | pub fn get_path_result(&self, name: &str) -> Result { 536 | self.result_flag(name,|v| v.as_path()) 537 | } 538 | 539 | /// get flag always as text, if it's defined 540 | pub fn get_text_result(&self, name: &str) -> Result<&String> { 541 | self.result_flag_flag(name).map(|f| &f.strings[0]) 542 | } 543 | 544 | /// get flag as any value which can parsed from a string. 545 | // The magic here is that Rust needs to be told that 546 | // the associated Err type can be displayed. 547 | pub fn get_result(&self, name: &str) -> Result 548 | where T: FromStr, ::Err : Display 549 | { 550 | match self.get_text_result(name)?.parse::() { 551 | Ok(v) => Ok(v), 552 | Err(e) => self.bad_flag(name,&e.to_string()) 553 | } 554 | } 555 | 556 | /// get flag as a string, quitting otherwise. 557 | pub fn get_string(&self, name: &str) -> String { 558 | self.unwrap(self.get_string_result(name)) 559 | } 560 | 561 | /// get flag as an integer, quitting otherwise. 562 | pub fn get_integer(&self, name: &str) -> i32 { 563 | self.unwrap(self.get_integer_result(name)) 564 | } 565 | 566 | /// get flag as a float, quitting otherwise. 567 | pub fn get_float(&self, name: &str) -> f32 { 568 | self.unwrap(self.get_float_result(name)) 569 | } 570 | 571 | /// get flag as a bool, quitting otherwise. 572 | pub fn get_bool(&self, name: &str) -> bool { 573 | self.unwrap(self.get_bool_result(name)) 574 | } 575 | 576 | /// get flag as a file for reading, quitting otherwise. 577 | pub fn get_infile(&self, name: &str) -> Box { 578 | self.unwrap(self.get_infile_result(name)) 579 | } 580 | 581 | /// get flag as a file for writing, quitting otherwise. 582 | pub fn get_outfile(&self, name: &str) -> Box { 583 | self.unwrap(self.get_outfile_result(name)) 584 | } 585 | 586 | /// get flag as a path, quitting otherwise. 587 | pub fn get_path(&self, name: &str) -> PathBuf { 588 | self.unwrap(self.get_path_result(name)) 589 | } 590 | 591 | /// get flag as any value which can parsed from a string, quitting otherwise. 592 | pub fn get(&self, name: &str) -> T 593 | where T: FromStr, ::Err : Display 594 | { 595 | match self.get_result::(name) { 596 | Ok(v) => v, 597 | Err(e) => self.quit(&e.to_string()) 598 | } 599 | } 600 | 601 | fn get_boxed_array(&self, name: &str, kind: &str) -> Result<&Vec>> { 602 | let arr = self.result_flag_value(name)?.as_array()?; 603 | // empty array matches all types 604 | if arr.len() == 0 { return Ok(arr); } 605 | // otherwise check the type of the first element 606 | let ref v = *(arr[0]); 607 | let tname = v.type_of().short_name(); 608 | if tname == kind { 609 | Ok(arr) 610 | } else { 611 | let msg = format!("wanted array of {}, but is array of {}",kind,tname); 612 | error(self.error_msg(name,&msg,None)) 613 | } 614 | } 615 | 616 | fn get_array_result(&self, name: &str, kind: &str, extract: F) -> Result> 617 | where T: Sized, F: Fn(&Box)->Result { 618 | let va = self.get_boxed_array(name,kind)?; 619 | let mut res = Vec::new(); 620 | for v in va { 621 | let n = extract(v)?; 622 | res.push(n); 623 | } 624 | Ok(res) 625 | } 626 | 627 | /// get a multiple flag as an array of strings 628 | pub fn get_strings_result(&self, name: &str) -> Result> { 629 | self.get_array_result(name,"string",|b| b.as_string()) 630 | } 631 | 632 | /// get a multiple flag as an array of integers 633 | pub fn get_integers_result(&self, name: &str) -> Result> { 634 | self.get_array_result(name,"integer",|b| b.as_int()) 635 | } 636 | 637 | /// get a multiple flag as an array of floats 638 | pub fn get_floats_result(&self, name: &str) -> Result> { 639 | self.get_array_result(name,"float",|b| b.as_float()) 640 | } 641 | 642 | /// get a multiple flag as an array of any parsable value. 643 | pub fn get_results(&self, name: &str) -> Result> 644 | where T: FromStr, ::Err : Display 645 | { 646 | let flag = self.result_flag_flag(name)?; 647 | flag.strings.iter() 648 | .map(|s| s.parse::()) // Result 649 | .map(|r| { match r { // but we want Result 650 | Ok(v) => Ok(v), 651 | Err(e) => self.bad_flag(name,&e.to_string()) 652 | }}) 653 | .collect() 654 | } 655 | 656 | 657 | /// get a multiple flag as an array of strings, quitting otherwise 658 | pub fn get_strings(&self, name: &str) -> Vec { 659 | self.unwrap(self.get_strings_result(name)) 660 | } 661 | 662 | /// get a multiple flag as an array of integers, quitting otherwise 663 | pub fn get_integers(&self, name: &str) -> Vec { 664 | self.unwrap(self.get_integers_result(name)) 665 | } 666 | 667 | /// get a multiple flag as an array of floats, quitting otherwise 668 | pub fn get_floats(&self, name: &str) -> Vec { 669 | self.unwrap(self.get_floats_result(name)) 670 | } 671 | 672 | /// get a multiple flag as an array of any parsable value, quitting otherwise 673 | pub fn get_array(&self, name: &str) -> Vec 674 | where T: FromStr, ::Err : Display 675 | { 676 | self.unwrap(self.get_results(name)) 677 | } 678 | 679 | 680 | } 681 | 682 | /// parse the command-line specification and use it 683 | /// to parse the program's command line args. 684 | /// As before, quits on any error. 685 | pub fn parse_args(s: &str) -> Args { 686 | let mut res = Args::new(s); 687 | res.parse(); 688 | res 689 | } 690 | 691 | #[cfg(test)] 692 | mod tests { 693 | use super::*; 694 | 695 | const SIMPLE: &'static str = " 696 | Testing Lapp 697 | -v, --verbose verbose flag 698 | -k arb flag 699 | -o, --output (default 'stdout') 700 | -p (integer...) 701 | -I, --include... (string) 702 | (string) 703 | (string...) 704 | "; 705 | 706 | 707 | fn arg_strings(a: &[&str]) -> Vec { 708 | a.iter().map(|s| s.to_string()).collect() 709 | } 710 | 711 | fn empty_strings() -> Vec { 712 | Vec::new() 713 | } 714 | 715 | fn parse_args(spec: &'static str, parms: &[&str]) -> Args<'static> { 716 | let mut args = Args::new(spec); 717 | args.parse_spec().expect("spec failed"); 718 | args.parse_command_line(arg_strings(parms)).expect("scan failed"); 719 | args 720 | } 721 | 722 | 723 | struct SimpleTest { 724 | verbose: bool, 725 | k: bool, 726 | output: String, 727 | p: Vec, 728 | include: Vec, 729 | out: Vec 730 | } 731 | 732 | impl SimpleTest { 733 | fn new(test_args: &[&str]) -> SimpleTest { 734 | let args = parse_args(SIMPLE,test_args); 735 | SimpleTest { 736 | verbose: args.get_bool("verbose"), 737 | k: args.get_bool("k"), 738 | output: args.get_string("output"), 739 | p: args.get_integers("p"), 740 | include: args.get_strings("include"), 741 | out: args.get_strings("out") 742 | } 743 | } 744 | } 745 | 746 | #[test] 747 | fn test_simple_just_out() { 748 | let res = SimpleTest::new(&["boo","hello"]); 749 | assert_eq!(res.verbose,false); 750 | assert_eq!(res.k,false); 751 | assert_eq!(res.output,"stdout"); 752 | assert_eq!(res.p,&[]); 753 | assert_eq!(res.out,&["hello"]); 754 | } 755 | 756 | #[test] 757 | fn test_simple_bool_flags() { 758 | let res = SimpleTest::new(&["boo","-vk","hello"]); 759 | assert_eq!(res.verbose,true); 760 | assert_eq!(res.k,true); 761 | assert_eq!(res.output,"stdout"); 762 | assert_eq!(res.p,&[]); 763 | assert_eq!(res.out,&["hello"]); 764 | } 765 | 766 | #[test] 767 | fn test_simple_array_flag() { 768 | let res = SimpleTest::new(&["boo","-p","10 20 30","hello"]); 769 | assert_eq!(res.verbose,false); 770 | assert_eq!(res.k,false); 771 | assert_eq!(res.output,"stdout"); 772 | assert_eq!(res.p,&[10,20,30]); 773 | assert_eq!(res.out,&["hello"]); 774 | } 775 | 776 | #[test] 777 | fn test_simple_multiple_positional_args() { 778 | let res = SimpleTest::new(&["boo","hello","baggins","--","--frodo"]); 779 | assert_eq!(res.verbose,false); 780 | assert_eq!(res.k,false); 781 | assert_eq!(res.output,"stdout"); 782 | assert_eq!(res.p,&[]); 783 | assert_eq!(res.include,empty_strings()); 784 | assert_eq!(res.out,&["hello","baggins","--frodo"]); 785 | } 786 | 787 | #[test] 788 | fn test_simple_multiple_flags() { 789 | let res = SimpleTest::new(&["boo","-I.","-I..","--include","lib","hello"]); 790 | assert_eq!(res.verbose,false); 791 | assert_eq!(res.k,false); 792 | assert_eq!(res.output,"stdout"); 793 | assert_eq!(res.p,&[]); 794 | assert_eq!(res.include,&[".","..","lib"]); 795 | assert_eq!(res.out,&["hello"]); 796 | } 797 | 798 | fn err(r: Result) -> String { 799 | r.err().unwrap().description().to_string() 800 | } 801 | 802 | fn ok(r: Result) -> T { 803 | r.unwrap() 804 | } 805 | 806 | static ERRS: &str = " 807 | testing lapp 808 | -s,--str (string) 809 | (float) 810 | ... (integer) 811 | "; 812 | 813 | #[test] 814 | fn test_errors_result() { 815 | let aa = parse_args(ERRS,&["1","10","20","30"]); 816 | assert_eq!(err(aa.get_string_result("str")),"flag \'str\': is required"); 817 | assert_eq!(err(aa.get_string_result("frodo")),"argument #1 \'frodo\': not a string, but float"); 818 | assert_eq!(ok(aa.get_float_result("frodo")),1.0); 819 | assert_eq!(ok(aa.get_integers_result("bonzo")),[10, 20, 30]); 820 | } 821 | 822 | 823 | 824 | const CUSTOM: &str = " 825 | Custom types need to be given names 826 | so we accept them as valid: 827 | --hex (hex) 828 | "; 829 | 830 | // they need to be user types that implement FromStr 831 | use std::str::FromStr; 832 | use std::num::ParseIntError; 833 | 834 | struct Hex { 835 | value: u64 836 | } 837 | 838 | impl FromStr for Hex { 839 | type Err = ParseIntError; 840 | 841 | fn from_str(s: &str) -> ::std::result::Result { 842 | let value = u64::from_str_radix(s,16)?; 843 | Ok(Hex{value: value}) 844 | } 845 | } 846 | 847 | 848 | #[test] 849 | fn test_custom() { 850 | let mut args = Args::new(CUSTOM); 851 | args.user_types(&["hex"]); 852 | args.parse_spec().expect("spec failed"); 853 | args.parse_command_line(arg_strings(&["--hex","FF"])).expect("scan failed"); 854 | let hex: Hex = args.get("hex"); 855 | assert_eq!(hex.value,0xFF); 856 | } 857 | 858 | 859 | } 860 | -------------------------------------------------------------------------------- /src/strutil.rs: -------------------------------------------------------------------------------- 1 | use super::LappError; 2 | 3 | pub fn skipws(slice: &str) -> &str { 4 | let nxt = slice.find(|c: char| ! c.is_whitespace()).unwrap_or(slice.len()); 5 | &slice[nxt..] 6 | } 7 | 8 | pub fn grab_word<'a>(pslice: &mut &str) -> String { 9 | let nxt = pslice.find(|c: char| c.is_whitespace()).unwrap_or(pslice.len()); 10 | let word = (&pslice[0..nxt]).to_string(); 11 | *pslice = skipws(&pslice[nxt..]); 12 | word 13 | } 14 | 15 | pub fn starts_with(pslice: &mut &str, start: &str) -> bool { 16 | if pslice.starts_with(start) { 17 | *pslice = &pslice[start.len()..]; 18 | true 19 | } else { 20 | false 21 | } 22 | } 23 | 24 | pub fn ends_with(pslice: &mut &str, start: &str) -> bool { 25 | if pslice.ends_with(start) { 26 | *pslice = &pslice[0..(pslice.len() - start.len())]; 27 | true 28 | } else { 29 | false 30 | } 31 | } 32 | 33 | pub fn grab_upto(pslice: &mut &str, sub: &str) -> Result { 34 | if let Some(idx) = pslice.find(sub) { 35 | let s = (&pslice[0..idx].trim()).to_string(); 36 | *pslice = &pslice[idx+sub.len()..]; 37 | Ok(s) 38 | } else { 39 | Err(LappError(format!("cannot find end {:?}",sub))) 40 | } 41 | } 42 | 43 | pub fn split_with<'a>(slice: &'a str, needle: &str) -> Option<(&'a str,&'a str)> { 44 | if let Some(idx) = slice.find(needle) { 45 | Some(( 46 | &slice[0..idx], &slice[idx+needle.len()..] 47 | )) 48 | } else { 49 | None 50 | } 51 | } 52 | 53 | pub fn dedent(s: &str) -> String { 54 | let mut lines = s.lines(); 55 | let mut res = String::new(); 56 | let mut idx = None; 57 | while let Some(line) = lines.next() { 58 | if let Some(pos) = line.chars().position(|c| ! c.is_whitespace()) { 59 | idx = Some(pos); 60 | res += &line[pos..]; 61 | res.push('\n'); 62 | break; 63 | } 64 | } 65 | if let Some(pos) = idx { 66 | while let Some(line) = lines.next() { 67 | res += if line.len() >= pos { &line[pos..] } else { line }; 68 | res.push('\n'); 69 | } 70 | } 71 | res 72 | } 73 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | // Type and Value structs; errors 2 | 3 | use std::error::Error; 4 | use std::fmt; 5 | use std::result; 6 | use std::string; 7 | use std::io; 8 | use std::env; 9 | use std::fs::File; 10 | use std::io::prelude::*; 11 | use std::path::PathBuf; 12 | 13 | #[derive(Debug)] 14 | pub struct LappError(pub String); 15 | 16 | impl fmt::Display for LappError { 17 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 18 | write!(f,"{}",self.0) 19 | } 20 | } 21 | 22 | impl Error for LappError { 23 | fn description(&self) -> &str { 24 | &self.0 25 | } 26 | } 27 | 28 | pub type Result = result::Result; 29 | 30 | pub fn error(msg: M) -> Result { 31 | Err(LappError(msg.to_string())) 32 | } 33 | 34 | 35 | // the flag types 36 | #[derive(Debug, PartialEq)] 37 | pub enum Type { 38 | Str, 39 | Int, 40 | Float, 41 | Bool, 42 | FileIn, 43 | FileOut, 44 | Path, 45 | None, 46 | Arr(Box), 47 | Error, 48 | } 49 | 50 | impl Default for Type { 51 | fn default() -> Type { Type::None } 52 | } 53 | 54 | 55 | impl Type { 56 | pub fn from_name(s: &str) -> Result { 57 | match s { 58 | "string" => Ok(Type::Str), 59 | "integer" => Ok(Type::Int), 60 | "float" => Ok(Type::Float), 61 | "bool" => Ok(Type::Bool), 62 | "infile" => Ok(Type::FileIn), 63 | "outfile" => Ok(Type::FileOut), 64 | "path" => Ok(Type::Path), 65 | _ => error(format!("not a known type {}",s)) 66 | } 67 | } 68 | 69 | pub fn array_type(&self) -> Option<&Type> { 70 | // &**bt means (from R to L) deref bt, unbox type, return reference 71 | match *self {Type::Arr(ref bt) => Some(&**bt), _ => None} 72 | } 73 | 74 | pub fn create_empty_array(self) -> Type { 75 | Type::Arr(Box::new(self)) 76 | } 77 | 78 | pub fn short_name(&self) -> String { 79 | let s; 80 | (match *self { 81 | Type::Str => "string", 82 | Type::Int => "integer", 83 | Type::Float => "float", 84 | Type::Bool => "bool", 85 | Type::FileIn => "infile", 86 | Type::FileOut => "outfile", 87 | Type::Arr(ref t) => { s=format!("array of {}",t.short_name()); s.as_str() } 88 | _ => "bad" 89 | }).to_string() 90 | } 91 | 92 | pub fn rust_name(&self, multiple: bool) -> String { 93 | let mut res = match *self { 94 | Type::Bool => "bool".into(), 95 | Type::Float => "f32".into(), 96 | Type::Int => "i32".into(), 97 | Type::Str => "String".into(), 98 | Type::FileIn => "Box".into(), 99 | Type::FileOut => "Box".into(), 100 | Type::Arr(ref t) => format!("Vec<{}>",t.rust_name(false)), 101 | _ => "bad".into() 102 | }; 103 | if multiple { 104 | res = format!("Vec<{}>",res); 105 | } 106 | res 107 | } 108 | 109 | pub fn parse_string(&self, s: &str) -> Result { 110 | match *self { 111 | Type::Str => Ok(Value::Str(s.to_string())), 112 | Type::Int => 113 | match s.parse::() { 114 | Ok(n) => Ok(Value::Int(n)), 115 | Err(e) => Ok(Value::Error(format!("can't convert '{}' to integer - {}",s,e.description()))) 116 | }, 117 | Type::Float => 118 | match s.parse::() { 119 | Ok(v) => Ok(Value::Float(v)), 120 | Err(e) => Ok(Value::Error(format!("can't convert '{}' to float - {}",s,e.description()))) 121 | }, 122 | Type::FileIn => Ok(Value::FileIn(s.to_string())), 123 | Type::FileOut => Ok(Value::FileOut(s.to_string())), 124 | Type::Arr(ref bt) => { 125 | // multiple values either space or comma separated, 126 | // but we will trim anyway 127 | let parts: Vec<_> = if s.find(',').is_some() { 128 | s.split(',').map(|s| s.trim()).collect() 129 | } else { 130 | s.split_whitespace().collect() 131 | }; 132 | let mut res = Vec::new(); 133 | for part in parts { 134 | let v = bt.parse_string(part)?; 135 | res.push(Box::new(v)); 136 | } 137 | Ok(Value::Arr(res)) 138 | }, 139 | Type::Path => Ok(Value::Path(s.into())), 140 | _ => error(format!("can't convert '{}' to {:?}",s,self)) 141 | } 142 | } 143 | 144 | } 145 | 146 | // and values... 147 | #[derive(Debug, Clone)] 148 | pub enum Value { 149 | Str(String), 150 | Int(i32), 151 | Float(f32), 152 | Bool(bool), 153 | FileIn(String), 154 | FileOut(String), 155 | Path(PathBuf), 156 | None, 157 | Arr(Vec>), 158 | Error(String), 159 | } 160 | 161 | impl Default for Value { 162 | fn default() -> Value { Value::None } 163 | } 164 | 165 | impl Value { 166 | fn type_error(&self, kind: &str) -> Result { 167 | error(format!("not a {}, but {}",kind,self.type_of().short_name())) 168 | } 169 | 170 | pub fn as_string(&self) -> Result { 171 | match *self { Value::Str(ref s) => Ok(s.clone()), _ => self.type_error("string") } 172 | } 173 | 174 | pub fn as_int(&self) -> Result { 175 | match *self { Value::Int(n) => Ok(n), _ => self.type_error("integer" )} 176 | } 177 | 178 | pub fn as_float(&self) -> Result { 179 | match *self { Value::Float(x) => Ok(x), _ => self.type_error("float") } 180 | } 181 | 182 | pub fn as_bool(&self) -> Result { 183 | match *self { Value::Bool(b) => Ok(b), _ => self.type_error("boolean") } 184 | } 185 | 186 | pub fn as_infile(&self) -> Result> { 187 | match *self { 188 | Value::FileIn(ref s) => { 189 | if s == "stdin" { return Ok(Box::new(io::stdin())); } 190 | match File::open(s) { 191 | Ok(f) => Ok(Box::new(f)), 192 | Err(e) => error(format!("can't open '{}' for reading: {}",s, e.description())) 193 | } 194 | }, 195 | _ => self.type_error("infile") 196 | } 197 | } 198 | 199 | pub fn as_outfile(&self) -> Result> { 200 | match *self { 201 | Value::FileOut(ref s) => { 202 | if s == "stdout" { return Ok(Box::new(io::stdout())); } 203 | match File::create(s) { 204 | Ok(f) => Ok(Box::new(f)), 205 | Err(e) => error(format!("can't open '{}' for writing: {}",s, e.description())) 206 | } 207 | }, 208 | _ => self.type_error("outfile") 209 | } 210 | } 211 | 212 | pub fn as_path(&self) -> Result { 213 | match *self { 214 | Value::Path(ref p) => Ok(p.clone()), 215 | Value::FileIn(ref s) => Ok(s.clone().into()), 216 | Value::FileOut(ref s) => Ok(s.clone().into()), 217 | _ => self.type_error("path") 218 | 219 | } 220 | } 221 | 222 | 223 | pub fn as_array(&self) -> Result<&Vec>> { 224 | match *self { 225 | Value::Arr(ref vi) => Ok(vi), 226 | _ => self.type_error("array") 227 | } 228 | } 229 | 230 | pub fn type_of(&self) -> Type { 231 | match *self { 232 | Value::Str(_) => Type::Str, 233 | Value::Int(_) => Type::Int, 234 | Value::Float(_) => Type::Float, 235 | Value::Bool(_) => Type::Bool, 236 | Value::FileIn(_) => Type::FileIn, 237 | Value::FileOut(_) => Type::FileOut, 238 | Value::Path(_) => Type::Path, 239 | Value::None => Type::None, 240 | Value::Error(_) => Type::Error, 241 | // watch out here... 242 | Value::Arr(ref v) => Type::Arr(Box::new((*v[0]).type_of())) 243 | } 244 | } 245 | 246 | // This converts the '(default STR)' specifier into the actual value (and hence type) 247 | pub fn from_value (val: &str, dtype: &Type) -> Result { 248 | let firstc = val.chars().next().unwrap(); 249 | if firstc.is_digit(10) { 250 | let dt; 251 | let t = if let Type::None = *dtype { 252 | dt = if val.find('.').is_some() { Type::Float } else { Type::Int }; 253 | &dt 254 | } else { 255 | dtype 256 | }; 257 | t.parse_string(val) 258 | } else 259 | if firstc == '\'' { // strip quotes, _definitely_ a string 260 | match *dtype { 261 | Type::None | Type::Str => {}, 262 | _ => 263 | return error(format!("cannot convert default string to {}",dtype.short_name())) 264 | } 265 | Ok(Value::Str((&val[1..(val.len()-1)]).into())) 266 | } else 267 | if let Type::Str = *dtype { 268 | Ok(Value::Str(val.into())) 269 | } else 270 | if val == "stdin" { 271 | Ok(Value::FileIn("stdin".into())) 272 | } else 273 | if val == "stdout" { 274 | Ok(Value::FileOut("stdout".into())) 275 | } else 276 | if let Type::Path = *dtype { 277 | let val = if val.starts_with('~') { 278 | env::home_dir().unwrap().join(&val[2..]) 279 | } else { 280 | val.into() 281 | }; 282 | Ok(Value::Path(val)) 283 | } else { 284 | Ok(Value::Str(val.into())) 285 | } 286 | } 287 | 288 | pub fn empty_array() -> Value { 289 | let empty: Vec> = Vec::new(); 290 | Value::Arr(empty) 291 | } 292 | 293 | pub fn is_none(&self) -> bool { 294 | match *self { 295 | Value::None => true, 296 | _ => false 297 | } 298 | } 299 | 300 | } 301 | 302 | --------------------------------------------------------------------------------