diff --git a/.editorconfig b/.editorconfig index 5c4a4c2..226e665 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,3 +12,6 @@ indent_size = 2 [*.sh] indent_size = 2 + +[configure] +indent_size = 2 diff --git a/.gitignore b/.gitignore index 567609b..3882502 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ build/ +dist/ +*.mk diff --git a/GNUmakefile b/GNUmakefile deleted file mode 100644 index 46f3aac..0000000 --- a/GNUmakefile +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (c) 2023 Emma Tebibyte -# Copyright (c) 2023 DTB -# Copyright (c) 2023 Sasha Koshka -# 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 - diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6105e26 --- /dev/null +++ b/Makefile @@ -0,0 +1,93 @@ +# Copyright (c) 2023–2024 Emma Tebibyte +# Copyright (c) 2023 DTB +# Copyright (c) 2023 Sasha Koshka +# 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 \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 diff --git a/configure b/configure new file mode 100755 index 0000000..eb1c8ae --- /dev/null +++ b/configure @@ -0,0 +1,39 @@ +#!/bin/sh + +# Copyright (c) 2023–2024 Emma Tebibyte +# 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 diff --git a/src/fop.rs b/src/fop.rs new file mode 100644 index 0000000..4f80bd6 --- /dev/null +++ b/src/fop.rs @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2023–2024 Emma Tebibyte + * 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::>(); + 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::>(); + 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::().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::>(); + + let opts = argv.iter().clone().skip(command_arg + 1).collect::>(); + + 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())); +} diff --git a/src/getopt-rs/error.rs b/src/getopt-rs/error.rs new file mode 100644 index 0000000..322af02 --- /dev/null +++ b/src/getopt-rs/error.rs @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2023 Emma Tebibyte + * 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 + } +} diff --git a/src/getopt-rs/errorkind.rs b/src/getopt-rs/errorkind.rs new file mode 100644 index 0000000..5475d8e --- /dev/null +++ b/src/getopt-rs/errorkind.rs @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023 Emma Tebibyte + * 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, +} diff --git a/src/getopt-rs/lib.rs b/src/getopt-rs/lib.rs new file mode 100644 index 0000000..62f0e0d --- /dev/null +++ b/src/getopt-rs/lib.rs @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023 Emma Tebibyte + * 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; diff --git a/src/getopt-rs/opt.rs b/src/getopt-rs/opt.rs new file mode 100644 index 0000000..05b51e6 --- /dev/null +++ b/src/getopt-rs/opt.rs @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023 Emma Tebibyte + * 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> { +/// use getopt::Opt; +/// +/// // args = ["program", "-abc", "foo"]; +/// # let args: Vec = 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); + +impl fmt::Display for Opt { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Opt({:?}, {:?})", self.0, self.1) + } +} diff --git a/src/getopt-rs/parser.rs b/src/getopt-rs/parser.rs new file mode 100644 index 0000000..6f06cc3 --- /dev/null +++ b/src/getopt-rs/parser.rs @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2023 Emma Tebibyte + * 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> { +/// use getopt::Opt; +/// +/// // args = ["program", "-abc", "foo"]; +/// # let args: Vec = 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> { +/// use getopt::Opt; +/// +/// // args = ["program", "-abc", "-d", "foo", "-e", "bar"]; +/// # let mut args: Vec = 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, + args: Vec>, + 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 = 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; + + /// 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 = 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 = 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 = 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 = 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> { + 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)))) + } + } + } +} diff --git a/src/getopt-rs/result.rs b/src/getopt-rs/result.rs new file mode 100644 index 0000000..015a402 --- /dev/null +++ b/src/getopt-rs/result.rs @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023 Emma Tebibyte + * 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 = result::Result; diff --git a/src/getopt-rs/tests.rs b/src/getopt-rs/tests.rs new file mode 100644 index 0000000..c53d517 --- /dev/null +++ b/src/getopt-rs/tests.rs @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2023 Emma Tebibyte + * 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 = $expect; + let args: Vec = vec![$($arg),+] + .into_iter() + .map(String::from) + .collect(); + let next: Option = $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 = 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 = 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 = $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 = 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 + } +} diff --git a/tests/cc-compat.sh b/tests/cc-compat.sh index f6f66ea..b5762c8 100755 --- a/tests/cc-compat.sh +++ b/tests/cc-compat.sh @@ -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