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(¤t_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