Compare commits

...

32 Commits

Author SHA1 Message Date
DTB
69510d76af
Merge branch 'main' into dj 2024-06-29 19:23:03 -06:00
881df1bb18
Merge branch 'usage-text' 2024-06-29 17:20:19 -06:00
40984453e3
Makefile: better leverage targets for sysexits build 2024-06-29 08:01:33 -06:00
cb88ed9809
Merge branch 'getopt-bindings' 2024-06-29 07:51:01 -06:00
cf5136d247
Makefile, swab(1): fixes swab build 2024-06-29 07:49:47 -06:00
17455baeab
intcmp(1), npc(1): removes vestigial option 2024-06-29 06:38:55 -06:00
DTB
6bd19c072d
swab(1): fix some silly mistakes 2024-06-29 06:02:39 -06:00
07a12ba81c
docs, src: fixing man page and usage text readability 2024-06-29 05:28:23 -06:00
e341c38cd6
docs, src: updates usage text for utilities 2024-06-29 05:18:20 -06:00
50bbee10a9
libgetopt.rs(3): fixes typecasting for ARM devices 2024-06-28 10:22:20 -06:00
DTB
d07bb7da41
swab(1): untested move to new getopt bindings 2024-06-28 08:33:31 -06:00
2f87ad948f
strerror.rs(3), getopt.rs(3): renames 2024-06-27 14:04:31 -06:00
6e4aeb7be7
fop(1): bring getopt(3) usage up-to-date 2024-06-23 01:00:48 -06:00
965d1bb76e
Merge branch 'main' into getopt-bindings 2024-06-23 00:57:20 -06:00
e1bf49c75a
getopt.rs(3): adds comments & documentation 2024-06-22 22:30:30 -06:00
8f990ba515
getopt.rs(3): adds testing 2024-06-22 22:18:13 -06:00
40da8135f1
getopt.rs(3): relicense to AGPLv3 2024-06-06 01:03:01 -06:00
02daca87dc
fop(1): updated to latest optind api 2024-06-05 11:55:16 -06:00
e862b7fec6
getopt.rs(3): safe api around optind reading and setting 2024-06-05 11:54:55 -06:00
05cde15105
fop(1): updated to use new optind api 2024-06-04 19:15:55 -06:00
35ddc729fd
getopt.rs(3): makes optind part of the Opt struct 2024-06-04 19:15:22 -06:00
b7283d54fe
getopt.rs(3): formatting & organization 2024-06-04 15:07:11 -06:00
a5a9c91cb6
getopt.rs(3): optind support 2024-06-04 15:04:01 -06:00
78eacd660a
getopt.rs(3): better Opt return 2024-04-13 23:42:11 -06:00
d1b77d652b
getopt.rs(3): returns last parsed option 2024-04-13 17:11:04 -06:00
8c255e61fc
COPYING.GPL: initial commit 2024-04-12 00:12:37 -06:00
95927ba8c5
getopt.rs(3): formatting 2024-04-11 23:51:52 -06:00
ad92fe27d4
fop(1): switch to getopt.rs(3) 2024-04-11 23:37:04 -06:00
bb2c63bfaf
getopt.rs(3): fixed pointer shenanigans 2024-04-11 20:33:42 -06:00
88b0d55440
getopt.rs(3): refactor to remove as much as possible from unsafe 2024-04-11 20:21:01 -06:00
0164b681c0
Makefile: remove unneeded test 2024-04-01 20:41:07 -06:00
320a70bc56
getopt.rs(3): initial commit 2024-04-01 20:39:10 -06:00
28 changed files with 292 additions and 1083 deletions

View File

