tty-option 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -1
- data/README.md +1653 -1
- data/lib/tty/option.rb +63 -4
- data/lib/tty/option/aggregate_errors.rb +95 -0
- data/lib/tty/option/conversions.rb +126 -0
- data/lib/tty/option/converter.rb +63 -0
- data/lib/tty/option/deep_dup.rb +48 -0
- data/lib/tty/option/dsl.rb +105 -0
- data/lib/tty/option/dsl/arity.rb +49 -0
- data/lib/tty/option/dsl/conversion.rb +17 -0
- data/lib/tty/option/error_aggregator.rb +35 -0
- data/lib/tty/option/errors.rb +144 -0
- data/lib/tty/option/formatter.rb +389 -0
- data/lib/tty/option/inflection.rb +50 -0
- data/lib/tty/option/param_conversion.rb +34 -0
- data/lib/tty/option/param_permitted.rb +30 -0
- data/lib/tty/option/param_validation.rb +48 -0
- data/lib/tty/option/parameter.rb +310 -0
- data/lib/tty/option/parameter/argument.rb +18 -0
- data/lib/tty/option/parameter/environment.rb +20 -0
- data/lib/tty/option/parameter/keyword.rb +15 -0
- data/lib/tty/option/parameter/option.rb +99 -0
- data/lib/tty/option/parameters.rb +157 -0
- data/lib/tty/option/params.rb +122 -0
- data/lib/tty/option/parser.rb +57 -3
- data/lib/tty/option/parser/arguments.rb +166 -0
- data/lib/tty/option/parser/arity_check.rb +34 -0
- data/lib/tty/option/parser/environments.rb +169 -0
- data/lib/tty/option/parser/keywords.rb +158 -0
- data/lib/tty/option/parser/options.rb +273 -0
- data/lib/tty/option/parser/param_types.rb +51 -0
- data/lib/tty/option/parser/required_check.rb +36 -0
- data/lib/tty/option/pipeline.rb +38 -0
- data/lib/tty/option/result.rb +46 -0
- data/lib/tty/option/section.rb +26 -0
- data/lib/tty/option/sections.rb +56 -0
- data/lib/tty/option/usage.rb +166 -0
- data/lib/tty/option/usage_wrapper.rb +58 -0
- data/lib/tty/option/version.rb +3 -3
- metadata +37 -3
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module TTY
         | 
| 4 | 
            +
              module Option
         | 
| 5 | 
            +
                module DSL
         | 
| 6 | 
            +
                  module Arity
         | 
| 7 | 
            +
                    # @api public
         | 
| 8 | 
            +
                    def one
         | 
| 9 | 
            +
                      1
         | 
| 10 | 
            +
                    end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                    # @api public
         | 
| 13 | 
            +
                    def two
         | 
| 14 | 
            +
                      2
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    # Zero or more arity
         | 
| 18 | 
            +
                    #
         | 
| 19 | 
            +
                    # @api public
         | 
| 20 | 
            +
                    def zero_or_more
         | 
| 21 | 
            +
                      -1
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                    alias any zero_or_more
         | 
| 24 | 
            +
                    alias any_args zero_or_more
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    # One or more arity
         | 
| 27 | 
            +
                    #
         | 
| 28 | 
            +
                    # @api public
         | 
| 29 | 
            +
                    def one_or_more
         | 
| 30 | 
            +
                      -2
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                    # Two or more arity
         | 
| 34 | 
            +
                    #
         | 
| 35 | 
            +
                    # @api public
         | 
| 36 | 
            +
                    def two_or_more
         | 
| 37 | 
            +
                      -3
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                    # At last number values for arity
         | 
| 41 | 
            +
                    #
         | 
| 42 | 
            +
                    # @api public
         | 
| 43 | 
            +
                    def at_least(number)
         | 
| 44 | 
            +
                      -number.to_i - 1
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
                  end # Arity
         | 
| 47 | 
            +
                end # DSL
         | 
| 48 | 
            +
              end # Option
         | 
| 49 | 
            +
            end # TTY
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "inflection"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module TTY
         | 
| 6 | 
            +
              module Option
         | 
| 7 | 
            +
                class ErrorAggregator
         | 
