10 Commits

Author SHA1 Message Date
75fc27bc58 Release 0.2.2 2025-11-26 15:30:43 +11:00
8d9e8aea89 Cargo stinky aaaa 2025-11-26 15:26:07 +11:00
20f5a0bf10 Backport positional argument handler fix. 2025-11-26 15:12:49 +11:00
7165bb9841 Rename test modules for previously included files.
(cherry picked from commit 75e2bde5fb)
2025-11-26 15:03:55 +11:00
8f6f1827ce Add simple argparse test for values
(cherry picked from commit 14028ed2c8)
2025-11-26 15:03:28 +11:00
33af658e93 Add homepage
(cherry picked from commit 23b6402db6)
2025-11-26 15:00:54 +11:00
87e3e5f4e0 Update README.md 2025-11-16 13:06:15 +11:00
a dinosaur
82ef9cf8d5 Merge pull request #1 from gay-pizza/include-to-mod
Swap include macros to `using mod` with `pub use` & clippy fixes
2025-11-16 12:55:00 +11:00
03e1953aae swap include to using mod with pub use 2025-11-15 17:51:23 -08:00
148a649273 Remove python section from editorconfig 2025-11-15 20:30:14 +11:00
13 changed files with 102 additions and 36 deletions

View File

@@ -10,8 +10,3 @@ tab_width = 4
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
trim_trailing_whitespace = true trim_trailing_whitespace = true
[*.py]
indent_style = tab
indent_size = tab
trim_trailing_whitespace = true

View File

@@ -4,11 +4,12 @@ members = ["jaarg-nostd"]
resolver = "3" resolver = "3"
[workspace.package] [workspace.package]
version = "0.2.1" version = "0.2.2"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
description = "It can parse your arguments you should use it it's called jaarg" description = "It can parse your arguments you should use it it's called jaarg"
repository = "https://github.com/gay-pizza/jaarg" repository = "https://github.com/gay-pizza/jaarg"
homepage = "https://gay.pizza/"
authors = ["a dinosaur", "Gay Pizza Specifications"] authors = ["a dinosaur", "Gay Pizza Specifications"]
[profile.release] [profile.release]

View File

