Skip to content

Commit

Permalink
Enhance help output formatting configurability
Browse files Browse the repository at this point in the history
* Allow manual ordering of help commands, Closes #83
* Disable automatic wrapping always or only for non-TTY, Closes #100
  • Loading branch information
davetron5000 committed Sep 15, 2012
1 parent c80d256 commit 63c0823
Show file tree
Hide file tree
Showing 14 changed files with 201 additions and 17 deletions.
11 changes: 11 additions & 0 deletions features/step_definitions/todo_steps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@
add_to_path(File.expand_path(File.join(File.dirname(__FILE__),'..','..','test','apps','todo','bin')))
end

Given /^the todo app is coded to avoid sorted help commands$/ do
ENV['TODO_SORT_HELP'] = 'manually'
end

Given /^the todo app is coded to avoid wrapping text$/ do
ENV['TODO_WRAP_HELP_TEXT'] = 'never'
end

Given /^the todo app is coded to wrap text only for tty$/ do
ENV['TODO_WRAP_HELP_TEXT'] = 'tty_only'
end

Given /^a clean home directory$/ do
FileUtils.rm_rf File.join(ENV['HOME'],'gli_test_todo.rc')
Expand Down
2 changes: 2 additions & 0 deletions features/support/env.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
end
ENV['PATH'] = @original_path.join(File::PATH_SEPARATOR)
ENV['HOME'] = @original_home
ENV['TODO_SORT_HELP'] = nil
ENV['TODO_WRAP_HELP_TEXT'] = nil
end

def add_to_path(dir)
Expand Down
79 changes: 79 additions & 0 deletions features/todo.feature
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,39 @@ Feature: The todo app has a nice user interface
contexts
"""

Scenario: Getting Help with self-ordered commands
Given the todo app is coded to avoid sorted help commands
When I successfully run `todo help`
Then the output should contain:
"""
NAME
todo - Manages tasks
SYNOPSIS
todo [global options] command [command options] [arguments...]
VERSION
0.0.1
GLOBAL OPTIONS
--flag=arg - (default: none)
--help - Show this message
--[no-]otherswitch -
--[no-]switch -
--version -
COMMANDS
help - Shows a list of commands or help for one command
initconfig - Initialize the config file using current global options
create, new - Create a new task or context
list - List things, such as tasks or contexts
ls - LS things, such as tasks or contexts
first -
second -
chained -
chained2, ch2 -
"""

Scenario: Getting Help for a top level command of todo
When I successfully run `todo help list`
Then the output should contain:
Expand All @@ -111,6 +144,52 @@ Feature: The todo app has a nice user interface
tasks - List tasks (default)
"""

Scenario: Getting Help without wrapping
Given the todo app is coded to avoid wrapping text
When I successfully run `todo help list`
Then the output should contain:
"""
NAME
list - List things, such as tasks or contexts
SYNOPSIS
todo [global options] list [command options] [--flag arg] [-x arg] [tasks]
todo [global options] list [command options] [--otherflag arg] [-b] [-f|--foobar] contexts
DESCRIPTION
List a whole lot of things that you might be keeping track of in your overall todo list. This is your go-to place or finding all of the things that you might have stored in your todo databases.
COMMAND OPTIONS
-l, --[no-]long - Show long form
COMMANDS
contexts - List contexts
tasks - List tasks (default)
"""

Scenario: Getting Help without wrapping
Given the todo app is coded to wrap text only for tty
When I successfully run `todo help list`
Then the output should contain:
"""
NAME
list - List things, such as tasks or contexts
SYNOPSIS
todo [global options] list [command options] [--flag arg] [-x arg] [tasks]
todo [global options] list [command options] [--otherflag arg] [-b] [-f|--foobar] contexts
DESCRIPTION
List a whole lot of things that you might be keeping track of in your overall todo list. This is your go-to place or finding all of the things that you might have stored in your todo databases.
COMMAND OPTIONS
-l, --[no-]long - Show long form
COMMANDS
contexts - List contexts
tasks - List tasks (default)
"""

Scenario: Getting Help for a sub command of todo list
When I successfully run `todo help list tasks`
Then the output should contain:
Expand Down
19 changes: 19 additions & 0 deletions lib/gli/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,25 @@ def exit_code; 64; end
raise exception
end

# Control how help commands are sorted. By default, the commands are sorted alphabetically.
#
# sort_type:: How you want help commands sorted:
# +:manually+:: help commands are ordered in the order declared.
# +:alpha+:: sort alphabetically (default)
def sort_help(sort_type)
@help_sort_type = sort_type
end

