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