| 8 | 
            +
                  include Inflection
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  # Collected errors
         | 
| 11 | 
            +
                  attr_reader :errors
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def initialize(errors = [], raise_on_parse_error: false)
         | 
| 14 | 
            +
                    @errors = errors
         | 
| 15 | 
            +
                    @raise_on_parse_error = raise_on_parse_error
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  # Record or raise an error
         | 
| 19 | 
            +
                  #
         | 
| 20 | 
            +
                  # @param [TTY::Option::Error] error
         | 
| 21 | 
            +
                  # @param [String] message
         | 
| 22 | 
            +
                  #
         | 
| 23 | 
            +
                  # @api public
         | 
| 24 | 
            +
                  def call(error, message = nil)
         | 
| 25 | 
            +
                    if error.is_a?(Class)
         | 
| 26 | 
            +
                      error = message.nil? ? error.new : error.new(message)
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    raise(error) if @raise_on_parse_error
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    @errors << error
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end # ErrorAggregator
         | 
| 34 | 
            +
              end # Option
         | 
| 35 | 
            +
            end # TTY
         | 
| @@ -0,0 +1,144 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module TTY
         | 
| 4 | 
            +
              module Option
         | 
| 5 | 
            +
                Error = Class.new(StandardError)
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                # Raised when a parameter invariant is invalid
         | 
| 8 | 
            +
                ConfigurationError = Class.new(Error)
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                # Raised when attempting to register already registered parameter
         | 
| 11 | 
            +
                ParameterConflict = Class.new(Error)
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                # Raised when overriding already defined conversion
         | 
| 14 | 
            +
                ConversionAlreadyDefined = Class.new(Error)
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                # Raised when conversion cannot be performed
         | 
| 17 | 
            +
                ConversionError = Class.new(Error)
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                # Raised when conversion type isn't registered
         | 
| 20 | 
            +
                UnsupportedConversion = Class.new(Error)
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                # Raised during command line input parsing
         | 
| 23 | 
            +
                class ParseError < Error
         | 
| 24 | 
            +
                  attr_accessor :param
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                # Raised when found unrecognized parameter
         | 
| 28 | 
            +
                InvalidParameter = Class.new(ParseError)
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                # Raised when an option matches more than one parameter option
         | 
| 31 | 
            +
                AmbiguousOption = Class.new(ParseError)
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                # Raised when parameter argument doesn't match expected value
         | 
| 34 | 
            +
                class InvalidArgument < ParseError
         | 
| 35 | 
            +
                  MESSAGE = "value of `%<value>s` fails validation for '%<name>s' %<type>s"
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  def initialize(param_or_message, value = nil)
         | 
| 38 | 
            +
                    if param_or_message.is_a?(Parameter)
         | 
| 39 | 
            +
                      @param = param_or_message
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                      message = format(MESSAGE,
         | 
| 42 | 
            +
                                       value: value,
         | 
| 43 | 
            +
                                       name: param.name,
         | 
| 44 | 
            +
                                       type: param.to_sym)
         | 
| 45 | 
            +
                    else
         | 
| 46 | 
            +
                      message = param_or_message
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    super(message)
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                # Raised when number of parameter arguments doesn't match
         | 
| 54 | 
            +
                class InvalidArity < ParseError
         | 
| 55 | 
            +
                  MESSAGE = "%<type>s '%<name>s' should appear %<expect>s but appeared %<actual>s"
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  def initialize(param_or_message, arity = nil)
         | 
| 58 | 
            +
                    if param_or_message.is_a?(Parameter)
         | 
| 59 | 
            +
                      @param = param_or_message
         | 
| 60 | 
            +
                      prefix = param.arity < 0 ? "at least " : ""
         | 
| 61 | 
            +
                      expected_arity = param.arity < 0 ? param.arity.abs - 1 : param.arity
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                      message = format(MESSAGE,
         | 
| 64 | 
            +
                                       type: param.to_sym,
         | 
| 65 | 
            +
                                       name: param.name,
         | 
| 66 | 
            +
                                       expect: prefix + pluralize("time", expected_arity),
         | 
| 67 | 
            +
                                       actual: pluralize("time", arity))
         | 
| 68 | 
            +
                    else
         | 