# Set how help text is wrapped.
#
# wrap_type:: Symbol indicating how you'd like text wrapped:
# +:to_terminal+:: Wrap text based on the width of the terminal (default)
# +:never+:: Do not wrap text at all. This will bring all help content onto one line, removing any newlines
# +:tty_only+:: Wrap like +:to_terminal+ if this output is going to a TTY, otherwise don't wrap (like +:never+)
def wrap_help_text(wrap_type)
@help_text_wrap_type = wrap_type
end

def program_name(override=nil) #:nodoc:
warn "#program_name has been deprecated"
end
Expand Down
8 changes: 8 additions & 0 deletions lib/gli/app_support.rb
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ def around_blocks
@around_blocks || []
end

def help_sort_type
@help_sort_type || :alpha
end

def help_text_wrap_type
@help_text_wrap_type || :to_terminal
end

# Sets the default values for flags based on the configuration
def override_defaults_based_on_config(config)
override_default(flags,config)
Expand Down
19 changes: 17 additions & 2 deletions lib/gli/commands/help.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
require 'gli/terminal'
require 'gli/commands/help_modules/list_formatter'
require 'gli/commands/help_modules/text_wrapper'
require 'gli/commands/help_modules/no_wrapping_wrapper'
require 'gli/commands/help_modules/tty_only_wrapper'
require 'gli/commands/help_modules/options_formatter'
require 'gli/commands/help_modules/global_help_format'
require 'gli/commands/help_modules/command_help_format'
Expand All @@ -11,6 +13,16 @@

module GLI
module Commands
SORTERS = {
:manually => lambda { |list| list },
:alpha => lambda { |list| list.sort },
}

WRAPPERS = {
:to_terminal => HelpModules::TextWrapper,
:never => HelpModules::NoWrappingWrapper,
:tty_only => HelpModules::TTYOnlyWrapper,
}
# The help command used for the two-level interactive help system
class Help < Command
def initialize(app,output=$stdout,error=$stderr)
Expand All @@ -22,6 +34,9 @@ def initialize(app,output=$stdout,error=$stderr)
:skips_post => true,
:skips_around => true)
@app = app
@sorter = SORTERS[@app.help_sort_type]
@text_wrapping_class = WRAPPERS[@app.help_text_wrap_type]

desc 'List commands one per line, to assist with shell completion'
switch :c

Expand All @@ -38,12 +53,12 @@ def show_help(global_options,options,arguments,out,error)
help_output = HelpModules::HelpCompletionFormat.new(@app,command_finder,arguments).format
out.puts help_output unless help_output.nil?
elsif arguments.empty? || options[:c]
out.puts HelpModules::GlobalHelpFormat.new(@app).format
out.puts HelpModules::GlobalHelpFormat.new(@app,@sorter,@text_wrapping_class).format
else
name = arguments.shift
command = command_finder.find_command(name)
unless command.nil?
out.puts HelpModules::CommandHelpFormat.new(command,@app,File.basename($0).to_s).format
out.puts HelpModules::CommandHelpFormat.new(command,@app,File.basename($0).to_s,@sorter,@text_wrapping_class).format
end
end
end
Expand Down
14 changes: 8 additions & 6 deletions lib/gli/commands/help_modules/command_help_format.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ module GLI
module Commands
module HelpModules
class CommandHelpFormat
def initialize(command,app,basic_invocation)
def initialize(command,app,basic_invocation,sorter,wrapper_class=TextWrapper)
@basic_invocation = basic_invocation
@app = app
@command = command
@sorter = sorter
@wrapper_class = wrapper_class
end

def format
command_wrapper = TextWrapper.new(Terminal.instance.size[0],4 + @command.name.to_s.size + 3)
wrapper = TextWrapper.new(Terminal.instance.size[0],4)
command_wrapper = @wrapper_class.new(Terminal.instance.size[0],4 + @command.name.to_s.size + 3)
wrapper = @wrapper_class.new(Terminal.instance.size[0],4)
flags_and_switches = Hash[@command.topmost_ancestor.flags.merge(@command.topmost_ancestor.switches).select { |_,option| option.associated_command == @command }]
options_description = OptionsFormatter.new(flags_and_switches).format
options_description = OptionsFormatter.new(flags_and_switches,@wrapper_class).format
commands_description = format_subcommands(@command)

synopses = []
Expand Down Expand Up @@ -110,7 +112,7 @@ def global_flags_and_switches
end

