getopt-rs(3): added getopt library for Rust
This commit is contained in:
		
							parent
							
								
									75ead441ec
								
							
						
					
					
						commit
						0c2223d4fb
					
				| @ -40,7 +40,7 @@ endif | |||||||
| build: build_dir false intcmp scrut str strcmp true | build: build_dir false intcmp scrut str strcmp true | ||||||
| 
 | 
 | ||||||
| build_dir: | build_dir: | ||||||
| 	mkdir -p build/o build/lib build/bin | 	mkdir -p build/o build/lib build/bin build/test | ||||||
| 
 | 
 | ||||||
| clean: | clean: | ||||||
| 	rm -rf build/ | 	rm -rf build/ | ||||||
| @ -53,9 +53,10 @@ install: build | |||||||
| 	cp -f docs/*.1 $(PREFIX)/share/man/man1/ | 	cp -f docs/*.1 $(PREFIX)/share/man/man1/ | ||||||
| 	# cp -f docs/*.3 $(PREFIX)/share/man/man3/ | 	# cp -f docs/*.3 $(PREFIX)/share/man/man3/ | ||||||
| 
 | 
 | ||||||
| test: build | test: build_dir tests/cc-compat.sh tests/posix-compat.sh src/getopt-rs/tests.rs | ||||||
| 	tests/cc-compat.sh | 	tests/cc-compat.sh | ||||||
| 	tests/posix-compat.sh | 	tests/posix-compat.sh | ||||||
|  | 	$(RUSTC) --test src/getopt-rs/lib.rs -o build/test/getopt | ||||||
| 
 | 
 | ||||||
| false: src/false.rs build_dir | false: src/false.rs build_dir | ||||||
| 	$(RUSTC) $(RUSTCFLAGS) -o build/bin/false src/false.rs | 	$(RUSTC) $(RUSTCFLAGS) -o build/bin/false src/false.rs | ||||||
| @ -75,3 +76,6 @@ strcmp: src/strcmp.c build_dir | |||||||
| true: src/true.rs build_dir | true: src/true.rs build_dir | ||||||
| 	$(RUSTC) $(RUSTCFLAGS) -o build/bin/true src/true.rs | 	$(RUSTC) $(RUSTCFLAGS) -o build/bin/true src/true.rs | ||||||
| 
 | 
 | ||||||
|  | libgetopt: src/getopt-rs/lib.rs | ||||||
|  | 	$(RUSTC) $(RUSTCFLAGS) --crate-type=lib --crate-name=getopt \
 | ||||||
|  | 		-o build/o/libgetopt.rlib src/lib.rs | ||||||
|  | |||||||
							
								
								
									
										95
									
								
								src/getopt-rs/error.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/getopt-rs/error.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,95 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media> | ||||||
|  |  * SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  |  * | ||||||
|  |  * This program is free software: you can redistribute it and/or modify it under | ||||||
|  |  * the terms of the GNU Affero General Public License as published by the Free | ||||||
|  |  * Software Foundation, either version 3 of the License, or (at your option) any | ||||||
|  |  * later version. | ||||||
|  |  * 
 | ||||||
|  |  * This program is distributed in the hope that it will be useful, but WITHOUT | ||||||
|  |  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||||||
|  |  * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more | ||||||
|  |  * details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU Affero General Public License | ||||||
|  |  * along with this program. If not, see https://www.gnu.org/licenses/.
 | ||||||
|  |  * | ||||||
|  |  * This file incorporates work covered by the following copyright and permission | ||||||
|  |  * notice: | ||||||
|  |  *     The Clear BSD License | ||||||
|  |  * | ||||||
|  |  *     Copyright © 2017-2023 David Wildasin | ||||||
|  |  *     All rights reserved. | ||||||
|  |  * | ||||||
|  |  *     Redistribution and use in source and binary forms, with or without | ||||||
|  |  *     modification, are permitted (subject to the limitations in the disclaimer | ||||||
|  |  *     below) provided that the following conditions are met: | ||||||
|  |  * | ||||||
|  |  *          * Redistributions of source code must retain the above copyright | ||||||
|  |  *          notice, this list of conditions, and the following disclaimer. | ||||||
|  |  * | ||||||
|  |  *          * Redistributions in binary form must reproduce the above copyright | ||||||
|  |  *          notice, this list of conditions, and the following disclaimer in the | ||||||
|  |  *          documentation and/or other materials provided with the distribution. | ||||||
|  |  * | ||||||
|  |  *          * Neither the name of the copyright holder nor the names of its | ||||||
|  |  *          contributors may be used to endorse or promote products derived from | ||||||
|  |  *          this software without specific prior written permission. | ||||||
|  |  * | ||||||
|  |  *     NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED | ||||||
|  |  *     BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | ||||||
|  |  *     CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, | ||||||
|  |  *     BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | ||||||
|  |  *     FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||||
|  |  *     HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||||
|  |  *     SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED | ||||||
|  |  *     TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | ||||||
|  |  *     PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | ||||||
|  |  *     LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | ||||||
|  |  *     NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||||||
|  |  *     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | use std::{ error, fmt }; | ||||||
|  | 
 | ||||||
|  | use crate::ErrorKind::{ self, * }; | ||||||
|  | 
 | ||||||
|  | /// A basic error type for [`Parser`](struct.Parser.html)
 | ||||||
|  | #[derive(Debug, Eq, PartialEq)] | ||||||
|  | pub struct Error { | ||||||
|  |     culprit: char, | ||||||
|  |     kind: ErrorKind, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Error { | ||||||
|  |     /// Creates a new error using a known kind and the character that caused the
 | ||||||
|  | 	/// issue.
 | ||||||
|  |     pub fn new(kind: ErrorKind, culprit: char) -> Self { | ||||||
|  |         Self { culprit, kind } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Returns the [`ErrorKind`](enum.ErrorKind.html) for this error.
 | ||||||
|  |     pub fn kind(self) -> ErrorKind { | ||||||
|  |         self.kind | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl fmt::Display for Error { | ||||||
|  |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|  |         match self.kind { | ||||||
|  |             MissingArgument => write!( | ||||||
|  | 				f, 
 | ||||||
|  | 				"option requires an argument -- {:?}", | ||||||
|  | 				self.culprit, | ||||||
|  | 			), | ||||||
|  |             UnknownOption => write!(f, "unknown option -- {:?}", self.culprit), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl error::Error for Error { | ||||||
|  |     fn source(&self) -> Option<&(dyn error::Error + 'static)> { | ||||||
|  |         None | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										61
									
								
								src/getopt-rs/errorkind.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/getopt-rs/errorkind.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media> | ||||||
|  |  * SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  |  * | ||||||
|  |  * This program is free software: you can redistribute it and/or modify it under | ||||||
|  |  * the terms of the GNU Affero General Public License as published by the Free | ||||||
|  |  * Software Foundation, either version 3 of the License, or (at your option) any | ||||||
|  |  * later version. | ||||||
|  |  * 
 | ||||||
|  |  * This program is distributed in the hope that it will be useful, but WITHOUT | ||||||
|  |  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||||||
|  |  * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more | ||||||
|  |  * details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU Affero General Public License | ||||||
|  |  * along with this program. If not, see https://www.gnu.org/licenses/.
 | ||||||
|  |  * | ||||||
|  |  * This file incorporates work covered by the following copyright and permission | ||||||
|  |  * notice: | ||||||
|  |  *     The Clear BSD License | ||||||
|  |  * | ||||||
|  |  *     Copyright © 2017-2023 David Wildasin | ||||||
|  |  *     All rights reserved. | ||||||
|  |  * | ||||||
|  |  *     Redistribution and use in source and binary forms, with or without | ||||||
|  |  *     modification, are permitted (subject to the limitations in the disclaimer | ||||||
|  |  *     below) provided that the following conditions are met: | ||||||
|  |  * | ||||||
|  |  *          * Redistributions of source code must retain the above copyright | ||||||
|  |  *          notice, this list of conditions, and the following disclaimer. | ||||||
|  |  * | ||||||
|  |  *          * Redistributions in binary form must reproduce the above copyright | ||||||
|  |  *          notice, this list of conditions, and the following disclaimer in the | ||||||
|  |  *          documentation and/or other materials provided with the distribution. | ||||||
|  |  * | ||||||
|  |  *          * Neither the name of the copyright holder nor the names of its | ||||||
|  |  *          contributors may be used to endorse or promote products derived from | ||||||
|  |  *          this software without specific prior written permission. | ||||||
|  |  * | ||||||
|  |  *     NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED | ||||||
|  |  *     BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | ||||||
|  |  *     CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, | ||||||
|  |  *     BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | ||||||
|  |  *     FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||||
|  |  *     HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||||
|  |  *     SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED | ||||||
|  |  *     TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | ||||||
|  |  *     PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | ||||||
|  |  *     LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | ||||||
|  |  *     NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||||||
|  |  *     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | /// What kinds of errors [`Parser`](struct.Parser.html) can return.
 | ||||||
|  | #[derive(Debug, Eq, PartialEq)] | ||||||
|  | pub enum ErrorKind { | ||||||
|  |     /// An argument was not found for an option that was expecting one.
 | ||||||
|  |     MissingArgument, | ||||||
|  |     /// An unknown option character was encountered.
 | ||||||
|  |     UnknownOption, | ||||||
|  | } | ||||||
							
								
								
									
										72
									
								
								src/getopt-rs/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/getopt-rs/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,72 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media> | ||||||
|  |  * SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  |  * | ||||||
|  |  * This program is free software: you can redistribute it and/or modify it under | ||||||
|  |  * the terms of the GNU Affero General Public License as published by the Free | ||||||
|  |  * Software Foundation, either version 3 of the License, or (at your option) any | ||||||
|  |  * later version. | ||||||
|  |  * 
 | ||||||
|  |  * This program is distributed in the hope that it will be useful, but WITHOUT | ||||||
|  |  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||||||
|  |  * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more | ||||||
|  |  * details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU Affero General Public License | ||||||
|  |  * along with this program. If not, see https://www.gnu.org/licenses/.
 | ||||||
|  |  * | ||||||
|  |  * This file incorporates work covered by the following copyright and permission | ||||||
|  |  * notice: | ||||||
|  |  *     The Clear BSD License | ||||||
|  |  * | ||||||
|  |  *     Copyright © 2017-2023 David Wildasin | ||||||
|  |  *     All rights reserved. | ||||||
|  |  * | ||||||
|  |  *     Redistribution and use in source and binary forms, with or without | ||||||
|  |  *     modification, are permitted (subject to the limitations in the disclaimer | ||||||
|  |  *     below) provided that the following conditions are met: | ||||||
|  |  * | ||||||
|  |  *          * Redistributions of source code must retain the above copyright | ||||||
|  |  *          notice, this list of conditions, and the following disclaimer. | ||||||
|  |  * | ||||||
|  |  *          * Redistributions in binary form must reproduce the above copyright | ||||||
|  |  *          notice, this list of conditions, and the following disclaimer in the | ||||||
|  |  *          documentation and/or other materials provided with the distribution. | ||||||
|  |  * | ||||||
|  |  *          * Neither the name of the copyright holder nor the names of its | ||||||
|  |  *          contributors may be used to endorse or promote products derived from | ||||||
|  |  *          this software without specific prior written permission. | ||||||
|  |  * | ||||||
|  |  *     NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED | ||||||
|  |  *     BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | ||||||
|  |  *     CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, | ||||||
|  |  *     BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | ||||||
|  |  *     FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||||
|  |  *     HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||||
|  |  *     SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED | ||||||
|  |  *     TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | ||||||
|  |  *     PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | ||||||
|  |  *     LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | ||||||
|  |  *     NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||||||
|  |  *     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | //! # getopt
 | ||||||
|  | //!
 | ||||||
|  | //! `getopt` provides a minimal, (essentially) POSIX-compliant option parser.
 | ||||||
|  | 
 | ||||||
|  | pub use crate::{ | ||||||
|  | 	error::Error, | ||||||
|  | 	errorkind::ErrorKind, | ||||||
|  | 	opt::Opt, | ||||||
|  | 	parser::Parser, | ||||||
|  | 	result::Result | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | mod error; | ||||||
|  | mod errorkind; | ||||||
|  | mod opt; | ||||||
|  | mod parser; | ||||||
|  | mod result; | ||||||
|  | #[cfg(test)] | ||||||
|  | mod tests; | ||||||
							
								
								
									
										89
									
								
								src/getopt-rs/opt.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/getopt-rs/opt.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media> | ||||||
|  |  * SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  |  * | ||||||
|  |  * This program is free software: you can redistribute it and/or modify it under | ||||||
|  |  * the terms of the GNU Affero General Public License as published by the Free | ||||||
|  |  * Software Foundation, either version 3 of the License, or (at your option) any | ||||||
|  |  * later version. | ||||||
|  |  * 
 | ||||||
|  |  * This program is distributed in the hope that it will be useful, but WITHOUT | ||||||
|  |  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||||||
|  |  * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more | ||||||
|  |  * details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU Affero General Public License | ||||||
|  |  * along with this program. If not, see https://www.gnu.org/licenses/.
 | ||||||
|  |  * | ||||||
|  |  * This file incorporates work covered by the following copyright and permission | ||||||
|  |  * notice: | ||||||
|  |  *     The Clear BSD License | ||||||
|  |  * | ||||||
|  |  *     Copyright © 2017-2023 David Wildasin | ||||||
|  |  *     All rights reserved. | ||||||
|  |  * | ||||||
|  |  *     Redistribution and use in source and binary forms, with or without | ||||||
|  |  *     modification, are permitted (subject to the limitations in the disclaimer | ||||||
|  |  *     below) provided that the following conditions are met: | ||||||
|  |  * | ||||||
|  |  *          * Redistributions of source code must retain the above copyright | ||||||
|  |  *          notice, this list of conditions, and the following disclaimer. | ||||||
|  |  * | ||||||
|  |  *          * Redistributions in binary form must reproduce the above copyright | ||||||
|  |  *          notice, this list of conditions, and the following disclaimer in the | ||||||
|  |  *          documentation and/or other materials provided with the distribution. | ||||||
|  |  * | ||||||
|  |  *          * Neither the name of the copyright holder nor the names of its | ||||||
|  |  *          contributors may be used to endorse or promote products derived from | ||||||
|  |  *          this software without specific prior written permission. | ||||||
|  |  * | ||||||
|  |  *     NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED | ||||||
|  |  *     BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | ||||||
|  |  *     CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, | ||||||
|  |  *     BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | ||||||
|  |  *     FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||||
|  |  *     HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||||
|  |  *     SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED | ||||||
|  |  *     TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | ||||||
|  |  *     PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | ||||||
|  |  *     LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | ||||||
|  |  *     NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||||||
|  |  *     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | use std::fmt; | ||||||
|  | 
 | ||||||
|  | /// A single option.
 | ||||||
|  | ///
 | ||||||
|  | /// For `Opt(x, y)`:
 | ||||||
|  | ///   - `x` is the character representing the option.
 | ||||||
|  | ///   - `y` is `Some` string, or `None` if no argument was expected.
 | ||||||
|  | ///
 | ||||||
|  | /// # Example
 | ||||||
|  | ///
 | ||||||
|  | /// ```
 | ||||||
|  | /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
 | ||||||
|  | /// use getopt::Opt;
 | ||||||
|  | ///
 | ||||||
|  | /// // args = ["program", "-abc", "foo"];
 | ||||||
|  | /// # let args: Vec<String> = vec!["program", "-abc", "foo"]
 | ||||||
|  | /// #     .into_iter()
 | ||||||
|  | /// #     .map(String::from)
 | ||||||
|  | /// #     .collect();
 | ||||||
|  | /// let optstring = "ab:c";
 | ||||||
|  | /// let mut opts = getopt::Parser::new(&args, optstring);
 | ||||||
|  | ///
 | ||||||
|  | /// assert_eq!(Opt('a', None), opts.next().transpose()?.unwrap());
 | ||||||
|  | /// assert_eq!(Opt('b', Some("c".to_string())), opts.next().transpose()?.unwrap());
 | ||||||
|  | /// assert_eq!(None, opts.next().transpose()?);
 | ||||||
|  | /// # Ok(())
 | ||||||
|  | /// # }
 | ||||||
|  | /// ```
 | ||||||
|  | #[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] | ||||||
|  | pub struct Opt(pub char, pub Option<String>); | ||||||
|  | 
 | ||||||
|  | impl fmt::Display for Opt { | ||||||
|  |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|  |         write!(f, "Opt({:?}, {:?})", self.0, self.1) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										382
									
								
								src/getopt-rs/parser.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										382
									
								
								src/getopt-rs/parser.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,382 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media> | ||||||
|  |  * SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  |  * | ||||||
|  |  * This program is free software: you can redistribute it and/or modify it under | ||||||
|  |  * the terms of the GNU Affero General Public License as published by the Free | ||||||
|  |  * Software Foundation, either version 3 of the License, or (at your option) any | ||||||
|  |  * later version. | ||||||
|  |  * 
 | ||||||
|  |  * This program is distributed in the hope that it will be useful, but WITHOUT | ||||||
|  |  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||||||
|  |  * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more | ||||||
|  |  * details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU Affero General Public License | ||||||
|  |  * along with this program. If not, see https://www.gnu.org/licenses/.
 | ||||||
|  |  * | ||||||
|  |  * This file incorporates work covered by the following copyright and permission | ||||||
|  |  * notice: | ||||||
|  |  *     The Clear BSD License | ||||||
|  |  * | ||||||
|  |  *     Copyright © 2017-2023 David Wildasin | ||||||
|  |  *     All rights reserved. | ||||||
|  |  * | ||||||
|  |  *     Redistribution and use in source and binary forms, with or without | ||||||
|  |  *     modification, are permitted (subject to the limitations in the disclaimer | ||||||
|  |  *     below) provided that the following conditions are met: | ||||||
|  |  * | ||||||
|  |  *          * Redistributions of source code must retain the above copyright | ||||||
|  |  *          notice, this list of conditions, and the following disclaimer. | ||||||
|  |  * | ||||||
|  |  *          * Redistributions in binary form must reproduce the above copyright | ||||||
|  |  *          notice, this list of conditions, and the following disclaimer in the | ||||||
|  |  *          documentation and/or other materials provided with the distribution. | ||||||
|  |  * | ||||||
|  |  *          * Neither the name of the copyright holder nor the names of its | ||||||
|  |  *          contributors may be used to endorse or promote products derived from | ||||||
|  |  *          this software without specific prior written permission. | ||||||
|  |  * | ||||||
|  |  *     NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED | ||||||
|  |  *     BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | ||||||
|  |  *     CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, | ||||||
|  |  *     BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | ||||||
|  |  *     FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||||
|  |  *     HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||||
|  |  *     SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED | ||||||
|  |  *     TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | ||||||
|  |  *     PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | ||||||
|  |  *     LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | ||||||
|  |  *     NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||||||
|  |  *     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | use std::collections::HashMap; | ||||||
|  | 
 | ||||||
|  | use crate::{ error::Error, errorkind::ErrorKind, opt::Opt, result::Result }; | ||||||
|  | 
 | ||||||
|  | /// The core of the `getopt` crate.
 | ||||||
|  | ///
 | ||||||
|  | /// `Parser` is implemented as an iterator over the options present in the given
 | ||||||
|  | /// argument vector.
 | ||||||
|  | ///
 | ||||||
|  | /// The method [`next`](#method.next) does the heavy lifting.
 | ||||||
|  | ///
 | ||||||
|  | /// # Examples
 | ||||||
|  | ///
 | ||||||
|  | /// ## Simplified usage:
 | ||||||
|  | /// ```
 | ||||||
|  | /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
 | ||||||
|  | /// use getopt::Opt;
 | ||||||
|  | ///
 | ||||||
|  | /// // args = ["program", "-abc", "foo"];
 | ||||||
|  | /// # let args: Vec<String> = vec!["program", "-abc", "foo"]
 | ||||||
|  | /// #     .into_iter()
 | ||||||
|  | /// #     .map(String::from)
 | ||||||
|  | /// #     .collect();
 | ||||||
|  | /// let mut opts = getopt::Parser::new(&args, "ab:c");
 | ||||||
|  | ///
 | ||||||
|  | /// assert_eq!(Some(Opt('a', None)), opts.next().transpose()?);
 | ||||||
|  | /// assert_eq!(1, opts.index());
 | ||||||
|  | /// assert_eq!(Some(Opt('b', Some("c".to_string()))), opts.next().transpose()?);
 | ||||||
|  | /// assert_eq!(2, opts.index());
 | ||||||
|  | /// assert_eq!(None, opts.next());
 | ||||||
|  | /// assert_eq!(2, opts.index());
 | ||||||
|  | /// assert_eq!("foo", args[opts.index()]);
 | ||||||
|  | /// # Ok(())
 | ||||||
|  | /// # }
 | ||||||
|  | /// ```
 | ||||||
|  | ///
 | ||||||
|  | /// ## A more idiomatic example:
 | ||||||
|  | /// ```
 | ||||||
|  | /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
 | ||||||
|  | /// use getopt::Opt;
 | ||||||
|  | ///
 | ||||||
|  | /// // args = ["program", "-abc", "-d", "foo", "-e", "bar"];
 | ||||||
|  | /// # let mut args: Vec<String> = vec!["program", "-abc", "-d", "foo", "-e", "bar"]
 | ||||||
|  | /// #     .into_iter()
 | ||||||
|  | /// #     .map(String::from)
 | ||||||
|  | /// #     .collect();
 | ||||||
|  | /// let mut opts = getopt::Parser::new(&args, "ab:cd:e");
 | ||||||
|  | ///
 | ||||||
|  | /// let mut a_flag = false;
 | ||||||
|  | /// let mut b_flag = String::new();
 | ||||||
|  | /// let mut c_flag = false;
 | ||||||
|  | /// let mut d_flag = String::new();
 | ||||||
|  | /// let mut e_flag = false;
 | ||||||
|  | ///
 | ||||||
|  | /// loop {
 | ||||||
|  | ///     match opts.next().transpose()? {
 | ||||||
|  | ///         None => break,
 | ||||||
|  | ///         Some(opt) => match opt {
 | ||||||
|  | ///             Opt('a', None) => a_flag = true,
 | ||||||
|  | ///             Opt('b', Some(arg)) => b_flag = arg.clone(),
 | ||||||
|  | ///             Opt('c', None) => c_flag = true,
 | ||||||
|  | ///             Opt('d', Some(arg)) => d_flag = arg.clone(),
 | ||||||
|  | ///             Opt('e', None) => e_flag = true,
 | ||||||
|  | ///             _ => unreachable!(),
 | ||||||
|  | ///         },
 | ||||||
|  | ///     }
 | ||||||
|  | /// }
 | ||||||
|  | ///
 | ||||||
|  | /// let new_args = args.split_off(opts.index());
 | ||||||
|  | ///
 | ||||||
|  | /// assert_eq!(true, a_flag);
 | ||||||
|  | /// assert_eq!("c", b_flag);
 | ||||||
|  | /// assert_eq!(false, c_flag);
 | ||||||
|  | /// assert_eq!("foo", d_flag);
 | ||||||
|  | /// assert_eq!(true, e_flag);
 | ||||||
|  | ///
 | ||||||
|  | /// assert_eq!(1, new_args.len());
 | ||||||
|  | /// assert_eq!("bar", new_args.first().unwrap());
 | ||||||
|  | /// # Ok(())
 | ||||||
|  | /// # }
 | ||||||
|  | /// ```
 | ||||||
|  | #[derive(Debug, Eq, PartialEq)] | ||||||
|  | pub struct Parser { | ||||||
|  |     opts: HashMap<char, bool>, | ||||||
|  |     args: Vec<Vec<char>>, | ||||||
|  |     index: usize, | ||||||
|  |     point: usize, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Parser { | ||||||
|  |     /// Create a new `Parser`, which will process the arguments in `args`
 | ||||||
|  | 	/// according to the options specified in `optstring`.
 | ||||||
|  |     ///
 | ||||||
|  |     /// For compatibility with
 | ||||||
|  | 	/// [`std::env::args`](https://doc.rust-lang.org/std/env/fn.args.html),
 | ||||||
|  |     /// valid options are expected to begin at the second element of `args`, and
 | ||||||
|  | 	/// `index` is
 | ||||||
|  |     /// initialised to `1`.
 | ||||||
|  |     /// If `args` is structured differently, be sure to call
 | ||||||
|  | 	/// [`set_index`](#method.set_index) before the first invocation of
 | ||||||
|  | 	/// [`next`](#method.next).
 | ||||||
|  |     ///
 | ||||||
|  |     /// `optstring` is a string of recognised option characters; if a character
 | ||||||
|  | 	/// is followed by a colon (`:`), that option takes an argument.
 | ||||||
|  |     ///
 | ||||||
|  |     /// # Note:
 | ||||||
|  |     /// Transforming the OS-specific argument strings into a vector of `String`s
 | ||||||
|  | 	/// is the sole responsibility of the calling program, as it involves some
 | ||||||
|  | 	/// level of potential information loss (which this crate does not presume
 | ||||||
|  | 	/// to handle unilaterally) and error handling (which would complicate the
 | ||||||
|  | 	/// interface).
 | ||||||
|  |     pub fn new(args: &[String], optstring: &str) -> Self { | ||||||
|  |         let optstring: Vec<char> = optstring.chars().collect(); | ||||||
|  |         let mut opts = HashMap::new(); | ||||||
|  |         let mut i = 0; | ||||||
|  |         let len = optstring.len(); | ||||||
|  | 
 | ||||||
|  |         while i < len { | ||||||
|  |             let j = i + 1; | ||||||
|  | 
 | ||||||
|  |             if j < len && optstring[j] == ':' { | ||||||
|  |                 opts.insert(optstring[i], true); | ||||||
|  |                 i += 1; | ||||||
|  |             } else { | ||||||
|  |                 opts.insert(optstring[i], false); | ||||||
|  |             } | ||||||
|  |             i += 1; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Self { | ||||||
|  |             opts, | ||||||
|  |             // "explode" the args into a vector of character vectors, to allow
 | ||||||
|  | 			// indexing
 | ||||||
|  |             args: args.iter().map(|e| e.chars().collect()).collect(), | ||||||
|  |             index: 1, | ||||||
|  |             point: 0, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Return the current `index` of the parser.
 | ||||||
|  |     ///
 | ||||||
|  |     /// `args[index]` will always point to the the next element of `args`; when
 | ||||||
|  | 	/// the parser is
 | ||||||
|  |     /// finished with an element, it will increment `index`.
 | ||||||
|  |     ///
 | ||||||
|  |     /// After the last option has been parsed (and [`next`](#method.next) is
 | ||||||
|  | 	/// returning `None`),
 | ||||||
|  |     /// `index` will point to the first non-option argument.
 | ||||||
|  |     pub fn index(&self) -> usize { | ||||||
|  |         self.index | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // `point` must be reset to 0 whenever `index` is changed
 | ||||||
|  | 
 | ||||||
|  |     /// Modify the current `index` of the parser.
 | ||||||
|  |     pub fn set_index(&mut self, value: usize) { | ||||||
|  |         self.index = value; | ||||||
|  |         self.point = 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Increment the current `index` of the parser.
 | ||||||
|  |     ///
 | ||||||
|  |     /// This use case is common enough to warrant its own optimised method.
 | ||||||
|  |     pub fn incr_index(&mut self) { | ||||||
|  |         self.index += 1; | ||||||
|  |         self.point = 0; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Iterator for Parser { | ||||||
|  |     type Item = Result<Opt>; | ||||||
|  | 
 | ||||||
|  |     /// Returns the next option, if any.
 | ||||||
|  |     ///
 | ||||||
|  |     /// Returns an [`Error`](struct.Error.html) if an unexpected option is
 | ||||||
|  | 	/// encountered or if an
 | ||||||
|  |     /// expected argument is not found.
 | ||||||
|  |     ///
 | ||||||
|  |     /// Parsing stops at the first non-hyphenated argument; or at the first
 | ||||||
|  | 	/// argument matching "-";
 | ||||||
|  |     /// or after the first argument matching "--".
 | ||||||
|  |     ///
 | ||||||
|  |     /// When no more options are available, `next` returns `None`.
 | ||||||
|  |     ///
 | ||||||
|  |     /// # Examples
 | ||||||
|  |     ///
 | ||||||
|  |     /// ## "-"
 | ||||||
|  |     /// ```
 | ||||||
|  |     /// use getopt::Parser;
 | ||||||
|  |     ///
 | ||||||
|  |     /// // args = ["program", "-", "-a"];
 | ||||||
|  |     /// # let args: Vec<String> = vec!["program", "-", "-a"]
 | ||||||
|  |     /// #     .into_iter()
 | ||||||
|  |     /// #     .map(String::from)
 | ||||||
|  |     /// #     .collect();
 | ||||||
|  |     /// let mut opts = Parser::new(&args, "a");
 | ||||||
|  |     ///
 | ||||||
|  |     /// assert_eq!(None, opts.next());
 | ||||||
|  |     /// assert_eq!("-", args[opts.index()]);
 | ||||||
|  |     /// ```
 | ||||||
|  |     ///
 | ||||||
|  |     /// ## "--"
 | ||||||
|  |     /// ```
 | ||||||
|  |     /// use getopt::Parser;
 | ||||||
|  |     ///
 | ||||||
|  |     /// // args = ["program", "--", "-a"];
 | ||||||
|  |     /// # let args: Vec<String> = vec!["program", "--", "-a"]
 | ||||||
|  |     /// #     .into_iter()
 | ||||||
|  |     /// #     .map(String::from)
 | ||||||
|  |     /// #     .collect();
 | ||||||
|  |     /// let mut opts = Parser::new(&args, "a");
 | ||||||
|  |     ///
 | ||||||
|  |     /// assert_eq!(None, opts.next());
 | ||||||
|  |     /// assert_eq!("-a", args[opts.index()]);
 | ||||||
|  |     /// ```
 | ||||||
|  |     ///
 | ||||||
|  |     /// ## Unexpected option:
 | ||||||
|  |     /// ```
 | ||||||
|  |     /// use getopt::Parser;
 | ||||||
|  |     ///
 | ||||||
|  |     /// // args = ["program", "-b"];
 | ||||||
|  |     /// # let args: Vec<String> = vec!["program", "-b"]
 | ||||||
|  |     /// #     .into_iter()
 | ||||||
|  |     /// #     .map(String::from)
 | ||||||
|  |     /// #     .collect();
 | ||||||
|  |     /// let mut opts = Parser::new(&args, "a");
 | ||||||
|  |     ///
 | ||||||
|  |     /// assert_eq!(
 | ||||||
|  |     ///     "unknown option -- 'b'".to_string(),
 | ||||||
|  |     ///     opts.next().unwrap().unwrap_err().to_string()
 | ||||||
|  |     /// );
 | ||||||
|  |     /// ```
 | ||||||
|  |     ///
 | ||||||
|  |     /// ## Missing argument:
 | ||||||
|  |     /// ```
 | ||||||
|  |     /// use getopt::Parser;
 | ||||||
|  |     ///
 | ||||||
|  |     /// // args = ["program", "-a"];
 | ||||||
|  |     /// # let args: Vec<String> = vec!["program", "-a"]
 | ||||||
|  |     /// #     .into_iter()
 | ||||||
|  |     /// #     .map(String::from)
 | ||||||
|  |     /// #     .collect();
 | ||||||
|  |     /// let mut opts = Parser::new(&args, "a:");
 | ||||||
|  |     ///
 | ||||||
|  |     /// assert_eq!(
 | ||||||
|  |     ///     "option requires an argument -- 'a'".to_string(),
 | ||||||
|  |     ///     opts.next().unwrap().unwrap_err().to_string()
 | ||||||
|  |     /// );
 | ||||||
|  |     /// ```
 | ||||||
|  |     fn next(&mut self) -> Option<Result<Opt>> { | ||||||
|  |         if self.point == 0 { | ||||||
|  |             /* | ||||||
|  |              * Rationale excerpts below taken verbatim from "The Open Group Base
 | ||||||
|  | 			 * Specifications Issue 7, 2018 edition", IEEE Std 1003.1-2017
 | ||||||
|  | 			 * (Revision of IEEE Std 1003.1-2008). | ||||||
|  |              * Copyright © 2001-2018 IEEE and The Open Group. | ||||||
|  |              */ | ||||||
|  | 
 | ||||||
