gli 2.11.0 → 2.20.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|