def format_subcommands(command)
commands_array = command.commands.values.sort.map { |cmd|
commands_array = @sorter.call(command.commands.values).map { |cmd|
if command.get_default_command == cmd.name
[cmd.names,cmd.description + " (default)"]
else
Expand All @@ -120,7 +122,7 @@ def format_subcommands(command)
if command.has_action?
commands_array.unshift(["<default>",command.default_description])
end
formatter = ListFormatter.new(commands_array)
formatter = ListFormatter.new(commands_array,@wrapper_class)
StringIO.new.tap { |io| formatter.output(io) }.string
end

Expand Down
10 changes: 6 additions & 4 deletions lib/gli/commands/help_modules/global_help_format.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@ module GLI
module Commands
module HelpModules
class GlobalHelpFormat
def initialize(app)
def initialize(app,sorter,wrapper_class)
@app = app
@sorter = sorter
@wrapper_class = wrapper_class
end

def format
program_desc = @app.program_desc

command_formatter = ListFormatter.new(@app.commands.values.sort.reject(&:nodoc).map { |command|
command_formatter = ListFormatter.new(@sorter.call(@app.commands.values.reject(&:nodoc)).map { |command|
[[command.name,Array(command.aliases)].flatten.join(', '),command.description]
})
}, @wrapper_class)
stringio = StringIO.new
command_formatter.output(stringio)
commands = stringio.string

global_option_descriptions = OptionsFormatter.new(global_flags_and_switches).format
global_option_descriptions = OptionsFormatter.new(global_flags_and_switches,@wrapper_class).format

GLOBAL_HELP.result(binding)
end
Expand Down
5 changes: 3 additions & 2 deletions lib/gli/commands/help_modules/list_formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ module Commands
module HelpModules
# Given a list of two-element lists, formats on the terminal
class ListFormatter
def initialize(list)
def initialize(list,wrapper_class=TextWrapper)
@list = list
@wrapper_class = wrapper_class
end

# Output the list to the output_device
def output(output_device)
return if @list.empty?
max_width = @list.map { |_| _[0].length }.max
wrapper = TextWrapper.new(Terminal.instance.size[0],4 + max_width + 3)
wrapper = @wrapper_class.new(Terminal.instance.size[0],4 + max_width + 3)
@list.each do |(name,description)|
output_device.printf(" %-#{max_width}s - %s\n",name,wrapper.wrap(String(description).strip))
end
Expand Down
18 changes: 18 additions & 0 deletions lib/gli/commands/help_modules/no_wrapping_wrapper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module GLI
module Commands
module HelpModules
# Formats text in one line, stripping newlines and NOT wrapping
class NoWrappingWrapper
# Args are ignored entirely; this keeps it consistent with the TextWrapper interface
def initialize(width,indent)
end

# Return a wrapped version of text, assuming that the first line has already been
# indented by @indent characters. Resulting text does NOT have a newline in it.
def wrap(text)
return String(text).gsub(/\n+/,' ').strip
end
end
end
end
end
5 changes: 3 additions & 2 deletions lib/gli/commands/help_modules/options_formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ module GLI
module Commands
module HelpModules
class OptionsFormatter
def initialize(flags_and_switches)
def initialize(flags_and_switches,wrapper_class)
@flags_and_switches = flags_and_switches
@wrapper_class = wrapper_class
end

def format
Expand All @@ -15,7 +16,7 @@ def format
else
[option_names_for_help_string(option),description_with_default(option)]
end
})
},@wrapper_class)
stringio = StringIO.new
list_formatter.output(stringio)
stringio.string
Expand Down
23 changes: 23 additions & 0 deletions lib/gli/commands/help_modules/tty_only_wrapper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module GLI
module Commands
module HelpModules
# Formats text in one line, stripping newlines and NOT wrapping
class TTYOnlyWrapper
# Args are ignored entirely; this keeps it consistent with the TextWrapper interface
def initialize(width,indent)
@proxy = if STDOUT.tty?
TextWrapper.new(width,indent)
else
NoWrappingWrapper.new(width,indent)
end
end

# Return a wrapped version of text, assuming that the first line has already been
# indented by @indent characters. Resulting text does NOT have a newline in it.
def wrap(text)
@proxy.wrap(text)
end
end
end
end
end
2 changes: 1 addition & 1 deletion lib/gli/terminal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def command_exists?(command)
#
# Returns an Array of size two Ints representing the terminal width and height
def size
SIZE_DETERMINERS.select { |predicate,ignore| predicate.call }.first[1].call
SIZE_DETERMINERS.select { |(predicate,ignore)| predicate.call }.first[1].call
rescue Exception => ex
raise ex if @unsafe
Terminal.default_size
Expand Down
3 changes: 3 additions & 0 deletions test/apps/todo/bin/todo
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ else
include GLI::App
end

sort_help (ENV['TODO_SORT_HELP'] || 'alpha').to_sym
wrap_help_text (ENV['TODO_WRAP_HELP_TEXT'] || 'to_terminal').to_sym

program_desc 'Manages tasks'

config_file "gli_test_todo.rc"
Expand Down

0 comments on commit 63c0823

Please sign in to comment.