| 69 | 
            +
                      message = param_or_message
         | 
| 70 | 
            +
                    end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                    super(message)
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  # Pluralize a noun
         | 
| 76 | 
            +
                  #
         | 
| 77 | 
            +
                  # @api private
         | 
| 78 | 
            +
                  def pluralize(noun, count = 1)
         | 
| 79 | 
            +
                    "#{count} #{noun}#{'s' unless count == 1}"
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                # Raised when conversion provided with unexpected argument
         | 
| 84 | 
            +
                class InvalidConversionArgument < ParseError
         | 
| 85 | 
            +
                  MESSAGE = "cannot convert value of `%<value>s` into '%<cast>s' type " \
         | 
| 86 | 
            +
                            "for '%<name>s' %<type>s"
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  def initialize(param, value)
         | 
| 89 | 
            +
                    @param = param
         | 
| 90 | 
            +
                    message = format(MESSAGE, value: value, cast: param.convert,
         | 
| 91 | 
            +
                                      name: param.name, type: param.to_sym)
         | 
| 92 | 
            +
                    super(message)
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                # Raised when option requires an argument
         | 
| 97 | 
            +
                class MissingArgument < ParseError
         | 
| 98 | 
            +
                  MESSAGE = "%<type>s %<name>s requires an argument"
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  def initialize(param)
         | 
| 101 | 
            +
                    @param = param
         | 
| 102 | 
            +
                    message = format(MESSAGE, type: param.to_sym, name: param.name)
         | 
| 103 | 
            +
                    super(message)
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                # Raised when a parameter is required but not present
         | 
| 108 | 
            +
                class MissingParameter < ParseError
         | 
| 109 | 
            +
                  MESSAGE = "%<type>s '%<name>s' must be provided"
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  def initialize(param_or_message)
         | 
| 112 | 
            +
                    if param_or_message.is_a?(Parameter)
         | 
| 113 | 
            +
                      @param = param_or_message
         | 
| 114 | 
            +
                      message = format(MESSAGE, name: param.name, type: param.to_sym)
         | 
| 115 | 
            +
                    else
         | 
| 116 | 
            +
                      message = param_or_message
         | 
| 117 | 
            +
                    end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                    super(message)
         | 
| 120 | 
            +
                  end
         | 
| 121 | 
            +
                end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                # Raised when argument value isn't permitted
         | 
| 124 | 
            +
                class UnpermittedArgument < ParseError
         | 
| 125 | 
            +
                  MESSAGE = "unpermitted value `%<value>s` for '%<name>s' %<type>s: " \
         | 
| 126 | 
            +
                            "choose from %<choices>s"
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                  def initialize(param_or_message, value = nil)
         | 
| 129 | 
            +
                    if param_or_message.is_a?(Parameter)
         | 
| 130 | 
            +
                      @param = param_or_message
         | 
| 131 | 
            +
                      message = format(MESSAGE,
         | 
| 132 | 
            +
                                       value: value,
         | 
| 133 | 
            +
                                       name: param.name,
         | 
| 134 | 
            +
                                       type: param.to_sym,
         | 
| 135 | 
            +
                                       choices: param.permit.join(", "))
         | 
| 136 | 
            +
                    else
         | 
| 137 | 
            +
                      message = param_or_message
         | 
| 138 | 
            +
                    end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                    super(message)
         | 
| 141 | 
            +
                  end
         | 
| 142 | 
            +
                end
         | 
| 143 | 
            +
              end # Option
         | 
| 144 | 
            +
            end # TTY
         | 
| @@ -0,0 +1,389 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "sections"
         | 
| 4 | 
            +
            require_relative "usage_wrapper"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module TTY
         | 
| 7 | 
            +
              module Option
         | 
| 8 | 
            +
                class Formatter
         | 
| 9 | 
            +
                  include UsageWrapper
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  SHORT_OPT_LENGTH = 4
         | 
| 12 | 
            +
                  DEFAULT_WIDTH = 80
         | 
| 13 | 
            +
                  NEWLINE = "\n"
         | 
| 14 | 
            +
                  ELLIPSIS = "..."
         | 
| 15 | 
            +
                  SPACE = " "
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  DEFAULT_PARAM_DISPLAY = ->(str) { str.to_s.upcase }
         | 