@ -50,9 +50,12 @@ install: dist
cp -r $(DESTDIR)/* /
.PHONY: test
test: build
test: build /tmp/getopt
/tmp/getopt
tests/posix-compat.sh
$(RUSTC) --test src/getopt-rs/lib.rs -o build/test/getopt
/tmp/getopt: src/libgetopt.rs
$(RUSTC) --test -o /tmp/getopt src/libgetopt.rs
.PHONY: docs
docs: docs/ build
@ -65,21 +68,22 @@ docs: docs/ build
rustlibs: build/o/libsysexits.rlib build/o/libgetopt.rlib \
build/o/libstrerror.rlib
build/o/libgetopt.rlib: build src/getopt-rs/lib.rs
build/o/libgetopt.rlib: build src/libgetopt.rs
$(RUSTC) $(RUSTFLAGS) --crate-type=lib --crate-name=getopt \
-o $@ src/getopt-rs/lib.rs
-o $@ src/libgetopt.rs
build/o/libstrerror.rlib: build src/strerror.rs
build/o/libstrerror.rlib: build src/libstrerror.rs
$(RUSTC) $(RUSTFLAGS) --crate-type=lib -o $@ \
src/strerror.rs
src/libstrerror.rs
# bandage solution until bindgen(1) gets stdin support
build/o/libsysexits.rlib: build $(SYSEXITS)sysexits.h
printf '\043define EXIT_FAILURE 1\n' | cat - $(SYSEXITS)sysexits.h \
> build/include/sysexits.h
build/o/libsysexits.rlib: build/include/sysexits.h
bindgen --default-macro-constant-type signed --use-core --formatter=none \
build/include/sysexits.h | $(RUSTC) $(RUSTFLAGS) --crate-type lib -o $@ -
# bandage solution until bindgen(1) gets stdin support
build/include/sysexits.h: build $(SYSEXITS)sysexits.h
printf '\043define EXIT_FAILURE 1\n' | cat - $(SYSEXITS)sysexits.h > $@
.PHONY: dj
dj: build/bin/dj
build/bin/dj: src/dj.c build
@ -137,10 +141,8 @@ build/bin/strcmp: src/strcmp.c build
.PHONY: swab
swab: build/bin/swab
build/bin/swab: src/swab.rs build build/o/libsysexits.rlib
$(RUSTC) $(RUSTFLAGS) --extern getopt=build/o/libgetopt.rlib \
--extern sysexits=build/o/libsysexits.rlib \
-o $@ src/swab.rs
build/bin/swab: src/swab.rs build rustlibs
$(RUSTC) $(RUSTFLAGS) $(RUSTLIBS) -o $@ src/swab.rs
.PHONY: true
true: build/bin/true

View File

@ -11,25 +11,17 @@ dj \(en disk jockey
.SH SYNOPSIS
dj
.RB ( -Hn )
.RB ( -a
.RB [ byte ])
.RB ( -c
.RB [ count ])
.RB [ -Hn ]
.RB [ -a\ byte ]
.RB [ -c\ count ]
.RB ( -i
[\fBinput file\fP])
.RB ( -b
[\fBinput block size\fP])
.RB ( -s
[\fBinput offset\fP])
.RB [ -i\ file ]
.RB [ -b\ block_size ]
.RB [ -s\ offset ]
.RB ( -o
[\fBoutput file\fP])
.RB ( -B
[\fBoutput block size\fP])
.RB ( -S
[\fBoutput offset\fP])
.RB [ -o\ file ]
.RB [ -B\ block_size ]
.RB [ -S\ offset ]
.\"
.SH DESCRIPTION
@ -47,9 +39,9 @@ immediately subsequent to the specified byte.
.\"
.SH OPTIONS
.IP \fB-i\fP
.IP \fB-i\fP\ \fIfile\fP
Takes a file path as an argument and opens it for use as an input.
.IP \fB-b\fP
.IP \fB-b\fP\ \fIblock_size\fP
Takes a numeric argument as the size in bytes of the input buffer, the default
being 1024.
.IP \fB-s\fP
@ -58,7 +50,7 @@ commence; \(lqskips\(rq that number of bytes. If the standard input is used,
bytes read to this point are discarded.
.IP \fB-o\fP
Takes a file path as an argument and opens it for use as an output.
.IP \fB-B\fP
.IP \fB-B\fP\ \fIblock_size\fP
Does the same as
.B -b
but for the output buffer.

View File

@ -22,7 +22,7 @@ Performs operations on specified fields in data read from the standard input.
.\"
.SH OPTIONS
.IP \fB-d\fP
.IP \fB-d\fP\ \fIdelimiter\fP
Sets a delimiter by which the input data will be split into fields. The default
is an ASCII record separator.
.\"

View File

@ -11,9 +11,7 @@ intcmp \(en compare integers
.SH SYNOPSIS
intcmp
.RB ( -egl )
.RB [ integer ]
.RB [ integer... ]
.RB [ -egl ]\ integer\ integer...
.SH DESCRIPTION
Compare integers to each other.
.\"

View File

@ -10,11 +10,9 @@ mm \(en middleman
.SH SYNOPSIS
mm
.RB ( -aenu )
.RB ( -i
.RB [ input ])
.RB ( -o
.RB [ output ])
.RB [ -aenu ]
.RB [ -i\ input ]
.RB [ -o\ output ]
.\"
.SH DESCRIPTION
@ -26,10 +24,10 @@ Catenate input files and write them to the start of each output file or stream.
Opens subsequent outputs for appending rather than updating.
.IP \fB-e\fP
Use the standard error as an output.
.IP \fB-i\fP
.IP \fB-i\fP\ \fIinput\fP
Opens a path as an input. If one or more of the input files is \(lq-\(rq or if
no inputs are specified, the standard input shall be used.
.IP \fB-o\fP
.IP \fB-o\fP\ \fIoutput\fP
Opens a path as an output. If one or more of the output files is \(lq-\(rq or if
no outputs are specified, the standard output shall be used.
.IP \fB-u\fP

View File

@ -11,7 +11,7 @@ npc \(en show non-printing characters
.SH SYNOPSIS
npc
.RB ( -et )
.RB [ -et ]
.\"
.SH DESCRIPTION

View File

@ -10,8 +10,8 @@ scrut \(en scrutinize file properties
.SH SYNOPSIS
scrut
.RB ( -LSbcdefgkprsuwx )
.RB [ file... ]
.RB [ -LSbcdefgkprsuwx ]
.B file...
.\"
.SH DESCRIPTION

View File

@ -11,8 +11,7 @@ str \(en test string arguments
.SH SYNOPSIS
str
.RB [ type ]
.RB [ string... ]
.B type string...
.\"
.SH DESCRIPTION

View File

@ -11,8 +11,7 @@ strcmp \(en compare strings
.SH SYNOPSIS
strcmp
.RM [ string ]
.RB [ strings... ]
.B string string...
.\"
.SH DESCRIPTION

View File

@ -11,11 +11,8 @@ swab \(en swap bytes
.SH SYNOPSIS
swab
.RB ( -f )
.RB ( -w
.R [
.B word size
.R ])
.RB [ -f ]
.RB [ -w\ word_size ]
.\"
.SH DESCRIPTION
@ -25,11 +22,10 @@ Swap the latter and former halves of a block of bytes.
.IP \fB-f\fP
Ignore SIGINT signal.
.IP \fB-w\fP
Configures the word size; that is, the size in bytes of the block size
on which to operate. The default word size is 2. The word size must be
cleanly divisible by 2, otherwise the block of bytes being processed can\(cqt be
halved.
.IP \fB-w\fP\ \fIword_size\fP
Configures the word size; that is, the size in bytes of the block size on which
to operate. The default word size is 2. The word size must be cleanly divisible
by 2, otherwise the block of bytes being processed can\(cqt be halved.
.\"
.SH EXAMPLES

View File

@ -249,10 +249,10 @@ parse(char *s){
static int
usage(char *s){
fprintf(stderr, "Usage: %s (-Hn) (-a [byte]) (-c [count])\n"
"\t(-i [input file]) (-b [input block size]) (-s [input offset])\n"
"\t(-o [output file]) (-B [output block size]) (-S [output offset])\n",
s);
fprintf(stderr, "Usage: %s [-Hn] [-a byte] [-c count]\n"
"\t[-i file] [-b block_size] [-s offset]\n"
"\t[-o file] [-B block_size] [-S offset]\n",
program_name);
return EX_USAGE;
}

View File

@ -26,27 +26,39 @@ extern crate getopt;
extern crate strerror;
extern crate sysexits;
use getopt::{ Opt, Parser };
use getopt::GetOpt;
use strerror::StrError;
use sysexits::{ EX_DATAERR, EX_IOERR, EX_UNAVAILABLE, EX_USAGE };
fn main() {
let argv = args().collect::<Vec<String>>();
let mut d = '\u{1E}'.to_string();
let mut arg_parser = Parser::new(&argv, "d:");
let mut index_arg = 0;
while let Some(opt) = arg_parser.next() {
match opt {
Ok(Opt('d', Some(arg))) => d = arg,
_ => {},
let usage = format!(
"Usage: {} [-d delimiter] index command [args...]",
argv[0],
);
while let Some(opt) = argv.getopt("d:") {
match opt.opt() {
Ok(_) => {
/* unwrap because Err(OptError::MissingArg) will be returned if
* opt.arg() is None */
d = opt.arg().unwrap();
index_arg = opt.ind();
},
Err(_) => {
eprintln!("{}", usage);
exit(EX_USAGE);
}
};
}
let index_arg = arg_parser.index();
let command_arg = arg_parser.index() + 1;
let command_arg = index_arg as usize + 1;
argv.get(command_arg).unwrap_or_else(|| {
eprintln!("Usage: {} [-d delimiter] index command [args...]", argv[0]);
eprintln!("{}", usage);
exit(EX_USAGE);
});

