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