| 18 | 
            +
                  DEFAULT_ORDER = ->(params) { params.sort }
         | 
| 19 | 
            +
                  NOOP_PROC = ->(param) { param }
         | 
| 20 | 
            +
                  DEFAULT_NAME_SELECTOR = ->(param) { param.name }
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  # @api public
         | 
| 23 | 
            +
                  def self.help(parameters, usage, **config, &block)
         | 
| 24 | 
            +
                    new(parameters, usage, **config).help(&block)
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  attr_reader :width
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  # Create a help formatter
         | 
| 30 | 
            +
                  #
         | 
| 31 | 
            +
                  # @param [Parameters]
         | 
| 32 | 
            +
                  #
         | 
| 33 | 
            +
                  # @api public
         | 
| 34 | 
            +
                  def initialize(parameters, usage, param_display: DEFAULT_PARAM_DISPLAY,
         | 
| 35 | 
            +
                                 width: DEFAULT_WIDTH, order: DEFAULT_ORDER, indent: 0)
         | 
| 36 | 
            +
                    @parameters = parameters
         | 
| 37 | 
            +
                    @usage = usage
         | 
| 38 | 
            +
                    @param_display = param_display
         | 
| 39 | 
            +
                    @order = order
         | 
| 40 | 
            +
                    @width = width
         | 
| 41 | 
            +
                    @indent = indent
         | 
| 42 | 
            +
                    @space_indent = SPACE * indent
         | 
| 43 | 
            +
                    @param_indent = indent + 2
         | 
| 44 | 
            +
                    @section_names = {
         | 
| 45 | 
            +
                      usage: "Usage:",
         | 
| 46 | 
            +
                      arguments: "Arguments:",
         | 
| 47 | 
            +
                      keywords: "Keywords:",
         | 
| 48 | 
            +
                      options: "Options:",
         | 
| 49 | 
            +
                      env: "Environment:",
         | 
| 50 | 
            +
                      examples: "Examples:"
         | 
| 51 | 
            +
                    }
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  # A formatted help usage information
         | 
| 55 | 
            +
                  #
         | 
| 56 | 
            +
                  # @return [String]
         | 
| 57 | 
            +
                  #
         | 
| 58 | 
            +
                  # @api public
         | 
| 59 | 
            +
                  def help(&block)
         | 
| 60 | 
            +
                    sections = Sections.new
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                    sections.add(:header, help_header) if @usage.header?
         | 
| 63 | 
            +
                    sections.add(:banner, help_banner)
         | 
| 64 | 
            +
                    sections.add(:description, help_description) if @usage.desc?
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                    if @parameters.arguments.any?(&:display?)
         | 
| 67 | 
            +
                      sections.add(:arguments, help_arguments)
         | 
| 68 | 
            +
                    end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                    if @parameters.keywords.any?(&:display?)
         | 
| 71 | 
            +
                      sections.add(:keywords, help_keywords)
         | 
| 72 | 
            +
                    end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                    if @parameters.options?
         | 
| 75 | 
            +
                      sections.add(:options, help_options)
         | 
| 76 | 
            +
                    end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                    if @parameters.environments.any?(&:display?)
         | 
| 79 | 
            +
                      sections.add(:environments, help_environments)
         | 
| 80 | 
            +
                    end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    sections.add(:examples, help_examples) if @usage.example?
         | 
| 83 | 
            +
                    sections.add(:footer, help_footer) if @usage.footer?
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                    if block_given?
         | 
| 86 | 
            +
                      yield(sections)
         | 
| 87 | 
            +
                    end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                    formatted = sections.reject(&:empty?).join(NEWLINE)
         | 
| 90 | 
            +
                    formatted.end_with?(NEWLINE) ? formatted : formatted + NEWLINE
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  def help_header
         | 
| 94 | 
            +
                    "#{format_multiline(@usage.header, @indent)}#{NEWLINE}"
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  def help_banner
         | 
| 98 | 
            +
                    (@usage.banner? ? @usage.banner : format_usage)
         | 
| 99 | 
            +
                  end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                  def help_description
         | 
| 102 | 
            +
                    "#{NEWLINE}#{format_description}"
         | 
