forked from bonsai/harakit
		
	closed #18
This commit is contained in:
		
						commit
						c444198efc
					
				| @ -12,3 +12,6 @@ indent_size = 2 | ||||
| 
 | ||||
| [*.sh] | ||||
| indent_size = 2 | ||||
| 
 | ||||
| [configure] | ||||
| indent_size = 2 | ||||
|  | ||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1 +1,3 @@ | ||||
| build/ | ||||
| dist/ | ||||
| *.mk | ||||
|  | ||||
							
								
								
									
										80
									
								
								GNUmakefile
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								GNUmakefile
									
									
									
									
									
								
							| @ -1,80 +0,0 @@ | ||||
| # Copyright (c) 2023 Emma Tebibyte <emma@tebibyte.media>
 | ||||
| # Copyright (c) 2023 DTB <trinity@trinity.moe>
 | ||||
| # Copyright (c) 2023 Sasha Koshka <sashakoshka@tebibyte.media>
 | ||||
| # SPDX-License-Identifier: FSFAP
 | ||||
| #
 | ||||
| # Copying and distribution of this file, with or without modification, are
 | ||||
| # permitted in any medium without royalty provided the copyright notice and this
 | ||||
| # notice are preserved.  This file is offered as-is, without any warranty.
 | ||||
| 
 | ||||
| # If we want to use POSIX make we can’t use ifeq
 | ||||
| # .POSIX:
 | ||||
| # .PRAGMA: posix_202x
 | ||||
| .PHONY: clean | ||||
| .PHONY: install | ||||
| .PHONY: test | ||||
| 
 | ||||
| PREFIX=/usr/local | ||||
| 
 | ||||
| CC=cc | ||||
| CFLAGS=-O3 -Lbuild/lib -idirafter include | ||||
| 
 | ||||
| RUSTC=rustc +nightly | ||||
| RUSTCFLAGS=-Zlocation-detail=none -Copt-level=z -Ccodegen-units=1 \
 | ||||
| 	-Cpanic=abort -Clto=y -Cstrip=symbols -Ctarget-cpu=native \
 | ||||
| 	-Clink-args=-Wl,-n,-N,--no-dynamic-linker,--no-pie,--build-id=none | ||||
| 
 | ||||
| ifeq ($(CC), gcc) | ||||
| 	CFLAGS=-O3 -s -Wl,-z,noseparate-code,-z,nosectionheader -flto -Lbuild/lib \
 | ||||
| 		-idirafter include | ||||
| endif | ||||
| 
 | ||||
| ifeq ($(CC), clang) | ||||
| 	CFLAGS=-O3 -Wall -Lbuild/lib -idirafter include | ||||
| endif | ||||
| 
 | ||||
| ifeq ($(CC), tcc) | ||||
| 	CFLAGS=-O3 -s -Wl -flto -Lbuild/lib -idirafter include | ||||
| endif | ||||
| 
 | ||||
| build: build_dir false intcmp scrut str strcmp true | ||||
| 
 | ||||
| build_dir: | ||||
| 	mkdir -p build/o build/lib build/bin | ||||
| 
 | ||||
| clean: | ||||
| 	rm -rf build/ | ||||
| 
 | ||||
