Skip to main content

argh_shared/
lib.rs

1// Copyright (c) 2020 Google LLC All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5//! Shared functionality between argh_derive and the argh runtime.
6//!
7//! This library is intended only for internal use by these two crates.
8
9/// Information about a particular command used for output.
10pub struct CommandInfo<'a> {
11    /// The name of the command.
12    pub name: &'a str,
13    /// A short name for the command (alias).
14    pub short: &'a char,
15    /// A short description of the command's functionality.
16    pub description: &'a str,
17}
18
19impl<'a> Default for CommandInfo<'a> {
20    fn default() -> Self {
21        Self { name: Default::default(), short: &'\0', description: Default::default() }
22    }
23}
24
25/// Information about the command line arguments for a given command.
26#[derive(Debug, PartialEq, Eq, Clone)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize))]
28pub struct CommandInfoWithArgs<'a> {
29    /// The name of the command.
30    pub name: &'a str,
31    /// A short name for the command (alias).
32    pub short: &'a char,
33    /// A short description of the command's functionality.
34    pub description: &'a str,
35    /// Examples of usage
36    pub examples: &'a [&'a str],
37    /// Flags
38    pub flags: &'a [FlagInfo<'a>],
39    /// Notes about usage
40    pub notes: &'a [&'a str],
41    /// The subcommands.
42    pub commands: Vec<SubCommandInfo<'a>>,
43    /// Positional args
44    pub positionals: &'a [PositionalInfo<'a>],
45    /// Error code information
46    pub error_codes: &'a [ErrorCodeInfo<'a>],
47}
48
49impl<'a> Default for CommandInfoWithArgs<'a> {
50    fn default() -> Self {
51        Self {
52            name: Default::default(),
53            short: &'\0',
54            description: Default::default(),
55            examples: Default::default(),
56            flags: Default::default(),
57            notes: Default::default(),
58            commands: Default::default(),
59            positionals: Default::default(),
60            error_codes: Default::default(),
61        }
62    }
63}
64
65/// Information about a documented error code.
66#[derive(Debug, PartialEq, Eq)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize))]
68pub struct ErrorCodeInfo<'a> {
69    /// The code value.
70    pub code: i32,
71    /// Short description about what this code indicates.
72    pub description: &'a str,
73}
74
75/// Information about positional arguments
76#[derive(Debug, PartialEq, Eq)]
77#[cfg_attr(feature = "serde", derive(serde::Serialize))]
78pub struct PositionalInfo<'a> {
79    /// Name of the argument.
80    pub name: &'a str,
81    /// Description of the argument.
82    pub description: &'a str,
83    /// Optionality of the argument.
84    pub optionality: Optionality,
85    /// Visibility in the help for this argument.
86    /// `false` indicates this argument will not appear
87    /// in the help message.
88    pub hidden: bool,
89}
90
91/// Information about a subcommand.
92/// Dynamic subcommands do not implement
93/// get_args_info(), so the command field
94/// only contains the name and description.
95#[derive(Debug, Default, PartialEq, Eq, Clone)]
96#[cfg_attr(feature = "serde", derive(serde::Serialize))]
97pub struct SubCommandInfo<'a> {
98    /// The subcommand name.
99    pub name: &'a str,
100    /// The information about the subcommand.
101    pub command: CommandInfoWithArgs<'a>,
102}
103
104/// Information about a flag or option.
105#[derive(Debug, Default, PartialEq, Eq)]
106#[cfg_attr(feature = "serde", derive(serde::Serialize))]
107pub struct FlagInfo<'a> {
108    /// The kind of flag.
109    pub kind: FlagInfoKind<'a>,
110    /// The optionality of the flag.
111    pub optionality: Optionality,
112    /// The long string of the flag.
113    pub long: &'a str,
114    /// The single character short indicator
115    /// for this flag.
116    pub short: Option<char>,
117    /// The description of the flag.
118    pub description: &'a str,
119    /// Visibility in the help for this argument.
120    /// `false` indicates this argument will not appear
121    /// in the help message.
122    pub hidden: bool,
123}
124
125/// The kind of flags.
126#[derive(Debug, Default, PartialEq, Eq)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize))]
128pub enum FlagInfoKind<'a> {
129    /// switch represents a boolean flag,
130    #[default]
131    Switch,
132    /// option is a flag that also has an associated
133    /// value. This value is named `arg_name`.
134    Option { arg_name: &'a str },
135}
136
137/// The optionality defines the requirements related
138/// to the presence of the argument on the command line.
139#[derive(Debug, Default, PartialEq, Eq)]
140#[cfg_attr(feature = "serde", derive(serde::Serialize))]
141pub enum Optionality {
142    /// Required indicates the argument is required
143    /// exactly once.
144    #[default]
145    Required,
146    /// Optional indicates the argument may or may not
147    /// be present.
148    Optional,
149    /// Repeating indicates the argument may appear zero
150    /// or more times.
151    Repeating,
152    /// Greedy is used for positional arguments which
153    /// capture the all command line input up to the next flag or
154    /// the end of the input.
155    Greedy,
156}
157
158pub const INDENT: &str = "  ";
159const DESCRIPTION_INDENT: usize = 20;
160const WRAP_WIDTH: usize = 80;
161
162/// Write command names and descriptions to an output string.
163pub fn write_description(out: &mut String, cmd: &CommandInfo<'_>) {
164    let mut current_line = INDENT.to_string();
165    current_line.push_str(cmd.name);
166
167    if *cmd.short != '\0' {
168        current_line.push_str(&format!("  {}", cmd.short));
169    }
170
171    if cmd.description.is_empty() {
172        new_line(&mut current_line, out);
173        return;
174    }
175
176    if !indent_description(&mut current_line) {
177        // Start the description on a new line if the flag names already
178        // add up to more than DESCRIPTION_INDENT.
179        new_line(&mut current_line, out);
180    }
181
182    let mut words = cmd.description.split(' ').peekable();
183    while let Some(first_word) = words.next() {
184        indent_description(&mut current_line);
185        current_line.push_str(first_word);
186
187        'inner: while let Some(&word) = words.peek() {
188            if (char_len(&current_line) + char_len(word) + 1) > WRAP_WIDTH {
189                new_line(&mut current_line, out);
190                break 'inner;
191            } else {
192                // advance the iterator
193                let _ = words.next();
194                current_line.push(' ');
195                current_line.push_str(word);
196            }
197        }
198    }
199    new_line(&mut current_line, out);
200}
201
202// Indent the current line in to DESCRIPTION_INDENT chars.
203// Returns a boolean indicating whether or not spacing was added.
204fn indent_description(line: &mut String) -> bool {
205    let cur_len = char_len(line);
206    if cur_len < DESCRIPTION_INDENT {
207        let num_spaces = DESCRIPTION_INDENT - cur_len;
208        line.extend(std::iter::repeat_n(' ', num_spaces));
209        true
210    } else {
211        false
212    }
213}
214
215fn char_len(s: &str) -> usize {
216    s.chars().count()
217}
218
219// Append a newline and the current line to the output,
220// clearing the current line.
221fn new_line(current_line: &mut String, out: &mut String) {
222    out.push('\n');
223    out.push_str(current_line);
224    current_line.truncate(0);
225}