| 103 | 
            +
                  end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                  def help_arguments
         | 
| 106 | 
            +
                    "#{NEWLINE}#{@space_indent}#{@section_names[:arguments]}#{NEWLINE}" +
         | 
| 107 | 
            +
                      format_section(@parameters.arguments, ->(param) do
         | 
| 108 | 
            +
                        @param_display.(param.name)
         | 
| 109 | 
            +
                      end)
         | 
| 110 | 
            +
                  end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                  def help_keywords
         | 
| 113 | 
            +
                    "#{NEWLINE}#{@space_indent}#{@section_names[:keywords]}#{NEWLINE}" +
         | 
| 114 | 
            +
                      format_section(@parameters.keywords, ->(param) do
         | 
| 115 | 
            +
                        kwarg_param_display(param).split("=").map(&@param_display).join("=")
         | 
| 116 | 
            +
                      end)
         | 
| 117 | 
            +
                  end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                  def help_options
         | 
| 120 | 
            +
                    "#{NEWLINE}#{@space_indent}#{@section_names[:options]}#{NEWLINE}" +
         | 
| 121 | 
            +
                      format_options
         | 
| 122 | 
            +
                  end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                  def help_environments
         | 
| 125 | 
            +
                    "#{NEWLINE}#{@space_indent}#{@section_names[:env]}#{NEWLINE}" +
         | 
| 126 | 
            +
                      format_section(@order.(@parameters.environments))
         | 
| 127 | 
            +
                  end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                  def help_examples
         | 
| 130 | 
            +
                    "#{NEWLINE}#{@space_indent}#{@section_names[:examples]}#{NEWLINE}" +
         | 
| 131 | 
            +
                      format_examples
         | 
| 132 | 
            +
                  end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                  def help_footer
         | 
| 135 | 
            +
                    "#{NEWLINE}#{format_multiline(@usage.footer, @indent)}"
         | 
| 136 | 
            +
                  end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                  private
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                  # Provide a default usage banner
         | 
| 141 | 
            +
                  #
         | 
| 142 | 
            +
                  # @api private
         | 
| 143 | 
            +
                  def format_usage
         | 
| 144 | 
            +
                    usage = @space_indent + @section_names[:usage] + SPACE
         | 
| 145 | 
            +
                    output = []
         | 
| 146 | 
            +
                    output << @usage.program
         | 
| 147 | 
            +
                    output << " #{@usage.commands.join(" ")}" if @usage.command?
         | 
| 148 | 
            +
                    output << " [#{@param_display.("options")}]" if @parameters.options?
         | 
| 149 | 
            +
                    output << " [#{@param_display.("environment")}]" if @parameters.environments?
         | 
| 150 | 
            +
                    output << " #{format_arguments_usage}" if @parameters.arguments?
         | 
| 151 | 
            +
                    output << " #{format_keywords_usage}" if @parameters.keywords?
         | 
| 152 | 
            +
                    usage + wrap(output.join, indent: usage.length, width: width)
         | 
| 153 | 
            +
                  end
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                  # Format arguments
         | 
| 156 | 
            +
                  #
         | 
| 157 | 
            +
                  # @api private
         | 
| 158 | 
            +
                  def format_arguments_usage
         | 
| 159 | 
            +
                    return "" unless @parameters.arguments?
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                    @parameters.arguments.reduce([]) do |acc, arg|
         | 
| 162 | 
            +
                      next acc if arg.hidden?
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                      acc << format_argument_usage(arg)
         | 
| 165 | 
            +
                    end.join(SPACE)
         | 
| 166 | 
            +
                  end
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                  # Provide an argument summary
         | 
| 169 | 
            +
                  #
         | 
| 170 | 
            +
                  # @api private
         | 
| 171 | 
            +
                  def format_argument_usage(arg)
         | 
| 172 | 
            +
                    arg_name = @param_display.(arg.name)
         | 
| 173 | 
            +
                    format_parameter_usage(arg, arg_name)
         | 
| 174 | 
            +
                  end
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                  # Format parameter usage
         | 
| 177 | 
            +
                  #
         | 
| 178 | 
            +
                  # @api private
         | 
