Skip to main content

aargvark/
lib.rs

1#![doc = include_str!("../readme.md")]
2
3/// Base types - return types, errors, etc.
4pub mod base;
5
6/// Types related to producing help text.
7pub mod help;
8
9/// The main trait for parsing arguments.
10pub mod traits;
11
12/// Default implementations and helper traits.
13pub mod traits_impls;
14
15pub use aargvark_proc_macros::Aargvark;
16use {
17    base::{
18        Error,
19        R,
20        VarkFailure,
21        VarkState,
22    },
23    help::VarkRetHelp,
24    traits::AargvarkTrait,
25};
26
27#[derive(PartialEq, Eq)]
28pub enum CompleteCursorPosition {
29    /// Cursor is at the start of a new argument
30    Empty,
31    /// Cursor is at the end of a partially-written argument
32    Partial,
33}
34
35/// Parse the command line arguments into the specified type. If parsing fails,
36/// prints an error to stderr and exits with code 1. See `vark_explicit` if you'd
37/// like more control (input, error handling, etc.)
38pub fn vark<T: AargvarkTrait>() -> T {
39    let mut args = std::env::args();
40    let command = args.next();
41    let mut args = args.collect::<Vec<String>>();
42    if let Some(complete) = std::env::var_os("AARGVARK_COMPLETE") {
43        let cursor = match complete.as_encoded_bytes() {
44            b"empty" => CompleteCursorPosition::Empty,
45            b"partial" => CompleteCursorPosition::Partial,
46            _ => {
47                eprintln!("Invalid value for AARGVARK_COMPLETE");
48                std::process::exit(1);
49            },
50        };
51
52        // Skip first argument, in bash the line comes with the invoked program name
53        args.remove(0);
54        let res = vark_complete::<T>(cursor, command, args);
55        println!(
56            "{}",
57            res
58                .into_iter()
59                .map(
60                    |args| args
61                        .iter()
62                        .map(|a| shell_escape::escape(std::borrow::Cow::Borrowed(&a)))
63                        .collect::<Vec<_>>()
64                        .join(" "),
65                )
66                .collect::<Vec<_>>()
67                .join("\n")
68        );
69        std::process::exit(0);
70    } else {
71        match vark_explicit::<T>(command, args) {
72            Ok(v) => match v {
73                VarkRet::Ok(v) => return v,
74                VarkRet::Help(h) => {
75                    println!("{}", h.render());
76                    std::process::exit(0);
77                },
78            },
79            Err(e) => {
80                eprintln!("{:?}", e);
81                std::process::exit(1);
82            },
83        }
84    }
85}
86
87/// Generate completions for the provided argument list.
88///
89/// The result is a list of completion options, where each option is a list of
90/// unquoted command line arguments. When output to a shell, the arguments should
91/// be quoted and joined by spaces as appropriate for the shell.
92pub fn vark_complete<
93    T: AargvarkTrait,
94>(cursor: CompleteCursorPosition, command: Option<String>, args: Vec<String>) -> Vec<Vec<String>> {
95    let mut args = args;
96    if args.is_empty() || cursor == CompleteCursorPosition::Empty {
97        args.push("".to_string());
98    }
99    let mut state = VarkState::new(true, command, args);
100    T::vark(&mut state);
101    return (state.last_completer.take().unwrap())();
102}
103
104/// Parse the explicitly passed in arguments - don't read application globals. The
105/// `command` is only used in help and error text. This abstracts the parsing away
106/// from command-line usage so it can be used in other contexts.
107pub fn vark_explicit<T: AargvarkTrait>(command: Option<String>, args: Vec<String>) -> Result<VarkRet<T>, Error> {
108    let mut state = VarkState::new(false, command, args);
109    match T::vark(&mut state) {
110        R::Err => {
111            return Err(Error {
112                command: state.command,
113                args: state.args,
114                detail: state.errors,
115            });
116        },
117        R::Help(builder) => {
118            return Ok(VarkRet::Help(VarkRetHelp {
119                command: state.command,
120                args: state.args,
121                consumed_args: state.i,
122                builder: builder,
123            }));
124        },
125        R::Ok(v) => {
126            if state.i != state.args.len() {
127                return Err(Error {
128                    command: state.command,
129                    detail: vec![VarkFailure {
130                        arg_offset: state.i,
131                        error: format!(
132                            "Error parsing command line arguments: final arguments are unrecognized\n{:?}",
133                            &state.args[state.i..]
134                        ),
135                    }],
136                    args: state.args,
137                });
138            }
139            return Ok(VarkRet::Ok(v));
140        },
141    }
142}
143
144/// Result of varking when no errors occurred. Either results in parsed value or
145/// the parsing was interrupted because help was requested.
146pub enum VarkRet<T> {
147    Help(VarkRetHelp),
148    Ok(T),
149}