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