mirror of
https://github.com/gay-pizza/jaarg.git
synced 2025-12-18 23:10:17 +00:00
Create no_std examples
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
[workspace]
|
||||
members = ["jaarg"]
|
||||
default-members = ["jaarg"]
|
||||
members = ["jaarg-nostd"]
|
||||
resolver = "3"
|
||||
|
||||
[workspace.package]
|
||||
|
||||
@@ -61,8 +61,9 @@ main:
|
||||
* Moved `Opts::parse_map` into newly introduced `alloc` crate, making it accessible for `no_std` users.
|
||||
* API updates, enough public constructs to roll a custom help writer.
|
||||
* Generalised internal error & usage into `StandardErrorUsageWriter` for reuse outside the easy API & in `no_std`.
|
||||
* Fixed uncontrollable newlines in user display in easy API.
|
||||
* Fixed forced newline in user display in easy API.
|
||||
* More tests for validating internal behaviour & enabled CI on GitHub.
|
||||
* New `no_std` examples.
|
||||
|
||||
v0.1.1:
|
||||
* Fixed incorrect error message format for coerced parsing errors.
|
||||
@@ -75,7 +76,6 @@ v0.1.0:
|
||||
### Roadmap ###
|
||||
|
||||
Near future:
|
||||
* Actual `no_std` tests & examples.
|
||||
* More control over parsing behaviour (getopt style, no special casing shorts for Windows style flags, etc.)
|
||||
* More practical examples.
|
||||
|
||||
|
||||
12
jaarg-nostd/.cargo/config.toml
Normal file
12
jaarg-nostd/.cargo/config.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[target.'cfg(target_os = "macos")']
|
||||
rustflags = ["-Cpanic=abort", "-C", "link-args=-lSystem"]
|
||||
[target.'cfg(target_family = "windows")']
|
||||
rustflags = ["-Cpanic=abort", "-C", "target-feature=+crt-static"]
|
||||
[target.'cfg(target_os = "linux")']
|
||||
rustflags = ["-Cpanic=abort", "-C", "link-args=-lc"]
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
10
jaarg-nostd/Cargo.toml
Normal file
10
jaarg-nostd/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
publish = false
|
||||
name = "jaarg-nostd"
|
||||
description = "nostd examples for jaarg"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies.jaarg]
|
||||
path = "../jaarg"
|
||||
default-features = false
|
||||
features = ["alloc"]
|
||||
4
jaarg-nostd/README.md
Normal file
4
jaarg-nostd/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# ⚠ ATTENTION ⚠
|
||||
Due to cargo limitations, these examples will fail to build & link unless
|
||||
`cargo build` is ran from this directory. See `.cargo/config.toml` for
|
||||
requisite build configuration.
|
||||
68
jaarg-nostd/examples/basic_nostd.rs
Normal file
68
jaarg-nostd/examples/basic_nostd.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
/* basic_nostd - jaarg example program using parse in `no_std`
|
||||
* SPDX-FileCopyrightText: (C) 2025 Gay Pizza Specifications
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use jaarg::{
|
||||
ErrorUsageWriter, ErrorUsageWriterContext, HelpWriter, HelpWriterContext, Opt, Opts,
|
||||
ParseControl, ParseResult, StandardErrorUsageWriter, StandardFullHelpWriter
|
||||
};
|
||||
use jaarg_nostd::{print, println, harness::ExitCode, simplepathbuf::SimplePathBuf};
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(improper_ctypes_definitions)]
|
||||
extern "C" fn safe_main(args: &[&str]) -> ExitCode {
|
||||
// Variables for arguments to fill
|
||||
let mut file = SimplePathBuf::default();
|
||||
let mut out: Option<SimplePathBuf> = None;
|
||||
let mut number = 0;
|
||||
|
||||
// Set up arguments table
|
||||
enum Arg { Help, Number, File, Out }
|
||||
const OPTIONS: Opts<Arg> = Opts::new(&[
|
||||
Opt::help_flag(Arg::Help, &["-h", "--help"]).help_text("Show this help and exit."),
|
||||
Opt::value(Arg::Number, &["-n", "--number"], "value")
|
||||
.help_text("Optionally specify a number (default: 0)"),
|
||||
Opt::positional(Arg::File, "file").required()
|
||||
.help_text("Input file."),
|
||||
Opt::positional(Arg::Out, "out")
|
||||
.help_text("Output destination (optional).")
|
||||
]).with_description("My simple utility.");
|
||||
|
||||
// Parse command-line arguments from argv
|
||||
match OPTIONS.parse(
|
||||
SimplePathBuf::from(*args.first().unwrap()).basename(),
|
||||
args.iter().skip(1),
|
||||
|program_name, id, _opt, _name, arg| {
|
||||
match id {
|
||||
Arg::Help => {
|
||||
let ctx = HelpWriterContext { options: &OPTIONS, program_name };
|
||||
print!("{}", StandardFullHelpWriter::<'_, Arg>::new(ctx));
|
||||
return Ok(ParseControl::Quit);
|
||||
}
|
||||
Arg::Number => { number = str::parse(arg)?; }
|
||||
Arg::File => { file = arg.into(); }
|
||||
Arg::Out => { out = Some(arg.into()); }
|
||||
}
|
||||
Ok(ParseControl::Continue)
|
||||
}, |program_name, error| {
|
||||
let ctx = ErrorUsageWriterContext { options: &OPTIONS, program_name, error };
|
||||
print!("{}", StandardErrorUsageWriter::<'_, Arg>::new(ctx));
|
||||
}
|
||||
) {
|
||||
ParseResult::ContinueSuccess => (),
|
||||
ParseResult::ExitSuccess => { return ExitCode::SUCCESS; }
|
||||
ParseResult::ExitError => { return ExitCode::FAILURE; }
|
||||
}
|
||||
|
||||
// Print the result variables
|
||||
println!("{file} -> {out} (number: {number})",
|
||||
out = out.unwrap_or(file.with_extension("out")));
|
||||
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
46
jaarg-nostd/examples/btreemap_nostd.rs
Normal file
46
jaarg-nostd/examples/btreemap_nostd.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
/* btreemap_nostd - jaarg example program using BTreeMap in `no_std`
|
||||
* SPDX-FileCopyrightText: (C) 2025 Gay Pizza Specifications
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use jaarg::{
|
||||
alloc::ParseMapResult, ErrorUsageWriter, ErrorUsageWriterContext, HelpWriter, HelpWriterContext,
|
||||
Opt, Opts, StandardErrorUsageWriter, StandardFullHelpWriter
|
||||
};
|
||||
use jaarg_nostd::{eprint, print, println, harness::ExitCode, simplepathbuf::SimplePathBuf};
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(improper_ctypes_definitions)]
|
||||
extern "C" fn safe_main(args: &[&str]) -> ExitCode {
|
||||
const OPTIONS: Opts<&'static str> = Opts::new(&[
|
||||
Opt::help_flag("help", &["--help"]).help_text("Show this help"),
|
||||
Opt::positional("positional", "positional").help_text("Positional argument"),
|
||||
Opt::value("value", &["-v", "--value"], "string").help_text("Value option"),
|
||||
Opt::flag("flag", &["-f", "--flag"]).help_text("Flag option"),
|
||||
]);
|
||||
|
||||
let map = match OPTIONS.parse_map(
|
||||
SimplePathBuf::from(*args.first().unwrap()).basename(),
|
||||
args.iter().skip(1),
|
||||
|name| {
|
||||
let ctx = HelpWriterContext { options: &OPTIONS, program_name: name };
|
||||
print!("{}", StandardFullHelpWriter::new(ctx));
|
||||
},
|
||||
|program_name, err| {
|
||||
let ctx = ErrorUsageWriterContext { options: &OPTIONS, program_name, error: err };
|
||||
eprint!("{}", StandardErrorUsageWriter::new(ctx));
|
||||
}
|
||||
) {
|
||||
ParseMapResult::Map(map) => map,
|
||||
ParseMapResult::ExitSuccess => { return ExitCode::SUCCESS; }
|
||||
ParseMapResult::ExitFailure => { return ExitCode::FAILURE; }
|
||||
};
|
||||
|
||||
println!("{:?}", map);
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
210
jaarg-nostd/src/harness.rs
Normal file
210
jaarg-nostd/src/harness.rs
Normal file
@@ -0,0 +1,210 @@
|
||||
/* jaarg-nostd - Minimal harness to run examples in no_std on desktop
|
||||
* SPDX-FileCopyrightText: (C) 2025 Gay Pizza Specifications
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
//!! [Okay... ready for the pain?](https://media.tenor.com/cJRcMyUAiMcAAAAC/tenor.gif)
|
||||
|
||||
use core::alloc::{GlobalAlloc, Layout};
|
||||
use core::fmt::Write;
|
||||
#[allow(unused_imports)]
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
/// Unix file descriptor
|
||||
pub struct FileDescriptor(core::ffi::c_int);
|
||||
#[allow(unused)]
|
||||
impl FileDescriptor {
|
||||
/// Standard input file descriptor
|
||||
const STDIN: Self = Self(0);
|
||||
/// Standard output file descriptor
|
||||
const STDOUT: Self = Self(1);
|
||||
/// Standard error file descriptor
|
||||
const STDERR: Self = Self(2);
|
||||
}
|
||||
|
||||
pub struct StandardOutWriter;
|
||||
impl Write for StandardOutWriter {
|
||||
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
||||
unsafe {
|
||||
c::write(FileDescriptor::STDOUT.0, s.as_ptr() as *const core::ffi::c_void, s.len());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print(args: core::fmt::Arguments) {
|
||||
StandardOutWriter{}.write_fmt(args).unwrap();
|
||||
}
|
||||
|
||||
pub struct StandardErrorWriter;
|
||||
impl Write for StandardErrorWriter {
|
||||
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
||||
unsafe {
|
||||
c::write(FileDescriptor::STDERR.0, s.as_ptr() as *const core::ffi::c_void, s.len());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eprint(args: core::fmt::Arguments) {
|
||||
StandardErrorWriter{}.write_fmt(args).unwrap();
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! print {
|
||||
($($arg:tt)*) => {{ $crate::harness::print(format_args!($($arg)*)); }};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! eprint {
|
||||
($($arg:tt)*) => {{ $crate::harness::eprint(format_args!($($arg)*)); }};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! println {
|
||||
() => {{ $crate::print!("\n"); }};
|
||||
($($arg:tt)*) => {{ $crate::print!("{}\n", format_args!($($arg)*)); }};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! eprintln {
|
||||
() => {{ $crate::eprint!("\n"); }};
|
||||
($($arg:tt)*) => {{ $crate::eprint!("{}\n", format_args!($($arg)*)); }};
|
||||
}
|
||||
|
||||
/// Calls system abort
|
||||
pub fn exit(status: i32) -> ! {
|
||||
unsafe { c::exit(status as core::ffi::c_int) }
|
||||
}
|
||||
|
||||
/// Bare minimum malloc-based global allocator
|
||||
#[derive(Default)]
|
||||
pub struct MallocAlloc;
|
||||
impl MallocAlloc {
|
||||
// Fundamental alignment table:
|
||||
// | Target | 32-bit | 64-bit | Note |
|
||||
// |---------|--------|--------|-----------------------------|
|
||||
// | macOS | 16 | 16 | Always 16-byte aligned |
|
||||
// | GNU | 8 | 16 | |
|
||||
// | Windows | 8 | 16 | nonstd aligned_alloc & free |
|
||||
// | OpenBSD | 16 | 16 | FIXME: Unsourced |
|
||||
// [Darwin source](https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/MemoryAlloc.html)
|
||||
// [GNU glibc source](https://sourceware.org/glibc/manual/2.42/html_node/Malloc-Examples.html)
|
||||
// [Windows source](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/malloc?view=msvc-170)
|
||||
|
||||
// if ptr == 32 && !(os == "macos" || os == "openbsd")
|
||||
#[cfg(all(target_pointer_width = "32", not(any(target_os = "macos", target_os = "openbsd"))))]
|
||||
const ALIGNMENT: Option<usize> = Some(8);
|
||||
// if ptr == 64 || (ptr == 32 && (os == "macos" || os == "openbsd"))
|
||||
#[cfg(any(target_pointer_width = "64", all(target_pointer_width = "32", any(target_os = "macos", target_os = "openbsd"))))]
|
||||
const ALIGNMENT: Option<usize> = Some(16);
|
||||
// if !(ptr == 32 || ptr == 64)
|
||||
#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
|
||||
const ALIGNMENT: Option<usize> = None;
|
||||
|
||||
/// If target alignment % requested_align == 0 then malloc is good enough.
|
||||
#[inline(always)]
|
||||
const fn layout_can_use_malloc(requested_layout: &Layout) -> bool {
|
||||
let align = requested_layout.align();
|
||||
align != 0 && matches!(Self::ALIGNMENT,
|
||||
Some(sys_align) if (sys_align & (align - 1)) == 0)
|
||||
}
|
||||
}
|
||||
unsafe impl GlobalAlloc for MallocAlloc {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
if Self::layout_can_use_malloc(&layout) {
|
||||
c::malloc(layout.size()).cast::<u8>()
|
||||
} else {
|
||||
#[cfg(target_family = "windows")]
|
||||
return c::aligned_alloc(layout.size(), layout.align()).cast::<u8>();
|
||||
#[cfg(not(target_family = "windows"))]
|
||||
return c::aligned_alloc(layout.align(), layout.size()).cast::<u8>();
|
||||
}
|
||||
}
|
||||
#[allow(unused_variables)]
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||
#[cfg(target_family = "windows")]
|
||||
if !Self::layout_can_use_malloc(&layout) {
|
||||
c::aligned_free(ptr as *mut core::ffi::c_void);
|
||||
return;
|
||||
}
|
||||
c::free(ptr as *mut core::ffi::c_void);
|
||||
}
|
||||
}
|
||||
|
||||
#[global_allocator]
|
||||
static GLOBAL_ALLOCATOR: MallocAlloc = MallocAlloc;
|
||||
|
||||
/// Hurt me plenty (intellidumb will think this a lang duplicate pls ignore)
|
||||
#[cfg(not(test))]
|
||||
#[panic_handler]
|
||||
unsafe fn panic(info: &PanicInfo) -> ! {
|
||||
eprintln!("panic abort: {}", info.message());
|
||||
c::abort()
|
||||
}
|
||||
|
||||
/// Ultra-Violence
|
||||
#[cfg(not(test))]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_eh_personality() {}
|
||||
|
||||
/// Nightmare!
|
||||
#[cfg(not(test))]
|
||||
#[allow(non_snake_case)]
|
||||
#[no_mangle]
|
||||
extern "C" fn _Unwind_Resume() {}
|
||||
|
||||
extern "C" {
|
||||
#[allow(improper_ctypes)]
|
||||
pub fn safe_main(args: &[&str]) -> ExitCode;
|
||||
}
|
||||
|
||||
/// Exit code to be passed to entry point wrapper.
|
||||
#[allow(non_camel_case_types)]
|
||||
#[repr(i32)]
|
||||
pub enum ExitCode {
|
||||
SUCCESS = 0,
|
||||
FAILURE = 1,
|
||||
}
|
||||
|
||||
/// C main entry point, collects argc/argv and calls `safe_main`.
|
||||
#[cfg(not(test))]
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn main(argc: core::ffi::c_int, argv: *const *const core::ffi::c_char) -> core::ffi::c_int {
|
||||
let mut args = alloc::vec::Vec::<&str>::with_capacity(argc as usize);
|
||||
for i in 0..argc as usize {
|
||||
args.push(core::ffi::CStr::from_ptr(*argv.wrapping_add(i)).to_str().unwrap());
|
||||
}
|
||||
safe_main(&args) as core::ffi::c_int
|
||||
}
|
||||
|
||||
mod c {
|
||||
use core::ffi::{c_int, c_void};
|
||||
|
||||
/// Until size_t is stabilised
|
||||
#[allow(non_camel_case_types)]
|
||||
type c_size_t = usize;
|
||||
|
||||
#[allow(dead_code)]
|
||||
extern "C" {
|
||||
pub(crate) fn atexit(function: extern "C" fn()) -> c_int;
|
||||
pub(crate) fn abort() -> !;
|
||||
pub(crate) fn exit(status: c_int) -> !;
|
||||
#[cfg(not(target_family = "windows"))]
|
||||
pub(crate) fn write(fd: c_int, buf: *const c_void, bytes: c_size_t) -> c_int;
|
||||
#[cfg(target_family = "windows")]
|
||||
#[link_name = "_write"]
|
||||
pub(crate) fn write(fd: c_int, buf: *const c_void, bytes: c_size_t) -> c_int;
|
||||
pub(crate) fn malloc(size: c_size_t) -> *mut c_void;
|
||||
pub(crate) fn calloc(count: c_size_t, size: c_size_t) -> *mut c_void;
|
||||
#[cfg(not(target_family = "windows"))]
|
||||
pub(crate) fn aligned_alloc(alignment: c_size_t, size: c_size_t) -> *mut c_void;
|
||||
#[cfg(target_family = "windows")]
|
||||
#[link_name = "_aligned_malloc"]
|
||||
pub(crate) fn aligned_alloc(size: c_size_t, alignment: c_size_t) -> *mut c_void;
|
||||
#[cfg(target_family = "windows")]
|
||||
#[link_name = "_aligned_free"]
|
||||
pub(crate) fn aligned_free(memblock: *mut c_void);
|
||||
pub(crate) fn free(ptr: *mut c_void);
|
||||
}
|
||||
}
|
||||
6
jaarg-nostd/src/lib.rs
Normal file
6
jaarg-nostd/src/lib.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub mod harness;
|
||||
pub mod simplepathbuf;
|
||||
81
jaarg-nostd/src/simplepathbuf.rs
Normal file
81
jaarg-nostd/src/simplepathbuf.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
/* jaarg-nostd - Minimal harness to run examples in no_std on desktop
|
||||
* SPDX-FileCopyrightText: (C) 2025 Gay Pizza Specifications
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use core::fmt::{Display, Formatter};
|
||||
|
||||
/// Dirty and simple path buffer that's good enough for the `no_std` examples, not for production use.
|
||||
#[derive(Default, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct SimplePathBuf(String);
|
||||
|
||||
impl<S: AsRef<str>> From<S> for SimplePathBuf where String: From<S> {
|
||||
fn from(value: S) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SimplePathBuf {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl SimplePathBuf {
|
||||
#[inline(always)]
|
||||
fn path_predicate(c: char) -> bool {
|
||||
#[cfg(target_family = "windows")]
|
||||
if c == '\\' { return true; }
|
||||
c == '/'
|
||||
}
|
||||
|
||||
pub fn with_extension(&self, ext: &str) -> Self {
|
||||
let dir_sep = self.0.rfind(Self::path_predicate)
|
||||
.map_or(0, |n| n + 1);
|
||||
let without_ext: &str = self.0[dir_sep..].rfind('.')
|
||||
.map_or(&self.0, |ext_sep_rel| {
|
||||
if ext_sep_rel == 0 { return &self.0; }
|
||||
let ext_sep = dir_sep + ext_sep_rel;
|
||||
&self.0[..ext_sep]
|
||||
});
|
||||
Self(format!("{without_ext}.{ext}"))
|
||||
}
|
||||
|
||||
pub fn basename(&self) -> &str {
|
||||
self.0.trim_end_matches(|c| Self::path_predicate(c) || c == '.')
|
||||
.rsplit_once(Self::path_predicate)
|
||||
.map_or(&self.0, |(_, base)| base)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_with_extension() {
|
||||
assert_eq!(
|
||||
SimplePathBuf::from("name.ext").with_extension("new"),
|
||||
SimplePathBuf::from("name.new"));
|
||||
assert_eq!(
|
||||
SimplePathBuf::from("/path/name.ext").with_extension("new"),
|
||||
SimplePathBuf::from("/path/name.new"));
|
||||
assert_eq!(
|
||||
SimplePathBuf::from("/path.ext/name").with_extension("new"),
|
||||
SimplePathBuf::from("/path.ext/name.new"));
|
||||
assert_eq!(
|
||||
SimplePathBuf::from("/path.ext/.name").with_extension("new"),
|
||||
SimplePathBuf::from("/path.ext/.name.new"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basename() {
|
||||
assert_eq!(SimplePathBuf::from("name.ext").basename(), "name.ext");
|
||||
assert_eq!(SimplePathBuf::from("/path/name.ext").basename(), "name.ext");
|
||||
assert_eq!(SimplePathBuf::from("/path/name/").basename(), "name");
|
||||
assert_eq!(SimplePathBuf::from("/path/name/.").basename(), "name");
|
||||
assert_eq!(SimplePathBuf::from("/path/name/.//").basename(), "name");
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,10 @@
|
||||
|
||||
extern crate std;
|
||||
|
||||
use crate::alloc::ParseMapResult;
|
||||
use crate::{ErrorUsageWriter, ErrorUsageWriterContext, HandlerResult, HelpWriter, HelpWriterContext, Opt, Opts, ParseControl, ParseError, ParseResult, StandardErrorUsageWriter, StandardFullHelpWriter};
|
||||
use crate::{
|
||||
ErrorUsageWriter, ErrorUsageWriterContext, HandlerResult, HelpWriter, HelpWriterContext, Opt, Opts,
|
||||
ParseControl, ParseError, ParseResult, StandardErrorUsageWriter, StandardFullHelpWriter, alloc::ParseMapResult
|
||||
};
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use std::{env, eprint, print};
|
||||
|
||||
Reference in New Issue
Block a user