Skip to content

Commit

Permalink
Add default usage banner with arguments formatting to the help display
Browse files Browse the repository at this point in the history
  • Loading branch information
piotrmurach committed Apr 26, 2020
1 parent ddc6f5f commit 673a0f7
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 8 deletions.
2 changes: 1 addition & 1 deletion lib/tty/option.rb
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions 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"
Expand All @@ -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
#
Expand Down
66 changes: 60 additions & 6 deletions lib/tty/option/formatter.rb
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions lib/tty/option/usage.rb
Expand Up @@ -30,6 +30,10 @@ def banner(value = (not_set = true))
end
end

def banner?
@properties.key?(:banner) && !@properties[:banner].nil?
end

# Description
#
# @api public
Expand Down
111 changes: 110 additions & 1 deletion spec/unit/help_spec.rb
Expand Up @@ -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
Expand All @@ -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
4 changes: 4 additions & 0 deletions spec/unit/usage_spec.rb
Expand Up @@ -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

Expand Down

0 comments on commit 673a0f7

Please sign in to comment.