View File

@ -1,95 +0,0 @@
/*
* 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
}
}

View File

@ -1,61 +0,0 @@
/*
* 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,
}

View File

@ -1,72 +0,0 @@
/*
* 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;

View File

@ -1,89 +0,0 @@
/*
* 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)
}
}

View File

@ -1,382 +0,0 @@
/*
* 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))))
}
}
}
}

View File

@ -1,59 +0,0 @@
/*
* 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>;

View File

@ -1,228 +0,0 @@
/*
* 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
}
}

View File

@ -52,7 +52,7 @@ int main(int argc, char *argv[]){
if(optind + 2 /* ref cmp */ > argc){
usage: fprintf(stderr,
"Usage: %s (-eghl) [integer] [integer...]\n",
"Usage: %s [-egl] integer integer...\n",
argv[0] == NULL ? program_name : argv[0]);
return EX_USAGE;
}

202
src/libgetopt.rs Normal file
View File

@ -0,0 +1,202 @@
/*
* Copyright (c) 20232024 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::ffi::{ c_int, c_char, CString, CStr };
/* binding to getopt(3p) */
extern "C" {
static mut optarg: *mut c_char;
static mut _opterr: c_int;
static mut optind: c_int;
static mut optopt: c_int;
fn getopt(
___argc: c_int,
___argv: *const *mut c_char,
__shortopts: *const c_char,
) -> c_int;
}
#[derive(Clone, Debug)]
pub enum OptError {
MissingArg(String),
UnknownOpt(String),
}
#[derive(Clone, Debug)]
pub struct Opt {
arg: Option<String>, /* option argument */
ind: *mut i32, /* option index */
opt: Result<String, OptError>, /* option option */
}
impl Opt {
pub fn arg(&self) -> Option<String> { self.arg.clone() }
/* sets optarg if default is desired */
pub fn arg_or(&self, default: impl std::fmt::Display) -> String {
default.to_string()
}
/* makes matching the output of this method more bearable */
pub fn opt(&self) -> Result<&str, OptError> {
self.opt.as_ref().map(|o| o.as_str()).map_err(OptError::clone)
}
/* From getopt(3p):
*
* The variable optind is the index of the next element of the argv[]
* vector to be processed. It shall be initialized to 1 by the system, and
* getopt() shall update it when it finishes with each element of argv[].
* If the application sets optind to zero before calling getopt(), the
* behavior is unspecified. When an element of argv[] contains multiple
* option characters, it is unspecified how getopt() determines which
* options have already been processed. */
pub fn ind(&self) -> usize { unsafe { *self.ind as usize } }
/* this is patently terrible and is only happening because Im stubborn */
pub fn set_ind(&self, ind: i32) { unsafe { *self.ind = ind; } }
}
/* function signature */
pub trait GetOpt {
fn getopt(&self, optstring: &str) -> Option<Opt>;
}
impl GetOpt for Vec<String> {
fn getopt(&self, optstring: &str) -> Option<Opt> {
let c_strings: Vec<_> = self
.iter()
.cloned()
.map(|x| CString::new(x).unwrap().into_raw())
.collect();
/* god knows what this does */
let boxed = Box::into_raw(c_strings.into_boxed_slice());
let argv = boxed as *const *mut c_char;
/* operations are separated out so that everything lives long enough */
let opts = CString::new(optstring).unwrap().into_raw();
let len = self.len() as c_int;
unsafe {
let ret = match getopt(len, argv, opts) {
/* From getopt(3p):
*
* The getopt() function shall return the next option character
* specified on the command line.
*
* A <colon> (':') shall be returned if getopt() detects a
* missing argument and the first character of optstring was a
* <colon> (':').
*
* A <question-mark> ('?') shall be returned if getopt()
* encounters an option character not in optstring or detects a
* missing argument and the first character of optstring was not
* a <colon> (':').
*
* Otherwise, getopt() shall return -1 when all command line
* options are parsed. */
58 => { /* ASCII ':' */
Some(Opt {
arg: None,
ind: std::ptr::addr_of_mut!(optind),
/* error containing option */
opt: Err(OptError::MissingArg(optopt.to_string())),
})
},
63 => { /* ASCII '?' */
Some(Opt {
arg: None,
ind: std::ptr::addr_of_mut!(optind),
/* error containing option */
opt: Err(OptError::UnknownOpt(optopt.to_string())),
})
},
/* From getopt(3p):
*
* 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:
*
* argv[optind] points to the string "--"
*
* getopt() shall return -1 after incrementing optind. */
-1 => return None,
opt => {
let arg: Option<String>;
if optarg.is_null() { arg = None; }
else {
arg = Some(CStr::from_ptr(optarg)
.to_string_lossy()
.into_owned());
}
Some(Opt {
arg,
ind: std::ptr::addr_of_mut!(optind),
/* I didnt need to cast this before; I rewrote the
* pointer logic and now I do
*
* I dont know why this is */
opt: Ok((opt as u8 as char).to_string()),
})
},
};
/* delloc argv (something online said I should do this) */
let _ = Box::from_raw(boxed);
return ret;
}
}
}
/* tests (good) */
#[cfg(test)]
mod tests {
use GetOpt;
#[test]
fn testing() {
let argv: Vec<String> = ["test", "-b", "-f", "arg", "-o", "arg"]
.iter()
.map(|s| s.to_string())
.collect();
while let Some(opt) = argv.getopt(":abf:o:") {
match opt.opt() {
Ok("a") => assert_eq!(opt.ind(), 1),
Ok("b") => assert_eq!(opt.ind(), 2),
Ok("f") | Ok("o") => {
assert_eq!(opt.arg(), Some("arg".into()));
},
_ => assert!(false),
};
}
if let Some(opt) = argv.getopt("abc:") {
opt.clone().set_ind(1);
assert_eq!(opt.ind(), 1);
}
}
}

