mirror of
https://github.com/gay-pizza/jaarg.git
synced 2025-12-19 07:20:18 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a013e86067 | |||
| b3091583ed | |||
| ae7c12ad62 | |||
| 274fbbf097 | |||
| 6d42221332 | |||
| 6b26188990 |
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "jaarg"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
license = "MIT"
|
||||
edition = "2021"
|
||||
description = "It can parse your arguments you should use it it's called jaarg"
|
||||
|
||||
69
README.md
69
README.md
@@ -1 +1,70 @@
|
||||
# jaarg argument parser library #
|
||||
nostd & (mostly) const, some say it can parse your arguments.
|
||||
|
||||
### Obligatory fancy banners ###
|
||||
<div>
|
||||
<a href="https://crates.io/crates/jaarg">
|
||||
<img src="https://img.shields.io/crates/v/jaarg.svg?logo=rust&style=for-the-badge" alt="Crates version" />
|
||||
</a>
|
||||
<a href="LICENSE">
|
||||
<img src="https://img.shields.io/badge/license-MIT-green.svg?style=for-the-badge" alt="MIT License" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
### Example usage ###
|
||||
```rust
|
||||
// Variables for arguments to fill
|
||||
let mut file = PathBuf::new();
|
||||
let mut out: Option<PathBuf> = 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 `std::env::args()`
|
||||
match OPTIONS.parse_easy(|program_name, id, _opt, _name, arg| {
|
||||
match id {
|
||||
Arg::Help => {
|
||||
OPTIONS.print_full_help(program_name);
|
||||
return Ok(ParseControl::Quit);
|
||||
}
|
||||
Arg::Number => { number = str::parse(arg)?; }
|
||||
Arg::File => { file = arg.into(); }
|
||||
Arg::Out => { out = Some(arg.into()); }
|
||||
}
|
||||
Ok(ParseControl::Continue)
|
||||
}) {
|
||||
ParseResult::ContinueSuccess => (),
|
||||
ParseResult::ExitSuccess => std::process::exit(0),
|
||||
ParseResult::ExitError => std::process::exit(1),
|
||||
}
|
||||
|
||||
// Print the result variables
|
||||
println!("{file:?} -> {out:?} (number: {number:?})",
|
||||
out = out.unwrap_or(file.with_extension("out")));
|
||||
```
|
||||
|
||||
### Changelog ###
|
||||
|
||||
<!-- main: -->
|
||||
|
||||
v0.1.1:
|
||||
* Fixed incorrect error message format for coerced parsing errors.
|
||||
* Cleaned up docstring formatting.
|
||||
* Added basic example.
|
||||
|
||||
v0.1.0:
|
||||
* Initial release.
|
||||
|
||||
### Projects using jaarg (very cool) ###
|
||||
<!-- soon... * [Sprout bootloader](https://github.com/edera-dev/sprout) -->
|
||||
* [lbminfo](https://github.com/ScrelliCopter/colourcyclinginthehousetonight/tree/main/lbminfo)
|
||||
|
||||
48
examples/basic.rs
Normal file
48
examples/basic.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
/* basic - jaarg example program using parse_easy
|
||||
* SPDX-FileCopyrightText: (C) 2025 Gay Pizza Specifications
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
use jaarg::{Opt, Opts, ParseControl, ParseResult};
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Variables for arguments to fill
|
||||
let mut file = PathBuf::new();
|
||||
let mut out: Option<PathBuf> = 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 `std::env::args()`
|
||||
match OPTIONS.parse_easy(|program_name, id, _opt, _name, arg| {
|
||||
match id {
|
||||
Arg::Help => {
|
||||
OPTIONS.print_full_help(program_name);
|
||||
return Ok(ParseControl::Quit);
|
||||
}
|
||||
Arg::Number => { number = str::parse(arg)?; }
|
||||
Arg::File => { file = arg.into(); }
|
||||
Arg::Out => { out = Some(arg.into()); }
|
||||
}
|
||||
Ok(ParseControl::Continue)
|
||||
}) {
|
||||
ParseResult::ContinueSuccess => (),
|
||||
ParseResult::ExitSuccess => std::process::exit(0),
|
||||
ParseResult::ExitError => std::process::exit(1),
|
||||
}
|
||||
|
||||
// Print the result variables
|
||||
println!("{file:?} -> {out:?} (number: {number:?})",
|
||||
out = out.unwrap_or(file.with_extension("out")));
|
||||
}
|
||||
@@ -6,15 +6,15 @@
|
||||
/// Enum describing the result of parsing arguments, and how the program should behave.
|
||||
#[derive(Debug)]
|
||||
pub enum ParseResult {
|
||||
/// Parsing succeeded and program execution should continue
|
||||
/// Parsing succeeded and program execution should continue.
|
||||
ContinueSuccess,
|
||||
/// Parsing succeeded and program should exit with success (eg; std::process::ExitCode::SUCCESS)
|
||||
/// Parsing succeeded and program should exit with success (eg; [std::process::ExitCode::SUCCESS]).
|
||||
ExitSuccess,
|
||||
/// There was an error while parsing and program should exit with failure (eg; std::process::ExitCode::FAILURE)
|
||||
/// There was an error while parsing and program should exit with failure (eg; [std::process::ExitCode::FAILURE]).
|
||||
ExitError,
|
||||
}
|
||||
|
||||
/// Execution control for the parser handler
|
||||
/// Execution control for parser handlers.
|
||||
pub enum ParseControl {
|
||||
/// Continue parsing arguments
|
||||
Continue,
|
||||
@@ -24,7 +24,7 @@ pub enum ParseControl {
|
||||
Quit,
|
||||
}
|
||||
|
||||
/// Result type used by the handler passed to the parser
|
||||
/// Result type used by the handler passed to the parser.
|
||||
type HandlerResult<'a, T> = core::result::Result<T, ParseError<'a>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -69,7 +69,7 @@ impl core::fmt::Display for ParseError<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience coercion for dealing with integer parsing errors
|
||||
/// Convenience coercion for dealing with integer parsing errors.
|
||||
impl From<core::num::ParseIntError> for ParseError<'_> {
|
||||
fn from(err: core::num::ParseIntError) -> Self {
|
||||
use core::num::IntErrorKind;
|
||||
@@ -83,7 +83,7 @@ impl From<core::num::ParseIntError> for ParseError<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience coercion for dealing with floating-point parsing errors
|
||||
/// Convenience coercion for dealing with floating-point parsing errors.
|
||||
impl From<core::num::ParseFloatError> for ParseError<'_> {
|
||||
fn from(_err: core::num::ParseFloatError) -> Self {
|
||||
// HACK: The empty option & argument fields will be fixed up by the parser
|
||||
@@ -94,7 +94,7 @@ impl From<core::num::ParseFloatError> for ParseError<'_> {
|
||||
|
||||
impl core::error::Error for ParseError<'_> {}
|
||||
|
||||
/// Internal state tracked by the parser
|
||||
/// Internal state tracked by the parser.
|
||||
struct ParserState<ID: 'static> {
|
||||
positional_index: usize,
|
||||
expects_arg: Option<(&'static str, &'static Opt<ID>)>,
|
||||
@@ -112,7 +112,7 @@ impl<ID> Default for ParserState<ID> {
|
||||
}
|
||||
|
||||
impl<ID: 'static> Opts<ID> {
|
||||
/// Parse an iterator of strings as arguments
|
||||
/// Parses an iterator of strings as argument tokens.
|
||||
pub fn parse<'a, S: AsRef<str> + 'a, I: Iterator<Item = S>>(&self, program_name: &str, args: I,
|
||||
mut handler: impl FnMut(&str, &ID, &Opt<ID>, &str, &str) -> HandlerResult<'a, ParseControl>,
|
||||
error: impl FnOnce(&str, ParseError),
|
||||
@@ -169,7 +169,7 @@ impl<ID: 'static> Opts<ID> {
|
||||
// HACK: Ensure the string fields are set properly, because coerced
|
||||
// ParseIntError/ParseFloatError will have the string fields blanked.
|
||||
Err(ParseError::ArgumentError("", "", kind))
|
||||
=> Err(ParseError::ArgumentError(name, token, kind)),
|
||||
=> Err(ParseError::ArgumentError(name, value, kind)),
|
||||
Err(err) => Err(err),
|
||||
Ok(ctl) => Ok(ctl),
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ impl<'a> CharIterator<'a> {
|
||||
|
||||
impl CharIterator<'_> {
|
||||
/// Gets a count of the number of Unicode characters (not graphemes) in the string.
|
||||
pub(crate) const fn count(self) -> usize {
|
||||
pub(crate) const fn count(&self) -> usize {
|
||||
let len = self.bytes.len();
|
||||
let mut count = 0;
|
||||
let mut i = 0;
|
||||
@@ -123,3 +123,19 @@ impl CharIterator<'_> {
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
for s in ["pizza", "/ˈpitt͡sə/", "pizzaskjærer", "🍕", "比薩", "ピザ", "Ćevapi", "🏳️⚧️"] {
|
||||
let mut it = CharIterator::from(s);
|
||||
assert_eq!(it.count(), s.chars().count());
|
||||
s.chars().for_each(|c| assert_eq!(it.next(), Some(c)));
|
||||
assert_eq!(it.next(), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ enum OptIdentifier {
|
||||
Multi(&'static[&'static str]),
|
||||
}
|
||||
|
||||
/// Represents an option argument or positional argument to be parsed
|
||||
/// Represents an option argument or positional argument to be parsed.
|
||||
#[derive(Debug)]
|
||||
pub struct Opt<ID> {
|
||||
id: ID,
|
||||
@@ -48,20 +48,20 @@ impl<ID> Opt<ID> {
|
||||
Self { id, names, value_name, help_string: None, r#type, flags: OptFlag::NONE }
|
||||
}
|
||||
|
||||
/// A positional argument that is parsed sequentially without being invoked by an option flag
|
||||
/// A positional argument that is parsed sequentially without being invoked by an option flag.
|
||||
pub const fn positional(id: ID, name: &'static str) -> Self {
|
||||
Self::new(id, OptIdentifier::Single(name), None, OptType::Positional)
|
||||
}
|
||||
/// A flag-type option that serves as the interface's help flag
|
||||
/// A flag-type option that serves as the interface's help flag.
|
||||
pub const fn help_flag(id: ID, names: &'static[&'static str]) -> Self {
|
||||
Self::new(id, OptIdentifier::Multi(names), None, OptType::Flag)
|
||||
.with_help_flag()
|
||||
}
|
||||
/// A flag-type option, takes no value
|
||||
/// A flag-type option, takes no value.
|
||||
pub const fn flag(id: ID, names: &'static[&'static str]) -> Self {
|
||||
Self::new(id, OptIdentifier::Multi(names), None, OptType::Flag)
|
||||
}
|
||||
/// An option argument that takes a value
|
||||
/// An option argument that takes a value.
|
||||
pub const fn value(id: ID, names: &'static[&'static str], value_name: &'static str) -> Self {
|
||||
Self::new(id, OptIdentifier::Multi(names), Some(value_name), OptType::Value)
|
||||
}
|
||||
@@ -93,7 +93,7 @@ impl<ID> Opt<ID> {
|
||||
}
|
||||
|
||||
impl<ID: 'static> Opt<ID> {
|
||||
/// Get the first name of the option
|
||||
/// Get the first name of the option.
|
||||
const fn first_name(&self) -> &str {
|
||||
match self.names {
|
||||
OptIdentifier::Single(name) => name,
|
||||
@@ -101,7 +101,7 @@ impl<ID: 'static> Opt<ID> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the first long option name, if one exists
|
||||
/// Get the first long option name, if one exists.
|
||||
const fn first_long_name(&self) -> Option<&'static str> {
|
||||
match self.names {
|
||||
OptIdentifier::Single(name) => if name.len() >= 3 { Some(name) } else { None },
|
||||
@@ -119,7 +119,7 @@ impl<ID: 'static> Opt<ID> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the first short option name, if one exists
|
||||
/// Get the first short option name, if one exists.
|
||||
const fn first_short_name(&self) -> Option<&'static str> {
|
||||
const fn predicate(name: &str) -> bool {
|
||||
let mut chars = const_utf8::CharIterator::from(name);
|
||||
@@ -148,7 +148,7 @@ impl<ID: 'static> Opt<ID> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the first applicable short option's flag character, if one exists
|
||||
/// Get the first applicable short option's flag character, if one exists.
|
||||
const fn first_short_name_char(&self) -> Option<char> {
|
||||
const fn predicate(name: &str) -> Option<char> {
|
||||
let mut chars = const_utf8::CharIterator::from(name);
|
||||
@@ -163,7 +163,7 @@ impl<ID: 'static> Opt<ID> {
|
||||
}
|
||||
match self.names {
|
||||
OptIdentifier::Single(name) => predicate(&name),
|
||||
// Can be replaced with `find_map` once iterators are const fn
|
||||
// Can be replaced with `find_map` once iterators are const fn.
|
||||
OptIdentifier::Multi(names) => {
|
||||
let mut i = 0;
|
||||
while i < names.len() {
|
||||
@@ -177,7 +177,7 @@ impl<ID: 'static> Opt<ID> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Search for a matching name in the option, offset allows to skip the first characters in the comparison
|
||||
/// Search for a matching name in the option, offset allows to skip the first `n = offset` characters in the comparison.
|
||||
fn match_name(&self, string: &str, offset: usize) -> Option<&'static str> {
|
||||
match self.names {
|
||||
OptIdentifier::Single(name) =>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/// Static structure that contains instructions for parsing command-line arguments
|
||||
/// Static structure that contains instructions for parsing command-line arguments.
|
||||
pub struct Opts<ID: 'static> {
|
||||
/// List of options
|
||||
options: &'static[Opt<ID>],
|
||||
@@ -19,7 +19,7 @@ type RequiredParamsBitSet = ordered_bitset::OrderedBitSet<u32, 4>;
|
||||
pub const MAX_REQUIRED_OPTIONS: usize = RequiredParamsBitSet::CAPACITY;
|
||||
|
||||
impl<ID: 'static> Opts<ID> {
|
||||
/// Build argument parser options with the default flag character of '-'
|
||||
/// Build argument parser options with the default flag character of '-'.
|
||||
pub const fn new(options: &'static[Opt<ID>]) -> Self {
|
||||
// Validate passed options
|
||||
let mut opt_idx = 0;
|
||||
|
||||
26
src/std.rs
26
src/std.rs
@@ -13,34 +13,34 @@ use std::string::String;
|
||||
use std::{env, eprintln, println};
|
||||
|
||||
impl<ID: 'static> Opts<ID> {
|
||||
/// Wrapper around `jaarg::parse` that gathers arguments from the command line and prints errors to stderr.
|
||||
/// Wrapper around [Opts::parse] that gathers arguments from the command line and prints errors to stderr.
|
||||
/// The errors are formatted in a standard user-friendly format.
|
||||
///
|
||||
/// Requires features = [std]
|
||||
/// Requires `features = [std]`.
|
||||
pub fn parse_easy<'a>(&self, handler: impl FnMut(&str, &ID, &Opt<ID>, &str, &str) -> HandlerResult<'a, ParseControl>
|
||||
) -> ParseResult {
|
||||
let (program_name, argv) = Self::easy_args();
|
||||
self.parse(&program_name, argv, handler, |name, e| self.easy_error(name, e))
|
||||
}
|
||||
|
||||
/// Prints full help text for the options using the standard full
|
||||
/// Prints full help text for the options using the standard full.
|
||||
///
|
||||
/// Requires features = [std]
|
||||
/// Requires `features = [std]`.
|
||||
pub fn print_full_help(&self, program_name: &str) {
|
||||
self.print_help::<StandardFullHelpWriter<'_, ID>>(program_name);
|
||||
}
|
||||
|
||||
/// Print help text to stdout using the provided help writer
|
||||
/// Print help text to stdout using the provided help writer.
|
||||
///
|
||||
/// Requires features = [std]
|
||||
/// Requires `features = [std]`.
|
||||
pub fn print_help<'a, W: HelpWriter<'a, ID>>(&'a self, program_name: &'a str) {
|
||||
let ctx = HelpWriterContext { options: self, program_name };
|
||||
println!("{}", W::new(ctx));
|
||||
}
|
||||
|
||||
/// Print help text to stderr using the provided help writer
|
||||
/// Print help text to stderr using the provided help writer.
|
||||
///
|
||||
/// Requires features = [std]
|
||||
/// Requires `features = [std]`.
|
||||
pub fn eprint_help<'a, W: HelpWriter<'a, ID>>(&'a self, program_name: &'a str) {
|
||||
let ctx = HelpWriterContext { options: self, program_name };
|
||||
eprintln!("{}", W::new(ctx));
|
||||
@@ -63,16 +63,16 @@ impl<ID: 'static> Opts<ID> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of parsing commands using `jaarg::Opts::parse_map`.
|
||||
/// The result of parsing commands with [Opts::parse_map].
|
||||
pub enum ParseMapResult {
|
||||
Map(BTreeMap<&'static str, String>),
|
||||
Exit(std::process::ExitCode),
|
||||
}
|
||||
|
||||
impl Opts<&'static str> {
|
||||
/// Parse an iterator of strings as arguments and return the results in a BTreeMap.
|
||||
/// Parse an iterator of strings as arguments and return the results in a [BTreeMap].
|
||||
///
|
||||
/// Requires features = [std]
|
||||
/// Requires `features = [std]`.
|
||||
pub fn parse_map<'a, S: AsRef<str> + 'a, I: Iterator<Item = S>>(&self, program_name: &str, args: I,
|
||||
help: impl Fn(&str), error: impl FnOnce(&str, ParseError)
|
||||
) -> ParseMapResult {
|
||||
@@ -92,10 +92,10 @@ impl Opts<&'static str> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse arguments from the command line and return the results in a BTreeMap.
|
||||
/// Parse arguments from the command line and return the results in a [BTreeMap].
|
||||
/// Help and errors are formatted in a standard user-friendly format.
|
||||
///
|
||||
/// Requires features = [std]
|
||||
/// Requires `features = [std]`.
|
||||
pub fn parse_map_easy(&self) -> ParseMapResult {
|
||||
let (program_name, argv) = Self::easy_args();
|
||||
self.parse_map(&program_name, argv,
|
||||
|
||||
Reference in New Issue
Block a user