diff --git a/lib/tty/option.rb b/lib/tty/option.rb index e923f77..cff569c 100644 --- a/lib/tty/option.rb +++ b/lib/tty/option.rb @@ -62,7 +62,7 @@ def parse(argv = ARGV, env = ENV, **config) # # @api public def help - Formatter.help(self.class.parameters) + Formatter.help(self.class.parameters, self.class.usage) end end end # Option diff --git a/lib/tty/option/dsl.rb b/lib/tty/option/dsl.rb index 17ec6aa..eade11f 100644 --- a/lib/tty/option/dsl.rb +++ b/lib/tty/option/dsl.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "forwardable" + require_relative "dsl/arity" require_relative "parameter/argument" require_relative "parameter/environment" @@ -12,6 +14,16 @@ module TTY module Option module DSL include Arity + extend Forwardable + + def_delegators :usage, :banner, :desc, :program + + # Holds the usage information + # + # @api public + def usage + @usage ||= Usage.new + end # Specify an argument # diff --git a/lib/tty/option/formatter.rb b/lib/tty/option/formatter.rb index cc3cc93..3212de5 100644 --- a/lib/tty/option/formatter.rb +++ b/lib/tty/option/formatter.rb @@ -4,33 +4,87 @@ module TTY module Option class Formatter SHORT_OPT_LENGTH = 4 - SHORT_OPT_SPACING = 6 NEWLINE = "\n" # @api public - def self.help(parameters) - new(parameters).help + def self.help(parameters, usage) + new(parameters, usage).help end + # Create a help formatter + # # @param [Parameters] # # @api public - def initialize(parameters) + def initialize(parameters, usage) @parameters = parameters + @usage = usage end + # A formatted help usage information + # + # @return [String] + # + # @api public def help - "Options:\n" + format_options + output = [] + + output << (@usage.banner? ? @usage.banner : format_usage) + NEWLINE + + if @usage.desc? + output << @usage.desc + NEWLINE + end + + if @parameters.options? + output << "Options:" + output << format_options + end + + formatted = output.join(NEWLINE) + formatted.end_with?(NEWLINE) ? formatted : formatted + NEWLINE end private + # @api private + def format_usage + output = [] + output << "Usage: " + output << @usage.program + output << " [OPTIONS]" if @parameters.options? + output << " #{format_arguments}" if @parameters.arguments? + output.join + end + + # @api private + def format_arguments + return "" unless @parameters.arguments? + + @parameters.arguments.reduce([]) do |acc, arg| + arg_name = arg.name.to_s.upcase + if 0 < arg.arity + args = [] + args << "[" if arg.optional? + args << arg_name + (arg.arity - 1).times { args << " #{arg_name}" } + args << "]" if arg.optional? + acc << args.join + else + (arg.arity.abs - 1).times { acc << arg_name } + acc << "[#{arg_name}...]" + end + acc + end.join(" ") + end + # Returns all the options formatted to fit 80 columns # # @return [String] # - # @api public + # @api private def format_options + return "" if @parameters.options.empty? + output = [] longest_option = @parameters.options.map(&:long) .compact.max_by(&:length).length diff --git a/lib/tty/option/usage.rb b/lib/tty/option/usage.rb index 7b483fa..3c1132e 100644 --- a/lib/tty/option/usage.rb +++ b/lib/tty/option/usage.rb @@ -30,6 +30,10 @@ def banner(value = (not_set = true)) end end + def banner? + @properties.key?(:banner) && !@properties[:banner].nil? + end + # Description # # @api public diff --git a/spec/unit/help_spec.rb b/spec/unit/help_spec.rb index b4d4379..b04e4e6 100644 --- a/spec/unit/help_spec.rb +++ b/spec/unit/help_spec.rb @@ -46,7 +46,9 @@ def new_command(&block) end end - expected_output = unindent(<<-EOS).strip + expected_output = unindent(<<-EOS) + Usage: rspec [OPTIONS] + Options: -b, --bar string Some description (default "baz") --baz @@ -58,4 +60,111 @@ def new_command(&block) expect(cmd.help).to eq(expected_output) end + + context "Usage banner" do + it "formats banner with a single argument, description and no options" do + cmd = new_command do + argument :foo do + required + end + + desc "Some description" + end + + expected_output = unindent(<<-EOS) + Usage: rspec FOO + + Some description + EOS + + expect(cmd.help).to eq(expected_output) + end + + it "formats banner with no arguments and some options" do + cmd = new_command do + option :foo do + desc "Some description" + end + end + + expected_output = unindent(<<-EOS) + Usage: rspec [OPTIONS] + + Options: + --foo Some description + EOS + + expect(cmd.help).to eq(expected_output) + end + + it "formats banner with required & optional arguments and options" do + cmd = new_command do + argument :foo do + required + arity 2 + end + + argument :bar do + optional + end + + option :baz do + desc "Some description" + end + end + + expected_output = unindent(<<-EOS) + Usage: rspec [OPTIONS] FOO FOO [BAR] + + Options: + --baz Some description + EOS + + expect(cmd.help).to eq(expected_output) + end + + it "formats banner with any arguments and options" do + cmd = new_command do + program "foo" + + argument :bar do + arity zero_or_more + end + end + + expected_output = unindent(<<-EOS) + Usage: foo [BAR...] + EOS + + expect(cmd.help).to eq(expected_output) + end + + it "formats banner with one or more arguments and no options" do + cmd = new_command do + program "foo" + + argument :bar do + arity one_or_more + end + end + + expected_output = unindent(<<-EOS) + Usage: foo BAR [BAR...] + EOS + + expect(cmd.help).to eq(expected_output) + end + + it "uses a custom banner" do + cmd = new_command do + banner "Usage: foo BAR BAZ" + end + + expected_output = unindent(<<-EOS) + Usage: foo BAR BAZ + EOS + + expect(cmd.help).to eq(expected_output) + end + end end diff --git a/spec/unit/usage_spec.rb b/spec/unit/usage_spec.rb index 04a5400..a129ab3 100644 --- a/spec/unit/usage_spec.rb +++ b/spec/unit/usage_spec.rb @@ -27,8 +27,12 @@ it "changes banner via method" do usage = described_class.new + expect(usage.banner?).to eq(false) + usage.banner("foo") + expect(usage.banner).to eq("foo") + expect(usage.banner?).to eq(true) end end