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(¤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.
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}