| install: build | ||||
| 	mkdir -p $(PREFIX)/bin $(PREFIX)/lib | ||||
| 	mkdir -p $(PREFIX)/share/man/man1 $(PREFIX)/share/man/man3 | ||||
| 	# cp -f build/lib/*.so $(PREFIX)/lib/ | ||||
| 	cp -f build/bin/* $(PREFIX)/bin/ | ||||
| 	cp -f docs/*.1 $(PREFIX)/share/man/man1/ | ||||
| 	# cp -f docs/*.3 $(PREFIX)/share/man/man3/ | ||||
| 
 | ||||
| test: build | ||||
| 	tests/cc-compat.sh | ||||
| 	tests/posix-compat.sh | ||||
| 
 | ||||
| dj: src/dj.c build_dir | ||||
| 	$(CC) $(CFLAGS) -o build/bin/dj src/dj.c | ||||
| 
 | ||||
| false: src/false.rs build_dir | ||||
| 	$(RUSTC) $(RUSTCFLAGS) -o build/bin/false src/false.rs | ||||
| 
 | ||||
| intcmp: src/intcmp.c build_dir | ||||
| 	$(CC) $(CFLAGS) -o build/bin/intcmp src/intcmp.c | ||||
| 
 | ||||
| scrut: src/scrut.c build_dir | ||||
| 	$(CC) $(CFLAGS) -o build/bin/scrut src/scrut.c | ||||
| 
 | ||||
| str: src/str.c build_dir | ||||
| 	$(CC) $(CFLAGS) -o build/bin/str src/str.c | ||||
| 
 | ||||
| strcmp: src/strcmp.c build_dir | ||||
| 	$(CC) $(CFLAGS) -o build/bin/strcmp src/strcmp.c | ||||
| 
 | ||||
| true: src/true.rs build_dir | ||||
| 	$(RUSTC) $(RUSTCFLAGS) -o build/bin/true src/true.rs | ||||
| 
 | ||||
							
								
								
									
										93
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								Makefile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | ||||
| # Copyright (c) 2023–2024 Emma Tebibyte <emma@tebibyte.media>
 | ||||
| # Copyright (c) 2023 DTB <trinity@trinity.moe>
 | ||||
| # Copyright (c) 2023 Sasha Koshka <sashakoshka@tebibyte.media>
 | ||||
| # SPDX-License-Identifier: FSFAP
 | ||||
| #
 | ||||
| # Copying and distribution of this file, with or without modification, are
 | ||||
| # permitted in any medium without royalty provided the copyright notice and this
 | ||||
| # notice are preserved.  This file is offered as-is, without any warranty.
 | ||||
| 
 | ||||
| .POSIX: | ||||
| .PRAGMA: posix_202x # future POSIX standard support à la pdpmake(1)
 | ||||
| 
 | ||||
| .PHONY: all | ||||
| .PHONY: clean | ||||
| .PHONY: install | ||||
| .PHONY: test | ||||
| 
 | ||||
| PREFIX=/usr/local | ||||
| 
 | ||||
| CC=cc | ||||
| RUSTC=rustc | ||||
| 
 | ||||
| # to build, first run ./configure
 | ||||
| include *.mk | ||||
| 
 | ||||
| all: dj false fop intcmp scrut str strcmp true | ||||
| 
 | ||||
| build: | ||||
| 	# keep build/include until bindgen(1) has stdin support | ||||
| 	# https://github.com/rust-lang/rust-bindgen/issues/2703 | ||||
| 	mkdir -p build/bin build/include build/lib build/o build/test | ||||
| 
 | ||||
| clean: | ||||
| 	rm -rf build/ dist/ | ||||
| 
 | ||||
| dist: all | ||||
| 	mkdir -p \
 | ||||
| 	dist/bin \
 | ||||
| 	dist/man/man1 \
 | ||||
| 	# dist/$(PREFIX)/lib \ | ||||
| 	# dist/$(PREFIX)/man/man3 | ||||
| 	cp build/bin/* dist/bin/ | ||||
| 	# cp build/lib/* dist/$(PREFIX)/lib/ | ||||
| 	cp docs/*.1 dist/man/man1/ | ||||
| 	# cp docs/*.3 dist/$(PREFIX)/man/man3/ | ||||
| 
 | ||||
| install: dist | ||||
| 	mkdir -p $(PREFIX) | ||||
| 	cp -r dist/* $(PREFIX)/ | ||||
| 
 | ||||
| test: build | ||||
| 	tests/cc-compat.sh | ||||
| 	tests/posix-compat.sh | ||||
| 	$(RUSTC) --test src/getopt-rs/lib.rs -o build/test/getopt | ||||
| 
 | ||||
| sysexits: build | ||||
| 	# bandage solution until bindgen(1) gets stdin support | ||||
| 	printf '#define EXIT_FAILURE 1\n' | cat - include/sysexits.h \
 | ||||
| 		> build/include/sysexits.h  | ||||
| 	bindgen --default-macro-constant-type signed --use-core --formatter=none \
 | ||||
| 		"$$(printf '#include <sysexits.h>\n' \
 | ||||
| 			| cpp -M -idirafter "build/include" - \
 | ||||
| 			| sed 's/ /\n/g' | grep sysexits.h)" \
 | ||||
| 		| $(RUSTC) $(RUSTCFLAGS) --crate-type lib -o build/o/libsysexits.rlib - | ||||
| 
 | ||||
| libgetopt: src/getopt-rs/lib.rs | ||||
| 	$(RUSTC) $(RUSTCFLAGS) --crate-type=lib --crate-name=getopt \
 | ||||
| 		-o build/o/libgetopt.rlib src/getopt-rs/lib.rs | ||||
| 
 | ||||
| dj: src/dj.c build_dir | ||||
| 	$(CC) $(CFLAGS) -o build/bin/dj src/dj.c | ||||
| 
 | ||||
| false: src/false.rs build | ||||
| 	$(RUSTC) $(RUSTFLAGS) -o build/bin/false src/false.rs | ||||
| 
 | ||||
| fop: src/fop.rs build libgetopt sysexits | ||||
| 	$(RUSTC) $(RUSTFLAGS) --extern getopt=build/o/libgetopt.rlib \
 | ||||
| 		-o build/bin/fop src/fop.rs | ||||
| 
 | ||||
| intcmp: src/intcmp.c build | ||||
| 	$(CC) $(CFLAGS) -o build/bin/intcmp src/intcmp.c | ||||
| 
 | ||||
| scrut: src/scrut.c build | ||||
| 	$(CC) $(CFLAGS) -o build/bin/scrut src/scrut.c | ||||
| 
 | ||||
| str: src/str.c build | ||||
| 	$(CC) $(CFLAGS) -o build/bin/str src/str.c | ||||
| 
 | ||||
| strcmp: src/strcmp.c build | ||||
| 	$(CC) $(CFLAGS) -o build/bin/strcmp src/strcmp.c | ||||
| 
 | ||||
| true: src/true.rs build | ||||
| 	$(RUSTC) $(RUSTFLAGS) -o build/bin/true src/true.rs | ||||
							
								
								
									
										39
									
								
								configure
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										39
									
								
								configure
									
									
									
									
										vendored
									
									
										Executable file
									
								
							| @ -0,0 +1,39 @@ | ||||
| #!/bin/sh | ||||
| 
 | ||||
| # Copyright (c) 2023–2024 Emma Tebibyte <emma@tebibyte.media> | ||||
| # SPDX-License-Identifier: FSFAP | ||||
| # | ||||
| # Copying and distribution of this file, with or without modification, are | ||||
| # permitted in any medium without royalty provided the copyright notice and this | ||||
| # notice are preserved.  This file is offered as-is, without any warranty. | ||||
| 
 | ||||
| set -e | ||||
| 
 | ||||
| CFLAGS='-Lbuild/lib -idirafter include -O3' | ||||
| RUSTFLAGS='-Copt-level=z -Ccodegen-units=1 -Cpanic=abort -Clto=y \ | ||||
| 	-Cstrip=symbols -Ctarget-cpu=native \ | ||||
| 	--extern sysexits=build/o/libsysexits.rlib' | ||||
| 
 | ||||
| case "$@" in | ||||
| 	clang) | ||||
| 		CFLAGS="$CFLAGS -Wall" | ||||
| 		;; | ||||
| 	clean) | ||||
| 		rm *.mk || true | ||||
| 		exit 0 | ||||
| 		;; | ||||
| 	gcc) | ||||
| 		CFLAGS="$CFLAGS -s -Wl,-z,noseparate-code,-z,nosectionheader -flto" | ||||
| 		;; | ||||
| 	'rustc +nightly') | ||||
| 		RUSTFLAGS="+nightly -Zlocation-detail=none $RUSTFLAGS" | ||||
| 		;; | ||||
| 	'') ;; | ||||
| 	*) | ||||
| 		printf 'Usage: %s [compiler]\n' "$0" | ||||
| 		exit 64 # sysexits.h(3) EX_USAGE | ||||
| 		;; | ||||
| esac | ||||
| 
 | ||||
| printf 'CFLAGS=%s\n' "$CFLAGS" >cc.mk | ||||
| printf 'RUSTFLAGS=%s\n' "$RUSTFLAGS" >rustc.mk | ||||
							
								
								
									
										96
									
								
								src/fop.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/fop.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,96 @@ | ||||
| /* | ||||
|  * Copyright (c) 2023–2024 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/.
 | ||||
|  */ | ||||
| 
 | ||||
