Skip to content

Commit

Permalink
&OsStr support
Browse files Browse the repository at this point in the history
Implement `&OsStr as Argument` on top of `&[u8] as Argument`.

The unsafe code to convert back from the sliced `&[u8]` keeps to the
safety invariants defined for the `OsStr::from_encoded_bytes_unchecked`
function.
  • Loading branch information
EliteTK committed Oct 2, 2024
1 parent 574e253 commit d2bd7f8
Show file tree
Hide file tree
Showing 8 changed files with 240 additions and 26 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ you do with it.

* Zero cost
* Zero copy
* Zero unsafe code
* Zero unsafe code except for `&OsStr`
* Zero dependencies
* Zero allocation
* Simple to use yet versatile
* `#![no_std]`-compatible
* Compatible with `&str` and `&[u8]`
* `#![no_std]`-compatible except for `&OsStr`
* Compatible with `&str`, `&[u8]` and `&OsStr`

## Performance

Expand Down
36 changes: 36 additions & 0 deletions bench/examples/vs.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use bench::{ARGS, ARGS_BYTES};
use getargs::Argument;
use std::ffi::OsStr;
use std::hint::black_box;
use std::time::Instant;

Expand Down Expand Up @@ -104,6 +105,34 @@ fn getargs5b<'arg, I: Iterator<Item = &'arg [u8]>>(iter: I) -> Settings<&'arg [u
settings
}

#[inline(never)]
fn getargs5o<'arg, I: Iterator<Item = &'arg OsStr>>(iter: I) -> Settings<&'arg OsStr> {
use getargs::{Opt, Options};

let mut settings = Settings::default();
let mut opts = Options::new(iter);

while let Some(opt) = opts.next_opt().unwrap() {
match opt {
Opt::Short('1') => settings.short_present1 = true,
Opt::Short('2') => settings.short_present2 = true,
Opt::Short('3') => settings.short_present3 = true,
Opt::Long("present1") => settings.long_present1 = true,
Opt::Long("present2") => settings.long_present2 = true,
Opt::Long("present3") => settings.long_present3 = true,
Opt::Short('4') => settings.short_value1 = Some(opts.value().unwrap()),
Opt::Short('5') => settings.short_value2 = Some(opts.value().unwrap()),
Opt::Short('6') => settings.short_value3 = Some(opts.value().unwrap()),
Opt::Long("val1") => settings.long_value1 = Some(opts.value().unwrap()),
Opt::Long("val2") => settings.long_value2 = Some(opts.value().unwrap()),
Opt::Long("val3") => settings.long_value3 = Some(opts.value().unwrap()),
_ => {}
}
}

settings
}

fn main() {
const ITERATIONS: usize = 10_000_000;

Expand All @@ -127,7 +156,14 @@ fn main() {

let d = Instant::now();

for _ in 0..ITERATIONS {
black_box(getargs5o(ARGS.iter().copied().map(AsRef::as_ref)));
}

let e = Instant::now();

eprintln!("getargs4: {}ns", (b - a).as_nanos() / ITERATIONS as u128);
eprintln!("getargs5: {}ns", (c - b).as_nanos() / ITERATIONS as u128);
eprintln!("getargs5b: {}ns", (d - c).as_nanos() / ITERATIONS as u128);
eprintln!("getargs5o: {}ns", (e - d).as_nanos() / ITERATIONS as u128);
}
79 changes: 79 additions & 0 deletions bench/src/evolution.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#![allow(non_snake_case)]

use std::ffi::OsStr;

use crate::{ARGS, ARGS_BYTES};
use getargs::Argument;
use test::Bencher;
Expand Down Expand Up @@ -161,6 +163,34 @@ fn getargsLb<'arg, I: Iterator<Item = &'arg [u8]>>(iter: I) -> Settings<&'arg [u
settings
}

