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.
10 pub 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)]
19 pub 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)]
40 pub 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)]
49 pub 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)]
67 pub 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)]
76 pub 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)]
96 pub 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)]
108 pub 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 
125 pub const INDENT: &str = "  ";
126 const DESCRIPTION_INDENT: usize = 20;
127 const WRAP_WIDTH: usize = 80;
128 
129 /// Write command names and descriptions to an output string.
write_description(out: &mut String, cmd: &CommandInfo<'_>)130 pub 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.
indent_description(line: &mut String) -> bool167 fn 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 
char_len(s: &str) -> usize178 fn 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.
new_line(current_line: &mut String, out: &mut String)184 fn 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 }
189