gli 2.11.0 → 2.20.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 +5 -5
- data/.circleci/config.yml +28 -0
- data/.gitignore +3 -3
- data/.tool-versions +1 -0
- data/Gemfile +0 -2
- data/README.rdoc +29 -19
- data/Rakefile +15 -37
- data/bin/ci +29 -0
- data/bin/gli +24 -54
- data/bin/rake +29 -0
- data/bin/setup +5 -0
- data/exe/gli +68 -0
- data/gli.gemspec +20 -24
- data/gli.rdoc +9 -9
- data/lib/gli/app.rb +31 -8
- data/lib/gli/app_support.rb +15 -3
- data/lib/gli/command.rb +24 -2
- data/lib/gli/command_finder.rb +42 -25
- data/lib/gli/command_support.rb +7 -6
- data/lib/gli/commands/doc.rb +9 -3
- data/lib/gli/commands/help.rb +2 -1
- data/lib/gli/commands/help_modules/arg_name_formatter.rb +2 -2
- data/lib/gli/commands/help_modules/command_help_format.rb +19 -1
- data/lib/gli/commands/help_modules/full_synopsis_formatter.rb +3 -2
- data/lib/gli/commands/help_modules/global_help_format.rb +1 -1
- data/lib/gli/commands/help_modules/options_formatter.rb +4 -6
- data/lib/gli/commands/initconfig.rb +3 -6
- data/lib/gli/commands/rdoc_document_listener.rb +2 -1
- data/lib/gli/commands/scaffold.rb +71 -142
- data/lib/gli/dsl.rb +2 -1
- data/lib/gli/flag.rb +23 -2
- data/lib/gli/gli_option_parser.rb +66 -15
- data/lib/gli/option_parser_factory.rb +9 -2
- data/lib/gli/options.rb +2 -2
- data/lib/gli/switch.rb +4 -0
- data/lib/gli/terminal.rb +6 -2
- data/lib/gli/version.rb +1 -1
- data/lib/gli.rb +1 -0
- data/object-model.dot +29 -0
- data/object-model.png +0 -0
- data/test/apps/todo/Gemfile +1 -1
- data/test/apps/todo/bin/todo +12 -6
- data/test/apps/todo/lib/todo/commands/create.rb +42 -41
- data/test/apps/todo/lib/todo/commands/list.rb +48 -36
- data/test/apps/todo/lib/todo/commands/ls.rb +25 -24
- data/test/apps/todo/lib/todo/commands/make.rb +42 -39
- data/test/apps/todo/todo.gemspec +1 -2
- data/test/apps/todo_legacy/todo.gemspec +1 -2
- data/test/apps/todo_plugins/commands/third.rb +2 -0
- data/test/integration/gli_cli_test.rb +69 -0
- data/test/integration/gli_powered_app_test.rb +52 -0
- data/test/integration/scaffold_test.rb +30 -0
- data/test/integration/test_helper.rb +52 -0
- data/test/unit/command_finder_test.rb +54 -0
- data/test/{tc_command.rb → unit/command_test.rb} +20 -7
- data/test/unit/compound_command_test.rb +17 -0
- data/test/{tc_doc.rb → unit/doc_test.rb} +38 -51
- data/test/{tc_flag.rb → unit/flag_test.rb} +19 -25
- data/test/{tc_gli.rb → unit/gli_test.rb} +78 -50
- data/test/{tc_help.rb → unit/help_test.rb} +54 -113
- data/test/{tc_options.rb → unit/options_test.rb} +4 -4
- data/test/unit/subcommand_parsing_test.rb +263 -0
- data/test/unit/subcommands_test.rb +245 -0
- data/test/{config.yaml → unit/support/gli_test_config.yml} +1 -0
- data/test/unit/switch_test.rb +49 -0
- data/test/{tc_terminal.rb → unit/terminal_test.rb} +28 -3
- data/test/unit/test_helper.rb +13 -0
- data/test/unit/verbatim_wrapper_test.rb +24 -0
- metadata +85 -141
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/.travis.yml +0 -12
- data/ObjectModel.graffle +0 -1191
- data/bin/report_on_rake_results +0 -10
- data/bin/test_all_rubies.sh +0 -6
- data/features/gli_executable.feature +0 -90
- data/features/gli_init.feature +0 -232
- data/features/step_definitions/gli_executable_steps.rb +0 -18
- data/features/step_definitions/gli_init_steps.rb +0 -11
- data/features/step_definitions/todo_steps.rb +0 -100
- data/features/support/env.rb +0 -55
- data/features/todo.feature +0 -546
- data/features/todo_legacy.feature +0 -128
- data/test/option_test_helper.rb +0 -13
- data/test/tc_compound_command.rb +0 -22
- data/test/tc_subcommand_parsing.rb +0 -104
- data/test/tc_subcommands.rb +0 -259
- data/test/tc_switch.rb +0 -55
- data/test/tc_verbatim_wrapper.rb +0 -36
- data/test/test_helper.rb +0 -20
- /data/test/{init_simplecov.rb → unit/init_simplecov.rb} +0 -0
- /data/test/{fake_std_out.rb → unit/support/fake_std_out.rb} +0 -0
data/lib/gli/app_support.rb
CHANGED
@@ -26,8 +26,10 @@ module GLI
|
|
26
26
|
@pre_block = false
|
27
27
|
@post_block = false
|
28
28
|
@default_command = :help
|
29
|
+
@autocomplete = false
|
29
30
|
@around_block = nil
|
30
31
|
@subcommand_option_handling_strategy = :legacy
|
32
|
+
@argument_handling_strategy = :loose
|
31
33
|
clear_nexts
|
32
34
|
end
|
33
35
|
|
@@ -67,8 +69,10 @@ module GLI
|
|
67
69
|
flags,
|
68
70
|
switches,
|
69
71
|
accepts,
|
70
|
-
@default_command,
|
71
|
-
|
72
|
+
:default_command => @default_command,
|
73
|
+
:autocomplete => autocomplete,
|
74
|
+
:subcommand_option_handling_strategy => subcommand_option_handling_strategy,
|
75
|
+
:argument_handling_strategy => argument_handling_strategy)
|
72
76
|
|
73
77
|
parsing_result = gli_option_parser.parse_options(args)
|
74
78
|
parsing_result.convert_to_openstruct! if @use_openstruct
|
@@ -197,14 +201,22 @@ module GLI
|
|
197
201
|
|
198
202
|
def override_default(tokens,config)
|
199
203
|
tokens.each do |name,token|
|
200
|
-
token.default_value=config[name]
|
204
|
+
token.default_value=config[name] unless config[name].nil?
|
201
205
|
end
|
202
206
|
end
|
203
207
|
|
208
|
+
def argument_handling_strategy
|
209
|
+
@argument_handling_strategy || :loose
|
210
|
+
end
|
211
|
+
|
204
212
|
def subcommand_option_handling_strategy
|
205
213
|
@subcommand_option_handling_strategy || :legacy
|
206
214
|
end
|
207
215
|
|
216
|
+
def autocomplete
|
217
|
+
@autocomplete.nil? ? true : @autocomplete
|
218
|
+
end
|
219
|
+
|
208
220
|
private
|
209
221
|
|
210
222
|
def handle_exception(ex,command)
|
data/lib/gli/command.rb
CHANGED
@@ -30,8 +30,16 @@ module GLI
|
|
30
30
|
include DSL
|
31
31
|
include CommandSupport
|
32
32
|
|
33
|
-
|
34
|
-
|
33
|
+
class ParentKey
|
34
|
+
def to_sym
|
35
|
+
"__parent__".to_sym
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Key in an options hash to find the parent's parsed options. Note that if you are
|
40
|
+
# using openstruct, e.g. via `use_openstruct true` in your app setup, you will need
|
41
|
+
# to use the method `__parent__` to access parent parsed options.
|
42
|
+
PARENT = ParentKey.new
|
35
43
|
|
36
44
|
# Create a new command.
|
37
45
|
#
|
@@ -45,6 +53,8 @@ module GLI
|
|
45
53
|
# +skips_post+:: if true, this command advertises that it doesn't want the post block called after it
|
46
54
|
# +skips_around+:: if true, this command advertises that it doesn't want the around block called
|
47
55
|
# +hide_commands_without_desc+:: if true and there isn't a description the command is not going to be shown in the help
|
56
|
+
# +examples+:: An array of Hashes, where each hash must have the key +:example+ mapping to a string, and may optionally have the key +:desc+
|
57
|
+
# that documents that example.
|
48
58
|
def initialize(options)
|
49
59
|
super(options[:names],options[:description],options[:long_desc])
|
50
60
|
@arguments_description = options[:arguments_name] || ''
|
@@ -57,9 +67,21 @@ module GLI
|
|
57
67
|
@commands_declaration_order = []
|
58
68
|
@flags_declaration_order = []
|
59
69
|
@switches_declaration_order = []
|
70
|
+
@examples = options[:examples] || []
|
60
71
|
clear_nexts
|
61
72
|
end
|
62
73
|
|
74
|
+
# Specify an example invocation.
|
75
|
+
#
|
76
|
+
# example_invocation:: test of a complete command-line invocation you want to show
|
77
|
+
# options:: refine the example:
|
78
|
+
# +:desc+:: A description of the example to be shown with it (optional)
|
79
|
+
def example(example_invocation,options = {})
|
80
|
+
@examples << {
|
81
|
+
example: example_invocation
|
82
|
+
}.merge(options)
|
83
|
+
end
|
84
|
+
|
63
85
|
# Set the default command if this command has subcommands and the user doesn't
|
64
86
|
# provide a subcommand when invoking THIS command. When nil, this will show an error and the help
|
65
87
|
# for this command; when set, the command with this name will be executed.
|
data/lib/gli/command_finder.rb
CHANGED
@@ -1,40 +1,57 @@
|
|
1
1
|
module GLI
|
2
2
|
class CommandFinder
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
3
|
+
attr_accessor :options
|
4
|
+
|
5
|
+
DEFAULT_OPTIONS = {
|
6
|
+
:default_command => nil,
|
7
|
+
:autocomplete => true
|
8
|
+
}
|
9
|
+
|
10
|
+
def initialize(commands, options = {})
|
11
|
+
self.options = DEFAULT_OPTIONS.merge(options)
|
12
|
+
self.commands_with_aliases = expand_with_aliases(commands)
|
13
13
|
end
|
14
14
|
|
15
|
-
# Finds the command with the given name, allowing for partial matches. Returns the command named by
|
16
|
-
# the default command if no command with +name+ matched
|
17
15
|
def find_command(name)
|
18
|
-
name
|
19
|
-
|
20
|
-
raise UnknownCommand.new("No command name given nor default available") if String(name).strip == ''
|
16
|
+
name = String(name || options[:default_command]).strip
|
17
|
+
raise UnknownCommand.new("No command name given nor default available") if name == ''
|
21
18
|
|
22
|
-
command_found =
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
19
|
+
command_found = commands_with_aliases.fetch(name) do |command_to_match|
|
20
|
+
if options[:autocomplete]
|
21
|
+
found_match = find_command_by_partial_name(commands_with_aliases, command_to_match)
|
22
|
+
if found_match.kind_of? GLI::Command
|
23
|
+
if ENV["GLI_DEBUG"] == 'true'
|
24
|
+
$stderr.puts "Using '#{name}' as it's is short for #{found_match.name}."
|
25
|
+
$stderr.puts "Set autocomplete false for any command you don't want matched like this"
|
26
|
+
end
|
27
|
+
elsif found_match.kind_of?(Array) && !found_match.empty?
|
28
|
+
raise AmbiguousCommand.new("Ambiguous command '#{name}'. It matches #{found_match.sort.join(',')}")
|
29
|
+
end
|
30
|
+
found_match
|
31
|
+
end
|
29
32
|
end
|
33
|
+
|
34
|
+
raise UnknownCommand.new("Unknown command '#{name}'") if Array(command_found).empty?
|
30
35
|
command_found
|
31
36
|
end
|
32
37
|
|
33
38
|
private
|
39
|
+
attr_accessor :commands_with_aliases
|
40
|
+
|
41
|
+
def expand_with_aliases(commands)
|
42
|
+
expanded = {}
|
43
|
+
commands.each do |command_name, command|
|
44
|
+
expanded[command_name.to_s] = command
|
45
|
+
Array(command.aliases).each do |command_alias|
|
46
|
+
expanded[command_alias.to_s] = command
|
47
|
+
end
|
48
|
+
end
|
49
|
+
expanded
|
50
|
+
end
|
34
51
|
|
35
|
-
def find_command_by_partial_name(
|
36
|
-
partial_matches =
|
37
|
-
return
|
52
|
+
def find_command_by_partial_name(commands_with_aliases, command_to_match)
|
53
|
+
partial_matches = commands_with_aliases.keys.select { |command_name| command_name =~ /^#{command_to_match}/ }
|
54
|
+
return commands_with_aliases[partial_matches[0]] if partial_matches.size == 1
|
38
55
|
partial_matches
|
39
56
|
end
|
40
57
|
end
|
data/lib/gli/command_support.rb
CHANGED
@@ -49,6 +49,11 @@ module GLI
|
|
49
49
|
all_forms
|
50
50
|
end
|
51
51
|
|
52
|
+
# Returns the array of examples
|
53
|
+
def examples
|
54
|
+
@examples
|
55
|
+
end
|
56
|
+
|
52
57
|
# Get an array of commands, ordered by when they were declared
|
53
58
|
def commands_declaration_order # :nodoc:
|
54
59
|
@commands_declaration_order
|
@@ -161,12 +166,8 @@ module GLI
|
|
161
166
|
|
162
167
|
def generate_error_action(arguments)
|
163
168
|
lambda { |global_options,options,arguments|
|
164
|
-
if am_subcommand?
|
165
|
-
|
166
|
-
raise UnknownCommand,"Unknown command '#{arguments[0]}'"
|
167
|
-
else
|
168
|
-
raise BadCommandLine,"Command '#{name}' requires a subcommand"
|
169
|
-
end
|
169
|
+
if am_subcommand? && arguments.size > 0
|
170
|
+
raise UnknownCommand,"Unknown command '#{arguments[0]}'"
|
170
171
|
elsif have_subcommands?
|
171
172
|
raise BadCommandLine,"Command '#{name}' requires a subcommand #{self.commands.keys.join(',')}"
|
172
173
|
else
|
data/lib/gli/commands/doc.rb
CHANGED
@@ -4,7 +4,7 @@ module GLI
|
|
4
4
|
# about your app, so as to create documentation in whatever format you want
|
5
5
|
class Doc < Command
|
6
6
|
FORMATS = {
|
7
|
-
'rdoc' => GLI::Commands::RdocDocumentListener
|
7
|
+
'rdoc' => GLI::Commands::RdocDocumentListener
|
8
8
|
}
|
9
9
|
# Create the Doc generator based on the GLI app passed in
|
10
10
|
def initialize(app)
|
@@ -139,7 +139,7 @@ module GLI
|
|
139
139
|
private
|
140
140
|
|
141
141
|
def format_class(format_name)
|
142
|
-
FORMATS.fetch(format_name) {
|
142
|
+
FORMATS.fetch(format_name) {
|
143
143
|
begin
|
144
144
|
return format_name.split(/::/).reduce(Kernel) { |context,part| context.const_get(part) }
|
145
145
|
rescue => ex
|
@@ -168,8 +168,14 @@ module GLI
|
|
168
168
|
command.description,
|
169
169
|
command.long_description,
|
170
170
|
command.arguments_description]
|
171
|
-
if document_listener.method(:command).arity
|
171
|
+
if document_listener.method(:command).arity >= 6
|
172
172
|
command_args << command.arguments_options
|
173
|
+
if document_listener.method(:command).arity >= 7
|
174
|
+
command_args << command.arguments
|
175
|
+
end
|
176
|
+
if document_listener.method(:command).arity >= 8
|
177
|
+
command_args << command.examples
|
178
|
+
end
|
173
179
|
end
|
174
180
|
document_listener.command(*command_args)
|
175
181
|
end
|
data/lib/gli/commands/help.rb
CHANGED
@@ -59,7 +59,8 @@ module GLI
|
|
59
59
|
super(:names => :help,
|
60
60
|
:description => 'Shows a list of commands or help for one command',
|
61
61
|
:arguments_name => 'command',
|
62
|
-
:long_desc => 'Gets help for the application or its commands. Can also list the commands in a way helpful to creating a bash-style completion function'
|
62
|
+
:long_desc => 'Gets help for the application or its commands. Can also list the commands in a way helpful to creating a bash-style completion function',
|
63
|
+
:arguments => [Argument.new(:command_name, [:multiple, :optional])])
|
63
64
|
@app = app
|
64
65
|
@parent = app
|
65
66
|
@sorter = SORTERS[@app.help_sort_type]
|
@@ -22,7 +22,7 @@ module GLI
|
|
22
22
|
arg_desc = "[#{arg_desc}]"
|
23
23
|
end
|
24
24
|
if arg.multiple?
|
25
|
-
arg_desc = "#{arg_desc}
|
25
|
+
arg_desc = "#{arg_desc}..."
|
26
26
|
end
|
27
27
|
desc = desc + " " + arg_desc
|
28
28
|
end
|
@@ -37,7 +37,7 @@ module GLI
|
|
37
37
|
desc = "[#{desc}]"
|
38
38
|
end
|
39
39
|
if arguments_options.include? :multiple
|
40
|
-
desc = "#{desc}
|
40
|
+
desc = "#{desc}..."
|
41
41
|
end
|
42
42
|
" " + desc
|
43
43
|
end
|
@@ -18,6 +18,7 @@ module GLI
|
|
18
18
|
|
19
19
|
options_description = OptionsFormatter.new(flags_and_switches(@command,@app),@sorter,@wrapper_class).format
|
20
20
|
commands_description = format_subcommands(@command)
|
21
|
+
command_examples = format_examples(@command)
|
21
22
|
|
22
23
|
synopses = @synopsis_formatter.synopses_for_command(@command)
|
23
24
|
COMMAND_HELP.result(binding)
|
@@ -45,7 +46,14 @@ COMMAND OPTIONS
|
|
45
46
|
|
46
47
|
COMMANDS
|
47
48
|
<%= commands_description %>
|
48
|
-
<% end %>
|
49
|
+
<% end %>
|
50
|
+
<% unless @command.examples.empty? %>
|
51
|
+
|
52
|
+
<%= @command.examples.size == 1 ? 'EXAMPLE' : 'EXAMPLES' %>
|
53
|
+
|
54
|
+
|
55
|
+
<%= command_examples %>
|
56
|
+
<% end %>))
|
49
57
|
|
50
58
|
|
51
59
|
def flags_and_switches(command,app)
|
@@ -76,6 +84,16 @@ COMMANDS
|
|
76
84
|
formatter = ListFormatter.new(commands_array,@wrapper_class)
|
77
85
|
StringIO.new.tap { |io| formatter.output(io) }.string
|
78
86
|
end
|
87
|
+
|
88
|
+
def format_examples(command)
|
89
|
+
command.examples.map {|example|
|
90
|
+
string = ""
|
91
|
+
if example[:desc]
|
92
|
+
string << " # #{example[:desc]}\n"
|
93
|
+
end
|
94
|
+
string << " #{example.fetch(:example)}\n"
|
95
|
+
}.join("\n")
|
96
|
+
end
|
79
97
|
end
|
80
98
|
end
|
81
99
|
end
|
@@ -27,10 +27,11 @@ module GLI
|
|
27
27
|
|
28
28
|
def sub_options_doc(sub_options)
|
29
29
|
sub_options_doc = sub_options.map { |_,option|
|
30
|
-
option.names_and_aliases.map { |name|
|
30
|
+
doc = option.names_and_aliases.map { |name|
|
31
31
|
CommandLineOption.name_as_string(name,false) + (option.kind_of?(Flag) ? " #{option.argument_name }" : '')
|
32
32
|
}.join('|')
|
33
|
-
|
33
|
+
option.required?? doc : "[#{doc}]"
|
34
|
+
}.sort.join(' ').strip
|
34
35
|
end
|
35
36
|
|
36
37
|
private
|
@@ -24,12 +24,10 @@ module GLI
|
|
24
24
|
|
25
25
|
def description_with_default(option)
|
26
26
|
if option.kind_of? Flag
|
27
|
-
required =
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
32
|
-
String(option.description) + " (#{required}default: #{option.safe_default_value || 'none'})"
|
27
|
+
required = option.required? ? 'required, ' : ''
|
28
|
+
multiple = option.multiple? ? 'may be used more than once, ' : ''
|
29
|
+
|
30
|
+
String(option.description) + " (#{required}#{multiple}default: #{option.safe_default_value || 'none'})"
|
33
31
|
else
|
34
32
|
String(option.description) + (option.default_value ? " (default: enabled)" : "")
|
35
33
|
end
|
@@ -35,12 +35,9 @@ module GLI
|
|
35
35
|
private
|
36
36
|
|
37
37
|
def create_config(global_options,options,arguments)
|
38
|
-
config = Hash[
|
39
|
-
|
40
|
-
|
41
|
-
else
|
42
|
-
[option_name,option_value]
|
43
|
-
end
|
38
|
+
config = Hash[(@app_switches.keys + @app_flags.keys).map { |option_name|
|
39
|
+
option_value = global_options[option_name]
|
40
|
+
[option_name,option_value]
|
44
41
|
}]
|
45
42
|
config[COMMANDS_KEY] = {}
|
46
43
|
@app_commands.each do |name,command|
|
@@ -5,6 +5,7 @@ module GLI
|
|
5
5
|
class RdocDocumentListener
|
6
6
|
|
7
7
|
def initialize(global_options,options,arguments,app)
|
8
|
+
@app = app
|
8
9
|
@io = File.new("#{app.exe_name}.rdoc",'w')
|
9
10
|
@nest = ''
|
10
11
|
@arg_name_formatter = GLI::Commands::HelpModules::ArgNameFormatter.new
|
@@ -81,7 +82,7 @@ module GLI
|
|
81
82
|
|
82
83
|
# Gives you a command in the current context and creates a new context of this command
|
83
84
|
def command(name,aliases,desc,long_desc,arg_name,arg_options)
|
84
|
-
@io.puts "#{@nest}=== Command: <tt>#{([name] + aliases).join('|')} #{@arg_name_formatter.format(arg_name,arg_options)}</tt>"
|
85
|
+
@io.puts "#{@nest}=== Command: <tt>#{([name] + aliases).join('|')} #{@arg_name_formatter.format(arg_name,arg_options,[])}</tt>"
|
85
86
|
@io.puts String(desc).strip
|
86
87
|
@io.puts
|
87
88
|
@io.puts String(long_desc).strip
|