11#![ warn( clippy:: pedantic) ]
22#![ warn( clippy:: complexity) ]
3- //! Sap - a Small Argument Parser
3+ //! # Sap - a Small Argument Parser
44//!
5- //! The only upside (currently) is being very minimal .
5+ //! A minimal Unix command-line argument parser with zero dependencies .
66//!
7- //! Unix only.
7+ //! ## Features
8+ //!
9+ //! - Parse short options (`-a`, `-b`), long options (`--verbose`), and option values
10+ //! - Combined short options (`-abc` = `-a -b -c`)
11+ //! - Support for option values (`--name=value`)
12+ //! - Error handling with descriptive messages
813
914use std:: {
1015 env,
@@ -15,25 +20,24 @@ use std::{
1520 os:: unix:: ffi:: { OsStrExt , OsStringExt } ,
1621} ;
1722
23+ /// Result type specific to Sap, using `ParsingError` as the default error type
1824pub type Result < T , E = ParsingError > = core:: result:: Result < T , E > ;
1925
2026/// Represents a command-line argument
2127#[ derive( Debug , PartialEq , PartialOrd , Ord , Eq , Hash , Clone , Copy ) ]
2228pub enum Argument < ' a > {
23- /// Represents an long option like
24- /// `--example`.
29+ /// A long option like `--example`
2530 Long ( & ' a str ) ,
2631
27- /// Represets a singular character
28- /// option, as in: `-q`
32+ /// A single character option like `-q`
2933 Short ( char ) ,
3034
31- /// Regular argument
32- /// like `/proc/meminfo`
35+ /// A positional argument like `file.txt` or `/path/to/file`
3336 Value ( & ' a str ) ,
3437}
3538
3639impl < ' a > Argument < ' a > {
40+ /// Converts this argument into an error, optionally with a value
3741 pub fn into_error < A > ( self , value : A ) -> ParsingError
3842 where
3943 A : Into < Option < & ' a str > > ,
@@ -45,43 +49,39 @@ impl<'a> Argument<'a> {
4549 offender : arg. to_string ( ) ,
4650 value : value. into ( ) . map ( String :: from) ,
4751 format : "=" ,
48-
4952 prefix : "--" ,
5053 } ,
5154
5255 Short ( arg) => ParsingError :: UnexpectedArg {
5356 offender : arg. to_string ( ) ,
5457 value : value. into ( ) . map ( String :: from) ,
55-
56- // cheap trick
57- // omit it one day.
5858 format : " " ,
59-
6059 prefix : "-" ,
6160 } ,
6261
6362 Value ( arg) => ParsingError :: UnexpectedArg {
6463 offender : arg. to_string ( ) ,
6564 value : None ,
6665 format : "" ,
67-
6866 prefix : "" ,
6967 } ,
7068 }
7169 }
7270}
7371
72+ /// Internal parser state
7473enum State {
74+ /// Normal state, no special processing needed
7575 NotInteresting ,
76+ /// Contains a value from previous option
7677 LeftoverValue ( OsString ) ,
78+ /// Combined short options state (-abc)
7779 Combined ( usize , OsString ) ,
80+ /// End of arguments (after --)
7881 End ,
7982}
8083
81- /// Parser of the command-line arguments
82- /// internally uses the `ArgsOs` iterator
83- /// when created via the `parser_from_env`
84- /// function.
84+ /// Parser for command-line arguments
8585pub struct Parser < I > {
8686 iter : I ,
8787 state : State ,
@@ -94,12 +94,12 @@ pub struct Parser<I> {
9494}
9595
9696impl Parser < env:: ArgsOs > {
97- /// Creates a `Parser` using the `ArgsOs` iterator
98- /// provided by the standard library.
97+ /// Creates a `Parser` using the program's command-line arguments.
9998 ///
10099 /// # Errors
101100 ///
102- /// See `from_arbitrary` for details
101+ /// Returns an error if no arguments are available or the program name
102+ /// contains invalid UTF-8.
103103 pub fn from_env ( ) -> Result < Self > {
104104 Self :: from_arbitrary ( env:: args_os ( ) )
105105 }
@@ -109,20 +109,16 @@ impl<I> Parser<I>
109109where
110110 I : Iterator < Item = OsString > ,
111111{
112- /// Creates a `Parser` from an arbitrary `Iterator` with
113- /// `Item` as `OsString`.
112+ /// Creates a `Parser` from any iterator that yields `OsString` items.
114113 ///
115114 /// # Errors
116115 ///
117- /// At creation it checks the created `Iterator`
118- /// if it contains the first value (the process name),
119- /// if it does not contain it and/or the value is malformed
120- /// the function will return `Err`
116+ /// Returns an error if the iterator is empty or the first item (program name)
117+ /// can't be converted to a valid UTF-8 string.
121118 pub fn from_arbitrary ( mut iter : I ) -> Result < Parser < I > > {
122119 let name = match iter. next ( ) {
123120 None => {
124121 let err = ParsingError :: Empty ;
125-
126122 return Err ( err) ;
127123 }
128124 Some ( val) => match val. into_string ( ) {
@@ -141,10 +137,13 @@ where
141137 } )
142138 }
143139
144- /// Moves the parser one option forward
145- /// if it hits a `--`, it will start returning `None`
140+ /// Moves to the next argument, parsing it into an `Argument` enum.
141+ ///
142+ /// # Returns
146143 ///
147- /// This happens because `--` signifies the end of arguments.
144+ /// - `Some(Ok(arg))` - Successfully parsed the next argument
145+ /// - `Some(Err(e))` - Found an argument but encountered an error parsing it
146+ /// - `None` - No more arguments or reached `--` separator
148147 pub fn forward ( & mut self ) -> Option < Result < Argument < ' _ > > > {
149148 if matches ! ( self . state, State :: End ) {
150149 return None ;
@@ -155,7 +154,6 @@ where
155154 State :: Combined ( ref mut pos, ref str) => match str. as_bytes ( ) . get ( * pos) {
156155 None => {
157156 self . state = State :: NotInteresting ;
158-
159157 return self . forward ( ) ;
160158 }
161159
@@ -170,7 +168,6 @@ where
170168
171169 Some ( ch) => {
172170 * pos += 1 ;
173-
174171 return Some ( Ok ( Argument :: Short ( * ch as char ) ) ) ;
175172 }
176173 } ,
@@ -182,7 +179,6 @@ where
182179 None => return None ,
183180 Some ( arg) => {
184181 self . last_long = Some ( arg) ;
185-
186182 // Safety:
187183 //
188184 // We just placed the value in the variable
@@ -215,7 +211,6 @@ where
215211 let str_arg = match str:: from_utf8 ( arg) {
216212 Err ( _e) => {
217213 let err = ParsingError :: InvalidString ;
218-
219214 return Some ( Err ( err) ) ;
220215 }
221216 Ok ( val) => val,
@@ -250,7 +245,6 @@ where
250245 let str_arg = match str:: from_utf8 ( arg. as_encoded_bytes ( ) ) {
251246 Err ( _e) => {
252247 let err = ParsingError :: InvalidString ;
253-
254248 return Some ( Err ( err) ) ;
255249 }
256250 Ok ( val) => val,
@@ -261,9 +255,14 @@ where
261255 }
262256 }
263257
264- /// Retrieves the value stored in the Parser
265- /// without converting it to a UTF-8 string.
266- /// not retrieving this value becomes an error.
258+ /// Retrieves the value associated with the previous option.
259+ ///
260+ /// Returns the raw `OsString` value without UTF-8 conversion.
261+ ///
262+ /// # Returns
263+ ///
264+ /// - `Some(value)` - The option has a value (e.g., `--name=value`)
265+ /// - `None` - The option has no value or the value has already been consumed
267266 pub fn raw_value ( & mut self ) -> Option < OsString > {
268267 match self . state {
269268 State :: LeftoverValue ( _) => match mem:: replace ( & mut self . state , State :: NotInteresting ) {
@@ -275,18 +274,27 @@ where
275274 }
276275 }
277276
278- /// Retrieves the value. This performs lossy conversion if the vlaue is not valid utf8
277+ /// Retrieves the value associated with the previous option as a UTF-8 string.
278+ ///
279+ /// Performs lossy UTF-8 conversion if the value contains invalid UTF-8.
280+ ///
281+ /// # Returns
282+ ///
283+ /// - `Some(value)` - The option has a value (e.g., `--name=value`)
284+ /// - `None` - The option has no value or the value has already been consumed
279285 pub fn value ( & mut self ) -> Option < String > {
280286 self . raw_value ( ) . map ( |v| v. to_string_lossy ( ) . into_owned ( ) )
281287 }
282288
283- /// Ignore the value
284- /// to not error out the parser
289+ /// Ignores any value associated with the current option.
290+ ///
291+ /// Call this when you want to acknowledge but discard a value
292+ /// without causing an error.
285293 pub fn ignore_value ( & mut self ) {
286294 let _ = self . raw_value ( ) ;
287295 }
288296
289- /// Retrieve the name of the process
297+ /// Returns the program name (first argument).
290298 pub fn name ( & self ) -> & str {
291299 & self . name
292300 }
@@ -298,31 +306,25 @@ struct ParserIter<I: Iterator<Item = OsString>> {
298306 inner : Parser < I > ,
299307}
300308
301- /// Error type describing the various ways
302- /// this algorithm can fail
303- /// and/or be induced to fail.
309+ /// Parsing error types with descriptive messages
304310#[ derive( Debug ) ]
305311pub enum ParsingError {
306- /// Something was wrong with a provided option.
312+ /// Invalid option syntax or format
307313 InvalidOption {
308314 reason : & ' static str ,
309315 offender : Option < OsString > ,
310316 } ,
311317
312- /// The value left after processing a long option
313- /// was not consumed.
318+ /// A value was provided but not consumed by calling `value()` or `ignore_value()`
314319 UnconsumedValue { value : OsString } ,
315320
316- /// The initial iterator was empty.
321+ /// The arguments iterator was empty (no program name)
317322 Empty ,
318323
319- /// The string is invalid.
324+ /// A string containing invalid UTF-8 was encountered
320325 InvalidString ,
321326
322- /// This error is not used by the parser,
323- /// however it is there to let users
324- /// create errors from
325- /// arguments that they deem unexpected
327+ /// An unexpected or unrecognized argument was provided
326328 UnexpectedArg {
327329 offender : String ,
328330 value : Option < String > ,
0 commit comments