1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// Copyright (c) 2020 Google LLC All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//! Shared functionality between argh_derive and the argh runtime.
//!
//! This library is intended only for internal use by these two crates.

/// Information about a particular command used for output.
pub struct CommandInfo<'a> {
    /// The name of the command.
    pub name: &'a str,
    /// A short description of the command's functionality.
    pub description: &'a str,
}

/// Information about the command line arguments for a given command.
#[derive(Debug, Default, PartialEq, Eq, Clone, serde::Serialize)]
pub struct CommandInfoWithArgs<'a> {
    /// The name of the command.
    pub name: &'a str,
    /// A short description of the command's functionality.
    pub description: &'a str,
    /// Examples of usage
    pub examples: &'a [&'a str],
    /// Flags
    pub flags: &'a [FlagInfo<'a>],
    /// Notes about usage
    pub notes: &'a [&'a str],
    /// The subcommands.
    pub commands: Vec<SubCommandInfo<'a>>,
    /// Positional args
    pub positionals: &'a [PositionalInfo<'a>],
    /// Error code information
    pub error_codes: &'a [ErrorCodeInfo<'a>],
}

/// Information about a documented error code.
#[derive(Debug, PartialEq, Eq, serde::Serialize)]
pub struct ErrorCodeInfo<'a> {
    /// The code value.
    pub code: i32,
    /// Short description about what this code indicates.
    pub description: &'a str,
}

/// Information about positional arguments
#[derive(Debug, PartialEq, Eq, serde::Serialize)]
pub struct PositionalInfo<'a> {
    /// Name of the argument.
    pub name: &'a str,
    /// Description of the argument.
    pub description: &'a str,
    /// Optionality of the argument.
    pub optionality: Optionality,
    /// Visibility in the help for this argument.
    /// `false` indicates this argument will not appear
    /// in the help message.
    pub hidden: bool,
}

/// Information about a subcommand.
/// Dynamic subcommands do not implement
/// get_args_info(), so the command field
/// only contains the name and description.
#[derive(Debug, Default, PartialEq, Eq, Clone, serde::Serialize)]
pub struct SubCommandInfo<'a> {
    /// The subcommand name.
    pub name: &'a str,
    /// The information about the subcommand.
    pub command: CommandInfoWithArgs<'a>,
}

/// Information about a flag or option.
#[derive(Debug, Default, PartialEq, Eq, serde::Serialize)]
pub struct FlagInfo<'a> {
    /// The kind of flag.
    pub kind: FlagInfoKind<'a>,
    /// The optionality of the flag.
    pub optionality: Optionality,
    /// The long string of the flag.
    pub long: &'a str,
    /// The single character short indicator
    /// for trhis flag.
    pub short: Option<char>,
    /// The description of the flag.
    pub description: &'a str,
    /// Visibility in the help for this argument.
    /// `false` indicates this argument will not appear
    /// in the help message.
    pub hidden: bool,
}

/// The kind of flags.
#[derive(Debug, Default, PartialEq, Eq, serde::Serialize)]
pub enum FlagInfoKind<'a> {
    /// switch represents a boolean flag,
    #[default]
    Switch,
    /// option is a flag that also has an associated
    /// value. This value is named `arg_name`.
    Option { arg_name: &'a str },
}

/// The optionality defines the requirments related
/// to the presence of the argument on the command line.
#[derive(Debug, Default, PartialEq, Eq, serde::Serialize)]
pub enum Optionality {
    /// Required indicates the argument is required
    /// exactly once.
    #[default]
    Required,
    /// Optional indicates the argument may or may not
    /// be present.
    Optional,
    /// Repeating indicates the argument may appear zero
    /// or more times.
    Repeating,
    /// Greedy is used for positional arguments which
    /// capture the all command line input upto the next flag or
    /// the end of the input.
    Greedy,
}

pub const INDENT: &str = "  ";
const DESCRIPTION_INDENT: usize = 20;
const WRAP_WIDTH: usize = 80;

/// Write command names and descriptions to an output string.
pub fn write_description(out: &mut String, cmd: &CommandInfo<'_>) {
    let mut current_line = INDENT.to_string();
    current_line.push_str(cmd.name);

    if cmd.description.is_empty() {
        new_line(&mut current_line, out);
        return;
    }

    if !indent_description(&mut current_line) {
        // Start the description on a new line if the flag names already
        // add up to more than DESCRIPTION_INDENT.
        new_line(&mut current_line, out);
    }

    let mut words = cmd.description.split(' ').peekable();
    while let Some(first_word) = words.next() {
        indent_description(&mut current_line);
        current_line.push_str(first_word);

        'inner: while let Some(&word) = words.peek() {
            if (char_len(&current_line) + char_len(word) + 1) > WRAP_WIDTH {
                new_line(&mut current_line, out);
                break 'inner;
            } else {
                // advance the iterator
                let _ = words.next();
                current_line.push(' ');
                current_line.push_str(word);
            }
        }
    }
    new_line(&mut current_line, out);
}

// Indent the current line in to DESCRIPTION_INDENT chars.
// Returns a boolean indicating whether or not spacing was added.
fn indent_description(line: &mut String) -> bool {
    let cur_len = char_len(line);
    if cur_len < DESCRIPTION_INDENT {
        let num_spaces = DESCRIPTION_INDENT - cur_len;
        line.extend(std::iter::repeat(' ').take(num_spaces));
        true
    } else {
        false
    }
}

fn char_len(s: &str) -> usize {
    s.chars().count()
}

// Append a newline and the current line to the output,
// clearing the current line.
fn new_line(current_line: &mut String, out: &mut String) {
    out.push('\n');
    out.push_str(current_line);
    current_line.truncate(0);
}