@@ -57,7 +57,10 @@ println!("{file:?} -> {out:?} (number: {number:?})",
### Changelog ### ### Changelog ###
<!-- main: --> v0.2.2:
* Fixed coerced `ArgumentError` not being rewritten for positional arguments.
* Moved top level includes to `pub use`.
* Hopefully work around licence & read me texts not being included in crate.
v0.2.1: v0.2.1:
* Fixed licence field in `Cargo.toml`. * Fixed licence field in `Cargo.toml`.
@@ -90,7 +93,7 @@ Long term:
* Make use of const traits when they land to improve table setup. * Make use of const traits when they land to improve table setup.
### Projects using jaarg (very cool) ### ### Projects using jaarg (very cool) ###
<!-- soon... * [Sprout bootloader](https://github.com/edera-dev/sprout) --> * [Sprout bootloader](https://github.com/edera-dev/sprout)
* [lbminfo](https://github.com/ScrelliCopter/colourcyclinginthehousetonight/tree/main/lbminfo) * [lbminfo](https://github.com/ScrelliCopter/colourcyclinginthehousetonight/tree/main/lbminfo)
### Licensing ### ### Licensing ###

View File

@@ -4,6 +4,7 @@ version.workspace = true
license.workspace = true license.workspace = true
edition.workspace = true edition.workspace = true
description.workspace = true description.workspace = true
homepage.workspace = true
repository.workspace = true repository.workspace = true
authors.workspace = true authors.workspace = true

1
jaarg/LICENSE.Apache-2.0 Symbolic link
View File

@@ -0,0 +1 @@
../LICENSE.Apache-2.0

1
jaarg/LICENSE.MIT Symbolic link
View File

@@ -0,0 +1 @@
../LICENSE.MIT

1
jaarg/README.md Symbolic link
View File

@@ -0,0 +1 @@
../README.md

View File

@@ -17,7 +17,7 @@ impl Opts<&'static str> {
help: impl Fn(&str), error: impl FnOnce(&str, ParseError) help: impl Fn(&str), error: impl FnOnce(&str, ParseError)
) -> ParseMapResult { ) -> ParseMapResult {
let mut out: BTreeMap<&'static str, String> = BTreeMap::new(); let mut out: BTreeMap<&'static str, String> = BTreeMap::new();
match self.parse(&program_name, args, |_program_name, id, opt, _name, arg| { match self.parse(program_name, args, |_program_name, id, opt, _name, arg| {
if opt.is_help() { if opt.is_help() {
help(program_name); help(program_name);
Ok(ParseControl::Quit) Ok(ParseControl::Quit)

View File

@@ -3,6 +3,10 @@
* SPDX-License-Identifier: MIT OR Apache-2.0 * SPDX-License-Identifier: MIT OR Apache-2.0
*/ */
use crate::{Opt, Opts};
use crate::option::OptType;
use crate::options::RequiredParamsBitSet;
/// Enum describing the result of parsing arguments, and how the program should behave. /// Enum describing the result of parsing arguments, and how the program should behave.
#[derive(Debug)] #[derive(Debug)]
pub enum ParseResult { pub enum ParseResult {
@@ -25,7 +29,7 @@ pub enum ParseControl {
} }
/// 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>>; pub(crate) type HandlerResult<'a, T> = core::result::Result<T, ParseError<'a>>;
#[derive(Debug)] #[derive(Debug)]
pub enum ParseError<'a> { pub enum ParseError<'a> {
@@ -228,7 +232,7 @@ impl<ID: 'static> Opts<ID> {
// Find the next positional argument // Find the next positional argument
for (i, option) in self.options[state.positional_index..].iter().enumerate() { for (i, option) in self.options[state.positional_index..].iter().enumerate() {
if matches!(option.r#type, OptType::Positional) { if matches!(option.r#type, OptType::Positional) {
handler(program_name, &option.id, option, option.first_name(), token)?; call_handler(option, option.first_name(), token)?;
state.positional_index += i + 1; state.positional_index += i + 1;
return Ok(ParseControl::Continue); return Ok(ParseControl::Continue);
} }
@@ -238,3 +242,49 @@ impl<ID: 'static> Opts<ID> {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse() {
extern crate alloc;
use alloc::string::String;
enum ArgID { One, Two, Three, Four, Five }
const OPTIONS: Opts<ArgID> = Opts::new(&[
Opt::positional(ArgID::One, "one"),
Opt::flag(ArgID::Two, &["--two"]),
Opt::value(ArgID::Three, &["--three"], "value"),
Opt::value(ArgID::Four, &["--four"], "value"),
Opt::value(ArgID::Five, &["--five"], "value"),
]);
const ARGUMENTS: &[&str] = &["one", "--two", "--three=three", "--five=", "--four", "four"];
//TODO: currently needs alloc to deal with arguments not being able to escape handler
let mut one: Option<String> = None;
let mut two = false;
let mut three: Option<String> = None;
let mut four: Option<String> = None;
let mut five: Option<String> = None;
assert!(matches!(OPTIONS.parse("", ARGUMENTS.iter(), |_program_name, id, _opt, _name, arg| {
match id {
ArgID::One => { one = Some(arg.into()); }
ArgID::Two => { two = true; }
ArgID::Three => { three = Some(arg.into()); }
ArgID::Four => { four = Some(arg.into()); }
ArgID::Five => { five = Some(arg.into()); }
}
Ok(ParseControl::Continue)
}, |_, error| {
panic!("unreachable: {error:?}");
}), ParseResult::ContinueSuccess));
assert_eq!(one, Some("one".into()));
assert!(two);
assert_eq!(three, Some("three".into()));
assert_eq!(four, Some("four".into()));
assert_eq!(five, Some("".into()));
}
}

View File

@@ -3,6 +3,9 @@
* SPDX-License-Identifier: MIT OR Apache-2.0 * SPDX-License-Identifier: MIT OR Apache-2.0
*/ */
use crate::{Opt, Opts, ParseError};
use crate::option::{OptIdentifier, OptType};
/// Enough context to show full help text. /// Enough context to show full help text.
pub struct HelpWriterContext<'a, ID: 'static> { pub struct HelpWriterContext<'a, ID: 'static> {
pub options: &'a Opts<ID>, pub options: &'a Opts<ID>,
@@ -166,7 +169,7 @@ impl<ID> core::fmt::Display for StandardFullHelpWriter<'_, ID> {
} }
// Write line for option, with aligned help text if needed // Write line for option, with aligned help text if needed
let line = OptionUsageLine(&option); let line = OptionUsageLine(option);
if let Some(help_text) = option.help_string { if let Some(help_text) = option.help_string {
write!(f, " {line:.<align_width$} {help_text}")?; write!(f, " {line:.<align_width$} {help_text}")?;
} else { } else {

View File

@@ -8,10 +8,15 @@
mod const_utf8; mod const_utf8;
mod ordered_bitset; mod ordered_bitset;
include!("option.rs"); mod option;
include!("options.rs"); mod options;
include!("argparse.rs"); mod argparse;
include!("help.rs"); mod help;
pub use option::*;
pub use options::*;
pub use argparse::*;
pub use help::*;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub mod alloc; pub mod alloc;

View File

@@ -3,15 +3,17 @@
* SPDX-License-Identifier: MIT OR Apache-2.0 * SPDX-License-Identifier: MIT OR Apache-2.0
*/ */
use crate::const_utf8;
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
enum OptType { pub(crate) enum OptType {
Positional, Positional,
Flag, Flag,
Value, Value,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
enum OptIdentifier { pub(crate) enum OptIdentifier {
Single(&'static str), Single(&'static str),
Multi(&'static[&'static str]), Multi(&'static[&'static str]),
} }
@@ -19,11 +21,11 @@ enum OptIdentifier {
/// Represents an option argument or positional argument to be parsed. /// Represents an option argument or positional argument to be parsed.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Opt<ID> { pub struct Opt<ID> {
id: ID, pub(crate) id: ID,
names: OptIdentifier, pub(crate) names: OptIdentifier,
value_name: Option<&'static str>, pub(crate) value_name: Option<&'static str>,
help_string: Option<&'static str>, pub(crate) help_string: Option<&'static str>,
r#type: OptType, pub(crate) r#type: OptType,
flags: OptFlag, flags: OptFlag,
} }
@@ -122,12 +124,12 @@ impl<ID> Opt<ID> {
} }
#[inline(always)] #[inline(always)]
const fn is_short_visible(&self) -> bool { pub(crate) const fn is_short_visible(&self) -> bool {
(self.flags.0 & OptFlag::VISIBLE_SHORT.0) != 0 (self.flags.0 & OptFlag::VISIBLE_SHORT.0) != 0
} }
#[inline(always)] #[inline(always)]
const fn is_full_visible(&self) -> bool { pub(crate) const fn is_full_visible(&self) -> bool {
(self.flags.0 & OptFlag::VISIBLE_FULL.0) != 0 (self.flags.0 & OptFlag::VISIBLE_FULL.0) != 0
} }
} }
@@ -161,7 +163,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> { pub(crate) const fn first_short_name(&self) -> Option<&'static str> {
const fn predicate(name: &str) -> bool { const fn predicate(name: &str) -> bool {
let mut chars = const_utf8::CharIterator::from(name); let mut chars = const_utf8::CharIterator::from(name);
if let Some(first) = chars.next() { if let Some(first) = chars.next() {
@@ -174,7 +176,7 @@ impl<ID: 'static> Opt<ID> {
false false
} }
match self.names { match self.names {
OptIdentifier::Single(name) => if predicate(&name) { Some(name) } else { None }, OptIdentifier::Single(name) => if predicate(name) { Some(name) } else { None },
// 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) => { OptIdentifier::Multi(names) => {
let mut i = 0; let mut i = 0;
@@ -190,7 +192,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> { pub(crate) const fn first_short_name_char(&self) -> Option<char> {
const fn predicate(name: &str) -> Option<char> { const fn predicate(name: &str) -> Option<char> {
let mut chars = const_utf8::CharIterator::from(name); let mut chars = const_utf8::CharIterator::from(name);
if let Some(first) = chars.next() { if let Some(first) = chars.next() {
@@ -203,7 +205,7 @@ impl<ID: 'static> Opt<ID> {
None None
} }
match self.names { match self.names {
OptIdentifier::Single(name) => predicate(&name), 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) => { OptIdentifier::Multi(names) => {
let mut i = 0; let mut i = 0;
@@ -219,7 +221,7 @@ impl<ID: 'static> Opt<ID> {
} }
/// Search for a matching name in the option, offset allows to skip the first `n = offset` 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> { pub(crate) fn match_name(&self, string: &str, offset: usize) -> Option<&'static str> {
let rhs = &string[offset..]; let rhs = &string[offset..];
if rhs.is_empty() { if rhs.is_empty() {
return None; return None;
@@ -239,7 +241,7 @@ impl core::ops::BitOr for OptFlag {
} }
#[cfg(test)] #[cfg(test)]
mod opt_tests { mod tests {
use super::*; use super::*;
#[test] #[test]

View File

@@ -3,18 +3,21 @@
* SPDX-License-Identifier: MIT OR Apache-2.0 * SPDX-License-Identifier: MIT OR Apache-2.0
*/ */
use crate::{ordered_bitset, Opt};
use crate::option::OptType;
/// Static structure that contains instructions for parsing command-line arguments. /// Static structure that contains instructions for parsing command-line arguments.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Opts<ID: 'static> { pub struct Opts<ID: 'static> {
/// List of options /// List of options
options: &'static[Opt<ID>], pub(crate) options: &'static[Opt<ID>],
/// String containing single characters that match option prefixes /// String containing single characters that match option prefixes
flag_chars: &'static str, pub(crate) flag_chars: &'static str,
/// A description of what the program does /// A description of what the program does
description: Option<&'static str>, pub(crate) description: Option<&'static str>,
} }
type RequiredParamsBitSet = ordered_bitset::OrderedBitSet<u32, 4>; pub(crate) type RequiredParamsBitSet = ordered_bitset::OrderedBitSet<u32, 4>;
/// The maximum amount of allowed required non-positional options. /// The maximum amount of allowed required non-positional options.
pub const MAX_REQUIRED_OPTIONS: usize = RequiredParamsBitSet::CAPACITY; pub const MAX_REQUIRED_OPTIONS: usize = RequiredParamsBitSet::CAPACITY;
@@ -76,7 +79,7 @@ impl<ID: 'static> Opts<ID> {
#[cfg(test)] #[cfg(test)]
mod opts_tests { mod tests {
use super::*; use super::*;
#[test] #[test]