// 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>, /// 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, /// 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(¤t_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); }