| 179 | 
            +
                  def format_parameter_usage(param, param_name)
         | 
| 180 | 
            +
                    args = []
         | 
| 181 | 
            +
                    if 0 < param.arity
         | 
| 182 | 
            +
                      args << "[" if param.optional?
         | 
| 183 | 
            +
                      args << param_name
         | 
| 184 | 
            +
                      (param.arity - 1).times { args << " #{param_name}" }
         | 
| 185 | 
            +
                      args. << "]" if param.optional?
         | 
| 186 | 
            +
                      args.join
         | 
| 187 | 
            +
                    else
         | 
| 188 | 
            +
                      (param.arity.abs - 1).times { args << param_name }
         | 
| 189 | 
            +
                      args << "[#{param_name}#{ELLIPSIS}]"
         | 
| 190 | 
            +
                      args.join(SPACE)
         | 
| 191 | 
            +
                    end
         | 
| 192 | 
            +
                  end
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                  # Format keywords usage
         | 
| 195 | 
            +
                  #
         | 
| 196 | 
            +
                  # @api private
         | 
| 197 | 
            +
                  def format_keywords_usage
         | 
| 198 | 
            +
                    return "" unless @parameters.keywords?
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                    @parameters.keywords.reduce([]) do |acc, kwarg|
         | 
| 201 | 
            +
                      next acc if kwarg.hidden?
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                      acc << format_keyword_usage(kwarg)
         | 
| 204 | 
            +
                    end.join(SPACE)
         | 
| 205 | 
            +
                  end
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                  # Provide a keyword summary
         | 
| 208 | 
            +
                  #
         | 
| 209 | 
            +
                  # @api private
         | 
| 210 | 
            +
                  def format_keyword_usage(kwarg)
         | 
| 211 | 
            +
                    param_name = kwarg_param_display(kwarg, @param_display)
         | 
| 212 | 
            +
                    format_parameter_usage(kwarg, param_name)
         | 
| 213 | 
            +
                  end
         | 
| 214 | 
            +
             | 
| 215 | 
            +
                  # Provide a keyword argument display format
         | 
| 216 | 
            +
                  #
         | 
| 217 | 
            +
                  # @api private
         | 
| 218 | 
            +
                  def kwarg_param_display(kwarg, param_display = NOOP_PROC)
         | 
| 219 | 
            +
                    kwarg_name = param_display.(kwarg.name)
         | 
| 220 | 
            +
                    conv_name = case kwarg.convert
         | 
| 221 | 
            +
                                when Proc, NilClass
         | 
| 222 | 
            +
                                  kwarg_name
         | 
| 223 | 
            +
                                else
         | 
| 224 | 
            +
                                  param_display.(kwarg.convert)
         | 
| 225 | 
            +
                                end
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                    "#{kwarg_name}=#{conv_name}"
         | 
| 228 | 
            +
                  end
         | 
| 229 | 
            +
             | 
| 230 | 
            +
                  # Format a parameter section in the help display
         | 
| 231 | 
            +
                  #
         | 
| 232 | 
            +
                  # @param [String] parameters_name
         | 
| 233 | 
            +
                  #   the name of parameter type
         | 
| 234 | 
            +
                  #
         | 
| 235 | 
            +
                  # @param [Proc] name_selector
         | 
| 236 | 
            +
                  #   selects a name from the parameter, by defeault the name
         | 
| 237 | 
            +
                  #
         | 
| 238 | 
            +
                  # @return [String]
         | 
| 239 | 
            +
                  #
         | 
| 240 | 
            +
                  # @api private
         | 
| 241 | 
            +
                  def format_section(params, name_selector = DEFAULT_NAME_SELECTOR)
         | 
| 242 | 
            +
                    longest_param = params.map(&name_selector).compact.max_by(&:length).length
         | 
| 243 | 
            +
             | 
| 244 | 
            +
                    params.reduce([]) do |acc, param|
         | 
| 245 | 
            +
                      next acc if param.hidden?
         | 
| 246 | 
            +
             | 
| 247 | 
            +
                      acc << format_section_parameter(param, longest_param, name_selector)
         | 
| 248 | 
            +
                    end.join(NEWLINE)
         | 
| 249 | 
            +
                  end
         | 