#[inline(always)]
fn getargsLo<'arg, I: Iterator<Item = &'arg OsStr>>(iter: I) -> Settings<&'arg OsStr> {
use getargs::{Opt, Options};

let mut settings = Settings::default();
let mut opts = Options::new(iter);

while let Some(opt) = opts.next_opt().unwrap() {
match opt {
Opt::Short('1') => settings.short_present1 = true,
Opt::Short('2') => settings.short_present2 = true,
Opt::Short('3') => settings.short_present3 = true,
Opt::Long("present1") => settings.long_present1 = true,
Opt::Long("present2") => settings.long_present2 = true,
Opt::Long("present3") => settings.long_present3 = true,
Opt::Short('4') => settings.short_value1 = Some(opts.value().unwrap()),
Opt::Short('5') => settings.short_value2 = Some(opts.value().unwrap()),
Opt::Short('6') => settings.short_value3 = Some(opts.value().unwrap()),
Opt::Long("val1") => settings.long_value1 = Some(opts.value().unwrap()),
Opt::Long("val2") => settings.long_value2 = Some(opts.value().unwrap()),
Opt::Long("val3") => settings.long_value3 = Some(opts.value().unwrap()),
_ => {}
}
}

settings
}

#[bench]
#[inline(never)]
fn getargs4_varied_small(bencher: &mut Bencher) {
Expand Down Expand Up @@ -191,6 +221,13 @@ fn getargsLb_varied_small(bencher: &mut Bencher) {
bencher.iter(|| getargsLb(ARGS_BYTES.iter().copied()));
}

#[bench]
#[inline(never)]
fn getargsLo_varied_small(bencher: &mut Bencher) {
let args_os: Box<[&OsStr]> = ARGS.iter().copied().map(AsRef::as_ref).collect();
bencher.iter(|| getargsLo(args_os.iter().copied()));
}

pub const ARGS_LONG: [&str; 1000] = ["--dsfigadsjfdgsfjkasbfjksdfabsdbfdaf"; 1000];
pub const ARGS_LONG_BYTES: [&[u8]; 1000] = [b"--dsfigadsjfdgsfjkasbfjksdfabsdbfdaf"; 1000];

Expand Down Expand Up @@ -224,6 +261,13 @@ fn getargsLb_long(bencher: &mut Bencher) {
bencher.iter(|| getargsLb(ARGS_LONG_BYTES.iter().copied()));
}

#[bench]
#[inline(never)]
fn getargsLo_long(bencher: &mut Bencher) {
let args_os: Vec<&OsStr> = ARGS_LONG.iter().copied().map(AsRef::as_ref).collect();
bencher.iter(|| getargsLo(args_os.iter().copied()));
}

pub const ARGS_SHORT_CLUSTER: [&str; 1000] =
["-rjryets8kzrlxu7lzvnmsooiac8u9lxluphwrfudxaitfdomtce78grull9cpcvk7lyi07mdoclybtolssg7w7kwei79k"; 1000];

Expand Down Expand Up @@ -260,6 +304,13 @@ fn getargsLb_short_cluster(bencher: &mut Bencher) {
bencher.iter(|| getargsLb(ARGS_SHORT_CLUSTER_BYTES.iter().copied()));
}

#[bench]
#[inline(never)]
fn getargsLo_short_cluster(bencher: &mut Bencher) {
let args_os: Vec<&OsStr> = ARGS_SHORT_CLUSTER.iter().copied().map(AsRef::as_ref).collect();
bencher.iter(|| getargsLo(args_os.iter().copied()));
}

pub const ARGS_SHORT_EVALUE: [&str; 1000] =
["-rjryets8kzrlxu7lzvnmso4oiac8u9lxluphwrfudxaitfdomtce78grull9cpcvk7lyi07mdoclybtolssg7w7kwei79k"; 1000];

Expand Down Expand Up @@ -296,6 +347,13 @@ fn getargsLb_short_evalue(bencher: &mut Bencher) {
bencher.iter(|| getargsLb(ARGS_SHORT_EVALUE_BYTES.iter().copied()));
}

#[bench]
#[inline(never)]
fn getargsLo_short_evalue(bencher: &mut Bencher) {
let args_os: Vec<&OsStr> = ARGS_SHORT_EVALUE.iter().copied().map(AsRef::as_ref).collect();
bencher.iter(|| getargsLo(args_os.iter().copied()));
}

pub const ARGS_SHORT_IVALUE: [&str; 1000] =
["-rjryets8kzrlxu7lzvnmsooiac8u9lxluphwrfudxaitfdomtce78grull9cpcvk7lyi07mdoclybtolssg7w7kwei79k4"; 1000];

Expand Down Expand Up @@ -332,6 +390,13 @@ fn getargsLb_short_ivalue(bencher: &mut Bencher) {
bencher.iter(|| getargsLb(ARGS_SHORT_IVALUE_BYTES.iter().copied()));
}

#[bench]
#[inline(never)]
fn getargsLo_short_ivalue(bencher: &mut Bencher) {
let args_os: Vec<&OsStr> = ARGS_SHORT_IVALUE.iter().copied().map(AsRef::as_ref).collect();
bencher.iter(|| getargsLo(args_os.iter().copied()));
}

pub const ARGS_LONG_EVALUE: [&str; 1000] =
["--val1=rjryets8kzrlxu7lzvnms4ooiac8u9lxluphwrfudxaitfdomtce78grull9cpcvk7lyi07mdoclybtolssg7w7kwei79k"; 1000];

Expand Down Expand Up @@ -368,6 +433,13 @@ fn getargsLb_long_evalue(bencher: &mut Bencher) {
bencher.iter(|| getargsLb(ARGS_LONG_EVALUE_BYTES.iter().copied()));
}

#[bench]
#[inline(never)]
fn getargsLo_long_evalue(bencher: &mut Bencher) {
let args_os: Vec<&OsStr> = ARGS_LONG_EVALUE.iter().copied().map(AsRef::as_ref).collect();
bencher.iter(|| getargsLo(args_os.iter().copied()));
}

pub const ARGS_LONG_IVALUE: [&str; 1000] = ["--val1"; 1000];
pub const ARGS_LONG_IVALUE_BYTES: [&[u8]; 1000] = [b"--val1"; 1000];

Expand Down Expand Up @@ -400,3 +472,10 @@ fn getargsL_long_ivalue(bencher: &mut Bencher) {
fn getargsLb_long_ivalue(bencher: &mut Bencher) {
bencher.iter(|| getargsLb(ARGS_LONG_IVALUE_BYTES.iter().copied()));
}

#[bench]
#[inline(never)]
fn getargsLo_long_ivalue(bencher: &mut Bencher) {
let args_os: Vec<&OsStr> = ARGS_LONG_IVALUE.iter().copied().map(AsRef::as_ref).collect();
bencher.iter(|| getargsLo(args_os.iter().copied()));
}
9 changes: 3 additions & 6 deletions examples/no_alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,10 @@
//! Additionally, all strings and errors are annotated with the correct lifetimes, so that the
//! lifetime of the iterator itself does not matter so much anymore.
use getargs::{Opt, Options};
use getargs::{Argument, Opt, Options};

fn main() {
let args = argv::iter().skip(1).map(|os| {
os.to_str()
.expect("argument couldn't be converted to UTF-8")
});
let args = argv::iter().skip(1);

let mut opts = Options::new(args);

Expand All @@ -24,6 +21,6 @@ fn main() {
}

for positional in opts.positionals() {
eprintln!("positional argument: {}", positional);
eprintln!("positional argument: {}", Argument::display(positional));
}
}
22 changes: 7 additions & 15 deletions examples/os_str.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,22 @@
use getargs::{Arg, Options};
use std::ffi::OsStr;
use getargs::{Arg, Argument, Options};
use std::ffi::OsString;
use std::path::PathBuf;

#[cfg(unix)]
fn main() {
use std::os::unix::ffi::OsStrExt;

let args: Vec<_> = std::env::args_os().skip(1).collect();

let mut opts = Options::new(args.iter().map(|s| s.as_bytes()));
let mut opts = Options::new(args.iter().map(OsString::as_os_str));

while let Some(arg) = opts.next_arg().expect("usage error") {
match arg {
Arg::Short('f') | Arg::Long("file") => {
let f = OsStr::from_bytes(opts.value().expect("usage error"));
let f = PathBuf::from(opts.value().expect("usage error"));
println!("file option: {f:?}");
}
Arg::Positional(pos) => {
let pos = OsStr::from_bytes(pos);
println!("positional: {pos:?}");
println!("positional: {}", Argument::display(pos));
}
_ => println!("other: {arg:?}"),
_ => println!("other: {}", arg),
}
}
}

#[cfg(not(unix))]
fn main() {
eprintln!("Only supported on Unix because UTF-16 is hard, sorry :(");
}
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@
//!
//! * Zero cost
//! * Zero copy
//! * Zero unsafe code
//! * Zero unsafe code except for `&OsStr`
//! * Zero dependencies
//! * Zero allocation
//! * Simple to use yet versatile
//! * `#![no_std]`-compatible
//! * Compatible with `&str` and `&[u8]`
//! * Compatible with `&str`, `&[u8]` and `&OsStr`
//!
//! ## Performance
//!
Expand Down
47 changes: 47 additions & 0 deletions src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::ffi::OsStr;

use super::*;

#[test]
Expand Down Expand Up @@ -574,3 +576,48 @@ fn repeating_iterator() {
assert_eq!(opts.next_positional(), None);
assert!(opts.is_empty());
}

#[test]
fn os_str() {
let args = [
"-ohi",
"--opt=HI",
"-o",
"hi",
"--opt",
"hi",
"--optional",
"--optional=value",
"-O",
"-Ovalue",
"--",
"one",
"two",
];

let mut opts = Options::<'_, &OsStr, _>::new(args.into_iter().map(AsRef::as_ref));

assert_eq!(opts.next_opt(), Ok(Some(Opt::Short('o'))));
assert_eq!(opts.value(), Ok("hi".as_ref()));
assert_eq!(opts.next_opt(), Ok(Some(Opt::Long("opt"))));
assert_eq!(opts.value(), Ok("HI".as_ref()));
assert_eq!(opts.next_opt(), Ok(Some(Opt::Short('o'))));
assert_eq!(opts.value(), Ok("hi".as_ref()));
assert_eq!(opts.next_opt(), Ok(Some(Opt::Long("opt"))));
assert_eq!(opts.value(), Ok("hi".as_ref()));
assert_eq!(opts.next_opt(), Ok(Some(Opt::Long("optional"))));
assert_eq!(opts.value_opt(), None);
assert_eq!(opts.next_opt(), Ok(Some(Opt::Long("optional"))));
assert_eq!(opts.value_opt(), Some("value".as_ref()));
assert_eq!(opts.next_opt(), Ok(Some(Opt::Short('O'))));
assert_eq!(opts.value_opt(), None);
assert_eq!(opts.next_opt(), Ok(Some(Opt::Short('O'))));
assert_eq!(opts.value_opt(), Some("value".as_ref()));
assert_eq!(opts.next_opt(), Ok(None));
assert!(opts.opts_ended());
assert_eq!(opts.next_positional(), Some("one".as_ref()));
assert_eq!(opts.next_positional(), Some("two".as_ref()));
assert_eq!(opts.next_positional(), None);
assert!(opts.opts_ended());
assert!(opts.is_empty());
}
Loading

0 comments on commit d2bd7f8

Please sign in to comment.