├── triagebot.toml ├── .gitignore ├── tests └── smoke.rs ├── README.md ├── .github └── workflows │ ├── publish.yaml │ └── main.yaml ├── Cargo.toml ├── LICENSE-MIT ├── CHANGELOG.md ├── LICENSE-APACHE └── src ├── tests └── mod.rs └── lib.rs /triagebot.toml: -------------------------------------------------------------------------------- 1 | [assign] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Cargo 2 | /target 3 | /Cargo.lock 4 | 5 | #IDE 6 | .vscode -------------------------------------------------------------------------------- /tests/smoke.rs: -------------------------------------------------------------------------------- 1 | extern crate getopts; 2 | 3 | use std::env; 4 | 5 | #[test] 6 | fn main() { 7 | getopts::Options::new().parse(env::args()).unwrap(); 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | getopts 2 | === 3 | 4 | A Rust library for option parsing for CLI utilities. 5 | 6 | [Documentation](https://docs.rs/getopts) 7 | 8 | ## Usage 9 | 10 | Add this to your `Cargo.toml`: 11 | 12 | ```toml 13 | [dependencies] 14 | getopts = "0.2" 15 | ``` 16 | 17 | ## Contributing 18 | 19 | The `getopts` library is used by `rustc`, so we have to be careful about not changing its behavior. 20 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Release-plz 2 | 3 | permissions: 4 | pull-requests: write 5 | contents: write 6 | 7 | on: 8 | push: { branches: [master] } 9 | 10 | jobs: 11 | release-plz: 12 | name: Release-plz 13 | runs-on: ubuntu-24.04 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | - name: Install Rust (rustup) 20 | run: rustup update nightly --no-self-update && rustup default nightly 21 | - name: Run release-plz 22 | uses: MarcoIeni/release-plz-action@v0.5 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "getopts" 3 | version = "0.2.24" 4 | authors = ["The Rust Project Developers"] 5 | license = "MIT OR Apache-2.0" 6 | repository = "https://github.com/rust-lang/getopts" 7 | description = "getopts-like option parsing" 8 | categories = ["command-line-interface"] 9 | edition = "2021" 10 | rust-version = "1.66" 11 | 12 | [dependencies] 13 | unicode-width = { version = "0.2.0", optional = true } 14 | std = { version = "1.0", package = "rustc-std-workspace-std", optional = true } 15 | core = { version = "1.0", package = "rustc-std-workspace-core", optional = true } 16 | 17 | [dev-dependencies] 18 | log = "0.4" 19 | 20 | [features] 21 | default = ["unicode"] 22 | rustc-dep-of-std = ["std", "core"] 23 | unicode = ["dep:unicode-width"] 24 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 The Rust Project Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | 4 | env: 5 | RUSTFLAGS: "-Dwarnings" 6 | RUSTDOCFLAGS: "-Dwarnings" 7 | 8 | jobs: 9 | test: 10 | name: Test 11 | strategy: 12 | matrix: 13 | os: [windows-2025, ubuntu-24.04, macos-15] 14 | rust: [stable, nightly] 15 | include: 16 | - os: ubuntu-24.04 17 | rust: beta 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - uses: actions/checkout@master 21 | - name: Install Rust 22 | run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} 23 | - uses: Swatinem/rust-cache@v2 24 | with: 25 | key: ${{ matrix.target }} 26 | - run: cargo test 27 | - run: cargo test --no-default-features 28 | 29 | doc_fmt: 30 | name: Document and check formatting 31 | runs-on: ubuntu-24.04 32 | steps: 33 | - uses: actions/checkout@master 34 | - name: Install Rust 35 | run: | 36 | rustup update nightly 37 | rustup default nightly 38 | rustup component add rustfmt 39 | - uses: Swatinem/rust-cache@v2 40 | - run: cargo doc 41 | - run: cargo fmt --check 42 | 43 | msrv: 44 | runs-on: ubuntu-24.04 45 | steps: 46 | - uses: actions/checkout@master 47 | - name: Install Rust 48 | run: | 49 | msrv="$(cargo metadata --format-version=1 | 50 | jq -r '.packages[] | select(.name == "getopts").rust_version' 51 | )" 52 | rustup update "$msrv" && rustup default "$msrv" 53 | - uses: Swatinem/rust-cache@v2 54 | - run: cargo check 55 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [0.2.24](https://github.com/rust-lang/getopts/compare/v0.2.23...v0.2.24) - 2025-08-29 11 | 12 | ### Other 13 | 14 | - Make unicode-width an optional default dependency ([#133](https://github.com/rust-lang/getopts/pull/133)) 15 | 16 | ## [0.2.23](https://github.com/rust-lang/getopts/compare/v0.2.22...v0.2.23) - 2025-06-09 17 | 18 | ### Other 19 | 20 | - Add caching 21 | - Remove redundant configuration from Cargo.toml 22 | - Bump unicode-width to 0.2.0 23 | - Update the MSRV to 1.66 and edition to 2021 24 | 25 | ## [0.2.22](https://github.com/rust-lang/getopts/compare/v0.2.21...v0.2.22) - 2025-06-05 26 | 27 | ### Other 28 | 29 | - Add a check for formatting, apply `cargo fmt` 30 | - Add a release job 31 | - Document and start testing the MSRV 32 | - Test on more platforms, deny warnings 33 | - Eliminate `html_root_url` 34 | - Update version number in html_root_url 35 | - Use SPDX license format 36 | - Fix compiler warning in documentation example 37 | - Merge pull request #100 from zdenek-crha/parse_args_end_position 38 | - Merge pull request #103 from zdenek-crha/better_usage_examples 39 | - Add usage examples for methods that add option config 40 | - Update outdated top level documentation 41 | - Add triagebot configuration 42 | - remove deprecated Error::description 43 | - Update documentation of opt_present() and other functions that might panic 44 | - Updated tests for opts_str() and opts_str_first() to check order of processing 45 | - Add opts_present_any() and opts_str_first() interface functions 46 | - Parse options without names vector 47 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | use super::each_split_within; 2 | use super::Fail::*; 3 | use super::{HasArg, Name, Occur, Opt, Options, ParsingStyle}; 4 | 5 | #[test] 6 | fn test_split_within() { 7 | fn t(s: &str, i: usize, u: &[String]) { 8 | let v = each_split_within(&(s.to_string()), i); 9 | assert!(v.iter().zip(u.iter()).all(|(a, b)| a == b)); 10 | } 11 | t("", 0, &[]); 12 | t("", 15, &[]); 13 | t("hello", 15, &["hello".to_string()]); 14 | t( 15 | "\nMary had a little lamb\nLittle lamb\n", 16 | 15, 17 | &[ 18 | "Mary had a".to_string(), 19 | "little lamb".to_string(), 20 | "Little lamb".to_string(), 21 | ], 22 | ); 23 | t( 24 | "\nMary had a little lamb\nLittle lamb\n", 25 | ::std::usize::MAX, 26 | &[ 27 | "Mary had a little lamb".to_string(), 28 | "Little lamb".to_string(), 29 | ], 30 | ); 31 | } 32 | 33 | // Tests for reqopt 34 | #[test] 35 | fn test_reqopt() { 36 | let long_args = vec!["--test=20".to_string()]; 37 | let mut opts = Options::new(); 38 | opts.reqopt("t", "test", "testing", "TEST"); 39 | match opts.parse(&long_args) { 40 | Ok(ref m) => { 41 | assert!(m.opt_present("test")); 42 | assert_eq!(m.opt_str("test").unwrap(), "20"); 43 | assert!(m.opt_present("t")); 44 | assert_eq!(m.opt_str("t").unwrap(), "20"); 45 | } 46 | _ => { 47 | panic!("test_reqopt failed (long arg)"); 48 | } 49 | } 50 | let short_args = vec!["-t".to_string(), "20".to_string()]; 51 | match opts.parse(&short_args) { 52 | Ok(ref m) => { 53 | assert!(m.opt_present("test")); 54 | assert_eq!(m.opt_str("test").unwrap(), "20"); 55 | assert!(m.opt_present("t")); 56 | assert_eq!(m.opt_str("t").unwrap(), "20"); 57 | } 58 | _ => { 59 | panic!("test_reqopt failed (short arg)"); 60 | } 61 | } 62 | } 63 | 64 | #[test] 65 | fn test_reqopt_missing() { 66 | let args = vec!["blah".to_string()]; 67 | match Options::new() 68 | .reqopt("t", "test", "testing", "TEST") 69 | .parse(&args) 70 | { 71 | Err(OptionMissing(_)) => {} 72 | _ => panic!(), 73 | } 74 | } 75 | 76 | #[test] 77 | fn test_reqopt_no_arg() { 78 | let long_args = vec!["--test".to_string()]; 79 | let mut opts = Options::new(); 80 | opts.reqopt("t", "test", "testing", "TEST"); 81 | match opts.parse(&long_args) { 82 | Err(ArgumentMissing(_)) => {} 83 | _ => panic!(), 84 | } 85 | let short_args = vec!["-t".to_string()]; 86 | match opts.parse(&short_args) { 87 | Err(ArgumentMissing(_)) => {} 88 | _ => panic!(), 89 | } 90 | } 91 | 92 | #[test] 93 | fn test_reqopt_multi() { 94 | let args = vec!["--test=20".to_string(), "-t".to_string(), "30".to_string()]; 95 | match Options::new() 96 | .reqopt("t", "test", "testing", "TEST") 97 | .parse(&args) 98 | { 99 | Err(OptionDuplicated(_)) => {} 100 | _ => panic!(), 101 | } 102 | } 103 | 104 | // Tests for optopt 105 | #[test] 106 | fn test_optopt() { 107 | let long_args = vec!["--test=20".to_string()]; 108 | let mut opts = Options::new(); 109 | opts.optopt("t", "test", "testing", "TEST"); 110 | match opts.parse(&long_args) { 111 | Ok(ref m) => { 112 | assert!(m.opt_present("test")); 113 | assert_eq!(m.opt_str("test").unwrap(), "20"); 114 | assert!(m.opt_present("t")); 115 | assert_eq!(m.opt_str("t").unwrap(), "20"); 116 | } 117 | _ => panic!(), 118 | } 119 | let short_args = vec!["-t".to_string(), "20".to_string()]; 120 | match opts.parse(&short_args) { 121 | Ok(ref m) => { 122 | assert!(m.opt_present("test")); 123 | assert_eq!(m.opt_str("test").unwrap(), "20"); 124 | assert!(m.opt_present("t")); 125 | assert_eq!(m.opt_str("t").unwrap(), "20"); 126 | } 127 | _ => panic!(), 128 | } 129 | } 130 | 131 | #[test] 132 | fn test_optopt_missing() { 133 | let args = vec!["blah".to_string()]; 134 | match Options::new() 135 | .optopt("t", "test", "testing", "TEST") 136 | .parse(&args) 137 | { 138 | Ok(ref m) => { 139 | assert!(!m.opt_present("test")); 140 | assert!(!m.opt_present("t")); 141 | } 142 | _ => panic!(), 143 | } 144 | } 145 | 146 | #[test] 147 | fn test_optopt_no_arg() { 148 | let long_args = vec!["--test".to_string()]; 149 | let mut opts = Options::new(); 150 | opts.optopt("t", "test", "testing", "TEST"); 151 | match opts.parse(&long_args) { 152 | Err(ArgumentMissing(_)) => {} 153 | _ => panic!(), 154 | } 155 | let short_args = vec!["-t".to_string()]; 156 | match opts.parse(&short_args) { 157 | Err(ArgumentMissing(_)) => {} 158 | _ => panic!(), 159 | } 160 | } 161 | 162 | #[test] 163 | fn test_optopt_multi() { 164 | let args = vec!["--test=20".to_string(), "-t".to_string(), "30".to_string()]; 165 | match Options::new() 166 | .optopt("t", "test", "testing", "TEST") 167 | .parse(&args) 168 | { 169 | Err(OptionDuplicated(_)) => {} 170 | _ => panic!(), 171 | } 172 | } 173 | 174 | // Tests for optflag 175 | #[test] 176 | fn test_optflag() { 177 | let long_args = vec!["--test".to_string()]; 178 | let mut opts = Options::new(); 179 | opts.optflag("t", "test", "testing"); 180 | match opts.parse(&long_args) { 181 | Ok(ref m) => { 182 | assert!(m.opt_present("test")); 183 | assert!(m.opt_present("t")); 184 | } 185 | _ => panic!(), 186 | } 187 | let short_args = vec!["-t".to_string()]; 188 | match opts.parse(&short_args) { 189 | Ok(ref m) => { 190 | assert!(m.opt_present("test")); 191 | assert!(m.opt_present("t")); 192 | } 193 | _ => panic!(), 194 | } 195 | } 196 | 197 | #[test] 198 | fn test_optflag_missing() { 199 | let args = vec!["blah".to_string()]; 200 | match Options::new().optflag("t", "test", "testing").parse(&args) { 201 | Ok(ref m) => { 202 | assert!(!m.opt_present("test")); 203 | assert!(!m.opt_present("t")); 204 | } 205 | _ => panic!(), 206 | } 207 | } 208 | 209 | #[test] 210 | fn test_free_trailing_missing() { 211 | let args = vec![] as Vec; 212 | match Options::new().parse(&args) { 213 | Ok(ref m) => { 214 | assert_eq!(m.free_trailing_start(), None); 215 | } 216 | _ => panic!(), 217 | } 218 | } 219 | 220 | #[test] 221 | fn test_free_trailing() { 222 | let args = vec!["--".to_owned(), "-t".to_owned()]; 223 | match Options::new().optflag("t", "test", "testing").parse(&args) { 224 | Ok(ref m) => { 225 | assert!(!m.opt_present("test")); 226 | assert!(!m.opt_present("t")); 227 | assert_eq!(m.free_trailing_start(), Some(0)); 228 | assert_eq!(m.free.len(), 1); 229 | assert_eq!(m.free[0], "-t"); 230 | } 231 | _ => panic!(), 232 | } 233 | } 234 | 235 | #[test] 236 | fn test_free_trailing_only() { 237 | let args = vec!["--".to_owned()]; 238 | match Options::new().optflag("t", "test", "testing").parse(&args) { 239 | Ok(ref m) => { 240 | assert!(!m.opt_present("test")); 241 | assert!(!m.opt_present("t")); 242 | assert_eq!(m.free_trailing_start(), None); 243 | assert_eq!(m.free.len(), 0); 244 | } 245 | _ => panic!(), 246 | } 247 | } 248 | 249 | #[test] 250 | fn test_free_trailing_args() { 251 | let args = vec!["pre".to_owned(), "--".to_owned(), "post".to_owned()]; 252 | match Options::new().parse(&args) { 253 | Ok(ref m) => { 254 | assert_eq!(m.free_trailing_start(), Some(1)); 255 | assert_eq!(m.free.len(), 2); 256 | } 257 | _ => panic!(), 258 | } 259 | } 260 | 261 | #[test] 262 | fn test_optflag_long_arg() { 263 | let args = vec!["--test=20".to_string()]; 264 | match Options::new().optflag("t", "test", "testing").parse(&args) { 265 | Err(UnexpectedArgument(_)) => {} 266 | _ => panic!(), 267 | } 268 | } 269 | 270 | #[test] 271 | fn test_optflag_multi() { 272 | let args = vec!["--test".to_string(), "-t".to_string()]; 273 | match Options::new().optflag("t", "test", "testing").parse(&args) { 274 | Err(OptionDuplicated(_)) => {} 275 | _ => panic!(), 276 | } 277 | } 278 | 279 | #[test] 280 | fn test_optflag_short_arg() { 281 | let args = vec!["-t".to_string(), "20".to_string()]; 282 | match Options::new().optflag("t", "test", "testing").parse(&args) { 283 | Ok(ref m) => { 284 | // The next variable after the flag is just a free argument 285 | 286 | assert!(m.free[0] == "20"); 287 | } 288 | _ => panic!(), 289 | } 290 | } 291 | 292 | // Tests for optflagmulti 293 | #[test] 294 | fn test_optflagmulti_short1() { 295 | let args = vec!["-v".to_string()]; 296 | match Options::new() 297 | .optflagmulti("v", "verbose", "verbosity") 298 | .parse(&args) 299 | { 300 | Ok(ref m) => { 301 | assert_eq!(m.opt_count("v"), 1); 302 | } 303 | _ => panic!(), 304 | } 305 | } 306 | 307 | #[test] 308 | fn test_optflagmulti_short2a() { 309 | let args = vec!["-v".to_string(), "-v".to_string()]; 310 | match Options::new() 311 | .optflagmulti("v", "verbose", "verbosity") 312 | .parse(&args) 313 | { 314 | Ok(ref m) => { 315 | assert_eq!(m.opt_count("v"), 2); 316 | } 317 | _ => panic!(), 318 | } 319 | } 320 | 321 | #[test] 322 | fn test_optflagmulti_short2b() { 323 | let args = vec!["-vv".to_string()]; 324 | match Options::new() 325 | .optflagmulti("v", "verbose", "verbosity") 326 | .parse(&args) 327 | { 328 | Ok(ref m) => { 329 | assert_eq!(m.opt_count("v"), 2); 330 | } 331 | _ => panic!(), 332 | } 333 | } 334 | 335 | #[test] 336 | fn test_optflagmulti_long1() { 337 | let args = vec!["--verbose".to_string()]; 338 | match Options::new() 339 | .optflagmulti("v", "verbose", "verbosity") 340 | .parse(&args) 341 | { 342 | Ok(ref m) => { 343 | assert_eq!(m.opt_count("verbose"), 1); 344 | } 345 | _ => panic!(), 346 | } 347 | } 348 | 349 | #[test] 350 | fn test_optflagmulti_long2() { 351 | let args = vec!["--verbose".to_string(), "--verbose".to_string()]; 352 | match Options::new() 353 | .optflagmulti("v", "verbose", "verbosity") 354 | .parse(&args) 355 | { 356 | Ok(ref m) => { 357 | assert_eq!(m.opt_count("verbose"), 2); 358 | } 359 | _ => panic!(), 360 | } 361 | } 362 | 363 | #[test] 364 | fn test_optflagmulti_mix() { 365 | let args = vec![ 366 | "--verbose".to_string(), 367 | "-v".to_string(), 368 | "-vv".to_string(), 369 | "verbose".to_string(), 370 | ]; 371 | match Options::new() 372 | .optflagmulti("v", "verbose", "verbosity") 373 | .parse(&args) 374 | { 375 | Ok(ref m) => { 376 | assert_eq!(m.opt_count("verbose"), 4); 377 | assert_eq!(m.opt_count("v"), 4); 378 | } 379 | _ => panic!(), 380 | } 381 | } 382 | 383 | // Tests for optflagopt 384 | #[test] 385 | fn test_optflagopt() { 386 | let long_args = vec!["--test".to_string()]; 387 | let mut opts = Options::new(); 388 | opts.optflagopt("t", "test", "testing", "ARG"); 389 | match opts.parse(&long_args) { 390 | Ok(ref m) => { 391 | assert!(m.opt_present("test")); 392 | assert!(m.opt_present("t")); 393 | } 394 | _ => panic!(), 395 | } 396 | let short_args = vec!["-t".to_string()]; 397 | match opts.parse(&short_args) { 398 | Ok(ref m) => { 399 | assert!(m.opt_present("test")); 400 | assert!(m.opt_present("t")); 401 | } 402 | _ => panic!(), 403 | } 404 | let short_args = vec!["-t".to_string(), "x".to_string()]; 405 | match opts.parse(&short_args) { 406 | Ok(ref m) => { 407 | assert_eq!(m.opt_str("t").unwrap(), "x"); 408 | assert_eq!(m.opt_str("test").unwrap(), "x"); 409 | } 410 | _ => panic!(), 411 | } 412 | let long_args = vec!["--test=x".to_string()]; 413 | match opts.parse(&long_args) { 414 | Ok(ref m) => { 415 | assert_eq!(m.opt_str("t").unwrap(), "x"); 416 | assert_eq!(m.opt_str("test").unwrap(), "x"); 417 | } 418 | _ => panic!(), 419 | } 420 | let long_args = vec!["--test".to_string(), "x".to_string()]; 421 | match opts.parse(&long_args) { 422 | Ok(ref m) => { 423 | assert_eq!(m.opt_str("t"), None); 424 | assert_eq!(m.opt_str("test"), None); 425 | } 426 | _ => panic!(), 427 | } 428 | let no_args: Vec = vec![]; 429 | match opts.parse(&no_args) { 430 | Ok(ref m) => { 431 | assert!(!m.opt_present("test")); 432 | assert!(!m.opt_present("t")); 433 | } 434 | _ => panic!(), 435 | } 436 | } 437 | 438 | // Tests for optmulti 439 | #[test] 440 | fn test_optmulti() { 441 | let long_args = vec!["--test=20".to_string()]; 442 | let mut opts = Options::new(); 443 | opts.optmulti("t", "test", "testing", "TEST"); 444 | match opts.parse(&long_args) { 445 | Ok(ref m) => { 446 | assert!(m.opt_present("test")); 447 | assert_eq!(m.opt_str("test").unwrap(), "20"); 448 | assert!(m.opt_present("t")); 449 | assert_eq!(m.opt_str("t").unwrap(), "20"); 450 | } 451 | _ => panic!(), 452 | } 453 | let short_args = vec!["-t".to_string(), "20".to_string()]; 454 | match opts.parse(&short_args) { 455 | Ok(ref m) => { 456 | assert!(m.opt_present("test")); 457 | assert_eq!(m.opt_str("test").unwrap(), "20"); 458 | assert!(m.opt_present("t")); 459 | assert_eq!(m.opt_str("t").unwrap(), "20"); 460 | } 461 | _ => panic!(), 462 | } 463 | } 464 | 465 | #[test] 466 | fn test_optmulti_missing() { 467 | let args = vec!["blah".to_string()]; 468 | match Options::new() 469 | .optmulti("t", "test", "testing", "TEST") 470 | .parse(&args) 471 | { 472 | Ok(ref m) => { 473 | assert!(!m.opt_present("test")); 474 | assert!(!m.opt_present("t")); 475 | } 476 | _ => panic!(), 477 | } 478 | } 479 | 480 | #[test] 481 | fn test_optmulti_no_arg() { 482 | let long_args = vec!["--test".to_string()]; 483 | let mut opts = Options::new(); 484 | opts.optmulti("t", "test", "testing", "TEST"); 485 | match opts.parse(&long_args) { 486 | Err(ArgumentMissing(_)) => {} 487 | _ => panic!(), 488 | } 489 | let short_args = vec!["-t".to_string()]; 490 | match opts.parse(&short_args) { 491 | Err(ArgumentMissing(_)) => {} 492 | _ => panic!(), 493 | } 494 | } 495 | 496 | #[test] 497 | fn test_optmulti_multi() { 498 | let args = vec!["--test=20".to_string(), "-t".to_string(), "30".to_string()]; 499 | match Options::new() 500 | .optmulti("t", "test", "testing", "TEST") 501 | .parse(&args) 502 | { 503 | Ok(ref m) => { 504 | assert!(m.opt_present("test")); 505 | assert_eq!(m.opt_str("test").unwrap(), "20"); 506 | assert!(m.opt_present("t")); 507 | assert_eq!(m.opt_str("t").unwrap(), "20"); 508 | let pair = m.opt_strs("test"); 509 | assert!(pair[0] == "20"); 510 | assert!(pair[1] == "30"); 511 | } 512 | _ => panic!(), 513 | } 514 | } 515 | 516 | #[test] 517 | fn test_free_argument_is_hyphen() { 518 | let args = vec!["-".to_string()]; 519 | match Options::new().parse(&args) { 520 | Ok(ref m) => { 521 | assert_eq!(m.free.len(), 1); 522 | assert_eq!(m.free[0], "-"); 523 | } 524 | _ => panic!(), 525 | } 526 | } 527 | 528 | #[test] 529 | fn test_unrecognized_option() { 530 | let long_args = vec!["--untest".to_string()]; 531 | let mut opts = Options::new(); 532 | opts.optmulti("t", "test", "testing", "TEST"); 533 | match opts.parse(&long_args) { 534 | Err(UnrecognizedOption(_)) => {} 535 | _ => panic!(), 536 | } 537 | let short_args = vec!["-u".to_string()]; 538 | match opts.parse(&short_args) { 539 | Err(UnrecognizedOption(_)) => {} 540 | _ => panic!(), 541 | } 542 | } 543 | 544 | #[test] 545 | fn test_combined() { 546 | let args = vec![ 547 | "prog".to_string(), 548 | "free1".to_string(), 549 | "-s".to_string(), 550 | "20".to_string(), 551 | "free2".to_string(), 552 | "--flag".to_string(), 553 | "--long=30".to_string(), 554 | "-f".to_string(), 555 | "-m".to_string(), 556 | "40".to_string(), 557 | "-m".to_string(), 558 | "50".to_string(), 559 | "-n".to_string(), 560 | "-A B".to_string(), 561 | "-n".to_string(), 562 | "-60 70".to_string(), 563 | ]; 564 | match Options::new() 565 | .optopt("s", "something", "something", "SOMETHING") 566 | .optflag("", "flag", "a flag") 567 | .reqopt("", "long", "hi", "LONG") 568 | .optflag("f", "", "another flag") 569 | .optmulti("m", "", "mmmmmm", "YUM") 570 | .optmulti("n", "", "nothing", "NOTHING") 571 | .optopt("", "notpresent", "nothing to see here", "NOPE") 572 | .parse(&args) 573 | { 574 | Ok(ref m) => { 575 | assert!(m.free[0] == "prog"); 576 | assert!(m.free[1] == "free1"); 577 | assert_eq!(m.opt_str("s").unwrap(), "20"); 578 | assert!(m.free[2] == "free2"); 579 | assert!(m.opt_present("flag")); 580 | assert_eq!(m.opt_str("long").unwrap(), "30"); 581 | assert!(m.opt_present("f")); 582 | let pair = m.opt_strs("m"); 583 | assert!(pair[0] == "40"); 584 | assert!(pair[1] == "50"); 585 | let pair = m.opt_strs("n"); 586 | assert!(pair[0] == "-A B"); 587 | assert!(pair[1] == "-60 70"); 588 | assert!(!m.opt_present("notpresent")); 589 | } 590 | _ => panic!(), 591 | } 592 | } 593 | 594 | #[test] 595 | fn test_mixed_stop() { 596 | let args = vec![ 597 | "-a".to_string(), 598 | "b".to_string(), 599 | "-c".to_string(), 600 | "d".to_string(), 601 | ]; 602 | match Options::new() 603 | .parsing_style(ParsingStyle::StopAtFirstFree) 604 | .optflag("a", "", "") 605 | .optopt("c", "", "", "") 606 | .parse(&args) 607 | { 608 | Ok(ref m) => { 609 | println!("{}", m.opt_present("c")); 610 | assert!(m.opt_present("a")); 611 | assert!(!m.opt_present("c")); 612 | assert_eq!(m.free.len(), 3); 613 | assert_eq!(m.free[0], "b"); 614 | assert_eq!(m.free[1], "-c"); 615 | assert_eq!(m.free[2], "d"); 616 | } 617 | _ => panic!(), 618 | } 619 | } 620 | 621 | #[test] 622 | fn test_mixed_stop_hyphen() { 623 | let args = vec![ 624 | "-a".to_string(), 625 | "-".to_string(), 626 | "-c".to_string(), 627 | "d".to_string(), 628 | ]; 629 | match Options::new() 630 | .parsing_style(ParsingStyle::StopAtFirstFree) 631 | .optflag("a", "", "") 632 | .optopt("c", "", "", "") 633 | .parse(&args) 634 | { 635 | Ok(ref m) => { 636 | println!("{}", m.opt_present("c")); 637 | assert!(m.opt_present("a")); 638 | assert!(!m.opt_present("c")); 639 | assert_eq!(m.free.len(), 3); 640 | assert_eq!(m.free[0], "-"); 641 | assert_eq!(m.free[1], "-c"); 642 | assert_eq!(m.free[2], "d"); 643 | } 644 | _ => panic!(), 645 | } 646 | } 647 | 648 | #[test] 649 | fn test_multi() { 650 | let mut opts = Options::new(); 651 | opts.optopt("e", "", "encrypt", "ENCRYPT"); 652 | opts.optopt("", "encrypt", "encrypt", "ENCRYPT"); 653 | opts.optopt("f", "", "flag", "FLAG"); 654 | let no_opts: &[&str] = &[]; 655 | 656 | let args_single = vec!["-e".to_string(), "foo".to_string()]; 657 | let matches_single = &match opts.parse(&args_single) { 658 | Ok(m) => m, 659 | _ => panic!(), 660 | }; 661 | assert!(matches_single.opts_present(&["e".to_string()])); 662 | assert!(matches_single.opts_present(&["encrypt".to_string(), "e".to_string()])); 663 | assert!(matches_single.opts_present(&["e".to_string(), "encrypt".to_string()])); 664 | assert!(!matches_single.opts_present(&["encrypt".to_string()])); 665 | assert!(!matches_single.opts_present(&["thing".to_string()])); 666 | assert!(!matches_single.opts_present(&[])); 667 | 668 | assert!(matches_single.opts_present_any(&["e"])); 669 | assert!(matches_single.opts_present_any(&["encrypt", "e"])); 670 | assert!(matches_single.opts_present_any(&["e", "encrypt"])); 671 | assert!(!matches_single.opts_present_any(&["encrypt"])); 672 | assert!(!matches_single.opts_present_any(no_opts)); 673 | 674 | assert_eq!(matches_single.opts_str(&["e".to_string()]).unwrap(), "foo"); 675 | assert_eq!( 676 | matches_single 677 | .opts_str(&["e".to_string(), "encrypt".to_string()]) 678 | .unwrap(), 679 | "foo" 680 | ); 681 | assert_eq!( 682 | matches_single 683 | .opts_str(&["encrypt".to_string(), "e".to_string()]) 684 | .unwrap(), 685 | "foo" 686 | ); 687 | 688 | assert_eq!(matches_single.opts_str_first(&["e"]).unwrap(), "foo"); 689 | assert_eq!( 690 | matches_single.opts_str_first(&["e", "encrypt"]).unwrap(), 691 | "foo" 692 | ); 693 | assert_eq!( 694 | matches_single.opts_str_first(&["encrypt", "e"]).unwrap(), 695 | "foo" 696 | ); 697 | assert_eq!(matches_single.opts_str_first(&["encrypt"]), None); 698 | assert_eq!(matches_single.opts_str_first(no_opts), None); 699 | 700 | let args_both = vec![ 701 | "-e".to_string(), 702 | "foo".to_string(), 703 | "--encrypt".to_string(), 704 | "bar".to_string(), 705 | ]; 706 | let matches_both = &match opts.parse(&args_both) { 707 | Ok(m) => m, 708 | _ => panic!(), 709 | }; 710 | assert!(matches_both.opts_present(&["e".to_string()])); 711 | assert!(matches_both.opts_present(&["encrypt".to_string()])); 712 | assert!(matches_both.opts_present(&["encrypt".to_string(), "e".to_string()])); 713 | assert!(matches_both.opts_present(&["e".to_string(), "encrypt".to_string()])); 714 | assert!(!matches_both.opts_present(&["f".to_string()])); 715 | assert!(!matches_both.opts_present(&["thing".to_string()])); 716 | assert!(!matches_both.opts_present(&[])); 717 | 718 | assert!(matches_both.opts_present_any(&["e"])); 719 | assert!(matches_both.opts_present_any(&["encrypt"])); 720 | assert!(matches_both.opts_present_any(&["encrypt", "e"])); 721 | assert!(matches_both.opts_present_any(&["e", "encrypt"])); 722 | assert!(!matches_both.opts_present_any(&["f"])); 723 | assert!(!matches_both.opts_present_any(no_opts)); 724 | 725 | assert_eq!(matches_both.opts_str(&["e".to_string()]).unwrap(), "foo"); 726 | assert_eq!( 727 | matches_both.opts_str(&["encrypt".to_string()]).unwrap(), 728 | "bar" 729 | ); 730 | assert_eq!( 731 | matches_both 732 | .opts_str(&["e".to_string(), "encrypt".to_string()]) 733 | .unwrap(), 734 | "foo" 735 | ); 736 | assert_eq!( 737 | matches_both 738 | .opts_str(&["encrypt".to_string(), "e".to_string()]) 739 | .unwrap(), 740 | "bar" 741 | ); 742 | 743 | assert_eq!(matches_both.opts_str_first(&["e"]).unwrap(), "foo"); 744 | assert_eq!(matches_both.opts_str_first(&["encrypt"]).unwrap(), "bar"); 745 | assert_eq!( 746 | matches_both.opts_str_first(&["e", "encrypt"]).unwrap(), 747 | "foo" 748 | ); 749 | assert_eq!( 750 | matches_both.opts_str_first(&["encrypt", "e"]).unwrap(), 751 | "bar" 752 | ); 753 | assert_eq!(matches_both.opts_str_first(&["f"]), None); 754 | assert_eq!(matches_both.opts_str_first(no_opts), None); 755 | } 756 | 757 | #[test] 758 | fn test_nospace() { 759 | let args = vec!["-Lfoo".to_string(), "-M.".to_string()]; 760 | let matches = &match Options::new() 761 | .optmulti("L", "", "library directory", "LIB") 762 | .optmulti("M", "", "something", "MMMM") 763 | .parse(&args) 764 | { 765 | Ok(m) => m, 766 | _ => panic!(), 767 | }; 768 | assert!(matches.opts_present(&["L".to_string()])); 769 | assert_eq!(matches.opts_str(&["L".to_string()]).unwrap(), "foo"); 770 | assert!(matches.opts_present(&["M".to_string()])); 771 | assert_eq!(matches.opts_str(&["M".to_string()]).unwrap(), "."); 772 | } 773 | 774 | #[test] 775 | fn test_nospace_conflict() { 776 | let args = vec!["-vvLverbose".to_string(), "-v".to_string()]; 777 | let matches = &match Options::new() 778 | .optmulti("L", "", "library directory", "LIB") 779 | .optflagmulti("v", "verbose", "Verbose") 780 | .parse(&args) 781 | { 782 | Ok(m) => m, 783 | Err(e) => panic!("{}", e), 784 | }; 785 | assert!(matches.opts_present(&["L".to_string()])); 786 | assert_eq!(matches.opts_str(&["L".to_string()]).unwrap(), "verbose"); 787 | assert!(matches.opts_present(&["v".to_string()])); 788 | assert_eq!(3, matches.opt_count("v")); 789 | } 790 | 791 | #[test] 792 | fn test_long_to_short() { 793 | let mut short = Opt { 794 | name: Name::Long("banana".to_string()), 795 | hasarg: HasArg::Yes, 796 | occur: Occur::Req, 797 | aliases: Vec::new(), 798 | }; 799 | short.aliases = vec![Opt { 800 | name: Name::Short('b'), 801 | hasarg: HasArg::Yes, 802 | occur: Occur::Req, 803 | aliases: Vec::new(), 804 | }]; 805 | let mut opts = Options::new(); 806 | opts.reqopt("b", "banana", "some bananas", "VAL"); 807 | let verbose = &opts.grps[0]; 808 | assert!(verbose.long_to_short() == short); 809 | } 810 | 811 | #[test] 812 | fn test_aliases_long_and_short() { 813 | let args = vec!["-a".to_string(), "--apple".to_string(), "-a".to_string()]; 814 | 815 | let matches = Options::new() 816 | .optflagmulti("a", "apple", "Desc") 817 | .parse(&args) 818 | .unwrap(); 819 | assert_eq!(3, matches.opt_count("a")); 820 | assert_eq!(3, matches.opt_count("apple")); 821 | } 822 | 823 | #[test] 824 | fn test_usage() { 825 | let mut opts = Options::new(); 826 | opts.reqopt("b", "banana", "Desc", "VAL"); 827 | opts.optopt("a", "012345678901234567890123456789", "Desc", "VAL"); 828 | opts.optflag("k", "kiwi", "Desc"); 829 | opts.optflagopt("p", "", "Desc", "VAL"); 830 | opts.optmulti("l", "", "Desc", "VAL"); 831 | opts.optflag("", "starfruit", "Starfruit"); 832 | 833 | let expected = "Usage: fruits 834 | 835 | Options: 836 | -b, --banana VAL Desc 837 | -a, --012345678901234567890123456789 VAL 838 | Desc 839 | -k, --kiwi Desc 840 | -p [VAL] Desc 841 | -l VAL Desc 842 | --starfruit Starfruit 843 | "; 844 | 845 | let generated_usage = opts.usage("Usage: fruits"); 846 | 847 | debug!("expected: <<{}>>", expected); 848 | debug!("generated: <<{}>>", generated_usage); 849 | assert_eq!(generated_usage, expected); 850 | } 851 | 852 | #[test] 853 | fn test_usage_description_wrapping() { 854 | // indentation should be 24 spaces 855 | // lines wrap after 78: or rather descriptions wrap after 54 856 | 857 | let mut opts = Options::new(); 858 | opts.optflag( 859 | "k", 860 | "kiwi", 861 | "This is a long description which won't be wrapped..+..", 862 | ); // 54 863 | opts.optflag( 864 | "a", 865 | "apple", 866 | "This is a long description which _will_ be wrapped..+..", 867 | ); 868 | opts.optflag( 869 | "b", 870 | "banana", 871 | "HereWeNeedOneSingleWordThatIsLongerThanTheWrappingLengthAndThisIsIt", 872 | ); 873 | 874 | let expected = "Usage: fruits 875 | 876 | Options: 877 | -k, --kiwi This is a long description which won't be wrapped..+.. 878 | -a, --apple This is a long description which _will_ be 879 | wrapped..+.. 880 | -b, --banana HereWeNeedOneSingleWordThatIsLongerThanTheWrappingLengthAndThisIsIt 881 | "; 882 | 883 | let usage = opts.usage("Usage: fruits"); 884 | 885 | debug!("expected: <<{}>>", expected); 886 | debug!("generated: <<{}>>", usage); 887 | assert!(usage == expected) 888 | } 889 | 890 | #[test] 891 | #[cfg(feature = "unicode")] 892 | fn test_usage_description_multibyte_handling() { 893 | let mut opts = Options::new(); 894 | opts.optflag( 895 | "k", 896 | "k\u{2013}w\u{2013}", 897 | "The word kiwi is normally spelled with two i's", 898 | ); 899 | opts.optflag( 900 | "a", 901 | "apple", 902 | "This \u{201C}description\u{201D} has some characters that could \ 903 | confuse the line wrapping; an apple costs 0.51€ in some parts of Europe.", 904 | ); 905 | 906 | let expected = "Usage: fruits 907 | 908 | Options: 909 | -k, --k–w– The word kiwi is normally spelled with two i's 910 | -a, --apple This “description” has some characters that could 911 | confuse the line wrapping; an apple costs 0.51€ in 912 | some parts of Europe. 913 | "; 914 | 915 | let usage = opts.usage("Usage: fruits"); 916 | 917 | debug!("expected: <<{}>>", expected); 918 | debug!("generated: <<{}>>", usage); 919 | assert!(usage == expected) 920 | } 921 | 922 | #[test] 923 | #[cfg(feature = "unicode")] 924 | fn test_usage_description_newline_handling() { 925 | let mut opts = Options::new(); 926 | opts.optflag( 927 | "k", 928 | "k\u{2013}w\u{2013}", 929 | "The word kiwi is normally spelled with two i's", 930 | ); 931 | opts.optflag( 932 | "a", 933 | "apple", 934 | "This description forces a new line.\n Here is a premature\n\ 935 | newline", 936 | ); 937 | 938 | let expected = "Usage: fruits 939 | 940 | Options: 941 | -k, --k–w– The word kiwi is normally spelled with two i's 942 | -a, --apple This description forces a new line. 943 | Here is a premature 944 | newline 945 | "; 946 | 947 | let usage = opts.usage("Usage: fruits"); 948 | 949 | debug!("expected: <<{}>>", expected); 950 | debug!("generated: <<{}>>", usage); 951 | assert!(usage == expected) 952 | } 953 | 954 | #[test] 955 | #[cfg(feature = "unicode")] 956 | fn test_usage_multiwidth() { 957 | let mut opts = Options::new(); 958 | opts.optflag("a", "apple", "apple description"); 959 | opts.optflag("b", "banana\u{00AB}", "banana description"); 960 | opts.optflag("c", "brûlée", "brûlée quite long description"); 961 | opts.optflag("k", "kiwi\u{20AC}", "kiwi description"); 962 | opts.optflag("o", "orange\u{2039}", "orange description"); 963 | opts.optflag( 964 | "r", 965 | "raspberry-but-making-this-option-way-too-long", 966 | "raspberry description is also quite long indeed longer than \ 967 | every other piece of text we might encounter here and thus will \ 968 | be automatically broken up", 969 | ); 970 | 971 | let expected = "Usage: fruits 972 | 973 | Options: 974 | -a, --apple apple description 975 | -b, --banana« banana description 976 | -c, --brûlée brûlée quite long description 977 | -k, --kiwi€ kiwi description 978 | -o, --orange‹ orange description 979 | -r, --raspberry-but-making-this-option-way-too-long\u{0020} 980 | raspberry description is also quite long indeed longer 981 | than every other piece of text we might encounter here 982 | and thus will be automatically broken up 983 | "; 984 | 985 | let usage = opts.usage("Usage: fruits"); 986 | 987 | debug!("expected: <<{}>>", expected); 988 | debug!("generated: <<{}>>", usage); 989 | assert!(usage == expected) 990 | } 991 | 992 | #[test] 993 | fn test_usage_short_only() { 994 | let mut opts = Options::new(); 995 | opts.optopt("k", "", "Kiwi", "VAL"); 996 | opts.optflag("s", "", "Starfruit"); 997 | opts.optflagopt("a", "", "Apple", "TYPE"); 998 | 999 | let expected = "Usage: fruits 1000 | 1001 | Options: 1002 | -k VAL Kiwi 1003 | -s Starfruit 1004 | -a [TYPE] Apple 1005 | "; 1006 | 1007 | let usage = opts.usage("Usage: fruits"); 1008 | debug!("expected: <<{}>>", expected); 1009 | debug!("generated: <<{}>>", usage); 1010 | assert!(usage == expected) 1011 | } 1012 | 1013 | #[test] 1014 | fn test_usage_long_only() { 1015 | let mut opts = Options::new(); 1016 | opts.optopt("", "kiwi", "Kiwi", "VAL"); 1017 | opts.optflag("", "starfruit", "Starfruit"); 1018 | opts.optflagopt("", "apple", "Apple", "TYPE"); 1019 | 1020 | let expected = "Usage: fruits 1021 | 1022 | Options: 1023 | --kiwi VAL Kiwi 1024 | --starfruit Starfruit 1025 | --apple [TYPE] Apple 1026 | "; 1027 | 1028 | let usage = opts.usage("Usage: fruits"); 1029 | debug!("expected: <<{}>>", expected); 1030 | debug!("generated: <<{}>>", usage); 1031 | assert!(usage == expected) 1032 | } 1033 | 1034 | #[test] 1035 | fn test_short_usage() { 1036 | let mut opts = Options::new(); 1037 | opts.reqopt("b", "banana", "Desc", "VAL"); 1038 | opts.optopt("a", "012345678901234567890123456789", "Desc", "VAL"); 1039 | opts.optflag("k", "kiwi", "Desc"); 1040 | opts.optflagopt("p", "", "Desc", "VAL"); 1041 | opts.optmulti("l", "", "Desc", "VAL"); 1042 | 1043 | let expected = "Usage: fruits -b VAL [-a VAL] [-k] [-p [VAL]] [-l VAL]..".to_string(); 1044 | let generated_usage = opts.short_usage("fruits"); 1045 | 1046 | debug!("expected: <<{}>>", expected); 1047 | debug!("generated: <<{}>>", generated_usage); 1048 | assert_eq!(generated_usage, expected); 1049 | } 1050 | #[test] 1051 | fn test_nonexistant_opt() { 1052 | let mut opts = Options::new(); 1053 | opts.optflag("b", "bar", "Desc"); 1054 | let args: Vec = Vec::new(); 1055 | let matches = opts.parse(&args).unwrap(); 1056 | assert_eq!(matches.opt_defined("foo"), false); 1057 | assert_eq!(matches.opt_defined("bar"), true); 1058 | } 1059 | #[test] 1060 | fn test_args_with_equals() { 1061 | let mut opts = Options::new(); 1062 | opts.optopt("o", "one", "One", "INFO"); 1063 | opts.optopt("t", "two", "Two", "INFO"); 1064 | 1065 | let args = vec![ 1066 | "--one".to_string(), 1067 | "A=B".to_string(), 1068 | "--two=C=D".to_string(), 1069 | ]; 1070 | let matches = &match opts.parse(&args) { 1071 | Ok(m) => m, 1072 | Err(e) => panic!("{}", e), 1073 | }; 1074 | assert_eq!(matches.opts_str(&["o".to_string()]).unwrap(), "A=B"); 1075 | assert_eq!(matches.opts_str(&["t".to_string()]).unwrap(), "C=D"); 1076 | } 1077 | 1078 | #[test] 1079 | fn test_long_only_usage() { 1080 | let mut opts = Options::new(); 1081 | opts.long_only(true); 1082 | opts.optflag("k", "kiwi", "Description"); 1083 | opts.optflag("a", "apple", "Description"); 1084 | 1085 | let expected = "Usage: fruits 1086 | 1087 | Options: 1088 | -k, -kiwi Description 1089 | -a, -apple Description 1090 | "; 1091 | 1092 | let usage = opts.usage("Usage: fruits"); 1093 | 1094 | debug!("expected: <<{}>>", expected); 1095 | debug!("generated: <<{}>>", usage); 1096 | assert!(usage == expected) 1097 | } 1098 | 1099 | #[test] 1100 | fn test_long_only_mode() { 1101 | let mut opts = Options::new(); 1102 | opts.long_only(true); 1103 | opts.optopt("a", "apple", "Description", "X"); 1104 | opts.optopt("b", "banana", "Description", "X"); 1105 | opts.optopt("c", "currant", "Description", "X"); 1106 | opts.optopt("", "durian", "Description", "X"); 1107 | opts.optopt("e", "", "Description", "X"); 1108 | opts.optopt("", "fruit", "Description", "X"); 1109 | 1110 | let args = vec![ 1111 | "-a", 1112 | "A", 1113 | "-b=B", 1114 | "--c=C", 1115 | "-durian", 1116 | "D", 1117 | "--e", 1118 | "E", 1119 | "-fruit=any", 1120 | ]; 1121 | let matches = &match opts.parse(&args) { 1122 | Ok(m) => m, 1123 | Err(e) => panic!("{}", e), 1124 | }; 1125 | assert_eq!(matches.opts_str(&["a".to_string()]).unwrap(), "A"); 1126 | assert_eq!(matches.opts_str(&["b".to_string()]).unwrap(), "B"); 1127 | assert_eq!(matches.opts_str(&["c".to_string()]).unwrap(), "C"); 1128 | assert_eq!(matches.opts_str(&["durian".to_string()]).unwrap(), "D"); 1129 | assert_eq!(matches.opts_str(&["e".to_string()]).unwrap(), "E"); 1130 | assert_eq!(matches.opts_str(&["fruit".to_string()]).unwrap(), "any"); 1131 | } 1132 | 1133 | #[test] 1134 | fn test_long_only_mode_no_short_parse() { 1135 | let mut opts = Options::new(); 1136 | opts.long_only(true); 1137 | opts.optflag("h", "help", "Description"); 1138 | opts.optflag("i", "ignore", "Description"); 1139 | opts.optflag("", "hi", "Description"); 1140 | 1141 | let args = vec!["-hi"]; 1142 | let matches = &match opts.parse(&args) { 1143 | Ok(m) => m, 1144 | Err(e) => panic!("{}", e), 1145 | }; 1146 | assert!(matches.opt_present("hi")); 1147 | assert!(!matches.opt_present("h")); 1148 | assert!(!matches.opt_present("i")); 1149 | } 1150 | 1151 | #[test] 1152 | fn test_normal_mode_no_long_parse() { 1153 | // Like test_long_only_mode_no_short_parse, but we make sure 1154 | // that long_only can be disabled, and the right thing 1155 | // happens. 1156 | let mut opts = Options::new(); 1157 | opts.long_only(true); 1158 | opts.optflag("h", "help", "Description"); 1159 | opts.optflag("i", "ignore", "Description"); 1160 | opts.optflag("", "hi", "Description"); 1161 | opts.long_only(false); 1162 | 1163 | let args = vec!["-hi"]; 1164 | let matches = &match opts.parse(&args) { 1165 | Ok(m) => m, 1166 | Err(e) => panic!("{}", e), 1167 | }; 1168 | assert!(!matches.opt_present("hi")); 1169 | assert!(matches.opt_present("h")); 1170 | assert!(matches.opt_present("i")); 1171 | } 1172 | 1173 | #[test] 1174 | #[should_panic] 1175 | fn test_long_name_too_short() { 1176 | let mut opts = Options::new(); 1177 | opts.optflag("", "a", "Oops, long option too short"); 1178 | } 1179 | 1180 | #[test] 1181 | #[should_panic] 1182 | fn test_undefined_opt_present() { 1183 | let mut opts = Options::new(); 1184 | opts.optflag("h", "help", "Description"); 1185 | let args = vec!["-h"]; 1186 | match opts.parse(args) { 1187 | Ok(matches) => assert!(!matches.opt_present("undefined")), 1188 | Err(e) => panic!("{}", e), 1189 | } 1190 | } 1191 | 1192 | #[test] 1193 | fn test_opt_default() { 1194 | let mut opts = Options::new(); 1195 | opts.optflag("h", "help", "Description"); 1196 | opts.optflag("i", "ignore", "Description"); 1197 | opts.optflag("r", "run", "Description"); 1198 | opts.long_only(false); 1199 | 1200 | let args: Vec = ["-i", "-r", "10"].iter().map(|x| x.to_string()).collect(); 1201 | let matches = &match opts.parse(&args) { 1202 | Ok(m) => m, 1203 | Err(e) => panic!("{}", e), 1204 | }; 1205 | assert_eq!(matches.opt_default("help", ""), None); 1206 | assert_eq!(matches.opt_default("i", "def"), Some("def".to_string())); 1207 | } 1208 | 1209 | #[test] 1210 | fn test_opt_get() { 1211 | let mut opts = Options::new(); 1212 | opts.optflag("h", "help", "Description"); 1213 | opts.optflagopt("i", "ignore", "Description", "true | false"); 1214 | opts.optflagopt("r", "run", "Description", "0 .. 10"); 1215 | opts.optflagopt("p", "percent", "Description", "0.0 .. 10.0"); 1216 | opts.long_only(false); 1217 | 1218 | let args: Vec = ["-i", "true", "-p", "1.1"] 1219 | .iter() 1220 | .map(|x| x.to_string()) 1221 | .collect(); 1222 | let matches = &match opts.parse(&args) { 1223 | Ok(m) => m, 1224 | Err(e) => panic!("{}", e), 1225 | }; 1226 | let h_arg = matches.opt_get::("help"); 1227 | assert_eq!(h_arg, Ok(None)); 1228 | let i_arg = matches.opt_get("i"); 1229 | assert_eq!(i_arg, Ok(Some(true))); 1230 | let p_arg = matches.opt_get("p"); 1231 | assert_eq!(p_arg, Ok(Some(1.1))); 1232 | } 1233 | 1234 | #[test] 1235 | fn test_opt_get_default() { 1236 | let mut opts = Options::new(); 1237 | opts.optflag("h", "help", "Description"); 1238 | opts.optflagopt("i", "ignore", "Description", "true | false"); 1239 | opts.optflagopt("r", "run", "Description", "0 .. 10"); 1240 | opts.optflagopt("p", "percent", "Description", "0.0 .. 10.0"); 1241 | opts.long_only(false); 1242 | 1243 | let args: Vec = ["-i", "true", "-p", "1.1"] 1244 | .iter() 1245 | .map(|x| x.to_string()) 1246 | .collect(); 1247 | let matches = &match opts.parse(&args) { 1248 | Ok(m) => m, 1249 | Err(e) => panic!("{}", e), 1250 | }; 1251 | let h_arg = matches.opt_get_default("help", 10); 1252 | assert_eq!(h_arg, Ok(10)); 1253 | let i_arg = matches.opt_get_default("i", false); 1254 | assert_eq!(i_arg, Ok(true)); 1255 | let p_arg = matches.opt_get_default("p", 10.2); 1256 | assert_eq!(p_arg, Ok(1.1)); 1257 | } 1258 | 1259 | #[test] 1260 | fn test_opt_positions() { 1261 | let mut opts = Options::new(); 1262 | opts.optflagmulti("a", "act", "Description"); 1263 | opts.optflagmulti("e", "enact", "Description"); 1264 | opts.optflagmulti("r", "react", "Description"); 1265 | 1266 | let args: Vec = ["-a", "-a", "-r", "-a", "-r", "-r"] 1267 | .iter() 1268 | .map(|x| x.to_string()) 1269 | .collect(); 1270 | 1271 | let matches = &match opts.parse(&args) { 1272 | Ok(m) => m, 1273 | Err(e) => panic!("{}", e), 1274 | }; 1275 | 1276 | let a_pos = matches.opt_positions("a"); 1277 | assert_eq!(a_pos, vec![0, 1, 3]); 1278 | let e_pos = matches.opt_positions("e"); 1279 | assert_eq!(e_pos, vec![]); 1280 | let r_pos = matches.opt_positions("r"); 1281 | assert_eq!(r_pos, vec![2, 4, 5]); 1282 | } 1283 | 1284 | #[test] 1285 | fn test_opt_strs_pos() { 1286 | let mut opts = Options::new(); 1287 | opts.optmulti("a", "act", "Description", "NUM"); 1288 | opts.optmulti("e", "enact", "Description", "NUM"); 1289 | opts.optmulti("r", "react", "Description", "NUM"); 1290 | 1291 | let args: Vec = ["-a1", "-a2", "-r3", "-a4", "-r5", "-r6"] 1292 | .iter() 1293 | .map(|x| x.to_string()) 1294 | .collect(); 1295 | 1296 | let matches = &match opts.parse(&args) { 1297 | Ok(m) => m, 1298 | Err(e) => panic!("{}", e), 1299 | }; 1300 | 1301 | let a_pos = matches.opt_strs_pos("a"); 1302 | assert_eq!( 1303 | a_pos, 1304 | vec![ 1305 | (0, "1".to_string()), 1306 | (1, "2".to_string()), 1307 | (3, "4".to_string()) 1308 | ] 1309 | ); 1310 | let e_pos = matches.opt_strs_pos("e"); 1311 | assert_eq!(e_pos, vec![]); 1312 | let r_pos = matches.opt_strs_pos("r"); 1313 | assert_eq!( 1314 | r_pos, 1315 | vec![ 1316 | (2, "3".to_string()), 1317 | (4, "5".to_string()), 1318 | (5, "6".to_string()) 1319 | ] 1320 | ); 1321 | } 1322 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | // 11 | // ignore-lexer-test FIXME #15677 12 | 13 | //! Simple getopt alternative. 14 | //! 15 | //! Construct instance of `Options` and configure it by using `reqopt()`, 16 | //! `optopt()` and other methods that add option configuration. Then call 17 | //! `parse()` method and pass into it a vector of actual arguments (not 18 | //! including `argv[0]`). 19 | //! 20 | //! You'll either get a failure code back, or a match. You'll have to verify 21 | //! whether the amount of 'free' arguments in the match is what you expect. Use 22 | //! `opt_*` accessors to get argument values out of the matches object. 23 | //! 24 | //! Single-character options are expected to appear on the command line with a 25 | //! single preceding dash; multiple-character options are expected to be 26 | //! proceeded by two dashes. Options that expect an argument accept their 27 | //! argument following either a space or an equals sign. Single-character 28 | //! options don't require the space. Everything after double-dash "--" argument 29 | //! is considered to be a 'free' argument, even if it starts with dash. 30 | //! 31 | //! # Usage 32 | //! 33 | //! This crate is [on crates.io](https://crates.io/crates/getopts) and can be 34 | //! used by adding `getopts` to the dependencies in your project's `Cargo.toml`. 35 | //! 36 | //! ```toml 37 | //! [dependencies] 38 | //! getopts = "0.2" 39 | //! ``` 40 | //! 41 | //! and this to your crate root: 42 | //! 43 | //! ```rust 44 | //! extern crate getopts; 45 | //! ``` 46 | //! 47 | //! # Example 48 | //! 49 | //! The following example shows simple command line parsing for an application 50 | //! that requires an input file to be specified, accepts an optional output file 51 | //! name following `-o`, and accepts both `-h` and `--help` as optional flags. 52 | //! 53 | //! ```{.rust} 54 | //! extern crate getopts; 55 | //! use getopts::Options; 56 | //! use std::env; 57 | //! 58 | //! fn do_work(inp: &str, out: Option) { 59 | //! println!("{}", inp); 60 | //! match out { 61 | //! Some(x) => println!("{}", x), 62 | //! None => println!("No Output"), 63 | //! } 64 | //! } 65 | //! 66 | //! fn print_usage(program: &str, opts: Options) { 67 | //! let brief = format!("Usage: {} FILE [options]", program); 68 | //! print!("{}", opts.usage(&brief)); 69 | //! } 70 | //! 71 | //! fn main() { 72 | //! let args: Vec = env::args().collect(); 73 | //! let program = args[0].clone(); 74 | //! 75 | //! let mut opts = Options::new(); 76 | //! opts.optopt("o", "", "set output file name", "NAME"); 77 | //! opts.optflag("h", "help", "print this help menu"); 78 | //! let matches = match opts.parse(&args[1..]) { 79 | //! Ok(m) => { m } 80 | //! Err(f) => { panic!("{}", f.to_string()) } 81 | //! }; 82 | //! if matches.opt_present("h") { 83 | //! print_usage(&program, opts); 84 | //! return; 85 | //! } 86 | //! let output = matches.opt_str("o"); 87 | //! let input = if !matches.free.is_empty() { 88 | //! matches.free[0].clone() 89 | //! } else { 90 | //! print_usage(&program, opts); 91 | //! return; 92 | //! }; 93 | //! do_work(&input, output); 94 | //! } 95 | //! ``` 96 | 97 | #![doc( 98 | html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", 99 | html_favicon_url = "https://www.rust-lang.org/favicon.ico" 100 | )] 101 | #![deny(missing_docs)] 102 | #![cfg_attr(test, deny(warnings))] 103 | 104 | #[cfg(test)] 105 | #[macro_use] 106 | extern crate log; 107 | 108 | use self::Fail::*; 109 | use self::HasArg::*; 110 | use self::Name::*; 111 | use self::Occur::*; 112 | use self::Optval::*; 113 | 114 | use std::error::Error; 115 | use std::ffi::OsStr; 116 | use std::fmt; 117 | use std::iter::{repeat, IntoIterator}; 118 | use std::result; 119 | use std::str::FromStr; 120 | 121 | #[cfg(feature = "unicode")] 122 | use unicode_width::UnicodeWidthStr; 123 | 124 | #[cfg(not(feature = "unicode"))] 125 | trait UnicodeWidthStr { 126 | fn width(&self) -> usize; 127 | } 128 | 129 | #[cfg(not(feature = "unicode"))] 130 | impl UnicodeWidthStr for str { 131 | fn width(&self) -> usize { 132 | self.len() 133 | } 134 | } 135 | 136 | #[cfg(test)] 137 | mod tests; 138 | 139 | /// A description of the options that a program can handle. 140 | #[derive(Debug, Clone, PartialEq, Eq)] 141 | pub struct Options { 142 | grps: Vec, 143 | parsing_style: ParsingStyle, 144 | long_only: bool, 145 | } 146 | 147 | impl Default for Options { 148 | fn default() -> Self { 149 | Self::new() 150 | } 151 | } 152 | 153 | impl Options { 154 | /// Create a blank set of options. 155 | pub fn new() -> Options { 156 | Options { 157 | grps: Vec::new(), 158 | parsing_style: ParsingStyle::FloatingFrees, 159 | long_only: false, 160 | } 161 | } 162 | 163 | /// Set the parsing style. 164 | pub fn parsing_style(&mut self, style: ParsingStyle) -> &mut Options { 165 | self.parsing_style = style; 166 | self 167 | } 168 | 169 | /// Set or clear "long options only" mode. 170 | /// 171 | /// In "long options only" mode, short options cannot be clustered 172 | /// together, and long options can be given with either a single 173 | /// "-" or the customary "--". This mode also changes the meaning 174 | /// of "-a=b"; in the ordinary mode this will parse a short option 175 | /// "-a" with argument "=b"; whereas in long-options-only mode the 176 | /// argument will be simply "b". 177 | pub fn long_only(&mut self, long_only: bool) -> &mut Options { 178 | self.long_only = long_only; 179 | self 180 | } 181 | 182 | /// Create a generic option group, stating all parameters explicitly. 183 | pub fn opt( 184 | &mut self, 185 | short_name: &str, 186 | long_name: &str, 187 | desc: &str, 188 | hint: &str, 189 | hasarg: HasArg, 190 | occur: Occur, 191 | ) -> &mut Options { 192 | validate_names(short_name, long_name); 193 | self.grps.push(OptGroup { 194 | short_name: short_name.to_string(), 195 | long_name: long_name.to_string(), 196 | hint: hint.to_string(), 197 | desc: desc.to_string(), 198 | hasarg, 199 | occur, 200 | }); 201 | self 202 | } 203 | 204 | /// Create a long option that is optional and does not take an argument. 205 | /// 206 | /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none 207 | /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none 208 | /// * `desc` - Description for usage help 209 | /// 210 | /// # Example 211 | /// 212 | /// ``` 213 | /// # use getopts::Options; 214 | /// let mut opts = Options::new(); 215 | /// opts.optflag("h", "help", "help flag"); 216 | /// 217 | /// let matches = opts.parse(&["-h"]).unwrap(); 218 | /// assert!(matches.opt_present("h")); 219 | /// ``` 220 | pub fn optflag(&mut self, short_name: &str, long_name: &str, desc: &str) -> &mut Options { 221 | validate_names(short_name, long_name); 222 | self.grps.push(OptGroup { 223 | short_name: short_name.to_string(), 224 | long_name: long_name.to_string(), 225 | hint: "".to_string(), 226 | desc: desc.to_string(), 227 | hasarg: No, 228 | occur: Optional, 229 | }); 230 | self 231 | } 232 | 233 | /// Create a long option that can occur more than once and does not 234 | /// take an argument. 235 | /// 236 | /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none 237 | /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none 238 | /// * `desc` - Description for usage help 239 | /// 240 | /// # Example 241 | /// 242 | /// ``` 243 | /// # use getopts::Options; 244 | /// let mut opts = Options::new(); 245 | /// opts.optflagmulti("v", "verbose", "verbosity flag"); 246 | /// 247 | /// let matches = opts.parse(&["-v", "--verbose"]).unwrap(); 248 | /// assert_eq!(2, matches.opt_count("v")); 249 | /// ``` 250 | pub fn optflagmulti(&mut self, short_name: &str, long_name: &str, desc: &str) -> &mut Options { 251 | validate_names(short_name, long_name); 252 | self.grps.push(OptGroup { 253 | short_name: short_name.to_string(), 254 | long_name: long_name.to_string(), 255 | hint: "".to_string(), 256 | desc: desc.to_string(), 257 | hasarg: No, 258 | occur: Multi, 259 | }); 260 | self 261 | } 262 | 263 | /// Create a long option that is optional and takes an optional argument. 264 | /// 265 | /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none 266 | /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none 267 | /// * `desc` - Description for usage help 268 | /// * `hint` - Hint that is used in place of the argument in the usage help, 269 | /// e.g. `"FILE"` for a `-o FILE` option 270 | /// 271 | /// # Example 272 | /// 273 | /// ``` 274 | /// # use getopts::Options; 275 | /// let mut opts = Options::new(); 276 | /// opts.optflagopt("t", "text", "flag with optional argument", "TEXT"); 277 | /// 278 | /// let matches = opts.parse(&["--text"]).unwrap(); 279 | /// assert_eq!(None, matches.opt_str("text")); 280 | /// 281 | /// let matches = opts.parse(&["--text=foo"]).unwrap(); 282 | /// assert_eq!(Some("foo".to_owned()), matches.opt_str("text")); 283 | /// ``` 284 | pub fn optflagopt( 285 | &mut self, 286 | short_name: &str, 287 | long_name: &str, 288 | desc: &str, 289 | hint: &str, 290 | ) -> &mut Options { 291 | validate_names(short_name, long_name); 292 | self.grps.push(OptGroup { 293 | short_name: short_name.to_string(), 294 | long_name: long_name.to_string(), 295 | hint: hint.to_string(), 296 | desc: desc.to_string(), 297 | hasarg: Maybe, 298 | occur: Optional, 299 | }); 300 | self 301 | } 302 | 303 | /// Create a long option that is optional, takes an argument, and may occur 304 | /// multiple times. 305 | /// 306 | /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none 307 | /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none 308 | /// * `desc` - Description for usage help 309 | /// * `hint` - Hint that is used in place of the argument in the usage help, 310 | /// e.g. `"FILE"` for a `-o FILE` option 311 | /// 312 | /// # Example 313 | /// 314 | /// ``` 315 | /// # use getopts::Options; 316 | /// let mut opts = Options::new(); 317 | /// opts.optmulti("t", "text", "text option", "TEXT"); 318 | /// 319 | /// let matches = opts.parse(&["-t", "foo", "--text=bar"]).unwrap(); 320 | /// 321 | /// let values = matches.opt_strs("t"); 322 | /// assert_eq!(2, values.len()); 323 | /// assert_eq!("foo", values[0]); 324 | /// assert_eq!("bar", values[1]); 325 | /// ``` 326 | pub fn optmulti( 327 | &mut self, 328 | short_name: &str, 329 | long_name: &str, 330 | desc: &str, 331 | hint: &str, 332 | ) -> &mut Options { 333 | validate_names(short_name, long_name); 334 | self.grps.push(OptGroup { 335 | short_name: short_name.to_string(), 336 | long_name: long_name.to_string(), 337 | hint: hint.to_string(), 338 | desc: desc.to_string(), 339 | hasarg: Yes, 340 | occur: Multi, 341 | }); 342 | self 343 | } 344 | 345 | /// Create a long option that is optional and takes an argument. 346 | /// 347 | /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none 348 | /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none 349 | /// * `desc` - Description for usage help 350 | /// * `hint` - Hint that is used in place of the argument in the usage help, 351 | /// e.g. `"FILE"` for a `-o FILE` option 352 | /// 353 | /// # Example 354 | /// 355 | /// ``` 356 | /// # use getopts::Options; 357 | /// # use getopts::Fail; 358 | /// let mut opts = Options::new(); 359 | /// opts.optopt("o", "optional", "optional text option", "TEXT"); 360 | /// 361 | /// let matches = opts.parse(&["arg1"]).unwrap(); 362 | /// assert_eq!(None, matches.opt_str("optional")); 363 | /// 364 | /// let matches = opts.parse(&["--optional", "foo", "arg1"]).unwrap(); 365 | /// assert_eq!(Some("foo".to_owned()), matches.opt_str("optional")); 366 | /// ``` 367 | pub fn optopt( 368 | &mut self, 369 | short_name: &str, 370 | long_name: &str, 371 | desc: &str, 372 | hint: &str, 373 | ) -> &mut Options { 374 | validate_names(short_name, long_name); 375 | self.grps.push(OptGroup { 376 | short_name: short_name.to_string(), 377 | long_name: long_name.to_string(), 378 | hint: hint.to_string(), 379 | desc: desc.to_string(), 380 | hasarg: Yes, 381 | occur: Optional, 382 | }); 383 | self 384 | } 385 | 386 | /// Create a long option that is required and takes an argument. 387 | /// 388 | /// * `short_name` - e.g. `"h"` for a `-h` option, or `""` for none 389 | /// * `long_name` - e.g. `"help"` for a `--help` option, or `""` for none 390 | /// * `desc` - Description for usage help 391 | /// * `hint` - Hint that is used in place of the argument in the usage help, 392 | /// e.g. `"FILE"` for a `-o FILE` option 393 | /// 394 | /// # Example 395 | /// 396 | /// ``` 397 | /// # use getopts::Options; 398 | /// # use getopts::Fail; 399 | /// let mut opts = Options::new(); 400 | /// opts.optopt("o", "optional", "optional text option", "TEXT"); 401 | /// opts.reqopt("m", "mandatory", "madatory text option", "TEXT"); 402 | /// 403 | /// let result = opts.parse(&["--mandatory", "foo"]); 404 | /// assert!(result.is_ok()); 405 | /// 406 | /// let result = opts.parse(&["--optional", "foo"]); 407 | /// assert!(result.is_err()); 408 | /// assert_eq!(Fail::OptionMissing("mandatory".to_owned()), result.unwrap_err()); 409 | /// ``` 410 | pub fn reqopt( 411 | &mut self, 412 | short_name: &str, 413 | long_name: &str, 414 | desc: &str, 415 | hint: &str, 416 | ) -> &mut Options { 417 | validate_names(short_name, long_name); 418 | self.grps.push(OptGroup { 419 | short_name: short_name.to_string(), 420 | long_name: long_name.to_string(), 421 | hint: hint.to_string(), 422 | desc: desc.to_string(), 423 | hasarg: Yes, 424 | occur: Req, 425 | }); 426 | self 427 | } 428 | 429 | /// Parse command line arguments according to the provided options. 430 | /// 431 | /// On success returns `Ok(Matches)`. Use methods such as `opt_present` 432 | /// `opt_str`, etc. to interrogate results. 433 | /// # Panics 434 | /// 435 | /// Returns `Err(Fail)` on failure: use the `Debug` implementation of `Fail` 436 | /// to display information about it. 437 | pub fn parse(&self, args: C) -> Result 438 | where 439 | C::Item: AsRef, 440 | { 441 | let opts: Vec = self.grps.iter().map(|x| x.long_to_short()).collect(); 442 | 443 | let mut vals = (0..opts.len()) 444 | .map(|_| Vec::new()) 445 | .collect::>>(); 446 | let mut free: Vec = Vec::new(); 447 | let mut args_end = None; 448 | 449 | let args = args 450 | .into_iter() 451 | .map(|i| { 452 | i.as_ref() 453 | .to_str() 454 | .ok_or_else(|| Fail::UnrecognizedOption(format!("{:?}", i.as_ref()))) 455 | .map(|s| s.to_owned()) 456 | }) 457 | .collect::<::std::result::Result, _>>()?; 458 | let mut args = args.into_iter().peekable(); 459 | let mut arg_pos = 0; 460 | while let Some(cur) = args.next() { 461 | if !is_arg(&cur) { 462 | free.push(cur); 463 | match self.parsing_style { 464 | ParsingStyle::FloatingFrees => {} 465 | ParsingStyle::StopAtFirstFree => { 466 | free.extend(args); 467 | break; 468 | } 469 | } 470 | } else if cur == "--" { 471 | args_end = Some(free.len()); 472 | free.extend(args); 473 | break; 474 | } else { 475 | let mut name = None; 476 | let mut i_arg = None; 477 | let mut was_long = true; 478 | if cur.as_bytes()[1] == b'-' || self.long_only { 479 | let tail = if cur.as_bytes()[1] == b'-' { 480 | &cur[2..] 481 | } else { 482 | assert!(self.long_only); 483 | &cur[1..] 484 | }; 485 | let mut parts = tail.splitn(2, '='); 486 | name = Some(Name::from_str(parts.next().unwrap())); 487 | if let Some(rest) = parts.next() { 488 | i_arg = Some(rest.to_string()); 489 | } 490 | } else { 491 | was_long = false; 492 | for (j, ch) in cur.char_indices().skip(1) { 493 | let opt = Short(ch); 494 | 495 | let opt_id = match find_opt(&opts, &opt) { 496 | Some(id) => id, 497 | None => return Err(UnrecognizedOption(opt.to_string())), 498 | }; 499 | 500 | // In a series of potential options (eg. -aheJ), if we 501 | // see one which takes an argument, we assume all 502 | // subsequent characters make up the argument. This 503 | // allows options such as -L/usr/local/lib/foo to be 504 | // interpreted correctly 505 | let arg_follows = match opts[opt_id].hasarg { 506 | Yes | Maybe => true, 507 | No => false, 508 | }; 509 | 510 | if arg_follows { 511 | name = Some(opt); 512 | let next = j + ch.len_utf8(); 513 | if next < cur.len() { 514 | i_arg = Some(cur[next..].to_string()); 515 | break; 516 | } 517 | } else { 518 | vals[opt_id].push((arg_pos, Given)); 519 | } 520 | } 521 | } 522 | if let Some(nm) = name { 523 | let opt_id = match find_opt(&opts, &nm) { 524 | Some(id) => id, 525 | None => return Err(UnrecognizedOption(nm.to_string())), 526 | }; 527 | match opts[opt_id].hasarg { 528 | No => { 529 | if i_arg.is_some() { 530 | return Err(UnexpectedArgument(nm.to_string())); 531 | } 532 | vals[opt_id].push((arg_pos, Given)); 533 | } 534 | Maybe => { 535 | // Note that here we do not handle `--arg value`. 536 | // This matches GNU getopt behavior; but also 537 | // makes sense, because if this were accepted, 538 | // then users could only write a "Maybe" long 539 | // option at the end of the arguments when 540 | // FloatingFrees is in use. 541 | if let Some(i_arg) = i_arg.take() { 542 | vals[opt_id].push((arg_pos, Val(i_arg))); 543 | } else if was_long || args.peek().map_or(true, |n| is_arg(&n)) { 544 | vals[opt_id].push((arg_pos, Given)); 545 | } else { 546 | vals[opt_id].push((arg_pos, Val(args.next().unwrap()))); 547 | } 548 | } 549 | Yes => { 550 | if let Some(i_arg) = i_arg.take() { 551 | vals[opt_id].push((arg_pos, Val(i_arg))); 552 | } else if let Some(n) = args.next() { 553 | vals[opt_id].push((arg_pos, Val(n))); 554 | } else { 555 | return Err(ArgumentMissing(nm.to_string())); 556 | } 557 | } 558 | } 559 | } 560 | } 561 | arg_pos += 1; 562 | } 563 | debug_assert_eq!(vals.len(), opts.len()); 564 | for (vals, opt) in vals.iter().zip(opts.iter()) { 565 | if opt.occur == Req && vals.is_empty() { 566 | return Err(OptionMissing(opt.name.to_string())); 567 | } 568 | if opt.occur != Multi && vals.len() > 1 { 569 | return Err(OptionDuplicated(opt.name.to_string())); 570 | } 571 | } 572 | 573 | // Note that if "--" is last argument on command line, then index stored 574 | // in option does not exist in `free` and must be replaced with `None` 575 | args_end = args_end.filter(|pos| pos != &free.len()); 576 | 577 | Ok(Matches { 578 | opts, 579 | vals, 580 | free, 581 | args_end, 582 | }) 583 | } 584 | 585 | /// Derive a short one-line usage summary from a set of long options. 586 | pub fn short_usage(&self, program_name: &str) -> String { 587 | let mut line = format!("Usage: {} ", program_name); 588 | line.push_str( 589 | &self 590 | .grps 591 | .iter() 592 | .map(format_option) 593 | .collect::>() 594 | .join(" "), 595 | ); 596 | line 597 | } 598 | 599 | /// Derive a formatted message from a set of options. 600 | pub fn usage(&self, brief: &str) -> String { 601 | self.usage_with_format(|opts| { 602 | format!( 603 | "{}\n\nOptions:\n{}\n", 604 | brief, 605 | opts.collect::>().join("\n") 606 | ) 607 | }) 608 | } 609 | 610 | /// Derive a custom formatted message from a set of options. The formatted options provided to 611 | /// a closure as an iterator. 612 | pub fn usage_with_format) -> String>( 613 | &self, 614 | mut formatter: F, 615 | ) -> String { 616 | formatter(&mut self.usage_items()) 617 | } 618 | 619 | /// Derive usage items from a set of options. 620 | fn usage_items<'a>(&'a self) -> Box + 'a> { 621 | let desc_sep = format!("\n{}", repeat(" ").take(24).collect::()); 622 | 623 | let any_short = self.grps.iter().any(|optref| !optref.short_name.is_empty()); 624 | 625 | let rows = self.grps.iter().map(move |optref| { 626 | let OptGroup { 627 | short_name, 628 | long_name, 629 | hint, 630 | desc, 631 | hasarg, 632 | .. 633 | } = (*optref).clone(); 634 | 635 | let mut row = " ".to_string(); 636 | 637 | // short option 638 | match short_name.width() { 639 | 0 => { 640 | if any_short { 641 | row.push_str(" "); 642 | } 643 | } 644 | 1 => { 645 | row.push('-'); 646 | row.push_str(&short_name); 647 | if long_name.width() > 0 { 648 | row.push_str(", "); 649 | } else { 650 | // Only a single space here, so that any 651 | // argument is printed in the correct spot. 652 | row.push(' '); 653 | } 654 | } 655 | // FIXME: refer issue #7. 656 | _ => panic!("the short name should only be 1 ascii char long"), 657 | } 658 | 659 | // long option 660 | match long_name.width() { 661 | 0 => {} 662 | _ => { 663 | row.push_str(if self.long_only { "-" } else { "--" }); 664 | row.push_str(&long_name); 665 | row.push(' '); 666 | } 667 | } 668 | 669 | // arg 670 | match hasarg { 671 | No => {} 672 | Yes => row.push_str(&hint), 673 | Maybe => { 674 | row.push('['); 675 | row.push_str(&hint); 676 | row.push(']'); 677 | } 678 | } 679 | 680 | let rowlen = row.width(); 681 | if rowlen < 24 { 682 | for _ in 0..24 - rowlen { 683 | row.push(' '); 684 | } 685 | } else { 686 | row.push_str(&desc_sep) 687 | } 688 | 689 | let desc_rows = each_split_within(&desc, 54); 690 | row.push_str(&desc_rows.join(&desc_sep)); 691 | 692 | row 693 | }); 694 | 695 | Box::new(rows) 696 | } 697 | } 698 | 699 | fn validate_names(short_name: &str, long_name: &str) { 700 | let len = short_name.len(); 701 | assert!( 702 | len == 1 || len == 0, 703 | "the short_name (first argument) should be a single character, \ 704 | or an empty string for none" 705 | ); 706 | let len = long_name.len(); 707 | assert!( 708 | len == 0 || len > 1, 709 | "the long_name (second argument) should be longer than a single \ 710 | character, or an empty string for none" 711 | ); 712 | } 713 | 714 | /// What parsing style to use when parsing arguments. 715 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 716 | pub enum ParsingStyle { 717 | /// Flags and "free" arguments can be freely inter-mixed. 718 | FloatingFrees, 719 | /// As soon as a "free" argument (i.e. non-flag) is encountered, stop 720 | /// considering any remaining arguments as flags. 721 | StopAtFirstFree, 722 | } 723 | 724 | /// Name of an option. Either a string or a single char. 725 | #[derive(Clone, Debug, PartialEq, Eq)] 726 | enum Name { 727 | /// A string representing the long name of an option. 728 | /// For example: "help" 729 | Long(String), 730 | /// A char representing the short name of an option. 731 | /// For example: 'h' 732 | Short(char), 733 | } 734 | 735 | /// Describes whether an option has an argument. 736 | #[derive(Clone, Debug, Copy, PartialEq, Eq)] 737 | pub enum HasArg { 738 | /// The option requires an argument. 739 | Yes, 740 | /// The option takes no argument. 741 | No, 742 | /// The option argument is optional. 743 | Maybe, 744 | } 745 | 746 | /// Describes how often an option may occur. 747 | #[derive(Clone, Debug, Copy, PartialEq, Eq)] 748 | pub enum Occur { 749 | /// The option occurs once. 750 | Req, 751 | /// The option occurs at most once. 752 | Optional, 753 | /// The option occurs zero or more times. 754 | Multi, 755 | } 756 | 757 | /// A description of a possible option. 758 | #[derive(Clone, Debug, PartialEq, Eq)] 759 | struct Opt { 760 | /// Name of the option 761 | name: Name, 762 | /// Whether it has an argument 763 | hasarg: HasArg, 764 | /// How often it can occur 765 | occur: Occur, 766 | /// Which options it aliases 767 | aliases: Vec, 768 | } 769 | 770 | /// One group of options, e.g., both `-h` and `--help`, along with 771 | /// their shared description and properties. 772 | #[derive(Debug, Clone, PartialEq, Eq)] 773 | struct OptGroup { 774 | /// Short name of the option, e.g. `h` for a `-h` option 775 | short_name: String, 776 | /// Long name of the option, e.g. `help` for a `--help` option 777 | long_name: String, 778 | /// Hint for argument, e.g. `FILE` for a `-o FILE` option 779 | hint: String, 780 | /// Description for usage help text 781 | desc: String, 782 | /// Whether option has an argument 783 | hasarg: HasArg, 784 | /// How often it can occur 785 | occur: Occur, 786 | } 787 | 788 | /// Describes whether an option is given at all or has a value. 789 | #[derive(Clone, Debug, PartialEq, Eq)] 790 | enum Optval { 791 | Val(String), 792 | Given, 793 | } 794 | 795 | /// The result of checking command line arguments. Contains a vector 796 | /// of matches and a vector of free strings. 797 | #[derive(Clone, Debug, PartialEq, Eq)] 798 | pub struct Matches { 799 | /// Options that matched 800 | opts: Vec, 801 | /// Values of the Options that matched and their positions 802 | vals: Vec>, 803 | 804 | /// Free string fragments 805 | pub free: Vec, 806 | 807 | /// Index of first free fragment after "--" separator 808 | args_end: Option, 809 | } 810 | 811 | /// The type returned when the command line does not conform to the 812 | /// expected format. Use the `Debug` implementation to output detailed 813 | /// information. 814 | #[derive(Clone, Debug, PartialEq, Eq)] 815 | pub enum Fail { 816 | /// The option requires an argument but none was passed. 817 | ArgumentMissing(String), 818 | /// The passed option is not declared among the possible options. 819 | UnrecognizedOption(String), 820 | /// A required option is not present. 821 | OptionMissing(String), 822 | /// A single occurrence option is being used multiple times. 823 | OptionDuplicated(String), 824 | /// There's an argument being passed to a non-argument option. 825 | UnexpectedArgument(String), 826 | } 827 | 828 | impl Error for Fail {} 829 | 830 | /// The result of parsing a command line with a set of options. 831 | pub type Result = result::Result; 832 | 833 | impl Name { 834 | fn from_str(nm: &str) -> Name { 835 | if nm.len() == 1 { 836 | Short(nm.as_bytes()[0] as char) 837 | } else { 838 | Long(nm.to_string()) 839 | } 840 | } 841 | 842 | fn to_string(&self) -> String { 843 | match *self { 844 | Short(ch) => ch.to_string(), 845 | Long(ref s) => s.to_string(), 846 | } 847 | } 848 | } 849 | 850 | impl OptGroup { 851 | /// Translate OptGroup into Opt. 852 | /// (Both short and long names correspond to different Opts). 853 | fn long_to_short(&self) -> Opt { 854 | let OptGroup { 855 | short_name, 856 | long_name, 857 | hasarg, 858 | occur, 859 | .. 860 | } = (*self).clone(); 861 | 862 | match (short_name.len(), long_name.len()) { 863 | (0, 0) => panic!("this long-format option was given no name"), 864 | (0, _) => Opt { 865 | name: Long(long_name), 866 | hasarg, 867 | occur, 868 | aliases: Vec::new(), 869 | }, 870 | (1, 0) => Opt { 871 | name: Short(short_name.as_bytes()[0] as char), 872 | hasarg, 873 | occur, 874 | aliases: Vec::new(), 875 | }, 876 | (1, _) => Opt { 877 | name: Long(long_name), 878 | hasarg, 879 | occur, 880 | aliases: vec![Opt { 881 | name: Short(short_name.as_bytes()[0] as char), 882 | hasarg: hasarg, 883 | occur: occur, 884 | aliases: Vec::new(), 885 | }], 886 | }, 887 | (_, _) => panic!("something is wrong with the long-form opt"), 888 | } 889 | } 890 | } 891 | 892 | impl Matches { 893 | fn opt_vals(&self, nm: &str) -> Vec<(usize, Optval)> { 894 | match find_opt(&self.opts, &Name::from_str(nm)) { 895 | Some(id) => self.vals[id].clone(), 896 | None => panic!("No option '{}' defined", nm), 897 | } 898 | } 899 | 900 | fn opt_val(&self, nm: &str) -> Option { 901 | self.opt_vals(nm).into_iter().map(|(_, o)| o).next() 902 | } 903 | /// Returns true if an option was defined 904 | pub fn opt_defined(&self, name: &str) -> bool { 905 | find_opt(&self.opts, &Name::from_str(name)).is_some() 906 | } 907 | 908 | /// Returns true if an option was matched. 909 | /// 910 | /// # Panics 911 | /// 912 | /// This function will panic if the option name is not defined. 913 | pub fn opt_present(&self, name: &str) -> bool { 914 | !self.opt_vals(name).is_empty() 915 | } 916 | 917 | /// Returns the number of times an option was matched. 918 | /// 919 | /// # Panics 920 | /// 921 | /// This function will panic if the option name is not defined. 922 | pub fn opt_count(&self, name: &str) -> usize { 923 | self.opt_vals(name).len() 924 | } 925 | 926 | /// Returns a vector of all the positions in which an option was matched. 927 | /// 928 | /// # Panics 929 | /// 930 | /// This function will panic if the option name is not defined. 931 | pub fn opt_positions(&self, name: &str) -> Vec { 932 | self.opt_vals(name) 933 | .into_iter() 934 | .map(|(pos, _)| pos) 935 | .collect() 936 | } 937 | 938 | /// Returns true if any of several options were matched. 939 | pub fn opts_present(&self, names: &[String]) -> bool { 940 | names 941 | .iter() 942 | .any(|nm| match find_opt(&self.opts, &Name::from_str(&nm)) { 943 | Some(id) if !self.vals[id].is_empty() => true, 944 | _ => false, 945 | }) 946 | } 947 | 948 | /// Returns true if any of several options were matched. 949 | /// 950 | /// Similar to `opts_present` but accepts any argument that can be converted 951 | /// into an iterator over string references. 952 | /// 953 | /// # Panics 954 | /// 955 | /// This function might panic if some option name is not defined. 956 | /// 957 | /// # Example 958 | /// 959 | /// ``` 960 | /// # use getopts::Options; 961 | /// let mut opts = Options::new(); 962 | /// opts.optopt("a", "alpha", "first option", "STR"); 963 | /// opts.optopt("b", "beta", "second option", "STR"); 964 | /// 965 | /// let args = vec!["-a", "foo"]; 966 | /// let matches = &match opts.parse(&args) { 967 | /// Ok(m) => m, 968 | /// _ => panic!(), 969 | /// }; 970 | /// 971 | /// assert!(matches.opts_present_any(&["alpha"])); 972 | /// assert!(!matches.opts_present_any(&["beta"])); 973 | /// ``` 974 | pub fn opts_present_any(&self, names: C) -> bool 975 | where 976 | C::Item: AsRef, 977 | { 978 | names 979 | .into_iter() 980 | .any(|nm| !self.opt_vals(nm.as_ref()).is_empty()) 981 | } 982 | 983 | /// Returns the string argument supplied to one of several matching options or `None`. 984 | pub fn opts_str(&self, names: &[String]) -> Option { 985 | names 986 | .iter() 987 | .filter_map(|nm| match self.opt_val(&nm) { 988 | Some(Val(s)) => Some(s), 989 | _ => None, 990 | }) 991 | .next() 992 | } 993 | 994 | /// Returns the string argument supplied to the first matching option of 995 | /// several options or `None`. 996 | /// 997 | /// Similar to `opts_str` but accepts any argument that can be converted 998 | /// into an iterator over string references. 999 | /// 1000 | /// # Panics 1001 | /// 1002 | /// This function might panic if some option name is not defined. 1003 | /// 1004 | /// # Example 1005 | /// 1006 | /// ``` 1007 | /// # use getopts::Options; 1008 | /// let mut opts = Options::new(); 1009 | /// opts.optopt("a", "alpha", "first option", "STR"); 1010 | /// opts.optopt("b", "beta", "second option", "STR"); 1011 | /// 1012 | /// let args = vec!["-a", "foo", "--beta", "bar"]; 1013 | /// let matches = &match opts.parse(&args) { 1014 | /// Ok(m) => m, 1015 | /// _ => panic!(), 1016 | /// }; 1017 | /// 1018 | /// assert_eq!(Some("foo".to_string()), matches.opts_str_first(&["alpha", "beta"])); 1019 | /// assert_eq!(Some("bar".to_string()), matches.opts_str_first(&["beta", "alpha"])); 1020 | /// ``` 1021 | pub fn opts_str_first(&self, names: C) -> Option 1022 | where 1023 | C::Item: AsRef, 1024 | { 1025 | names 1026 | .into_iter() 1027 | .filter_map(|nm| match self.opt_val(nm.as_ref()) { 1028 | Some(Val(s)) => Some(s), 1029 | _ => None, 1030 | }) 1031 | .next() 1032 | } 1033 | 1034 | /// Returns a vector of the arguments provided to all matches of the given 1035 | /// option. 1036 | /// 1037 | /// Used when an option accepts multiple values. 1038 | /// 1039 | /// # Panics 1040 | /// 1041 | /// This function will panic if the option name is not defined. 1042 | pub fn opt_strs(&self, name: &str) -> Vec { 1043 | self.opt_vals(name) 1044 | .into_iter() 1045 | .filter_map(|(_, v)| match v { 1046 | Val(s) => Some(s), 1047 | _ => None, 1048 | }) 1049 | .collect() 1050 | } 1051 | 1052 | /// Returns a vector of the arguments provided to all matches of the given 1053 | /// option, together with their positions. 1054 | /// 1055 | /// Used when an option accepts multiple values. 1056 | /// 1057 | /// # Panics 1058 | /// 1059 | /// This function will panic if the option name is not defined. 1060 | pub fn opt_strs_pos(&self, name: &str) -> Vec<(usize, String)> { 1061 | self.opt_vals(name) 1062 | .into_iter() 1063 | .filter_map(|(p, v)| match v { 1064 | Val(s) => Some((p, s)), 1065 | _ => None, 1066 | }) 1067 | .collect() 1068 | } 1069 | 1070 | /// Returns the string argument supplied to a matching option or `None`. 1071 | /// 1072 | /// # Panics 1073 | /// 1074 | /// This function will panic if the option name is not defined. 1075 | pub fn opt_str(&self, name: &str) -> Option { 1076 | match self.opt_val(name) { 1077 | Some(Val(s)) => Some(s), 1078 | _ => None, 1079 | } 1080 | } 1081 | 1082 | /// Returns the matching string, a default, or `None`. 1083 | /// 1084 | /// Returns `None` if the option was not present, `def` if the option was 1085 | /// present but no argument was provided, and the argument if the option was 1086 | /// present and an argument was provided. 1087 | /// 1088 | /// # Panics 1089 | /// 1090 | /// This function will panic if the option name is not defined. 1091 | pub fn opt_default(&self, name: &str, def: &str) -> Option { 1092 | match self.opt_val(name) { 1093 | Some(Val(s)) => Some(s), 1094 | Some(_) => Some(def.to_string()), 1095 | None => None, 1096 | } 1097 | } 1098 | 1099 | /// Returns some matching value or `None`. 1100 | /// 1101 | /// Similar to opt_str, also converts matching argument using FromStr. 1102 | /// 1103 | /// # Panics 1104 | /// 1105 | /// This function will panic if the option name is not defined. 1106 | pub fn opt_get(&self, name: &str) -> result::Result, T::Err> 1107 | where 1108 | T: FromStr, 1109 | { 1110 | match self.opt_val(name) { 1111 | Some(Val(s)) => Ok(Some(s.parse()?)), 1112 | Some(Given) => Ok(None), 1113 | None => Ok(None), 1114 | } 1115 | } 1116 | 1117 | /// Returns a matching value or default. 1118 | /// 1119 | /// Similar to opt_default, except the two differences. 1120 | /// Instead of returning None when argument was not present, return `def`. 1121 | /// Instead of returning &str return type T, parsed using str::parse(). 1122 | /// 1123 | /// # Panics 1124 | /// 1125 | /// This function will panic if the option name is not defined. 1126 | pub fn opt_get_default(&self, name: &str, def: T) -> result::Result 1127 | where 1128 | T: FromStr, 1129 | { 1130 | match self.opt_val(name) { 1131 | Some(Val(s)) => s.parse(), 1132 | Some(Given) => Ok(def), 1133 | None => Ok(def), 1134 | } 1135 | } 1136 | 1137 | /// Returns index of first free argument after "--". 1138 | /// 1139 | /// If double-dash separator is present and there are some args after it in 1140 | /// the argument list then the method returns index into `free` vector 1141 | /// indicating first argument after it. 1142 | /// behind it. 1143 | /// 1144 | /// # Examples 1145 | /// 1146 | /// ``` 1147 | /// # use getopts::Options; 1148 | /// let mut opts = Options::new(); 1149 | /// 1150 | /// let matches = opts.parse(&vec!["arg1", "--", "arg2"]).unwrap(); 1151 | /// let end_pos = matches.free_trailing_start().unwrap(); 1152 | /// assert_eq!(end_pos, 1); 1153 | /// assert_eq!(matches.free[end_pos], "arg2".to_owned()); 1154 | /// ``` 1155 | /// 1156 | /// If the double-dash is missing from argument list or if there are no 1157 | /// arguments after it: 1158 | /// 1159 | /// ``` 1160 | /// # use getopts::Options; 1161 | /// let mut opts = Options::new(); 1162 | /// 1163 | /// let matches = opts.parse(&vec!["arg1", "--"]).unwrap(); 1164 | /// assert_eq!(matches.free_trailing_start(), None); 1165 | /// 1166 | /// let matches = opts.parse(&vec!["arg1", "arg2"]).unwrap(); 1167 | /// assert_eq!(matches.free_trailing_start(), None); 1168 | /// ``` 1169 | /// 1170 | pub fn free_trailing_start(&self) -> Option { 1171 | self.args_end 1172 | } 1173 | } 1174 | 1175 | fn is_arg(arg: &str) -> bool { 1176 | arg.as_bytes().get(0) == Some(&b'-') && arg.len() > 1 1177 | } 1178 | 1179 | fn find_opt(opts: &[Opt], nm: &Name) -> Option { 1180 | // Search main options. 1181 | let pos = opts.iter().position(|opt| &opt.name == nm); 1182 | if pos.is_some() { 1183 | return pos; 1184 | } 1185 | 1186 | // Search in aliases. 1187 | for candidate in opts.iter() { 1188 | if candidate.aliases.iter().any(|opt| &opt.name == nm) { 1189 | return opts.iter().position(|opt| opt.name == candidate.name); 1190 | } 1191 | } 1192 | 1193 | None 1194 | } 1195 | 1196 | impl fmt::Display for Fail { 1197 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 1198 | match *self { 1199 | ArgumentMissing(ref nm) => write!(f, "Argument to option '{}' missing", *nm), 1200 | UnrecognizedOption(ref nm) => write!(f, "Unrecognized option: '{}'", *nm), 1201 | OptionMissing(ref nm) => write!(f, "Required option '{}' missing", *nm), 1202 | OptionDuplicated(ref nm) => write!(f, "Option '{}' given more than once", *nm), 1203 | UnexpectedArgument(ref nm) => write!(f, "Option '{}' does not take an argument", *nm), 1204 | } 1205 | } 1206 | } 1207 | 1208 | fn format_option(opt: &OptGroup) -> String { 1209 | let mut line = String::new(); 1210 | 1211 | if opt.occur != Req { 1212 | line.push('['); 1213 | } 1214 | 1215 | // Use short_name if possible, but fall back to long_name. 1216 | if !opt.short_name.is_empty() { 1217 | line.push('-'); 1218 | line.push_str(&opt.short_name); 1219 | } else { 1220 | line.push_str("--"); 1221 | line.push_str(&opt.long_name); 1222 | } 1223 | 1224 | if opt.hasarg != No { 1225 | line.push(' '); 1226 | if opt.hasarg == Maybe { 1227 | line.push('['); 1228 | } 1229 | line.push_str(&opt.hint); 1230 | if opt.hasarg == Maybe { 1231 | line.push(']'); 1232 | } 1233 | } 1234 | 1235 | if opt.occur != Req { 1236 | line.push(']'); 1237 | } 1238 | if opt.occur == Multi { 1239 | line.push_str(".."); 1240 | } 1241 | 1242 | line 1243 | } 1244 | 1245 | /// Splits a string into substrings with possibly internal whitespace, 1246 | /// each of them at most `lim` bytes long, if possible. The substrings 1247 | /// have leading and trailing whitespace removed, and are only cut at 1248 | /// whitespace boundaries. 1249 | fn each_split_within(desc: &str, lim: usize) -> Vec { 1250 | let mut rows = Vec::new(); 1251 | for line in desc.trim().lines() { 1252 | let line_chars = line.chars().chain(Some(' ')); 1253 | let words = line_chars 1254 | .fold((Vec::new(), 0, 0), |(mut words, a, z), c| { 1255 | let idx = z + c.len_utf8(); // Get the current byte offset 1256 | 1257 | // If the char is whitespace, advance the word start and maybe push a word 1258 | if c.is_whitespace() { 1259 | if a != z { 1260 | words.push(&line[a..z]); 1261 | } 1262 | (words, idx, idx) 1263 | } 1264 | // If the char is not whitespace, continue, retaining the current 1265 | else { 1266 | (words, a, idx) 1267 | } 1268 | }) 1269 | .0; 1270 | 1271 | let mut row = String::new(); 1272 | for word in words.iter() { 1273 | let sep = if !row.is_empty() { Some(" ") } else { None }; 1274 | let width = row.width() + word.width() + sep.map(UnicodeWidthStr::width).unwrap_or(0); 1275 | 1276 | if width <= lim { 1277 | if let Some(sep) = sep { 1278 | row.push_str(sep) 1279 | } 1280 | row.push_str(word); 1281 | continue; 1282 | } 1283 | if !row.is_empty() { 1284 | rows.push(row.clone()); 1285 | row.clear(); 1286 | } 1287 | row.push_str(word); 1288 | } 1289 | if !row.is_empty() { 1290 | rows.push(row); 1291 | } 1292 | } 1293 | rows 1294 | } 1295 | --------------------------------------------------------------------------------