| 250 | 
            +
             | 
| 251 | 
            +
                  # Format a section parameter line
         | 
| 252 | 
            +
                  #
         | 
| 253 | 
            +
                  # @return [String]
         | 
| 254 | 
            +
                  #
         | 
| 255 | 
            +
                  # @api private
         | 
| 256 | 
            +
                  def format_section_parameter(param, longest_param, name_selector)
         | 
| 257 | 
            +
                    line = []
         | 
| 258 | 
            +
                    desc = []
         | 
| 259 | 
            +
                    indent = @param_indent + longest_param + 2
         | 
| 260 | 
            +
                    param_name = name_selector.(param)
         | 
| 261 | 
            +
             | 
| 262 | 
            +
                    if param.desc?
         | 
| 263 | 
            +
                      line << format("%s%-#{longest_param}s", SPACE * @param_indent, param_name)
         | 
| 264 | 
            +
                      desc << "  #{param.desc}"
         | 
| 265 | 
            +
                    else
         | 
| 266 | 
            +
                      line << format("%s%s", SPACE * @param_indent, param_name)
         | 
| 267 | 
            +
                    end
         | 
| 268 | 
            +
             | 
| 269 | 
            +
                    if param.permit?
         | 
| 270 | 
            +
                      desc << format(" (permitted: %s)", param.permit.join(", "))
         | 
| 271 | 
            +
                    end
         | 
| 272 | 
            +
             | 
| 273 | 
            +
                    if (default = format_default(param))
         | 
| 274 | 
            +
                      desc << default
         | 
| 275 | 
            +
                    end
         | 
| 276 | 
            +
             | 
| 277 | 
            +
                    line << wrap(desc.join, indent: indent, width: width)
         | 
| 278 | 
            +
                    line.join
         | 
| 279 | 
            +
                  end
         | 
| 280 | 
            +
             | 
| 281 | 
            +
                  # Format multiline description
         | 
| 282 | 
            +
                  #
         | 
| 283 | 
            +
                  # @api private
         | 
| 284 | 
            +
                  def format_description
         | 
| 285 | 
            +
                    format_multiline(@usage.desc, @indent)
         | 
| 286 | 
            +
                  end
         | 
| 287 | 
            +
             | 
| 288 | 
            +
                  # Returns all the options formatted to fit 80 columns
         | 
| 289 | 
            +
                  #
         | 
| 290 | 
            +
                  # @return [String]
         | 
| 291 | 
            +
                  #
         | 
| 292 | 
            +
                  # @api private
         | 
| 293 | 
            +
                  def format_options
         | 
| 294 | 
            +
                    return "" if @parameters.options.empty?
         | 
| 295 | 
            +
             | 
| 296 | 
            +
                    longest_option = @parameters.options.map(&:long)
         | 
| 297 | 
            +
                                                .compact.max_by(&:length).length
         | 
| 298 | 
            +
                    any_short = @parameters.options.map(&:short).compact.any?
         | 
| 299 | 
            +
                    ordered_options = @order.(@parameters.options)
         | 
| 300 | 
            +
             | 
| 301 | 
            +
                    ordered_options.reduce([]) do |acc, option|
         | 
| 302 | 
            +
                      next acc if option.hidden?
         | 
| 303 | 
            +
                      acc << format_option(option, longest_option, any_short)
         | 
| 304 | 
            +
                    end.join(NEWLINE)
         | 
| 305 | 
            +
                  end
         | 
| 306 | 
            +
             | 
| 307 | 
            +
                  # Format an option
         | 
| 308 | 
            +
                  #
         | 
| 309 | 
            +
                  # @api private
         | 
| 310 | 
            +
                  def format_option(option, longest_length, any_short)
         | 
| 311 | 
            +
                    line = [@space_indent]
         | 
| 312 | 
            +
                    desc = []
         | 
| 313 | 
            +
                    indent = @indent
         | 
| 314 | 
            +
             | 
| 315 | 
            +
                    if any_short
         | 
| 316 | 
            +
                      short_option = option.short? ? option.short_name : SPACE
         | 
| 317 | 
            +
                      line << format("%#{SHORT_OPT_LENGTH}s", short_option)
         | 