|  |             /* | ||||||
|  |              * If, when getopt() is called: | ||||||
|  |              *      argv[optind]    is a null pointer | ||||||
|  |              *      *argv[optind]   is not the character '-' | ||||||
|  |              *      argv[optind]    points to the string "-" | ||||||
|  |              * getopt() shall return -1 without changing optind. | ||||||
|  |              */ | ||||||
|  |             if self.index >= self.args.len() | ||||||
|  |                 || self.args[self.index].is_empty() | ||||||
|  |                 || self.args[self.index][0] != '-' | ||||||
|  |                 || self.args[self.index].len() == 1 | ||||||
|  |             { | ||||||
|  |                 return None; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             /* | ||||||
|  |              * If: | ||||||
|  |              *      argv[optind]    points to the string "--" | ||||||
|  |              * getopt() shall return -1 after incrementing index. | ||||||
|  |              */ | ||||||
|  |             if self.args[self.index][1] == '-' && self.args[self.index].len() == 2 { | ||||||
|  |                 self.incr_index(); | ||||||
|  |                 return None; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // move past the starting '-'
 | ||||||
|  |             self.point += 1; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let opt = self.args[self.index][self.point]; | ||||||
|  |         self.point += 1; | ||||||
|  | 
 | ||||||
|  |         match self.opts.get(&opt) { | ||||||
|  |             None => { | ||||||
|  |                 if self.point >= self.args[self.index].len() { | ||||||
|  |                     self.incr_index(); | ||||||
|  |                 } | ||||||
|  |                 Some(Err(Error::new(ErrorKind::UnknownOption, opt))) | ||||||
|  |             } | ||||||
|  |             Some(false) => { | ||||||
|  |                 if self.point >= self.args[self.index].len() { | ||||||
|  |                     self.incr_index(); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 Some(Ok(Opt(opt, None))) | ||||||
|  |             } | ||||||
|  |             Some(true) => { | ||||||
|  |                 let arg: String = if self.point >= self.args[self.index].len() { | ||||||
|  |                     self.incr_index(); | ||||||
|  |                     if self.index >= self.args.len() { | ||||||
|  |                         return Some(Err(Error::new( | ||||||
|  | 									ErrorKind::MissingArgument, | ||||||
|  | 									opt, | ||||||
|  | 								))); | ||||||
|  |                     } | ||||||
|  |                     self.args[self.index].iter().collect() | ||||||
|  |                 } else { | ||||||
|  |                     self.args[self.index] | ||||||
|  |                         .clone() | ||||||
|  |                         .split_off(self.point) | ||||||
|  |                         .iter() | ||||||
|  |                         .collect() | ||||||
|  |                 }; | ||||||
|  | 
 | ||||||
|  |                 self.incr_index(); | ||||||
|  | 
 | ||||||
|  |                 Some(Ok(Opt(opt, Some(arg)))) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										59
									
								
								src/getopt-rs/result.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/getopt-rs/result.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media> | ||||||
|  |  * SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  |  * | ||||||
|  |  * This program is free software: you can redistribute it and/or modify it under | ||||||
|  |  * the terms of the GNU Affero General Public License as published by the Free | ||||||
|  |  * Software Foundation, either version 3 of the License, or (at your option) any | ||||||
|  |  * later version. | ||||||
|  |  * 
 | ||||||
|  |  * This program is distributed in the hope that it will be useful, but WITHOUT | ||||||
|  |  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||||||
|  |  * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more | ||||||
|  |  * details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU Affero General Public License | ||||||
|  |  * along with this program. If not, see https://www.gnu.org/licenses/.
 | ||||||
|  |  * | ||||||
|  |  * This file incorporates work covered by the following copyright and permission | ||||||
|  |  * notice: | ||||||
|  |  *     The Clear BSD License | ||||||
|  |  * | ||||||
|  |  *     Copyright © 2017-2023 David Wildasin | ||||||
|  |  *     All rights reserved. | ||||||
|  |  * | ||||||
|  |  *     Redistribution and use in source and binary forms, with or without | ||||||
|  |  *     modification, are permitted (subject to the limitations in the disclaimer | ||||||
|  |  *     below) provided that the following conditions are met: | ||||||
|  |  * | ||||||
|  |  *          * Redistributions of source code must retain the above copyright | ||||||
|  |  *          notice, this list of conditions, and the following disclaimer. | ||||||
|  |  * | ||||||
|  |  *          * Redistributions in binary form must reproduce the above copyright | ||||||
|  |  *          notice, this list of conditions, and the following disclaimer in the | ||||||
|  |  *          documentation and/or other materials provided with the distribution. | ||||||
|  |  * | ||||||
|  |  *          * Neither the name of the copyright holder nor the names of its | ||||||
|  |  *          contributors may be used to endorse or promote products derived from | ||||||
|  |  *          this software without specific prior written permission. | ||||||
|  |  * | ||||||
|  |  *     NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED | ||||||
|  |  *     BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | ||||||
|  |  *     CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, | ||||||
|  |  *     BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | ||||||
|  |  *     FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||||
|  |  *     HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||||
|  |  *     SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED | ||||||
|  |  *     TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | ||||||
|  |  *     PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | ||||||
|  |  *     LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | ||||||
|  |  *     NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||||||
|  |  *     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | use std::result; | ||||||
|  | 
 | ||||||
|  | use crate::error::Error; | ||||||
|  | 
 | ||||||
|  | /// A specialized `Result` type for use with [`Parser`](struct.Parser.html)
 | ||||||
|  | pub type Result<T> = result::Result<T, Error>; | ||||||
							
								
								
									
										228
									
								
								src/getopt-rs/tests.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								src/getopt-rs/tests.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,228 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media> | ||||||
|  |  * SPDX-License-Identifier: AGPL-3.0-or-later | ||||||
|  |  * | ||||||
|  |  * This program is free software: you can redistribute it and/or modify it under | ||||||
|  |  * the terms of the GNU Affero General Public License as published by the Free | ||||||
|  |  * Software Foundation, either version 3 of the License, or (at your option) any | ||||||
|  |  * later version. | ||||||
|  |  * 
 | ||||||
|  |  * This program is distributed in the hope that it will be useful, but WITHOUT | ||||||
|  |  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||||||
|  |  * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more | ||||||
|  |  * details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU Affero General Public License | ||||||
|  |  * along with this program. If not, see https://www.gnu.org/licenses/.
 | ||||||
|  |  * | ||||||
|  |  * This file incorporates work covered by the following copyright and permission | ||||||
|  |  * notice: | ||||||
|  |  *     The Clear BSD License | ||||||
|  |  * | ||||||
|  |  *     Copyright © 2017-2023 David Wildasin | ||||||
|  |  *     All rights reserved. | ||||||
|  |  * | ||||||
|  |  *     Redistribution and use in source and binary forms, with or without | ||||||
|  |  *     modification, are permitted (subject to the limitations in the disclaimer | ||||||
|  |  *     below) provided that the following conditions are met: | ||||||
|  |  * | ||||||
|  |  *          * Redistributions of source code must retain the above copyright | ||||||
|  |  *          notice, this list of conditions, and the following disclaimer. | ||||||
|  |  * | ||||||
|  |  *          * Redistributions in binary form must reproduce the above copyright | ||||||
|  |  *          notice, this list of conditions, and the following disclaimer in the | ||||||
|  |  *          documentation and/or other materials provided with the distribution. | ||||||
|  |  * | ||||||
|  |  *          * Neither the name of the copyright holder nor the names of its | ||||||
|  |  *          contributors may be used to endorse or promote products derived from | ||||||
|  |  *          this software without specific prior written permission. | ||||||
|  |  * | ||||||
|  |  *     NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED | ||||||
|  |  *     BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | ||||||
|  |  *     CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, | ||||||
|  |  *     BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | ||||||
|  |  *     FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||||
|  |  *     HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||||
|  |  *     SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED | ||||||
|  |  *     TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | ||||||
|  |  *     PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | ||||||
|  |  *     LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | ||||||
|  |  *     NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||||||
|  |  *     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | use crate::{Opt, Parser}; | ||||||
|  | 
 | ||||||
|  | macro_rules! basic_test { | ||||||
|  |     ($name:ident, $expect:expr, $next:expr, [$($arg:expr),+], $optstr:expr) => ( | ||||||
|  |         #[test] | ||||||
|  |         fn $name() -> Result<(), String> { | ||||||
|  |             let expect: Option<Opt> = $expect; | ||||||
|  |             let args: Vec<String> = vec![$($arg),+] | ||||||
|  | 				.into_iter() | ||||||
|  | 				.map(String::from) | ||||||
|  | 				.collect(); | ||||||
|  |             let next: Option<String> = $next; | ||||||
|  |             let mut opts = Parser::new(&args, $optstr); | ||||||
|  | 
 | ||||||
|  |             match opts.next().transpose() { | ||||||
|  |                 Err(error) => { | ||||||
|  |                     return Err(format!("next() returned {:?}", error)) | ||||||
|  |                 }, | ||||||
|  |                 Ok(actual) => if actual != expect { | ||||||
|  |                     return Err( | ||||||
|  | 						format!("expected {:?}; got {:?}", expect, actual) | ||||||
|  | 					) | ||||||
|  |                 }, | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             match next { | ||||||
|  |                 None => if opts.index() < args.len() { | ||||||
|  |                     return Err(format!( | ||||||
|  | 							"expected end of args; got {:?}", args[opts.index()] | ||||||
|  | 					)) | ||||||
|  |                 }, | ||||||
|  |                 Some(n) => if args[opts.index()] != n { | ||||||
|  |                     return Err(format!( | ||||||
|  | 							"next arg: expected {:?}; got {:?}", | ||||||
|  | 							n, | ||||||
|  | 							args[opts.index()] | ||||||
|  | 					)) | ||||||
|  |                 }, | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             Ok(()) | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[rustfmt::skip] basic_test!( | ||||||
|  | 	blank_arg, None, Some(String::new()), ["x", ""], "a" | ||||||
|  | ); | ||||||
|  | #[rustfmt::skip] basic_test!( | ||||||
|  | 	double_dash, None, Some("-a".to_string()), ["x", "--", "-a", "foo"], "a" | ||||||
|  | ); | ||||||
|  | #[rustfmt::skip] basic_test!(no_opts_1, None, None, ["x"], "a"); | ||||||
|  | #[rustfmt::skip] basic_test!( | ||||||
|  | 	no_opts_2, None, Some("foo".to_string()), ["x", "foo"], "a" | ||||||
|  | ); | ||||||
|  | #[rustfmt::skip] basic_test!( | ||||||
|  | 	no_opts_3, None, Some("foo".to_string()), ["x", "foo", "-a"], "a" | ||||||
|  | ); | ||||||
|  | #[rustfmt::skip] basic_test!( | ||||||
|  | 	single_dash, None, Some("-".to_string()), ["x", "-", "-a", "foo"], "a" | ||||||
|  | ); | ||||||
|  | #[rustfmt::skip] basic_test!( | ||||||
|  | 	single_opt, | ||||||
|  | 	Some(Opt('a', None)), | ||||||
|  | 	Some("foo".to_string()), | ||||||
|  | 	["x", "-a", "foo"], | ||||||
|  | 	"a" | ||||||
|  | ); | ||||||
|  | #[rustfmt::skip] basic_test!( | ||||||
|  | 	single_optarg, | ||||||
|  | 	Some(Opt('a', Some("foo".to_string()))), | ||||||
|  | 	None, | ||||||
|  | 	["x", "-a", "foo"], | ||||||
|  | 	"a:" | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | macro_rules! error_test { | ||||||
|  |     ($name:ident, $expect:expr, [$($arg:expr),+], $optstr:expr) => ( | ||||||
|  |         #[test] | ||||||
|  |         fn $name() -> Result<(), String> { | ||||||
|  |             let expect: String = $expect.to_string(); | ||||||
|  |             let args: Vec<String> = vec![$($arg),+] | ||||||
|  | 				.into_iter() | ||||||
|  | 				.map(String::from) | ||||||
|  | 				.collect(); | ||||||
|  |             let mut opts = Parser::new(&args, $optstr); | ||||||
|  | 
 | ||||||
|  |             match opts.next() { | ||||||
|  |                 None => { | ||||||
|  |                     return Err(format!( | ||||||
|  | 							"unexpected successful response: end of options" | ||||||
|  | 					)) | ||||||
|  |                 }, | ||||||
|  |                 Some(Err(actual)) => { | ||||||
|  |                     let actual = actual.to_string(); | ||||||
|  | 
 | ||||||
|  |                     if actual != expect { | ||||||
|  |                         return Err( | ||||||
|  | 							format!("expected {:?}; got {:?}", expect, actual) | ||||||
|  | 						); | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 Some(Ok(opt)) => { | ||||||
|  |                     return Err( | ||||||
|  | 						format!("unexpected successful response: {:?}", opt) | ||||||
|  | 					) | ||||||
|  |                 }, | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             Ok(()) | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[rustfmt::skip] error_test!( | ||||||
|  | 	bad_opt, | ||||||
|  | 	"unknown option -- 'b'", | ||||||
|  | 	["x", "-b"], | ||||||
|  | 	"a" | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | #[rustfmt::skip] error_test!( | ||||||
|  | 	missing_optarg, | ||||||
|  | 	"option requires an argument -- 'a'", | ||||||
|  | 	["x", "-a"], | ||||||
|  | 	"a:" | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | #[test] | ||||||
|  | fn multiple() -> Result<(), String> { | ||||||
|  |     let args: Vec<String> = vec!["x", "-abc", "-d", "foo", "-e", "bar"] | ||||||
|  |         .into_iter() | ||||||
|  |         .map(String::from) | ||||||
|  |         .collect(); | ||||||
|  |     let optstring = "ab:d:e".to_string(); | ||||||
|  |     let mut opts = Parser::new(&args, &optstring); | ||||||
|  | 
 | ||||||
|  |     macro_rules! check_result { | ||||||
|  |         ($expect:expr) => { | ||||||
|  |             let expect: Option<Opt> = $expect; | ||||||
|  |             match opts.next().transpose() { | ||||||
|  |                 Err(error) => { | ||||||
|  | 					return Err(format!("next() returned {:?}", error)); | ||||||
|  | 				}, | ||||||
|  |                 Ok(actual) => { | ||||||
|  |                     if actual != expect { | ||||||
|  |                         return Err( | ||||||
|  | 							format!("expected {:?}; got {:?}", expect, actual) | ||||||
|  | 						); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     check_result!(Some(Opt('a', None))); | ||||||
|  |     check_result!(Some(Opt('b', Some("c".to_string())))); | ||||||
|  |     check_result!(Some(Opt('d', Some("foo".to_string())))); | ||||||
|  |     check_result!(Some(Opt('e', None))); | ||||||
|  |     check_result!(None); | ||||||
|  | 
 | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[test] | ||||||
|  | fn continue_after_error() { | ||||||
|  |     let args: Vec<String> = vec!["x", "-z", "-abc"] | ||||||
|  |         .into_iter() | ||||||
|  |         .map(String::from) | ||||||
|  |         .collect(); | ||||||
|  |     let optstring = "ab:d:e".to_string(); | ||||||
|  |     for _opt in Parser::new(&args, &optstring) { | ||||||
|  |         // do nothing, should not panic
 | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user