View File

@ -110,7 +110,7 @@ oserr(char *s, char *r){
* returns an exit status appropriate for a usage error. */
int usage(char *s){
fprintf(stderr, "Usage: %s (-aenu) (-i [input])... (-o [output])...\n", s);
fprintf(stderr, "Usage: %s [-aenu] [-i input]... [-o output]...\n", s);
return EX_USAGE;
}

View File

@ -39,7 +39,7 @@ int main(int argc, char *argv[]){
}
if(argc > optind){
usage: fprintf(stderr, "Usage: %s (-eht)\n", argv[0]);
usage: fprintf(stderr, "Usage: %s [-et]\n", argv[0]);
return EX_USAGE;
}

View File

@ -66,7 +66,7 @@ int main(int argc, char *argv[]){
if(ops[i] == 'e')
continue;
else if(ops[i] == 'h'){
usage: fprintf(stderr, "Usage: %s (-%s) [file...]\n",
usage: fprintf(stderr, "Usage: %s [-%s] file...\n",
argv[0] == NULL
? program_name
: argv[0],

View File

@ -56,7 +56,7 @@ int main(int argc, char *argv[]){
goto pass;
}
fprintf(stderr, "Usage: %s [type] [string...]\n",
fprintf(stderr, "Usage: %s type string...\n",
argv[0] == NULL ? program_name : argv[0]);
return EX_USAGE;

View File

@ -8,7 +8,7 @@ int main(int argc, char *argv[]){
int i;
if(argc < 3){
fprintf(stderr, "Usage: %s [string] [string...]\n",
fprintf(stderr, "Usage: %s string string...\n",
argv[0] == NULL ? program_name : argv[0]);
return EX_USAGE;
}

View File

@ -24,7 +24,7 @@ use std::{
};
extern crate getopt;
use getopt::{ Opt, Parser };
use getopt::GetOpt;
extern crate sysexits;
use sysexits::{ EX_OK, EX_OSERR, EX_USAGE };
@ -35,7 +35,7 @@ fn oserr(s: &str, e: Error) -> ExitCode {
}
fn usage(s: &str) -> ExitCode {
eprintln!("Usage: {} (-f) (-w [wordsize])", s);
eprintln!("Usage: {} [-f] [-w word_size]", s);
ExitCode::from(EX_USAGE as u8)
}
@ -45,24 +45,21 @@ fn main() -> ExitCode {
let mut input = stdin();
let mut output = stdout().lock();
let mut opts = Parser::new(&argv, "fw:");
let mut force = false;
let mut wordsize: usize = 2;
loop {
match opts.next() {
None => break,
Some(opt) =>
match opt {
Ok(Opt('f', None)) => force = true,
Ok(Opt('w', Some(arg))) => {
match arg.parse::<usize>() {
Ok(w) if w % 2 == 0 => { wordsize = w; () },
_ => { return usage(&argv[0]); },
}
},
_ => { return usage(&argv[0]); }
while let Some(opt) = argv.getopt("fw:") {
match opt.opt() {
Ok("f") => force = true,
Ok("w") => {
if let Some(arg) = opt.arg() {
match arg.parse::<usize>() {
Ok(w) if w % 2 == 0 => { wordsize = w; () },
_ => { return usage(&argv[0]); },
}
}
},
_ => { return usage(&argv[0]); }
}
}