| 318 | 
            +
                      indent += SHORT_OPT_LENGTH
         | 
| 319 | 
            +
                    end
         | 
| 320 | 
            +
             | 
| 321 | 
            +
                    # short & long option separator
         | 
| 322 | 
            +
                    line << ((option.short? && option.long?) ? ", " : "  ")
         | 
| 323 | 
            +
                    indent += 2
         | 
| 324 | 
            +
             | 
| 325 | 
            +
                    if option.long?
         | 
| 326 | 
            +
                      if option.desc?
         | 
| 327 | 
            +
                        line << format("%-#{longest_length}s", option.long)
         | 
| 328 | 
            +
                      else
         | 
| 329 | 
            +
                        line << option.long
         | 
| 330 | 
            +
                      end
         | 
| 331 | 
            +
                    else
         | 
| 332 | 
            +
                      line << format("%-#{longest_length}s", SPACE)
         | 
| 333 | 
            +
                    end
         | 
| 334 | 
            +
                    indent += longest_length
         | 
| 335 | 
            +
             | 
| 336 | 
            +
                    if option.desc?
         | 
| 337 | 
            +
                      desc << "  #{option.desc}"
         | 
| 338 | 
            +
                    end
         | 
| 339 | 
            +
                    indent += 2
         | 
| 340 | 
            +
             | 
| 341 | 
            +
                    if option.permit?
         | 
| 342 | 
            +
                      desc << format(" (permitted: %s)", option.permit.join(","))
         | 
| 343 | 
            +
                    end
         | 
| 344 | 
            +
             | 
| 345 | 
            +
                    if (default = format_default(option))
         | 
| 346 | 
            +
                      desc << default
         | 
| 347 | 
            +
                    end
         | 
| 348 | 
            +
             | 
| 349 | 
            +
                    line << wrap(desc.join, indent: indent, width: width)
         | 
| 350 | 
            +
             | 
| 351 | 
            +
                    line.join
         | 
| 352 | 
            +
                  end
         | 
| 353 | 
            +
             | 
| 354 | 
            +
                  # Format default value
         | 
| 355 | 
            +
                  #
         | 
| 356 | 
            +
                  # @api private
         | 
| 357 | 
            +
                  def format_default(param)
         | 
| 358 | 
            +
                    return if !param.default? || [true, false].include?(param.default)
         | 
| 359 | 
            +
             | 
| 360 | 
            +
                    if param.default.is_a?(String)
         | 
| 361 | 
            +
                      format(" (default %p)", param.default)
         | 
| 362 | 
            +
                    else
         | 
| 363 | 
            +
                      format(" (default %s)", param.default)
         | 
| 364 | 
            +
                    end
         | 
| 365 | 
            +
                  end
         | 
| 366 | 
            +
             | 
| 367 | 
            +
                  # Format examples section
         | 
| 368 | 
            +
                  #
         | 
| 369 | 
            +
                  # @api private
         | 
| 370 | 
            +
                  def format_examples
         | 
| 371 | 
            +
                    format_multiline(@usage.example, @param_indent)
         | 
| 372 | 
            +
                  end
         | 
| 373 | 
            +
             | 
| 374 | 
            +
                  # Format multiline content
         | 
| 375 | 
            +
                  #
         | 
| 376 | 
            +
                  # @api private
         | 
| 377 | 
            +
                  def format_multiline(lines, indent)
         | 
| 378 | 
            +
                    last_index = lines.size - 1
         | 
| 379 | 
            +
                    lines.map.with_index do |line, i|
         | 
| 380 | 
            +
                      line.map do |part|
         | 
| 381 | 
            +
                        part.split(NEWLINE).map do |p|
         | 
| 382 | 
            +
                          wrap(p, indent: indent, width: width, indent_first: true)
         | 
| 383 | 
            +
                        end.join(NEWLINE)
         | 
| 384 | 
            +
                      end.join(NEWLINE) + (last_index != i ? NEWLINE : "")
         | 
| 385 | 
            +
                    end.join(NEWLINE)
         | 
| 386 | 
            +
                  end
         | 
| 387 | 
            +
                end # Formatter
         | 
| 388 | 
            +
              end # Option
         | 
| 389 | 
            +
            end # TTY
         |