diff --git a/.gitignore b/.gitignore index 11101efcfb3..34253e90833 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ Cargo.lock # Project files .vscode/* +.idea/* diff --git a/CHANGELOG.md b/CHANGELOG.md index 43a9a897a0c..03431f23b2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ + +### 2.29.1 (2018-01-09) + + +#### Documentation + +* fixes broken links. ([56e734b8](https://github.com/kbknapp/clap-rs/commit/56e734b839303d733d2e5baf7dac39bd7b97b8e4)) +* updates contributors list ([e1313a5a](https://github.com/kbknapp/clap-rs/commit/e1313a5a0f69d8f4016f73b860a63af8318a6676)) + +#### Performance + +* further debloating by removing generics from error cases ([eb8d919e](https://github.com/kbknapp/clap-rs/commit/eb8d919e6f3443db279ba0c902f15d76676c02dc)) +* debloats clap by deduplicating logic and refactors ([03e413d7](https://github.com/kbknapp/clap-rs/commit/03e413d7175d35827cd7d8908d47dbae15a849a3)) + +#### Bug Fixes + +* fixes the ripgrep benchmark by adding a value to a flag that expects it ([d26ab2b9](https://github.com/kbknapp/clap-rs/commit/d26ab2b97cf9c0ea675b440b7b0eaf6ac3ad01f4)) +* **bash completion:** Change the bash completion script code generation to support hyphens. ([ba7f1d18](https://github.com/kbknapp/clap-rs/commit/ba7f1d18eba7a07ce7f57e0981986f66c994b639)) +* **completions/zsh.rs:** Fix completion of long option values ([46365cf8](https://github.com/kbknapp/clap-rs/commit/46365cf8be5331ba04c895eb183e2f230b5aad51)) + + ## 2.29.0 (2017-12-02) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index d6b83808a6c..c4f238597bf 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -17,69 +17,69 @@ the following is a list of contributors: :---: |:---: |:---: |:---: |:---: |:---: | [Arnavion](https://github.com/Arnavion) |[japaric](https://github.com/japaric) |[untitaker](https://github.com/untitaker) |[afiune](https://github.com/afiune) |[crazymerlyn](https://github.com/crazymerlyn) |[SuperFluffy](https://github.com/SuperFluffy) | -[malbarbo](https://github.com/malbarbo) |[matthiasbeyer](https://github.com/matthiasbeyer) |[gohyda](https://github.com/gohyda) |[tshepang](https://github.com/tshepang) |[golem131](https://github.com/golem131) |[jimmycuadra](https://github.com/jimmycuadra) | +[matthiasbeyer](https://github.com/matthiasbeyer) |[malbarbo](https://github.com/malbarbo) |[tshepang](https://github.com/tshepang) |[golem131](https://github.com/golem131) |[jimmycuadra](https://github.com/jimmycuadra) |[Nemo157](https://github.com/Nemo157) | :---: |:---: |:---: |:---: |:---: |:---: | -[malbarbo](https://github.com/malbarbo) |[matthiasbeyer](https://github.com/matthiasbeyer) |[gohyda](https://github.com/gohyda) |[tshepang](https://github.com/tshepang) |[golem131](https://github.com/golem131) |[jimmycuadra](https://github.com/jimmycuadra) | +[matthiasbeyer](https://github.com/matthiasbeyer) |[malbarbo](https://github.com/malbarbo) |[tshepang](https://github.com/tshepang) |[golem131](https://github.com/golem131) |[jimmycuadra](https://github.com/jimmycuadra) |[Nemo157](https://github.com/Nemo157) | -[Nemo157](https://github.com/Nemo157) |[SShrike](https://github.com/SShrike) |[Eijebong](https://github.com/Eijebong) |[cstorey](https://github.com/cstorey) |[wdv4758h](https://github.com/wdv4758h) |[frewsxcv](https://github.com/frewsxcv) | +[severen](https://github.com/severen) |[Eijebong](https://github.com/Eijebong) |[cstorey](https://github.com/cstorey) |[wdv4758h](https://github.com/wdv4758h) |[frewsxcv](https://github.com/frewsxcv) |[hoodie](https://github.com/hoodie) | :---: |:---: |:---: |:---: |:---: |:---: | -[Nemo157](https://github.com/Nemo157) |[SShrike](https://github.com/SShrike) |[Eijebong](https://github.com/Eijebong) |[cstorey](https://github.com/cstorey) |[wdv4758h](https://github.com/wdv4758h) |[frewsxcv](https://github.com/frewsxcv) | +[severen](https://github.com/severen) |[Eijebong](https://github.com/Eijebong) |[cstorey](https://github.com/cstorey) |[wdv4758h](https://github.com/wdv4758h) |[frewsxcv](https://github.com/frewsxcv) |[hoodie](https://github.com/hoodie) | -[hoodie](https://github.com/hoodie) |[huonw](https://github.com/huonw) |[GrappigPanda](https://github.com/GrappigPanda) |[shepmaster](https://github.com/shepmaster) |[porglezomp](https://github.com/porglezomp) |[kieraneglin](https://github.com/kieraneglin) | +[huonw](https://github.com/huonw) |[GrappigPanda](https://github.com/GrappigPanda) |[shepmaster](https://github.com/shepmaster) |[porglezomp](https://github.com/porglezomp) |[kieraneglin](https://github.com/kieraneglin) |[musoke](https://github.com/musoke) | :---: |:---: |:---: |:---: |:---: |:---: | -[hoodie](https://github.com/hoodie) |[huonw](https://github.com/huonw) |[GrappigPanda](https://github.com/GrappigPanda) |[shepmaster](https://github.com/shepmaster) |[porglezomp](https://github.com/porglezomp) |[kieraneglin](https://github.com/kieraneglin) | +[huonw](https://github.com/huonw) |[GrappigPanda](https://github.com/GrappigPanda) |[shepmaster](https://github.com/shepmaster) |[porglezomp](https://github.com/porglezomp) |[kieraneglin](https://github.com/kieraneglin) |[musoke](https://github.com/musoke) | -[musoke](https://github.com/musoke) |[nelsonjchen](https://github.com/nelsonjchen) |[pkgw](https://github.com/pkgw) |[Deedasmi](https://github.com/Deedasmi) |[vmchale](https://github.com/vmchale) |[messense](https://github.com/messense) | +[nelsonjchen](https://github.com/nelsonjchen) |[pkgw](https://github.com/pkgw) |[Deedasmi](https://github.com/Deedasmi) |[vmchale](https://github.com/vmchale) |[messense](https://github.com/messense) |[Keats](https://github.com/Keats) | :---: |:---: |:---: |:---: |:---: |:---: | -[musoke](https://github.com/musoke) |[nelsonjchen](https://github.com/nelsonjchen) |[pkgw](https://github.com/pkgw) |[Deedasmi](https://github.com/Deedasmi) |[vmchale](https://github.com/vmchale) |[messense](https://github.com/messense) | +[nelsonjchen](https://github.com/nelsonjchen) |[pkgw](https://github.com/pkgw) |[Deedasmi](https://github.com/Deedasmi) |[vmchale](https://github.com/vmchale) |[messense](https://github.com/messense) |[Keats](https://github.com/Keats) | -[Keats](https://github.com/Keats) |[starkat99](https://github.com/starkat99) |[durka](https://github.com/durka) |[alex-gulyas](https://github.com/alex-gulyas) |[cite-reader](https://github.com/cite-reader) |[alexbool](https://github.com/alexbool) | +[starkat99](https://github.com/starkat99) |[durka](https://github.com/durka) |[alex-gulyas](https://github.com/alex-gulyas) |[cite-reader](https://github.com/cite-reader) |[alexbool](https://github.com/alexbool) |[AluisioASG](https://github.com/AluisioASG) | :---: |:---: |:---: |:---: |:---: |:---: | -[Keats](https://github.com/Keats) |[starkat99](https://github.com/starkat99) |[durka](https://github.com/durka) |[alex-gulyas](https://github.com/alex-gulyas) |[cite-reader](https://github.com/cite-reader) |[alexbool](https://github.com/alexbool) | +[starkat99](https://github.com/starkat99) |[durka](https://github.com/durka) |[alex-gulyas](https://github.com/alex-gulyas) |[cite-reader](https://github.com/cite-reader) |[alexbool](https://github.com/alexbool) |[AluisioASG](https://github.com/AluisioASG) | -[AluisioASG](https://github.com/AluisioASG) |[BurntSushi](https://github.com/BurntSushi) |[nox](https://github.com/nox) |[pixelistik](https://github.com/pixelistik) |[brennie](https://github.com/brennie) |[ogham](https://github.com/ogham) | +[BurntSushi](https://github.com/BurntSushi) |[nox](https://github.com/nox) |[mitsuhiko](https://github.com/mitsuhiko) |[brennie](https://github.com/brennie) |[ogham](https://github.com/ogham) |[pixelistik](https://github.com/pixelistik) | :---: |:---: |:---: |:---: |:---: |:---: | -[AluisioASG](https://github.com/AluisioASG) |[BurntSushi](https://github.com/BurntSushi) |[nox](https://github.com/nox) |[pixelistik](https://github.com/pixelistik) |[brennie](https://github.com/brennie) |[ogham](https://github.com/ogham) | +[BurntSushi](https://github.com/BurntSushi) |[nox](https://github.com/nox) |[mitsuhiko](https://github.com/mitsuhiko) |[brennie](https://github.com/brennie) |[ogham](https://github.com/ogham) |[pixelistik](https://github.com/pixelistik) | -[Bilalh](https://github.com/Bilalh) |[dotdash](https://github.com/dotdash) |[bradurani](https://github.com/bradurani) |[Seeker14491](https://github.com/Seeker14491) |[brianp](https://github.com/brianp) |[casey](https://github.com/casey) | +[dotdash](https://github.com/dotdash) |[bradurani](https://github.com/bradurani) |[Seeker14491](https://github.com/Seeker14491) |[brianp](https://github.com/brianp) |[cldershem](https://github.com/cldershem) |[casey](https://github.com/casey) | :---: |:---: |:---: |:---: |:---: |:---: | -[Bilalh](https://github.com/Bilalh) |[dotdash](https://github.com/dotdash) |[bradurani](https://github.com/bradurani) |[Seeker14491](https://github.com/Seeker14491) |[brianp](https://github.com/brianp) |[casey](https://github.com/casey) | +[dotdash](https://github.com/dotdash) |[bradurani](https://github.com/bradurani) |[Seeker14491](https://github.com/Seeker14491) |[brianp](https://github.com/brianp) |[cldershem](https://github.com/cldershem) |[casey](https://github.com/casey) | [volks73](https://github.com/volks73) |[daboross](https://github.com/daboross) |[mernen](https://github.com/mernen) |[dguo](https://github.com/dguo) |[davidszotten](https://github.com/davidszotten) |[drusellers](https://github.com/drusellers) | :---: |:---: |:---: |:---: |:---: |:---: | [volks73](https://github.com/volks73) |[daboross](https://github.com/daboross) |[mernen](https://github.com/mernen) |[dguo](https://github.com/dguo) |[davidszotten](https://github.com/davidszotten) |[drusellers](https://github.com/drusellers) | -[eddyb](https://github.com/eddyb) |[Fraser999](https://github.com/Fraser999) |[birkenfeld](https://github.com/birkenfeld) |[guanqun](https://github.com/guanqun) |[tanakh](https://github.com/tanakh) |[SirVer](https://github.com/SirVer) | +[eddyb](https://github.com/eddyb) |[Enet4](https://github.com/Enet4) |[Fraser999](https://github.com/Fraser999) |[birkenfeld](https://github.com/birkenfeld) |[guanqun](https://github.com/guanqun) |[tanakh](https://github.com/tanakh) | :---: |:---: |:---: |:---: |:---: |:---: | -[eddyb](https://github.com/eddyb) |[Fraser999](https://github.com/Fraser999) |[birkenfeld](https://github.com/birkenfeld) |[guanqun](https://github.com/guanqun) |[tanakh](https://github.com/tanakh) |[SirVer](https://github.com/SirVer) | +[eddyb](https://github.com/eddyb) |[Enet4](https://github.com/Enet4) |[Fraser999](https://github.com/Fraser999) |[birkenfeld](https://github.com/birkenfeld) |[guanqun](https://github.com/guanqun) |[tanakh](https://github.com/tanakh) | -[idmit](https://github.com/idmit) |[archer884](https://github.com/archer884) |[jacobmischka](https://github.com/jacobmischka) |[jespino](https://github.com/jespino) |[jfrankenau](https://github.com/jfrankenau) |[jtdowney](https://github.com/jtdowney) | +[SirVer](https://github.com/SirVer) |[idmit](https://github.com/idmit) |[archer884](https://github.com/archer884) |[jacobmischka](https://github.com/jacobmischka) |[jespino](https://github.com/jespino) |[jfrankenau](https://github.com/jfrankenau) | :---: |:---: |:---: |:---: |:---: |:---: | -[idmit](https://github.com/idmit) |[archer884](https://github.com/archer884) |[jacobmischka](https://github.com/jacobmischka) |[jespino](https://github.com/jespino) |[jfrankenau](https://github.com/jfrankenau) |[jtdowney](https://github.com/jtdowney) | +[SirVer](https://github.com/SirVer) |[idmit](https://github.com/idmit) |[archer884](https://github.com/archer884) |[jacobmischka](https://github.com/jacobmischka) |[jespino](https://github.com/jespino) |[jfrankenau](https://github.com/jfrankenau) | -[andete](https://github.com/andete) |[joshtriplett](https://github.com/joshtriplett) |[Kalwyn](https://github.com/Kalwyn) |[manuel-rhdt](https://github.com/manuel-rhdt) |[Marwes](https://github.com/Marwes) |[mdaffin](https://github.com/mdaffin) | +[jtdowney](https://github.com/jtdowney) |[andete](https://github.com/andete) |[joshtriplett](https://github.com/joshtriplett) |[Kalwyn](https://github.com/Kalwyn) |[manuel-rhdt](https://github.com/manuel-rhdt) |[Marwes](https://github.com/Marwes) | :---: |:---: |:---: |:---: |:---: |:---: | -[andete](https://github.com/andete) |[joshtriplett](https://github.com/joshtriplett) |[Kalwyn](https://github.com/Kalwyn) |[manuel-rhdt](https://github.com/manuel-rhdt) |[Marwes](https://github.com/Marwes) |[mdaffin](https://github.com/mdaffin) | +[jtdowney](https://github.com/jtdowney) |[andete](https://github.com/andete) |[joshtriplett](https://github.com/joshtriplett) |[Kalwyn](https://github.com/Kalwyn) |[manuel-rhdt](https://github.com/manuel-rhdt) |[Marwes](https://github.com/Marwes) | -[iliekturtles](https://github.com/iliekturtles) |[nicompte](https://github.com/nicompte) |[NickeZ](https://github.com/NickeZ) |[nvzqz](https://github.com/nvzqz) |[nuew](https://github.com/nuew) |[Geogi](https://github.com/Geogi) | +[mdaffin](https://github.com/mdaffin) |[iliekturtles](https://github.com/iliekturtles) |[nicompte](https://github.com/nicompte) |[NickeZ](https://github.com/NickeZ) |[nvzqz](https://github.com/nvzqz) |[nuew](https://github.com/nuew) | :---: |:---: |:---: |:---: |:---: |:---: | -[iliekturtles](https://github.com/iliekturtles) |[nicompte](https://github.com/nicompte) |[NickeZ](https://github.com/NickeZ) |[nvzqz](https://github.com/nvzqz) |[nuew](https://github.com/nuew) |[Geogi](https://github.com/Geogi) | +[mdaffin](https://github.com/mdaffin) |[iliekturtles](https://github.com/iliekturtles) |[nicompte](https://github.com/nicompte) |[NickeZ](https://github.com/NickeZ) |[nvzqz](https://github.com/nvzqz) |[nuew](https://github.com/nuew) | -[focusaurus](https://github.com/focusaurus) |[flying-sheep](https://github.com/flying-sheep) |[Phlosioneer](https://github.com/Phlosioneer) |[peppsac](https://github.com/peppsac) |[golddranks](https://github.com/golddranks) |[hexjelly](https://github.com/hexjelly) | +[Geogi](https://github.com/Geogi) |[focusaurus](https://github.com/focusaurus) |[flying-sheep](https://github.com/flying-sheep) |[Phlosioneer](https://github.com/Phlosioneer) |[peppsac](https://github.com/peppsac) |[golddranks](https://github.com/golddranks) | :---: |:---: |:---: |:---: |:---: |:---: | -[focusaurus](https://github.com/focusaurus) |[flying-sheep](https://github.com/flying-sheep) |[Phlosioneer](https://github.com/Phlosioneer) |[peppsac](https://github.com/peppsac) |[golddranks](https://github.com/golddranks) |[hexjelly](https://github.com/hexjelly) | +[Geogi](https://github.com/Geogi) |[focusaurus](https://github.com/focusaurus) |[flying-sheep](https://github.com/flying-sheep) |[Phlosioneer](https://github.com/Phlosioneer) |[peppsac](https://github.com/peppsac) |[golddranks](https://github.com/golddranks) | -[rnelson](https://github.com/rnelson) |[swatteau](https://github.com/swatteau) |[tspiteri](https://github.com/tspiteri) |[siiptuo](https://github.com/siiptuo) |[vks](https://github.com/vks) |[vsupalov](https://github.com/vsupalov) | +[hexjelly](https://github.com/hexjelly) |[rom1v](https://github.com/rom1v) |[rnelson](https://github.com/rnelson) |[segevfiner](https://github.com/segevfiner) |[swatteau](https://github.com/swatteau) |[tspiteri](https://github.com/tspiteri) | :---: |:---: |:---: |:---: |:---: |:---: | -[rnelson](https://github.com/rnelson) |[swatteau](https://github.com/swatteau) |[tspiteri](https://github.com/tspiteri) |[siiptuo](https://github.com/siiptuo) |[vks](https://github.com/vks) |[vsupalov](https://github.com/vsupalov) | +[hexjelly](https://github.com/hexjelly) |[rom1v](https://github.com/rom1v) |[rnelson](https://github.com/rnelson) |[segevfiner](https://github.com/segevfiner) |[swatteau](https://github.com/swatteau) |[tspiteri](https://github.com/tspiteri) | -[mineo](https://github.com/mineo) |[wabain](https://github.com/wabain) |[grossws](https://github.com/grossws) |[kennytm](https://github.com/kennytm) |[mvaude](https://github.com/mvaude) |[panicbit](https://github.com/panicbit) | +[siiptuo](https://github.com/siiptuo) |[vks](https://github.com/vks) |[vsupalov](https://github.com/vsupalov) |[mineo](https://github.com/mineo) |[wabain](https://github.com/wabain) |[grossws](https://github.com/grossws) | :---: |:---: |:---: |:---: |:---: |:---: | -[mineo](https://github.com/mineo) |[wabain](https://github.com/wabain) |[grossws](https://github.com/grossws) |[kennytm](https://github.com/kennytm) |[mvaude](https://github.com/mvaude) |[panicbit](https://github.com/panicbit) | +[siiptuo](https://github.com/siiptuo) |[vks](https://github.com/vks) |[vsupalov](https://github.com/vsupalov) |[mineo](https://github.com/mineo) |[wabain](https://github.com/wabain) |[grossws](https://github.com/grossws) | -[mitsuhiko](https://github.com/mitsuhiko) | -:---: | -[mitsuhiko](https://github.com/mitsuhiko) | +[kennytm](https://github.com/kennytm) |[mvaude](https://github.com/mvaude) |[panicbit](https://github.com/panicbit) |[Bilalh](https://github.com/Bilalh) | +:---: |:---: |:---: |:---: | +[kennytm](https://github.com/kennytm) |[mvaude](https://github.com/mvaude) |[panicbit](https://github.com/panicbit) |[Bilalh](https://github.com/Bilalh) | diff --git a/Cargo.toml b/Cargo.toml index 63e9922b975..4c6bb773cb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "clap" -version = "2.29.0" +version = "2.29.1" authors = ["Kevin K. "] exclude = ["examples/*", "clap-test/*", "tests/*", "benches/*", "*.png", "clap-perf/*", "*.dot"] repository = "https://github.com/kbknapp/clap-rs" diff --git a/README.md b/README.md index eb488a59fce..95969569871 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,15 @@ Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc) ## What's New +Here's whats new in 2.29.1: + +* Debloats clap by deduplicating logic and refactors for a ~57% decrease in code size! This is with zero functinoality lost, and a slight perf increase! +* Change the bash completion script code generation to support hyphens. +* Fix completion of long option values in ZSH completions +* Fixes broken links in docs +* Updates contributors list +* Fixes the ripgrep benchmark by adding a value to a flag that expects it + Here's whats new in 2.29.0: * **Arg:** adds Arg::hide_env_values(bool) which allows one to hide any current env values and display only the key in help messages @@ -301,7 +310,7 @@ subcommands: Since this feature requires additional dependencies that not everyone may want, it is *not* compiled in by default and we need to enable a feature flag in Cargo.toml: -Simply change your `clap = "2.29"` to `clap = {version = "2.87", features = ["yaml"]}`. +Simply change your `clap = "2.29"` to `clap = {version = "2.29", features = ["yaml"]}`. Finally we create our `main.rs` file just like we would have with the previous two examples: diff --git a/benches/05_ripgrep.rs b/benches/05_ripgrep.rs index 48c0819bd24..7db1552550b 100644 --- a/benches/05_ripgrep.rs +++ b/benches/05_ripgrep.rs @@ -43,7 +43,7 @@ fn parse_complex(b: &mut Bencher) { app_short().get_matches_from(vec!["rg", "pat", "-cFlN", - "-pqr", + "-pqr=some", "--null", "--no-filename", "--no-messages", diff --git a/clap-test.rs b/clap-test.rs index 2f91b9e818e..3e01edee837 100644 --- a/clap-test.rs +++ b/clap-test.rs @@ -39,6 +39,16 @@ mod test { assert_eq!(stderr, err.use_stderr()); compare(left, right) } + pub fn compare_output2(l: App, args: &str, right1: &str, right2: &str, stderr: bool) -> bool { + let mut buf = Cursor::new(Vec::with_capacity(50)); + let res = l.get_matches_from_safe(args.split(' ').collect::>()); + let err = res.unwrap_err(); + err.write_to(&mut buf).unwrap(); + let content = buf.into_inner(); + let left = String::from_utf8(content).unwrap(); + assert_eq!(stderr, err.use_stderr()); + compare(&*left, right1) || compare(&*left, right2) + } // Legacy tests from the pyhton script days diff --git a/src/app/macros.rs b/src/app/macros.rs deleted file mode 100644 index e3f15e96733..00000000000 --- a/src/app/macros.rs +++ /dev/null @@ -1,165 +0,0 @@ -macro_rules! remove_overriden { - (@remove_requires $rem_from:expr, $a:ident.$ov:ident) => { - if let Some(ora) = $a.$ov() { - for i in (0 .. $rem_from.len()).rev() { - let should_remove = ora.iter().any(|&(_, ref name)| name == &$rem_from[i]); - if should_remove { $rem_from.swap_remove(i); } - } - } - }; - (@remove $rem_from:expr, $a:ident.$ov:ident) => { - if let Some(ora) = $a.$ov() { - vec_remove_all!($rem_from, ora.iter()); - } - }; - (@arg $_self:ident, $arg:ident) => { - remove_overriden!(@remove_requires $_self.required, $arg.requires); - remove_overriden!(@remove $_self.blacklist, $arg.blacklist); - remove_overriden!(@remove $_self.overrides, $arg.overrides); - }; - ($_self:ident, $name:expr) => { - debugln!("remove_overriden!;"); - if let Some(o) = $_self.opts.iter() .find(|o| o.b.name == *$name) { - remove_overriden!(@arg $_self, o); - } else if let Some(f) = $_self.flags.iter() .find(|f| f.b.name == *$name) { - remove_overriden!(@arg $_self, f); - } else { - let p = $_self.positionals.values() - .find(|p| p.b.name == *$name) - .expect(INTERNAL_ERROR_MSG); - remove_overriden!(@arg $_self, p); - } - }; -} - -macro_rules! arg_post_processing { - ($me:ident, $arg:ident, $matcher:ident) => { - debugln!("arg_post_processing!;"); - // Handle POSIX overrides - debug!("arg_post_processing!: Is '{}' in overrides...", $arg.to_string()); - if $me.overrides.contains(&$arg.name()) { - if let Some(ref name) = find_name_from!($me, &$arg.name(), overrides, $matcher) { - sdebugln!("Yes by {}", name); - $matcher.remove(name); - remove_overriden!($me, name); - } - } else { sdebugln!("No"); } - - // Add overrides - debug!("arg_post_processing!: Does '{}' have overrides...", $arg.to_string()); - if let Some(or) = $arg.overrides() { - sdebugln!("Yes"); - $matcher.remove_all(or); - for pa in or { remove_overriden!($me, pa); } - $me.overrides.extend(or); - vec_remove_all!($me.required, or.iter()); - } else { sdebugln!("No"); } - - // Handle conflicts - debug!("arg_post_processing!: Does '{}' have conflicts...", $arg.to_string()); - if let Some(bl) = $arg.blacklist() { - sdebugln!("Yes"); - - for c in bl { - // Inject two-way conflicts - debug!("arg_post_processing!: Has '{}' already been matched...", c); - if $matcher.contains(c) { - sdebugln!("Yes"); - // find who blacklisted us... - $me.blacklist.push(&$arg.b.name); - } else { - sdebugln!("No"); - } - } - - $me.blacklist.extend_from_slice(bl); - vec_remove_all!($me.overrides, bl.iter()); - // vec_remove_all!($me.required, bl.iter()); - } else { sdebugln!("No"); } - - // Add all required args which aren't already found in matcher to the master - // list - debug!("arg_post_processing!: Does '{}' have requirements...", $arg.to_string()); - if let Some(reqs) = $arg.requires() { - for n in reqs.iter() - .filter(|&&(val, _)| val.is_none()) - .filter(|&&(_, req)| !$matcher.contains(&req)) - .map(|&(_, name)| name) { - - $me.required.push(n); - } - } else { sdebugln!("No"); } - - _handle_group_reqs!($me, $arg); - }; -} - -macro_rules! _handle_group_reqs{ - ($me:ident, $arg:ident) => ({ - use args::AnyArg; - debugln!("_handle_group_reqs!;"); - for grp in &$me.groups { - let found = if grp.args.contains(&$arg.name()) { - if let Some(ref reqs) = grp.requires { - debugln!("_handle_group_reqs!: Adding {:?} to the required list", reqs); - $me.required.extend(reqs); - } - if let Some(ref bl) = grp.conflicts { - $me.blacklist.extend(bl); - } - true // What if arg is in more than one group with different reqs? - } else { - false - }; - debugln!("_handle_group_reqs!:iter: grp={}, found={:?}", grp.name, found); - if found { - for i in (0 .. $me.required.len()).rev() { - let should_remove = grp.args.contains(&$me.required[i]); - if should_remove { $me.required.swap_remove(i); } - } - debugln!("_handle_group_reqs!:iter: Adding args from group to blacklist...{:?}", grp.args); - if !grp.multiple { - $me.blacklist.extend(&grp.args); - debugln!("_handle_group_reqs!: removing {:?} from blacklist", $arg.name()); - for i in (0 .. $me.blacklist.len()).rev() { - let should_remove = $me.blacklist[i] == $arg.name(); - if should_remove { $me.blacklist.swap_remove(i); } - } - } - } - } - }) -} - -macro_rules! parse_positional { - ( - $_self:ident, - $p:ident, - $arg_os:ident, - $pos_counter:ident, - $matcher:ident - ) => { - debugln!("parse_positional!;"); - - if !$_self.is_set(AS::TrailingValues) && - ($_self.is_set(AS::TrailingVarArg) && - $pos_counter == $_self.positionals.len()) { - $_self.settings.set(AS::TrailingValues); - } - let _ = $_self.add_val_to_arg($p, &$arg_os, $matcher)?; - - $matcher.inc_occurrence_of($p.b.name); - let _ = $_self.groups_for_arg($p.b.name) - .and_then(|vec| Some($matcher.inc_occurrences_of(&*vec))); - if $_self.cache.map_or(true, |name| name != $p.b.name) { - arg_post_processing!($_self, $p, $matcher); - $_self.cache = Some($p.b.name); - } - - $_self.settings.set(AS::ValidArgFound); - // Only increment the positional counter if it doesn't allow multiples - if !$p.b.settings.is_set(ArgSettings::Multiple) { - $pos_counter += 1; - } - }; -} diff --git a/src/app/mod.rs b/src/app/mod.rs index 9c2d852b74b..908e17170be 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,6 +1,4 @@ mod settings; -#[macro_use] -mod macros; pub mod parser; mod meta; mod help; diff --git a/src/app/parser.rs b/src/app/parser.rs index 22c45c9a769..01e0e1f3561 100644 --- a/src/app/parser.rs +++ b/src/app/parser.rs @@ -61,8 +61,7 @@ where pub global_args: Vec>, pub required: Vec<&'a str>, pub r_ifs: Vec<(&'a str, &'b str, &'a str)>, - pub blacklist: Vec<&'b str>, - pub overrides: Vec<&'b str>, + pub overrides: Vec<(&'b str, &'a str)>, help_short: Option, version_short: Option, cache: Option<&'a str>, @@ -346,9 +345,9 @@ where if let Some(ref reqs) = group.requires { self.required.extend_from_slice(reqs); } - if let Some(ref bl) = group.conflicts { - self.blacklist.extend_from_slice(bl); - } +// if let Some(ref bl) = group.conflicts { +// self.blacklist.extend_from_slice(bl); +// } } if self.groups.iter().any(|g| g.name == group.name) { let grp = self.groups @@ -773,12 +772,8 @@ where // allow wrong self convention due to self.valid_neg_num = true and it's a private method #[cfg_attr(feature = "lints", allow(wrong_self_convention))] - fn is_new_arg(&mut self, arg_os: &OsStr, needs_val_of: ParseResult<'a>) -> bool { - debugln!( - "Parser::is_new_arg: arg={:?}, Needs Val of={:?}", - arg_os, - needs_val_of - ); + fn is_new_arg(&mut self, arg_os: &OsStr, needs_val_of: ParseResult) -> bool { + debugln!( "Parser::is_new_arg:{:?}:{:?}", arg_os, needs_val_of); let app_wide_settings = if self.is_set(AS::AllowLeadingHyphen) { true } else if self.is_set(AS::AllowNegativeNumbers) { @@ -807,12 +802,10 @@ where .expect(INTERNAL_ERROR_MSG); (p.is_set(ArgSettings::AllowLeadingHyphen) || app_wide_settings) } + ParseResult::ValuesDone => return true, _ => false, }; - debugln!( - "Parser::is_new_arg: Arg::allow_leading_hyphen({:?})", - arg_allows_tac - ); + debugln!( "Parser::is_new_arg: arg_allows_tac={:?}", arg_allows_tac ); // Is this a new argument, or values from a previous option? let mut ret = if arg_os.starts_with(b"--") { @@ -913,7 +906,52 @@ where } } - if !starts_new_arg { + if starts_new_arg { + { + let any_arg = find_any_by_name!(self, self.cache.unwrap_or("")); + matcher.process_arg_overrides(any_arg, &mut self.overrides, &mut self.required); + } + + if arg_os.starts_with(b"--") { + needs_val_of = self.parse_long_arg(matcher, &arg_os)?; + debugln!( "Parser:get_matches_with: After parse_long_arg {:?}", needs_val_of ); + match needs_val_of { + ParseResult::Flag | ParseResult::Opt(..) | ParseResult::ValuesDone => { + continue + } + _ => (), + } + } else if arg_os.starts_with(b"-") && arg_os.len_() != 1 { + // Try to parse short args like normal, if AllowLeadingHyphen or + // AllowNegativeNumbers is set, parse_short_arg will *not* throw + // an error, and instead return Ok(None) + needs_val_of = self.parse_short_arg(matcher, &arg_os)?; + // If it's None, we then check if one of those two AppSettings was set + debugln!( + "Parser:get_matches_with: After parse_short_arg {:?}", + needs_val_of + ); + match needs_val_of { + ParseResult::MaybeNegNum => { + if !(arg_os.to_string_lossy().parse::().is_ok() + || arg_os.to_string_lossy().parse::().is_ok()) + { + return Err(Error::unknown_argument( + &*arg_os.to_string_lossy(), + "", + &*usage::create_error_usage(self, matcher, None), + self.color(), + )); + } + }, + ParseResult::Opt(..) | ParseResult::Flag | ParseResult::ValuesDone => { + continue + } + _ => (), + } + } + + } else { if let ParseResult::Opt(name) = needs_val_of { // Check to see if parsing a value from a previous arg let arg = self.opts @@ -925,62 +963,20 @@ where // get the next value from the iterator continue; } - } else if arg_os.starts_with(b"--") { - needs_val_of = self.parse_long_arg(matcher, &arg_os)?; - debugln!( - "Parser:get_matches_with: After parse_long_arg {:?}", - needs_val_of - ); - match needs_val_of { - ParseResult::Flag | ParseResult::Opt(..) | ParseResult::ValuesDone => { - continue - } - _ => (), - } - } else if arg_os.starts_with(b"-") && arg_os.len_() != 1 { - // Try to parse short args like normal, if AllowLeadingHyphen or - // AllowNegativeNumbers is set, parse_short_arg will *not* throw - // an error, and instead return Ok(None) - needs_val_of = self.parse_short_arg(matcher, &arg_os)?; - // If it's None, we then check if one of those two AppSettings was set - debugln!( - "Parser:get_matches_with: After parse_short_arg {:?}", - needs_val_of - ); - match needs_val_of { - ParseResult::MaybeNegNum => { - if !(arg_os.to_string_lossy().parse::().is_ok() - || arg_os.to_string_lossy().parse::().is_ok()) - { - return Err(Error::unknown_argument( - &*arg_os.to_string_lossy(), - "", - &*usage::create_error_usage(self, matcher, None), - self.color(), - )); - } - } - ParseResult::Opt(..) | ParseResult::Flag | ParseResult::ValuesDone => { - continue - } - _ => (), - } } + } - if !(self.is_set(AS::ArgsNegateSubcommands) && self.is_set(AS::ValidArgFound)) - && !self.is_set(AS::InferSubcommands) - { - if let Some(cdate) = - suggestions::did_you_mean(&*arg_os.to_string_lossy(), sc_names!(self)) - { - return Err(Error::invalid_subcommand( - arg_os.to_string_lossy().into_owned(), - cdate, - self.meta.bin_name.as_ref().unwrap_or(&self.meta.name), - &*usage::create_error_usage(self, matcher, None), - self.color(), - )); - } + if !(self.is_set(AS::ArgsNegateSubcommands) && self.is_set(AS::ValidArgFound)) + && !self.is_set(AS::InferSubcommands) + { + if let Some(cdate) = suggestions::did_you_mean(&*arg_os.to_string_lossy(), sc_names!(self)) { + return Err(Error::invalid_subcommand( + arg_os.to_string_lossy().into_owned(), + cdate, + self.meta.bin_name.as_ref().unwrap_or(&self.meta.name), + &*usage::create_error_usage(self, matcher, None), + self.color(), + )); } } @@ -1035,7 +1031,29 @@ where self.color(), )); } - parse_positional!(self, p, arg_os, pos_counter, matcher); + if !self.is_set(AS::TrailingValues) && + (self.is_set(AS::TrailingVarArg) && + pos_counter == self.positionals.len()) { + self.settings.set(AS::TrailingValues); + } + if self.cache.map_or(true, |name| name != p.b.name) { + { + let any_arg = find_any_by_name!(self, self.cache.unwrap_or("")); + matcher.process_arg_overrides(any_arg, &mut self.overrides, &mut self.required); + } + self.cache = Some(p.b.name); + } + let _ = self.add_val_to_arg(p, &arg_os, matcher)?; + + matcher.inc_occurrence_of(p.b.name); + let _ = self.groups_for_arg(p.b.name) + .and_then(|vec| Some(matcher.inc_occurrences_of(&*vec))); + + self.settings.set(AS::ValidArgFound); + // Only increment the positional counter if it doesn't allow multiples + if !p.b.settings.is_set(ArgSettings::Multiple) { + pos_counter += 1; + } self.settings.set(AS::ValidArgFound); } else if self.is_set(AS::AllowExternalSubcommands) { // Get external subcommand name @@ -1136,9 +1154,34 @@ where }); } + // In case the last arg was new, we need to process it's overrides + { + let any_arg = find_any_by_name!(self, self.cache.unwrap_or("")); + matcher.process_arg_overrides(any_arg, &mut self.overrides, &mut self.required); + } + + self.remove_overrides(matcher); + Validator::new(self).validate(needs_val_of, subcmd_name, matcher) } + fn remove_overrides(&mut self, matcher: &mut ArgMatcher) { + debugln!("Parser::remove_overrides:{:?};", self.overrides); + for &(overr, name) in &self.overrides { + debugln!("Parser::remove_overrides:iter:({},{});", overr, name); + if matcher.is_present(overr) { + debugln!("Parser::remove_overrides:iter:({},{}): removing {};", overr, name, name); + matcher.remove(name); + for i in (0 .. self.required.len()).rev() { + debugln!("Parser::remove_overrides:iter:({},{}): removing required {};", overr, name, name); + if self.required[i] == name { + self.required.swap_remove(i); + break; + } + } + } + } + } fn propagate_help_version(&mut self) { debugln!("Parser::propagate_help_version;"); @@ -1483,7 +1526,6 @@ where self.settings.set(AS::ValidArgFound); let ret = self.parse_opt(val, opt, val.is_some(), matcher)?; if self.cache.map_or(true, |name| name != opt.b.name) { - arg_post_processing!(self, opt, matcher); self.cache = Some(opt.b.name); } @@ -1501,10 +1543,9 @@ where self.parse_flag(flag, matcher)?; // Handle conflicts, requirements, etc. - // if self.cache.map_or(true, |name| name != flag.b.name) { - arg_post_processing!(self, flag, matcher); - // self.cache = Some(flag.b.name); - // } + if self.cache.map_or(true, |name| name != flag.b.name) { + self.cache = Some(flag.b.name); + } return Ok(ParseResult::Flag); } else if self.is_set(AS::AllowLeadingHyphen) { @@ -1580,7 +1621,6 @@ where let ret = self.parse_opt(val, opt, false, matcher)?; if self.cache.map_or(true, |name| name != opt.b.name) { - arg_post_processing!(self, opt, matcher); self.cache = Some(opt.b.name); } @@ -1595,7 +1635,6 @@ where // Handle conflicts, requirements, overrides, etc. // Must be called here due to mutablilty if self.cache.map_or(true, |name| name != flag.b.name) { - arg_post_processing!(self, flag, matcher); self.cache = Some(flag.b.name); } } else { @@ -1844,7 +1883,6 @@ where $_self.add_val_to_arg($a, OsStr::new(val), $m)?; if $_self.cache.map_or(true, |name| name != $a.name()) { - arg_post_processing!($_self, $a, $m); $_self.cache = Some($a.name()); } } else if $m.get($a.b.name).is_some() { @@ -1855,7 +1893,6 @@ where $_self.add_val_to_arg($a, OsStr::new(val), $m)?; if $_self.cache.map_or(true, |name| name != $a.name()) { - arg_post_processing!($_self, $a, $m); $_self.cache = Some($a.name()); } } @@ -1881,7 +1918,6 @@ where if add { $_self.add_val_to_arg($a, OsStr::new(default), $m)?; if $_self.cache.map_or(true, |name| name != $a.name()) { - arg_post_processing!($_self, $a, $m); $_self.cache = Some($a.name()); } done = true; @@ -1920,7 +1956,6 @@ where $_self.add_val_to_arg($a, OsStr::new(val), $m)?; if $_self.cache.map_or(true, |name| name != $a.name()) { - arg_post_processing!($_self, $a, $m); $_self.cache = Some($a.name()); } } @@ -1929,7 +1964,6 @@ where $_self.add_val_to_arg($a, OsStr::new(val), $m)?; if $_self.cache.map_or(true, |name| name != $a.name()) { - arg_post_processing!($_self, $a, $m); $_self.cache = Some($a.name()); } } @@ -1972,7 +2006,7 @@ where } } - pub fn find_any_arg(&self, name: &str) -> Option<&AnyArg> { + pub fn find_any_arg(&self, name: &str) -> Option<&AnyArg<'a, 'b>> { if let Some(f) = find_by_name!(self, name, flags, iter) { return Some(f); } diff --git a/src/app/validator.rs b/src/app/validator.rs index 01373dd5f9f..9751321a149 100644 --- a/src/app/validator.rs +++ b/src/app/validator.rs @@ -78,7 +78,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { Ok(()) } - fn validate_values( + fn validate_arg_values( &self, arg: &A, ma: &MatchedArg, @@ -87,11 +87,11 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { where A: AnyArg<'a, 'b> + Display, { - debugln!("Validator::validate_values: arg={:?}", arg.name()); + debugln!("Validator::validate_arg_values: arg={:?}", arg.name()); for val in &ma.vals { if self.0.is_set(AS::StrictUtf8) && val.to_str().is_none() { debugln!( - "Validator::validate_values: invalid UTF-8 found in val {:?}", + "Validator::validate_arg_values: invalid UTF-8 found in val {:?}", val ); return Err(Error::invalid_utf8( @@ -100,7 +100,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { )); } if let Some(p_vals) = arg.possible_vals() { - debugln!("Validator::validate_values: possible_vals={:?}", p_vals); + debugln!("Validator::validate_arg_values: possible_vals={:?}", p_vals); let val_str = val.to_string_lossy(); let ok = if arg.is_set(ArgSettings::CaseInsensitive) { p_vals.iter().any(|pv| pv.eq_ignore_ascii_case(&*val_str)) @@ -120,7 +120,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { if !arg.is_set(ArgSettings::EmptyValues) && val.is_empty_() && matcher.contains(&*arg.name()) { - debugln!("Validator::validate_values: illegal empty val found"); + debugln!("Validator::validate_arg_values: illegal empty val found"); return Err(Error::empty_value( arg, &*usage::create_error_usage(self.0, matcher, None), @@ -128,7 +128,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { )); } if let Some(vtor) = arg.validator() { - debug!("Validator::validate_values: checking validator..."); + debug!("Validator::validate_arg_values: checking validator..."); if let Err(e) = vtor(val.to_string_lossy().into_owned()) { sdebugln!("error"); return Err(Error::value_validation(Some(arg), e, self.0.color())); @@ -137,7 +137,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { } } if let Some(vtor) = arg.validator_os() { - debug!("Validator::validate_values: checking validator_os..."); + debug!("Validator::validate_arg_values: checking validator_os..."); if let Err(e) = vtor(val) { sdebugln!("error"); return Err(Error::value_validation( @@ -153,44 +153,83 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { Ok(()) } - fn validate_blacklist(&self, matcher: &mut ArgMatcher) -> ClapResult<()> { - debugln!( - "Validator::validate_blacklist: blacklist={:?}", - self.0.blacklist + fn build_err(&self, name: &str, matcher: &ArgMatcher) -> ClapResult<()> { + debugln!("build_err!: name={}", name); + let mut c_with = find_from!(self.0, &name, blacklist, &matcher); + c_with = c_with.or( + self.0.find_any_arg(&name).map_or(None, |aa| aa.blacklist()) + .map_or(None, + |bl| bl.iter().find(|arg| matcher.contains(arg))) + .map_or(None, |an| self.0.find_any_arg(an)) + .map_or(None, |aa| Some(format!("{}", aa))) ); - macro_rules! build_err { - ($p:expr, $name:expr, $matcher:ident) => ({ - debugln!("build_err!: name={}", $name); - let mut c_with = find_from!($p, &$name, blacklist, &$matcher); - c_with = c_with.or( - $p.find_any_arg(&$name).map_or(None, |aa| aa.blacklist()) - .map_or(None, - |bl| bl.iter().find(|arg| $matcher.contains(arg))) - .map_or(None, |an| $p.find_any_arg(an)) - .map_or(None, |aa| Some(format!("{}", aa))) - ); - debugln!("build_err!: '{:?}' conflicts with '{}'", c_with, &$name); - $matcher.remove(&$name); - let usg = usage::create_error_usage($p, $matcher, None); - if let Some(f) = find_by_name!($p, $name, flags, iter) { - debugln!("build_err!: It was a flag..."); - Error::argument_conflict(f, c_with, &*usg, self.0.color()) - } else if let Some(o) = find_by_name!($p, $name, opts, iter) { - debugln!("build_err!: It was an option..."); - Error::argument_conflict(o, c_with, &*usg, self.0.color()) - } else { - match find_by_name!($p, $name, positionals, values) { - Some(p) => { - debugln!("build_err!: It was a positional..."); - Error::argument_conflict(p, c_with, &*usg, self.0.color()) - }, - None => panic!(INTERNAL_ERROR_MSG) + debugln!("build_err!: '{:?}' conflicts with '{}'", c_with, &name); +// matcher.remove(&name); + let usg = usage::create_error_usage(self.0, matcher, None); + if let Some(f) = find_by_name!(self.0, name, flags, iter) { + debugln!("build_err!: It was a flag..."); + Err(Error::argument_conflict(f, c_with, &*usg, self.0.color())) + } else if let Some(o) = find_by_name!(self.0, name, opts, iter) { + debugln!("build_err!: It was an option..."); + Err(Error::argument_conflict(o, c_with, &*usg, self.0.color())) + } else { + match find_by_name!(self.0, name, positionals, values) { + Some(p) => { + debugln!("build_err!: It was a positional..."); + Err(Error::argument_conflict(p, c_with, &*usg, self.0.color())) + }, + None => panic!(INTERNAL_ERROR_MSG) + } + } + } + + fn validate_blacklist(&self, matcher: &mut ArgMatcher) -> ClapResult<()> { + debugln!("Validator::validate_blacklist;"); + let mut conflicts: Vec<&str> = vec![]; + for (&name, _) in matcher.iter() { + debugln!("Validator::validate_blacklist:iter:{};", name); + if let Some(grps) = self.0.groups_for_arg(name) { + for grp in &grps { + if let Some(g) = self.0.groups.iter().find(|g| &g.name == grp) { + if !g.multiple { + for arg in &g.args { + if arg == &name { + continue; + } + conflicts.push(arg); + } + } + if let Some(ref gc) = g.conflicts { + conflicts.extend(&*gc); + } } } - }); + } + if let Some(arg) = find_any_by_name!(self.0, name) { + if let Some(bl) = arg.blacklist() { + for conf in bl { + if matcher.get(conf).is_some() { + conflicts.push(conf); + } + } + } + } else { + debugln!("Validator::validate_blacklist:iter:{}:group;", name); + let args = self.0.arg_names_in_group(name); + for arg in &args { + debugln!("Validator::validate_blacklist:iter:{}:group:iter:{};", name, arg); + if let Some(bl) = find_any_by_name!(self.0, *arg).unwrap().blacklist() { + for conf in bl { + if matcher.get(conf).is_some() { + conflicts.push(conf); + } + } + } + } + } } - for name in &self.0.blacklist { + for name in &conflicts { debugln!( "Validator::validate_blacklist:iter:{}: Checking blacklisted arg", name @@ -213,7 +252,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { name, n ); - return Err(build_err!(self.0, n, matcher)); + return self.build_err(n, matcher); } } } else if let Some(ma) = matcher.get(name) { @@ -224,7 +263,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { should_err = ma.occurs > 0; } if should_err { - return Err(build_err!(self.0, *name, matcher)); + return self.build_err(*name, matcher); } } Ok(()) @@ -240,7 +279,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { ); if let Some(opt) = find_by_name!(self.0, *name, opts, iter) { self.validate_arg_num_vals(opt, ma, matcher)?; - self.validate_values(opt, ma, matcher)?; + self.validate_arg_values(opt, ma, matcher)?; self.validate_arg_requires(opt, ma, matcher)?; self.validate_arg_num_occurs(opt, ma, matcher)?; } else if let Some(flag) = find_by_name!(self.0, *name, flags, iter) { @@ -249,7 +288,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { } else if let Some(pos) = find_by_name!(self.0, *name, positionals, values) { self.validate_arg_num_vals(pos, ma, matcher)?; self.validate_arg_num_occurs(pos, ma, matcher)?; - self.validate_values(pos, ma, matcher)?; + self.validate_arg_values(pos, ma, matcher)?; self.validate_arg_requires(pos, ma, matcher)?; } else { let grp = self.0 @@ -381,7 +420,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { where A: AnyArg<'a, 'b> + Display, { - debugln!("Validator::validate_arg_requires;"); + debugln!("Validator::validate_arg_requires:{};", a.name()); if let Some(a_reqs) = a.requires() { for &(val, name) in a_reqs.iter().filter(|&&(val, _)| val.is_some()) { let missing_req = @@ -390,6 +429,11 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { return self.missing_required_error(matcher, None); } } + for &(_, name) in a_reqs.iter().filter(|&&(val, _)| val.is_none()) { + if !matcher.contains(name) { + return self.missing_required_error(matcher, Some(name)); + } + } } Ok(()) } @@ -399,20 +443,13 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { "Validator::validate_required: required={:?};", self.0.required ); + 'outer: for name in &self.0.required { debugln!("Validator::validate_required:iter:{}:", name); if matcher.contains(name) { continue 'outer; } - if let Some(a) = find_by_name!(self.0, *name, flags, iter) { - if self.is_missing_required_ok(a, matcher) { - continue 'outer; - } - } else if let Some(a) = find_by_name!(self.0, *name, opts, iter) { - if self.is_missing_required_ok(a, matcher) { - continue 'outer; - } - } else if let Some(a) = find_by_name!(self.0, *name, positionals, values) { + if let Some(a) = find_any_by_name!(self.0, *name) { if self.is_missing_required_ok(a, matcher) { continue 'outer; } @@ -431,11 +468,8 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { Ok(()) } - fn validate_conflicts(&self, a: &A, matcher: &ArgMatcher) -> Option - where - A: AnyArg<'a, 'b>, - { - debugln!("Validator::validate_conflicts: a={:?};", a.name()); + fn validate_arg_conflicts(&self, a: &AnyArg, matcher: &ArgMatcher) -> Option { + debugln!("Validator::validate_arg_conflicts: a={:?};", a.name()); a.blacklist().map(|bl| { bl.iter().any(|conf| { matcher.contains(conf) @@ -448,10 +482,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { }) } - fn validate_required_unless(&self, a: &A, matcher: &ArgMatcher) -> Option - where - A: AnyArg<'a, 'b>, - { + fn validate_required_unless(&self, a: &AnyArg, matcher: &ArgMatcher) -> Option { debugln!("Validator::validate_required_unless: a={:?};", a.name()); macro_rules! check { ($how:ident, $_self:expr, $a:ident, $m:ident) => {{ @@ -506,12 +537,9 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> { } #[inline] - fn is_missing_required_ok(&self, a: &A, matcher: &ArgMatcher) -> bool - where - A: AnyArg<'a, 'b>, - { + fn is_missing_required_ok(&self, a: &AnyArg, matcher: &ArgMatcher) -> bool { debugln!("Validator::is_missing_required_ok: a={}", a.name()); - self.validate_conflicts(a, matcher).unwrap_or(false) + self.validate_arg_conflicts(a, matcher).unwrap_or(false) || self.validate_required_unless(a, matcher).unwrap_or(false) } } diff --git a/src/args/any_arg.rs b/src/args/any_arg.rs index 9fa34c7fa06..eee52283328 100644 --- a/src/args/any_arg.rs +++ b/src/args/any_arg.rs @@ -6,6 +6,7 @@ use std::ffi::{OsStr, OsString}; // Internal use args::settings::ArgSettings; use map::{self, VecMap}; +use INTERNAL_ERROR_MSG; #[doc(hidden)] pub trait AnyArg<'n, 'e>: std_fmt::Display { @@ -41,3 +42,33 @@ pub trait AnyArg<'n, 'e>: std_fmt::Display { pub trait DispOrder { fn disp_ord(&self) -> usize; } + +impl<'n, 'e, 'z, T: ?Sized> AnyArg<'n, 'e> for &'z T where T: AnyArg<'n, 'e> + 'z { + fn name(&self) -> &'n str { (*self).name() } + fn overrides(&self) -> Option<&[&'e str]> { (*self).overrides() } + fn aliases(&self) -> Option> { (*self).aliases() } + fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { (*self).requires() } + fn blacklist(&self) -> Option<&[&'e str]> { (*self).blacklist() } + fn required_unless(&self) -> Option<&[&'e str]> { (*self).required_unless() } + fn is_set(&self, a: ArgSettings) -> bool { (*self).is_set(a) } + fn set(&mut self, _: ArgSettings) { panic!(INTERNAL_ERROR_MSG) } + fn has_switch(&self) -> bool { (*self).has_switch() } + fn max_vals(&self) -> Option { (*self).max_vals() } + fn min_vals(&self) -> Option { (*self).min_vals() } + fn num_vals(&self) -> Option { (*self).num_vals() } + fn possible_vals(&self) -> Option<&[&'e str]> { (*self).possible_vals() } + fn validator(&self) -> Option<&Rc Result<(), String>>> { (*self).validator() } + fn validator_os(&self) -> Option<&Rc Result<(), OsString>>> { (*self).validator_os() } + fn short(&self) -> Option { (*self).short() } + fn long(&self) -> Option<&'e str> { (*self).long() } + fn val_delim(&self) -> Option { (*self).val_delim() } + fn takes_value(&self) -> bool { (*self).takes_value() } + fn val_names(&self) -> Option<&VecMap<&'e str>> { (*self).val_names() } + fn help(&self) -> Option<&'e str> { (*self).help() } + fn long_help(&self) -> Option<&'e str> { (*self).long_help() } + fn default_val(&self) -> Option<&'e OsStr> { (*self).default_val() } + fn default_vals_ifs(&self) -> Option, &'e OsStr)>> { (*self).default_vals_ifs() } + fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)> { (*self).env() } + fn longest_filter(&self) -> bool { (*self).longest_filter() } + fn val_terminator(&self) -> Option<&'e str> { (*self).val_terminator() } +} diff --git a/src/args/arg_matcher.rs b/src/args/arg_matcher.rs index 4e76802e6ed..25f3fc5d749 100644 --- a/src/args/arg_matcher.rs +++ b/src/args/arg_matcher.rs @@ -21,11 +21,36 @@ impl<'a> Default for ArgMatcher<'a> { impl<'a> ArgMatcher<'a> { pub fn new() -> Self { ArgMatcher::default() } + pub fn process_arg_overrides<'b>(&mut self, a: Option<&AnyArg<'a, 'b>>, overrides: &mut Vec<(&'b str, &'a str)>, required: &mut Vec<&'a str>) { + debugln!("ArgMatcher::process_arg_overrides:{:?};", a.map_or(None, |a| Some(a.name()))); + if let Some(aa) = a { + if let Some(a_overrides) = aa.overrides() { + for overr in a_overrides { + debugln!("ArgMatcher::process_arg_overrides:iter:{};", overr); + if self.is_present(overr) { + debugln!("ArgMatcher::process_arg_overrides:iter:{}: removing from matches;", overr); + self.remove(overr); + for i in (0 .. required.len()).rev() { + if &required[i] == overr { + debugln!("ArgMatcher::process_arg_overrides:iter:{}: removing required;", overr); + required.swap_remove(i); + break; + } + } + } else { + overrides.push((overr, aa.name())); + } + } + } + } + } + + pub fn is_present(&self, name: &str) -> bool { + self.0.is_present(name) + } + pub fn propagate_globals(&mut self, global_arg_vec: &[&'a str]) { - debugln!( - "ArgMatcher::get_global_values: global_arg_vec={:?}", - global_arg_vec - ); + debugln!( "ArgMatcher::get_global_values: global_arg_vec={:?}", global_arg_vec ); let mut vals_map = HashMap::new(); self.fill_in_global_values(global_arg_vec, &mut vals_map); } diff --git a/src/errors.rs b/src/errors.rs index c4108a64678..664819d6c90 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -8,7 +8,7 @@ use std::process; use std::result::Result as StdResult; // Internal -use args::{AnyArg, FlagBuilder}; +use args::AnyArg; use fmt::{ColorWhen, Colorizer, ColorizerOption}; use suggestions; @@ -404,14 +404,13 @@ impl Error { pub fn write_to(&self, w: &mut W) -> io::Result<()> { write!(w, "{}", self.message) } #[doc(hidden)] - pub fn argument_conflict<'a, 'b, A, O, U>( - arg: &A, + pub fn argument_conflict<'a, 'b, O, U>( + arg: &AnyArg, other: Option, usage: U, color: ColorWhen, ) -> Self where - A: AnyArg<'a, 'b> + Display, O: Into, U: Display, { @@ -444,9 +443,8 @@ impl Error { } #[doc(hidden)] - pub fn empty_value<'a, 'b, A, U>(arg: &A, usage: U, color: ColorWhen) -> Self + pub fn empty_value<'a, 'b, U>(arg: &AnyArg, usage: U, color: ColorWhen) -> Self where - A: AnyArg<'a, 'b> + Display, U: Display, { let c = Colorizer::new(ColorizerOption { @@ -470,17 +468,16 @@ impl Error { } #[doc(hidden)] - pub fn invalid_value<'a, 'b, B, G, A, U>( + pub fn invalid_value<'a, 'b, B, G, U>( bad_val: B, good_vals: &[G], - arg: &A, + arg: &AnyArg, usage: U, color: ColorWhen, ) -> Self where B: AsRef, G: AsRef + Display, - A: AnyArg<'a, 'b> + Display, U: Display, { let c = Colorizer::new(ColorizerOption { @@ -660,10 +657,9 @@ impl Error { } #[doc(hidden)] - pub fn too_many_values<'a, 'b, V, A, U>(val: V, arg: &A, usage: U, color: ColorWhen) -> Self + pub fn too_many_values<'a, 'b, V, U>(val: V, arg: &AnyArg, usage: U, color: ColorWhen) -> Self where V: AsRef + Display + ToOwned, - A: AnyArg<'a, 'b> + Display, U: Display, { let v = val.as_ref(); @@ -689,15 +685,14 @@ impl Error { } #[doc(hidden)] - pub fn too_few_values<'a, 'b, A, U>( - arg: &A, + pub fn too_few_values<'a, 'b, U>( + arg: &AnyArg, min_vals: u64, curr_vals: usize, usage: U, color: ColorWhen, ) -> Self where - A: AnyArg<'a, 'b> + Display, U: Display, { let c = Colorizer::new(ColorizerOption { @@ -724,9 +719,7 @@ impl Error { } #[doc(hidden)] - pub fn value_validation<'a, 'b, A>(arg: Option<&A>, err: String, color: ColorWhen) -> Self - where - A: AnyArg<'a, 'b> + Display, + pub fn value_validation<'a, 'b>(arg: Option<&AnyArg>, err: String, color: ColorWhen) -> Self { let c = Colorizer::new(ColorizerOption { use_stderr: true, @@ -750,13 +743,13 @@ impl Error { #[doc(hidden)] pub fn value_validation_auto(err: String) -> Self { - let n: Option<&FlagBuilder> = None; + let n: Option<&AnyArg> = None; Error::value_validation(n, err, ColorWhen::Auto) } #[doc(hidden)] - pub fn wrong_number_of_values<'a, 'b, A, S, U>( - arg: &A, + pub fn wrong_number_of_values<'a, 'b, S, U>( + arg: &AnyArg, num_vals: u64, curr_vals: usize, suffix: S, @@ -764,7 +757,6 @@ impl Error { color: ColorWhen, ) -> Self where - A: AnyArg<'a, 'b> + Display, S: Display, U: Display, { @@ -792,9 +784,8 @@ impl Error { } #[doc(hidden)] - pub fn unexpected_multiple_usage<'a, 'b, A, U>(arg: &A, usage: U, color: ColorWhen) -> Self + pub fn unexpected_multiple_usage<'a, 'b, U>(arg: &AnyArg, usage: U, color: ColorWhen) -> Self where - A: AnyArg<'a, 'b> + Display, U: Display, { let c = Colorizer::new(ColorizerOption { diff --git a/src/lib.rs b/src/lib.rs index de23769c796..d60eddd7642 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -513,7 +513,7 @@ //! this repository for more information. #![crate_type = "lib"] -#![doc(html_root_url = "https://docs.rs/clap/2.29.0")] +#![doc(html_root_url = "https://docs.rs/clap/2.29.1")] #![deny(missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts, unused_import_braces, unused_allocation)] // Lints we'd like to deny but are currently failing for upstream crates diff --git a/src/macros.rs b/src/macros.rs index 51bb95a7b07..52fb35a4de9 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -853,15 +853,15 @@ macro_rules! write_nspaces { } // convenience macro for remove an item from a vec -macro_rules! vec_remove_all { - ($vec:expr, $to_rem:expr) => { - debugln!("vec_remove_all! to_rem={:?}", $to_rem); - for i in (0 .. $vec.len()).rev() { - let should_remove = $to_rem.any(|name| name == &$vec[i]); - if should_remove { $vec.swap_remove(i); } - } - }; -} +//macro_rules! vec_remove_all { +// ($vec:expr, $to_rem:expr) => { +// debugln!("vec_remove_all! to_rem={:?}", $to_rem); +// for i in (0 .. $vec.len()).rev() { +// let should_remove = $to_rem.any(|name| name == &$vec[i]); +// if should_remove { $vec.swap_remove(i); } +// } +// }; +//} macro_rules! find_from { ($_self:expr, $arg_name:expr, $from:ident, $matcher:expr) => {{ let mut ret = None; @@ -892,36 +892,49 @@ macro_rules! find_from { }}; } -macro_rules! find_name_from { - ($_self:expr, $arg_name:expr, $from:ident, $matcher:expr) => {{ - let mut ret = None; - for k in $matcher.arg_names() { - if let Some(f) = find_by_name!($_self, k, flags, iter) { - if let Some(ref v) = f.$from() { - if v.contains($arg_name) { - ret = Some(f.b.name); - } - } - } - if let Some(o) = find_by_name!($_self, k, opts, iter) { - if let Some(ref v) = o.$from() { - if v.contains(&$arg_name) { - ret = Some(o.b.name); - } - } - } - if let Some(pos) = find_by_name!($_self, k, positionals, values) { - if let Some(ref v) = pos.$from() { - if v.contains($arg_name) { - ret = Some(pos.b.name); - } - } - } +//macro_rules! find_name_from { +// ($_self:expr, $arg_name:expr, $from:ident, $matcher:expr) => {{ +// let mut ret = None; +// for k in $matcher.arg_names() { +// if let Some(f) = find_by_name!($_self, k, flags, iter) { +// if let Some(ref v) = f.$from() { +// if v.contains($arg_name) { +// ret = Some(f.b.name); +// } +// } +// } +// if let Some(o) = find_by_name!($_self, k, opts, iter) { +// if let Some(ref v) = o.$from() { +// if v.contains(&$arg_name) { +// ret = Some(o.b.name); +// } +// } +// } +// if let Some(pos) = find_by_name!($_self, k, positionals, values) { +// if let Some(ref v) = pos.$from() { +// if v.contains($arg_name) { +// ret = Some(pos.b.name); +// } +// } +// } +// } +// ret +// }}; +//} + + +macro_rules! find_any_by_name { + ($p:expr, $name:expr) => { + { + fn as_trait_obj<'a, 'b, T: AnyArg<'a, 'b>>(x: &T) -> &AnyArg<'a, 'b> { x } + find_by_name!($p, $name, flags, iter).map(as_trait_obj).or( + find_by_name!($p, $name, opts, iter).map(as_trait_obj).or( + find_by_name!($p, $name, positionals, values).map(as_trait_obj) + ) + ) } - ret - }}; + } } - // Finds an arg by name macro_rules! find_by_name { ($p:expr, $name:expr, $what:ident, $how:ident) => { diff --git a/tests/global_args.rs b/tests/global_args.rs index 6bcf11c5a3f..28eaac1124f 100644 --- a/tests/global_args.rs +++ b/tests/global_args.rs @@ -4,7 +4,7 @@ extern crate regex; #[cfg(test)] mod tests { include!("../clap-test.rs"); - use clap::{App, Arg, SubCommand, ArgMatches}; + use clap::{App, Arg, SubCommand}; fn get_app() -> App<'static, 'static> { App::new("myprog") diff --git a/tests/groups.rs b/tests/groups.rs index fa497bfb643..fde3f32c264 100644 --- a/tests/groups.rs +++ b/tests/groups.rs @@ -55,36 +55,45 @@ fn non_existing_arg() { #[test] fn group_single_value() { - let m = App::new("group") + let res = App::new("group") .args_from_usage("-f, --flag 'some flag' -c, --color [color] 'some option'") .group(ArgGroup::with_name("grp") .args(&["flag", "color"])) - .get_matches_from(vec!["", "-c", "blue"]); + .get_matches_from_safe(vec!["", "-c", "blue"]); + assert!(res.is_ok()); + + let m = res.unwrap(); assert!(m.is_present("grp")); assert_eq!(m.value_of("grp").unwrap(), "blue"); } #[test] fn group_single_flag() { - let m = App::new("group") + let res = App::new("group") .args_from_usage("-f, --flag 'some flag' -c, --color [color] 'some option'") .group(ArgGroup::with_name("grp") .args(&["flag", "color"])) - .get_matches_from(vec!["", "-f"]); + .get_matches_from_safe(vec!["", "-f"]); + assert!(res.is_ok()); + + let m = res.unwrap(); assert!(m.is_present("grp")); assert!(m.value_of("grp").is_none()); } #[test] fn group_empty() { - let m = App::new("group") + let res = App::new("group") .args_from_usage("-f, --flag 'some flag' -c, --color [color] 'some option'") .group(ArgGroup::with_name("grp") .args(&["flag", "color"])) - .get_matches_from(vec![""]); + .get_matches_from_safe(vec![""]); + assert!(res.is_ok()); + + let m = res.unwrap(); assert!(!m.is_present("grp")); assert!(m.value_of("grp").is_none()); } @@ -105,12 +114,15 @@ fn group_reqired_flags_empty() { #[test] fn group_multi_value_single_arg() { - let m = App::new("group") + let res = App::new("group") .args_from_usage("-f, --flag 'some flag' -c, --color [color]... 'some option'") .group(ArgGroup::with_name("grp") .args(&["flag", "color"])) - .get_matches_from(vec!["", "-c", "blue", "red", "green"]); + .get_matches_from_safe(vec!["", "-c", "blue", "red", "green"]); + assert!(res.is_ok(), "{:?}", res.unwrap_err().kind); + + let m = res.unwrap(); assert!(m.is_present("grp")); assert_eq!(&*m.values_of("grp").unwrap().collect::>(), &["blue", "red", "green"]); } @@ -148,19 +160,7 @@ fn req_group_with_conflict_usage_string() { .args(&["base", "delete"]) .required(true)); - assert!(test::compare_output(app, "clap-test --delete base", REQ_GROUP_CONFLICT_REV, true)); -} - -#[test] -fn req_group_with_conflict_rev_usage_string() { - let app = App::new("req_group") - .arg(Arg::from_usage("[base] 'Base commit'").conflicts_with("delete")) - .arg(Arg::from_usage("-d, --delete 'Remove the base commit information'")) - .group(ArgGroup::with_name("base_or_delete") - .args(&["base", "delete"]) - .required(true)); - - assert!(test::compare_output(app, "clap-test base --delete", REQ_GROUP_CONFLICT_USAGE, true)); + assert!(test::compare_output2(app, "clap-test --delete base", REQ_GROUP_CONFLICT_REV, REQ_GROUP_CONFLICT_USAGE, true)); } #[test] diff --git a/tests/multiple_values.rs b/tests/multiple_values.rs index 32f8a08e71a..7f35e06a82b 100644 --- a/tests/multiple_values.rs +++ b/tests/multiple_values.rs @@ -1106,13 +1106,16 @@ fn multiple_value_terminator_option_other_arg() { #[test] fn multiple_vals_with_hyphen() { - let m = App::new("do") + let res = App::new("do") .arg(Arg::with_name("cmds") .multiple(true) .allow_hyphen_values(true) .value_terminator(";")) .arg(Arg::with_name("location")) - .get_matches_from(vec!["do", "find", "-type", "f", "-name", "special", ";", "/home/clap"]); + .get_matches_from_safe(vec!["do", "find", "-type", "f", "-name", "special", ";", "/home/clap"]); + assert!(res.is_ok(), "{:?}", res.unwrap_err().kind); + + let m = res.unwrap(); let cmds: Vec<_> = m.values_of("cmds").unwrap().collect(); assert_eq!(&cmds, &["find", "-type", "f", "-name", "special"]); assert_eq!(m.value_of("location"), Some("/home/clap")); diff --git a/tests/posix_compatible.rs b/tests/posix_compatible.rs index 060bc5f7431..83f14481239 100644 --- a/tests/posix_compatible.rs +++ b/tests/posix_compatible.rs @@ -166,14 +166,13 @@ fn pos_required_overridden_by_flag() { #[test] fn require_overriden_2() { let m = App::new("require_overriden") - .arg(Arg::with_name("flag") - .index(1) + .arg(Arg::with_name("req_pos") .required(true)) .arg(Arg::from_usage("-c, --color 'other flag'") - .overrides_with("flag")) - .get_matches_from(vec!["", "-c", "flag"]); + .overrides_with("req_pos")) + .get_matches_from(vec!["", "-c", "req_pos"]); assert!(!m.is_present("color")); - assert!(m.is_present("flag")); + assert!(m.is_present("req_pos")); } #[test] diff --git a/tests/possible_values.rs b/tests/possible_values.rs index 0085c9e9e83..4939a7d7d8c 100644 --- a/tests/possible_values.rs +++ b/tests/possible_values.rs @@ -3,7 +3,7 @@ extern crate regex; include!("../clap-test.rs"); -#[allow(unsused_imports)] +#[allow(unused_imports)] use std::ascii::AsciiExt; use clap::{App, Arg, ErrorKind}; diff --git a/tests/subcommands.rs b/tests/subcommands.rs index e15de696974..de2f8b56117 100644 --- a/tests/subcommands.rs +++ b/tests/subcommands.rs @@ -32,22 +32,22 @@ SUBCOMMANDS: test Some help"; #[cfg(feature = "suggestions")] -static DYM: &'static str = "error: The subcommand 'subcm' wasn't recognized -\tDid you mean 'subcmd'? +static DYM_SUBCMD: &'static str = "error: The subcommand 'subcm' wasn't recognized + Did you mean 'subcmd'? -If you believe you received this message in error, try re-running with 'clap-test -- subcm' +If you believe you received this message in error, try re-running with 'dym -- subcm' USAGE: - clap-test [FLAGS] [OPTIONS] [ARGS] [SUBCOMMAND] + dym [SUBCOMMAND] For more information try --help"; #[cfg(feature = "suggestions")] -static DYM2: &'static str = "error: Found argument '--subcm' which wasn't expected, or isn't valid in this context +static DYM_ARG: &'static str = "error: Found argument '--subcm' which wasn't expected, or isn't valid in this context \tDid you mean to put '--subcmdarg' after the subcommand 'subcmd'? USAGE: - clap-test [FLAGS] [OPTIONS] [ARGS] [SUBCOMMAND] + dym [SUBCOMMAND] For more information try --help"; @@ -129,8 +129,18 @@ fn multiple_aliases() { #[test] #[cfg(feature="suggestions")] fn subcmd_did_you_mean_output() { - assert!(test::compare_output(test::complex_app(), "clap-test subcm", DYM, true)); - assert!(test::compare_output(test::complex_app(), "clap-test --subcm foo", DYM2, true)); + let app = App::new("dym") + .subcommand(SubCommand::with_name("subcmd")); + assert!(test::compare_output(app, "dym subcm", DYM_SUBCMD, true)); +} + +#[test] +#[cfg(feature="suggestions")] +fn subcmd_did_you_mean_output_arg() { + let app = App::new("dym") + .subcommand(SubCommand::with_name("subcmd") + .arg_from_usage("-s --subcmdarg [subcmdarg] 'tests'") ); + assert!(test::compare_output(app, "dym --subcm foo", DYM_ARG, true)); } #[test]