| use std::{ | ||||
| 	env::args, | ||||
| 	io::{ Read, stdin, Write }, | ||||
| 	process::{ Command, exit, Stdio }, | ||||
| }; | ||||
| 
 | ||||
| extern crate sysexits; | ||||
| extern crate getopt; | ||||
| 
 | ||||
| use getopt::{ Opt, Parser }; | ||||
| use sysexits::{ EX_DATAERR, EX_USAGE }; | ||||
| 
 | ||||
| fn main() { | ||||
| 	let argv = args().collect::<Vec<String>>(); | ||||
| 	let mut d = '␞'; | ||||
| 	let mut arg_parser = Parser::new(&argv, "d:"); | ||||
| 
 | ||||
| 	while let Some(opt) = arg_parser.next() { | ||||
| 		match opt { | ||||
| 			Ok(Opt('d', Some(arg))) => { | ||||
| 				let arg_char = arg.chars().collect::<Vec<char>>(); | ||||
| 				if arg_char.len() > 1 { | ||||
| 					eprintln!("{}: {}: Not a character.", argv[0], arg); | ||||
| 					exit(EX_USAGE); | ||||
| 				} else { d = arg_char[0]; } | ||||
| 			}, | ||||
| 			_ => {}, | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	let index_arg = arg_parser.index(); | ||||
| 	let command_arg = arg_parser.index() + 1; | ||||
| 
 | ||||
| 	argv.get(command_arg).unwrap_or_else(|| { | ||||
| 		eprintln!("Usage: {} [-d delimiter] index command [args...]", argv[0]); | ||||
| 		exit(EX_USAGE); | ||||
| 	}); | ||||
| 
 | ||||
| 	let index = argv[index_arg].parse::<usize>().unwrap_or_else(|_| { | ||||
| 		eprintln!("{}: {}: Not an integer.", argv[0], argv[1]); | ||||
| 		exit(EX_DATAERR); | ||||
| 	}); | ||||
| 
 | ||||
| 	let mut buf = String::new(); | ||||
| 	stdin().read_to_string(&mut buf).unwrap(); | ||||
| 	let mut fields = buf.split(d).collect::<Vec<&str>>(); | ||||
| 
 | ||||
| 	let opts = argv.iter().clone().skip(command_arg + 1).collect::<Vec<&String>>(); | ||||
| 
 | ||||
| 	let mut spawned = Command::new(argv.get(command_arg).unwrap()) | ||||
| 		.args(opts) | ||||
| 		.stdin(Stdio::piped()) | ||||
| 		.stdout(Stdio::piped()) | ||||
| 		.spawn() | ||||
| 		.unwrap(); | ||||
| 
 | ||||
| 	let field = fields.get(index).unwrap_or_else(|| { | ||||
| 		eprintln!( | ||||
| 			"{}: {}: No such index in input.", | ||||
| 			argv[0], | ||||
| 			index.to_string() | ||||
| 		); | ||||
| 		exit(EX_DATAERR); | ||||
| 	}); | ||||
| 
 | ||||
| 	if let Some(mut child_stdin) = spawned.stdin.take() { | ||||
| 		child_stdin.write_all(field.as_bytes()).unwrap(); | ||||
| 		drop(child_stdin); | ||||
| 	} | ||||
| 
 | ||||
| 	let output = spawned.wait_with_output().unwrap(); | ||||
| 
 | ||||
| 	let new_field = String::from_utf8(output.stdout).unwrap(); | ||||
| 
 | ||||
| 	fields[index] = &new_field; | ||||
| 
 | ||||
| 	print!("{}", fields.join(&d.to_string())); | ||||
| } | ||||
							
								
								
									
										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
 | ||||
|     } | ||||
| } | ||||
| @ -9,18 +9,19 @@ | ||||
| 
 | ||||
| set -e | ||||
| 
 | ||||
| if ! ls GNUmakefile >/dev/null 2>&1 | ||||
| if ! ls Makefile >/dev/null 2>&1 | ||||
| then | ||||
| 	printf '%s: Run this script in the root of the project.\n' "$0" 1>&2  | ||||
| 	exit 64 # sysexits.h(3) EX_USAGE | ||||
| fi | ||||
| 
 | ||||
| make clean | ||||
| ./configure clean | ||||
| ./configure | ||||
| 
 | ||||
| for CC in cc \ | ||||
| 	clang \ | ||||
| 	gcc \ | ||||
| 	tcc \ | ||||
| 	'zig cc' | ||||
| do | ||||
| 	export CC | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user