gli 2.8.1 → 2.9.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 +4 -4
- data/.travis.yml +1 -5
- data/features/step_definitions/todo_steps.rb +8 -0
- data/features/support/env.rb +1 -0
- data/features/todo.feature +55 -19
- data/features/todo_legacy.feature +7 -7
- data/lib/gli/app.rb +10 -0
- data/lib/gli/app_support.rb +4 -0
- data/lib/gli/commands/help.rb +16 -1
- data/lib/gli/commands/help_modules/command_help_format.rb +3 -77
- data/lib/gli/commands/help_modules/compact_synopsis_formatter.rb +20 -0
- data/lib/gli/commands/help_modules/full_synopsis_formatter.rb +109 -0
- data/lib/gli/commands/help_modules/options_formatter.rb +6 -1
- data/lib/gli/commands/help_modules/terminal_synopsis_formatter.rb +22 -0
- data/lib/gli/commands/scaffold.rb +1 -6
- data/lib/gli/flag.rb +8 -0
- data/lib/gli/gli_option_parser.rb +26 -3
- data/lib/gli/version.rb +1 -1
- data/test/apps/todo/Rakefile +1 -1
- data/test/apps/todo/bin/todo +1 -0
- data/test/apps/todo/lib/todo/commands/list.rb +2 -0
- data/test/apps/todo_legacy/Rakefile +1 -1
- data/test/tc_gli.rb +55 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1fffe3ab7b8b3aa4881cfb38de3b7cc1e31508b
|
4
|
+
data.tar.gz: 057ab9061b13527ed56f448cc20ee3ea54a7472d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c61409e0f16a9d07d9e4f704e05890e48868dc359be530dd2c094d3856a841b224968c1f2902b761355ad3ff2574ab8e9a33af626ceb77e897e3a2dfa17a3580
|
7
|
+
data.tar.gz: 3f26ab6c6026483c986241c506fdf1742a43408eac52605aa7ba2c5a36076f1d48acba70fbba5031b649fc042043d2cdf112ebc175cf9afeb35b49821fd15979
|
data/.travis.yml
CHANGED
@@ -86,3 +86,11 @@ end
|
|
86
86
|
Given /^the todo app is coded to use verbatim formatting$/ do
|
87
87
|
ENV['TODO_WRAP_HELP_TEXT'] = 'verbatim'
|
88
88
|
end
|
89
|
+
|
90
|
+
Given(/^my terminal is (\d+) characters wide$/) do |terminal_width|
|
91
|
+
ENV['COLUMNS'] = terminal_width.to_s
|
92
|
+
end
|
93
|
+
|
94
|
+
Given(/^my app is configured for "(.*?)" synopses$/) do |synopsis|
|
95
|
+
ENV['SYNOPSES'] = synopsis
|
96
|
+
end
|
data/features/support/env.rb
CHANGED
data/features/todo.feature
CHANGED
@@ -152,8 +152,8 @@ Feature: The todo app has a nice user interface
|
|
152
152
|
list - List things, such as tasks or contexts
|
153
153
|
|
154
154
|
SYNOPSIS
|
155
|
-
todo [global options] list [command options] [--flag arg] [-x arg]
|
156
|
-
todo [global options] list [command options] [--otherflag arg] [-b] [-f|--foobar]
|
155
|
+
todo [global options] list [command options] [tasks] [--flag arg] [-x arg]
|
156
|
+
todo [global options] list [command options] contexts [--otherflag arg] [-b] [-f|--foobar]
|
157
157
|
|
158
158
|
DESCRIPTION
|
159
159
|
List a whole lot of things that you might be keeping track of in your
|
@@ -163,7 +163,8 @@ Feature: The todo app has a nice user interface
|
|
163
163
|
stored in your todo databases.
|
164
164
|
|
165
165
|
COMMAND OPTIONS
|
166
|
-
-l, --[no-]long
|
166
|
+
-l, --[no-]long - Show long form
|
167
|
+
--required_flag=arg - (required, default: none)
|
167
168
|
|
168
169
|
COMMANDS
|
169
170
|
contexts - List contexts
|
@@ -198,14 +199,15 @@ Feature: The todo app has a nice user interface
|
|
198
199
|
list - List things, such as tasks or contexts
|
199
200
|
|
200
201
|
SYNOPSIS
|
201
|
-
todo [global options] list [command options] [--flag arg] [-x arg]
|
202
|
-
todo [global options] list [command options] [--otherflag arg] [-b] [-f|--foobar]
|
202
|
+
todo [global options] list [command options] [tasks] [--flag arg] [-x arg]
|
203
|
+
todo [global options] list [command options] contexts [--otherflag arg] [-b] [-f|--foobar]
|
203
204
|
|
204
205
|
DESCRIPTION
|
205
206
|
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.
|
206
207
|
|
207
208
|
COMMAND OPTIONS
|
208
|
-
-l, --[no-]long
|
209
|
+
-l, --[no-]long - Show long form
|
210
|
+
--required_flag=arg - (required, default: none)
|
209
211
|
|
210
212
|
COMMANDS
|
211
213
|
contexts - List contexts
|
@@ -221,8 +223,8 @@ Feature: The todo app has a nice user interface
|
|
221
223
|
list - List things, such as tasks or contexts
|
222
224
|
|
223
225
|
SYNOPSIS
|
224
|
-
todo [global options] list [command options] [--flag arg] [-x arg]
|
225
|
-
todo [global options] list [command options] [--otherflag arg] [-b] [-f|--foobar]
|
226
|
+
todo [global options] list [command options] [tasks] [--flag arg] [-x arg]
|
227
|
+
todo [global options] list [command options] contexts [--otherflag arg] [-b] [-f|--foobar]
|
226
228
|
|
227
229
|
DESCRIPTION
|
228
230
|
|
@@ -236,7 +238,8 @@ Feature: The todo app has a nice user interface
|
|
236
238
|
|
237
239
|
|
238
240
|
COMMAND OPTIONS
|
239
|
-
-l, --[no-]long
|
241
|
+
-l, --[no-]long - Show long form
|
242
|
+
--required_flag=arg - (required, default: none)
|
240
243
|
|
241
244
|
COMMANDS
|
242
245
|
contexts - List contexts
|
@@ -252,14 +255,15 @@ Feature: The todo app has a nice user interface
|
|
252
255
|
list - List things, such as tasks or contexts
|
253
256
|
|
254
257
|
SYNOPSIS
|
255
|
-
todo [global options] list [command options] [--flag arg] [-x arg]
|
256
|
-
todo [global options] list [command options] [--otherflag arg] [-b] [-f|--foobar]
|
258
|
+
todo [global options] list [command options] [tasks] [--flag arg] [-x arg]
|
259
|
+
todo [global options] list [command options] contexts [--otherflag arg] [-b] [-f|--foobar]
|
257
260
|
|
258
261
|
DESCRIPTION
|
259
262
|
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.
|
260
263
|
|
261
264
|
COMMAND OPTIONS
|
262
|
-
-l, --[no-]long
|
265
|
+
-l, --[no-]long - Show long form
|
266
|
+
--required_flag=arg - (required, default: none)
|
263
267
|
|
264
268
|
COMMANDS
|
265
269
|
contexts - List contexts
|
@@ -275,7 +279,7 @@ Feature: The todo app has a nice user interface
|
|
275
279
|
|
276
280
|
SYNOPSIS
|
277
281
|
todo [global options] list tasks [command options]
|
278
|
-
todo [global options] list tasks [command options]
|
282
|
+
todo [global options] list tasks [command options] open
|
279
283
|
|
280
284
|
DESCRIPTION
|
281
285
|
Lists all of your tasks that you have, in varying orders, and all that
|
@@ -299,8 +303,8 @@ Feature: The todo app has a nice user interface
|
|
299
303
|
|
300
304
|
SYNOPSIS
|
301
305
|
todo [global options] create
|
302
|
-
todo [global options] create
|
303
|
-
todo [global options] create
|
306
|
+
todo [global options] create contexts [context_name]
|
307
|
+
todo [global options] create tasks task_name[, task_name]*
|
304
308
|
|
305
309
|
COMMANDS
|
306
310
|
<default> - Makes a new task
|
@@ -310,11 +314,11 @@ Feature: The todo app has a nice user interface
|
|
310
314
|
And the output should not contain "COMMAND OPTIONS"
|
311
315
|
|
312
316
|
Scenario: Running list w/out subcommand performs list tasks by default
|
313
|
-
When I successfully run `todo list boo yay`
|
317
|
+
When I successfully run `todo list --required_flag=blah boo yay`
|
314
318
|
Then the output should contain "list tasks: boo,yay"
|
315
319
|
|
316
320
|
Scenario: Running list w/out subcommand or any arguments performs list tasks by default
|
317
|
-
When I successfully run `todo list`
|
321
|
+
When I successfully run `todo list --required_flag=blah`
|
318
322
|
Then the output should contain "list tasks:"
|
319
323
|
|
320
324
|
Scenario: Running chained commands works
|
@@ -351,8 +355,8 @@ Feature: The todo app has a nice user interface
|
|
351
355
|
ls - LS things, such as tasks or contexts
|
352
356
|
|
353
357
|
SYNOPSIS
|
354
|
-
todo [global options] ls [command options] [-b] [-f|--foobar]
|
355
|
-
todo [global options] ls [command options] [-x arg]
|
358
|
+
todo [global options] ls [command options] contexts [-b] [-f|--foobar]
|
359
|
+
todo [global options] ls [command options] tasks [-x arg]
|
356
360
|
|
357
361
|
DESCRIPTION
|
358
362
|
List a whole lot of things that you might be keeping track of in your
|
@@ -411,3 +415,35 @@ Feature: The todo app has a nice user interface
|
|
411
415
|
And a config file that specifies defaults for some commands with subcommands
|
412
416
|
When I successfully run `todo help list contexts`
|
413
417
|
Then I should see the defaults for 'list contexts' from the config file in the help
|
418
|
+
|
419
|
+
Scenario: A complex SYNOPSIS section gets summarized in terminal mode
|
420
|
+
Given my terminal is 50 characters wide
|
421
|
+
And my app is configured for "terminal" synopses
|
422
|
+
When I run `todo ls`
|
423
|
+
Then the exit status should not be 0
|
424
|
+
And the stderr should contain "error: Command 'ls' requires a subcommand"
|
425
|
+
And the stdout should contain:
|
426
|
+
"""
|
427
|
+
NAME
|
428
|
+
ls - LS things, such as tasks or contexts
|
429
|
+
|
430
|
+
SYNOPSIS
|
431
|
+
todo [global options] ls [command options] contexts [subcommand options]
|
432
|
+
todo [global options] ls [command options] tasks [subcommand options]
|
433
|
+
"""
|
434
|
+
|
435
|
+
Scenario: We can always use a compact SYNOPSIS
|
436
|
+
Given my terminal is 500 characters wide
|
437
|
+
And my app is configured for "compact" synopses
|
438
|
+
When I run `todo ls`
|
439
|
+
Then the exit status should not be 0
|
440
|
+
And the stderr should contain "error: Command 'ls' requires a subcommand"
|
441
|
+
And the stdout should contain:
|
442
|
+
"""
|
443
|
+
NAME
|
444
|
+
ls - LS things, such as tasks or contexts
|
445
|
+
|
446
|
+
SYNOPSIS
|
447
|
+
todo [global options] ls [command options] contexts [subcommand options]
|
448
|
+
todo [global options] ls [command options] tasks [subcommand options]
|
449
|
+
"""
|
@@ -31,8 +31,8 @@ Feature: The todo app is backwards compatible with legacy subcommand parsing
|
|
31
31
|
list - List things, such as tasks or contexts
|
32
32
|
|
33
33
|
SYNOPSIS
|
34
|
-
todo [global options] list [command options] [--flag arg] [-x arg]
|
35
|
-
todo [global options] list [command options] [--otherflag arg] [-b] [-f|--foobar]
|
34
|
+
todo [global options] list [command options] [tasks] [--flag arg] [-x arg]
|
35
|
+
todo [global options] list [command options] contexts [--otherflag arg] [-b] [-f|--foobar]
|
36
36
|
|
37
37
|
DESCRIPTION
|
38
38
|
List a whole lot of things that you might be keeping track of in your
|
@@ -65,7 +65,7 @@ Feature: The todo app is backwards compatible with legacy subcommand parsing
|
|
65
65
|
|
66
66
|
SYNOPSIS
|
67
67
|
todo [global options] list tasks [command options]
|
68
|
-
todo [global options] list tasks [command options]
|
68
|
+
todo [global options] list tasks [command options] open
|
69
69
|
|
70
70
|
DESCRIPTION
|
71
71
|
Lists all of your tasks that you have, in varying orders, and all that
|
@@ -89,8 +89,8 @@ Feature: The todo app is backwards compatible with legacy subcommand parsing
|
|
89
89
|
|
90
90
|
SYNOPSIS
|
91
91
|
todo [global options] create
|
92
|
-
todo [global options] create
|
93
|
-
todo [global options] create
|
92
|
+
todo [global options] create contexts [context_name]
|
93
|
+
todo [global options] create tasks task_name[, task_name]*
|
94
94
|
|
95
95
|
COMMANDS
|
96
96
|
<default> - Makes a new task
|
@@ -109,8 +109,8 @@ Feature: The todo app is backwards compatible with legacy subcommand parsing
|
|
109
109
|
ls - LS things, such as tasks or contexts
|
110
110
|
|
111
111
|
SYNOPSIS
|
112
|
-
todo [global options] ls [command options] [-b] [-f|--foobar]
|
113
|
-
todo [global options] ls [command options] [-x arg]
|
112
|
+
todo [global options] ls [command options] contexts [-b] [-f|--foobar]
|
113
|
+
todo [global options] ls [command options] tasks [-x arg]
|
114
114
|
|
115
115
|
DESCRIPTION
|
116
116
|
List a whole lot of things that you might be keeping track of in your
|
data/lib/gli/app.rb
CHANGED
@@ -245,6 +245,16 @@ module GLI
|
|
245
245
|
@help_text_wrap_type = wrap_type
|
246
246
|
end
|
247
247
|
|
248
|
+
# Control how the SYNOPSIS is formatted.
|
249
|
+
#
|
250
|
+
# format:: one of:
|
251
|
+
# +:full+:: the default, show subcommand options and flags inline
|
252
|
+
# +:terminal+:: if :full would be wider than the terminal, use :compact
|
253
|
+
# +:compact+:: use a simpler and shorter SYNOPSIS. Useful if your app has a lot of options and showing them in the SYNOPSIS makes things more confusing
|
254
|
+
def synopsis_format(format)
|
255
|
+
@synopsis_format_type = format
|
256
|
+
end
|
257
|
+
|
248
258
|
def program_name(override=nil) #:nodoc:
|
249
259
|
warn "#program_name has been deprecated"
|
250
260
|
end
|
data/lib/gli/app_support.rb
CHANGED
@@ -166,6 +166,10 @@ module GLI
|
|
166
166
|
@help_text_wrap_type || :to_terminal
|
167
167
|
end
|
168
168
|
|
169
|
+
def synopsis_format_type
|
170
|
+
@synopsis_format_type || :full
|
171
|
+
end
|
172
|
+
|
169
173
|
# Sets the default values for flags based on the configuration
|
170
174
|
def override_defaults_based_on_config(config)
|
171
175
|
override_default(flags,config)
|
data/lib/gli/commands/help.rb
CHANGED
@@ -12,6 +12,9 @@ require 'gli/commands/help_modules/command_help_format'
|
|
12
12
|
require 'gli/commands/help_modules/help_completion_format'
|
13
13
|
require 'gli/commands/help_modules/command_finder'
|
14
14
|
require 'gli/commands/help_modules/arg_name_formatter'
|
15
|
+
require 'gli/commands/help_modules/full_synopsis_formatter'
|
16
|
+
require 'gli/commands/help_modules/compact_synopsis_formatter'
|
17
|
+
require 'gli/commands/help_modules/terminal_synopsis_formatter'
|
15
18
|
|
16
19
|
module GLI
|
17
20
|
module Commands
|
@@ -28,6 +31,12 @@ module GLI
|
|
28
31
|
:none => HelpModules::VerbatimWrapper,
|
29
32
|
:verbatim => HelpModules::VerbatimWrapper,
|
30
33
|
}
|
34
|
+
|
35
|
+
SYNOPSIS_FORMATTERS = {
|
36
|
+
:full => HelpModules::FullSynopsisFormatter,
|
37
|
+
:compact => HelpModules::CompactSynopsisFormatter,
|
38
|
+
:terminal => HelpModules::TerminalSynopsisFormatter,
|
39
|
+
}
|
31
40
|
# The help command used for the two-level interactive help system
|
32
41
|
class Help < Command
|
33
42
|
@@skips_pre = true
|
@@ -55,6 +64,7 @@ module GLI
|
|
55
64
|
@parent = app
|
56
65
|
@sorter = SORTERS[@app.help_sort_type]
|
57
66
|
@text_wrapping_class = WRAPPERS[@app.help_text_wrap_type]
|
67
|
+
@synopsis_formatter_class = SYNOPSIS_FORMATTERS[@app.synopsis_format_type]
|
58
68
|
|
59
69
|
desc 'List commands one per line, to assist with shell completion'
|
60
70
|
switch :c
|
@@ -85,7 +95,12 @@ module GLI
|
|
85
95
|
name = arguments.shift
|
86
96
|
command = command_finder.find_command(name)
|
87
97
|
unless command.nil?
|
88
|
-
out.puts HelpModules::CommandHelpFormat.new(
|
98
|
+
out.puts HelpModules::CommandHelpFormat.new(
|
99
|
+
command,
|
100
|
+
@app,
|
101
|
+
@sorter,
|
102
|
+
@synopsis_formatter_class,
|
103
|
+
@text_wrapping_class).format
|
89
104
|
end
|
90
105
|
end
|
91
106
|
end
|
@@ -4,12 +4,12 @@ module GLI
|
|
4
4
|
module Commands
|
5
5
|
module HelpModules
|
6
6
|
class CommandHelpFormat
|
7
|
-
def initialize(command,app,
|
8
|
-
@basic_invocation = basic_invocation
|
7
|
+
def initialize(command,app,sorter,synopsis_formatter_class,wrapper_class=TextWrapper)
|
9
8
|
@app = app
|
10
9
|
@command = command
|
11
10
|
@sorter = sorter
|
12
11
|
@wrapper_class = wrapper_class
|
12
|
+
@synopsis_formatter = synopsis_formatter_class.new(@app,flags_and_switches(@command,@app))
|
13
13
|
end
|
14
14
|
|
15
15
|
def format
|
@@ -19,19 +19,7 @@ module GLI
|
|
19
19
|
options_description = OptionsFormatter.new(flags_and_switches(@command,@app),@sorter,@wrapper_class).format
|
20
20
|
commands_description = format_subcommands(@command)
|
21
21
|
|
22
|
-
synopses =
|
23
|
-
one_line_usage = basic_usage
|
24
|
-
one_line_usage << @command.arguments_description
|
25
|
-
if @command.commands.empty?
|
26
|
-
synopses << one_line_usage
|
27
|
-
else
|
28
|
-
synopses = sorted_synopses
|
29
|
-
if @command.has_action?
|
30
|
-
synopses.unshift(one_line_usage)
|
31
|
-
end
|
32
|
-
|
33
|
-
end
|
34
|
-
|
22
|
+
synopses = @synopsis_formatter.synopses_for_command(@command)
|
35
23
|
COMMAND_HELP.result(binding)
|
36
24
|
end
|
37
25
|
|
@@ -59,27 +47,6 @@ COMMANDS
|
|
59
47
|
<%= commands_description %>
|
60
48
|
<% end %>),nil,'<>')
|
61
49
|
|
62
|
-
def command_with_subcommand_usage(sub,is_default_command)
|
63
|
-
usage = basic_usage
|
64
|
-
sub_options = if @app.subcommand_option_handling_strategy == :legacy
|
65
|
-
@command.flags.merge(@command.switches).select { |_,o| o.associated_command == sub }
|
66
|
-
else
|
67
|
-
sub.flags.merge(sub.switches)
|
68
|
-
end
|
69
|
-
usage << sub_options.map { |option_name,option|
|
70
|
-
option.names_and_aliases.map { |_|
|
71
|
-
CommandLineOption.name_as_string(_,false) + (option.kind_of?(Flag) ? " #{option.argument_name }" : '')
|
72
|
-
}.join('|')
|
73
|
-
}.map { |_| "[#{_}]" }.sort.join(' ')
|
74
|
-
usage << ' '
|
75
|
-
if is_default_command
|
76
|
-
usage << "[#{sub.name}]"
|
77
|
-
else
|
78
|
-
usage << sub.name.to_s
|
79
|
-
end
|
80
|
-
usage << ArgNameFormatter.new.format(sub.arguments_description,sub.arguments_options)
|
81
|
-
usage
|
82
|
-
end
|
83
50
|
|
84
51
|
def flags_and_switches(command,app)
|
85
52
|
if app.subcommand_option_handling_strategy == :legacy
|
@@ -95,29 +62,6 @@ COMMANDS
|
|
95
62
|
end
|
96
63
|
end
|
97
64
|
|
98
|
-
def basic_usage
|
99
|
-
usage = @basic_invocation.dup
|
100
|
-
usage << " [global options]" unless global_flags_and_switches.empty?
|
101
|
-
usage << " #{path_to_command}"
|
102
|
-
usage << " [command options]" unless flags_and_switches(@command,@app).empty?
|
103
|
-
usage << " "
|
104
|
-
usage
|
105
|
-
end
|
106
|
-
|
107
|
-
def path_to_command
|
108
|
-
path = []
|
109
|
-
c = @command
|
110
|
-
while c.kind_of? Command
|
111
|
-
path.unshift(c.name)
|
112
|
-
c = c.parent
|
113
|
-
end
|
114
|
-
path.join(' ')
|
115
|
-
end
|
116
|
-
|
117
|
-
def global_flags_and_switches
|
118
|
-
@app.flags.merge(@app.switches)
|
119
|
-
end
|
120
|
-
|
121
65
|
def format_subcommands(command)
|
122
66
|
commands_array = @sorter.call(command.commands_declaration_order).map { |cmd|
|
123
67
|
if command.get_default_command == cmd.name
|
@@ -132,24 +76,6 @@ COMMANDS
|
|
132
76
|
formatter = ListFormatter.new(commands_array,@wrapper_class)
|
133
77
|
StringIO.new.tap { |io| formatter.output(io) }.string
|
134
78
|
end
|
135
|
-
|
136
|
-
def sorted_synopses
|
137
|
-
synopses_command = {}
|
138
|
-
@command.commands.each do |name,sub|
|
139
|
-
default = @command.get_default_command == name
|
140
|
-
synopsis = command_with_subcommand_usage(sub,default)
|
141
|
-
synopses_command[synopsis] = sub
|
142
|
-
end
|
143
|
-
synopses = synopses_command.keys.sort { |one,two|
|
144
|
-
if synopses_command[one].name == @command.get_default_command
|
145
|
-
-1
|
146
|
-
elsif synopses_command[two].name == @command.get_default_command
|
147
|
-
1
|
148
|
-
else
|
149
|
-
synopses_command[one] <=> synopses_command[two]
|
150
|
-
end
|
151
|
-
}
|
152
|
-
end
|
153
79
|
end
|
154
80
|
end
|
155
81
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module GLI
|
2
|
+
module Commands
|
3
|
+
module HelpModules
|
4
|
+
class CompactSynopsisFormatter < FullSynopsisFormatter
|
5
|
+
|
6
|
+
protected
|
7
|
+
|
8
|
+
def sub_options_doc(sub_options)
|
9
|
+
if sub_options.empty?
|
10
|
+
''
|
11
|
+
else
|
12
|
+
'[subcommand options]'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module GLI
|
2
|
+
module Commands
|
3
|
+
module HelpModules
|
4
|
+
class FullSynopsisFormatter
|
5
|
+
def initialize(app,flags_and_switches)
|
6
|
+
@app = app
|
7
|
+
@basic_invocation = @app.exe_name.to_s
|
8
|
+
@flags_and_switches = flags_and_switches
|
9
|
+
end
|
10
|
+
|
11
|
+
def synopses_for_command(command)
|
12
|
+
synopses = []
|
13
|
+
one_line_usage = basic_usage(command)
|
14
|
+
one_line_usage << command.arguments_description
|
15
|
+
if command.commands.empty?
|
16
|
+
synopses << one_line_usage
|
17
|
+
else
|
18
|
+
synopses = sorted_synopses(command)
|
19
|
+
if command.has_action?
|
20
|
+
synopses.unshift(one_line_usage)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
synopses
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def sub_options_doc(sub_options)
|
29
|
+
sub_options_doc = sub_options.map { |_,option|
|
30
|
+
option.names_and_aliases.map { |name|
|
31
|
+
CommandLineOption.name_as_string(name,false) + (option.kind_of?(Flag) ? " #{option.argument_name }" : '')
|
32
|
+
}.join('|')
|
33
|
+
}.map { |invocations| "[#{invocations}]" }.sort.join(' ').strip
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def path_to_command(command)
|
39
|
+
path = []
|
40
|
+
c = command
|
41
|
+
while c.kind_of? Command
|
42
|
+
path.unshift(c.name)
|
43
|
+
c = c.parent
|
44
|
+
end
|
45
|
+
path.join(' ')
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def basic_usage(command)
|
50
|
+
usage = @basic_invocation.dup
|
51
|
+
usage << " [global options]" unless global_flags_and_switches.empty?
|
52
|
+
usage << " #{path_to_command(command)}"
|
53
|
+
usage << " [command options]" unless @flags_and_switches.empty?
|
54
|
+
usage << " "
|
55
|
+
usage
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def command_with_subcommand_usage(command,sub,is_default_command)
|
60
|
+
usage = basic_usage(command)
|
61
|
+
sub_options = if @app.subcommand_option_handling_strategy == :legacy
|
62
|
+
command.flags.merge(command.switches).select { |_,o| o.associated_command == sub }
|
63
|
+
else
|
64
|
+
sub.flags.merge(sub.switches)
|
65
|
+
end
|
66
|
+
if is_default_command
|
67
|
+
usage << "[#{sub.name}]"
|
68
|
+
else
|
69
|
+
usage << sub.name.to_s
|
70
|
+
end
|
71
|
+
sub_options_doc = sub_options_doc(sub_options)
|
72
|
+
if sub_options_doc.length > 0
|
73
|
+
usage << ' '
|
74
|
+
usage << sub_options_doc
|
75
|
+
end
|
76
|
+
arg_name_doc = ArgNameFormatter.new.format(sub.arguments_description,sub.arguments_options).strip
|
77
|
+
if arg_name_doc.length > 0
|
78
|
+
usage << ' '
|
79
|
+
usage << arg_name_doc
|
80
|
+
end
|
81
|
+
usage
|
82
|
+
end
|
83
|
+
|
84
|
+
def sorted_synopses(command)
|
85
|
+
synopses_command = {}
|
86
|
+
command.commands.each do |name,sub|
|
87
|
+
default = command.get_default_command == name
|
88
|
+
synopsis = command_with_subcommand_usage(command,sub,default)
|
89
|
+
synopses_command[synopsis] = sub
|
90
|
+
end
|
91
|
+
synopses = synopses_command.keys.sort { |one,two|
|
92
|
+
if synopses_command[one].name == command.get_default_command
|
93
|
+
-1
|
94
|
+
elsif synopses_command[two].name == command.get_default_command
|
95
|
+
1
|
96
|
+
else
|
97
|
+
synopses_command[one] <=> synopses_command[two]
|
98
|
+
end
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
def global_flags_and_switches
|
103
|
+
@app.flags.merge(@app.switches)
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -24,7 +24,12 @@ module GLI
|
|
24
24
|
|
25
25
|
def description_with_default(option)
|
26
26
|
if option.kind_of? Flag
|
27
|
-
|
27
|
+
required = if option.required?
|
28
|
+
'required, '
|
29
|
+
else
|
30
|
+
''
|
31
|
+
end
|
32
|
+
String(option.description) + " (#{required}default: #{option.safe_default_value || 'none'})"
|
28
33
|
else
|
29
34
|
String(option.description) + (option.default_value ? " (default: enabled)" : "")
|
30
35
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module GLI
|
2
|
+
module Commands
|
3
|
+
module HelpModules
|
4
|
+
class TerminalSynopsisFormatter
|
5
|
+
def initialize(app,flags_and_switches)
|
6
|
+
@app = app
|
7
|
+
@basic_invocation = @app.exe_name.to_s
|
8
|
+
@flags_and_switches = flags_and_switches
|
9
|
+
end
|
10
|
+
def synopses_for_command(command)
|
11
|
+
synopses = FullSynopsisFormatter.new(@app,@flags_and_switches).synopses_for_command(command)
|
12
|
+
if synopses.any? { |synopsis| synopsis.length > Terminal.instance.size[0] }
|
13
|
+
CompactSynopsisFormatter.new(@app,@flags_and_switches).synopses_for_command(command)
|
14
|
+
|
15
|
+
else
|
16
|
+
synopses
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -63,12 +63,7 @@ spec = Gem::Specification.new do |s|
|
|
63
63
|
s.homepage = 'http://your.website.com'
|
64
64
|
s.platform = Gem::Platform::RUBY
|
65
65
|
s.summary = 'A description of your project'
|
66
|
-
|
67
|
-
s.files = %w(
|
68
|
-
bin/#{project_name}
|
69
|
-
lib/#{project_name}/version.rb
|
70
|
-
lib/#{project_name}.rb
|
71
|
-
)
|
66
|
+
s.files = `git ls-files`.split("\n")
|
72
67
|
s.require_paths << 'lib'
|
73
68
|
s.has_rdoc = true
|
74
69
|
s.extra_rdoc_files = ['README.rdoc','#{project_name}.rdoc']
|
data/lib/gli/flag.rb
CHANGED
@@ -23,6 +23,7 @@ module GLI
|
|
23
23
|
# :arg_name:: the name of the flag's argument, default is "arg"
|
24
24
|
# :must_match:: a regexp that the flag's value must match
|
25
25
|
# :type:: a class to convert the value to
|
26
|
+
# :required:: if true, this flag must be specified on the command line
|
26
27
|
# :mask:: if true, the default value of this flag will not be output in the help.
|
27
28
|
# This is useful for password flags where you might not want to show it
|
28
29
|
# on the command-line.
|
@@ -33,8 +34,15 @@ module GLI
|
|
33
34
|
@must_match = options[:must_match]
|
34
35
|
@type = options[:type]
|
35
36
|
@mask = options[:mask]
|
37
|
+
@required = options[:required]
|
36
38
|
end
|
37
39
|
|
40
|
+
# True if this flag is required on the command line
|
41
|
+
def required?
|
42
|
+
@required
|
43
|
+
end
|
44
|
+
|
45
|
+
|
38
46
|
def safe_default_value
|
39
47
|
if @mask
|
40
48
|
"********"
|
@@ -3,7 +3,7 @@ module GLI
|
|
3
3
|
class GLIOptionParser
|
4
4
|
def initialize(commands,flags,switches,accepts,default_command = nil,subcommand_option_handling_strategy=:legacy)
|
5
5
|
command_finder = CommandFinder.new(commands,default_command || "help")
|
6
|
-
@global_option_parser = GlobalOptionParser.new(OptionParserFactory.new(flags,switches,accepts),command_finder)
|
6
|
+
@global_option_parser = GlobalOptionParser.new(OptionParserFactory.new(flags,switches,accepts),command_finder,flags)
|
7
7
|
@accepts = accepts
|
8
8
|
@subcommand_option_handling_strategy = subcommand_option_handling_strategy
|
9
9
|
end
|
@@ -21,9 +21,10 @@ module GLI
|
|
21
21
|
private
|
22
22
|
|
23
23
|
class GlobalOptionParser
|
24
|
-
def initialize(option_parser_factory,command_finder)
|
24
|
+
def initialize(option_parser_factory,command_finder,flags)
|
25
25
|
@option_parser_factory = option_parser_factory
|
26
26
|
@command_finder = command_finder
|
27
|
+
@flags = flags
|
27
28
|
end
|
28
29
|
|
29
30
|
def parse!(parsing_result)
|
@@ -35,11 +36,29 @@ module GLI
|
|
35
36
|
parsing_result.arguments.shift
|
36
37
|
end
|
37
38
|
parsing_result.command = @command_finder.find_command(command_name)
|
39
|
+
unless command_name == 'help'
|
40
|
+
verify_required_options!(@flags,parsing_result.global_options)
|
41
|
+
end
|
38
42
|
parsing_result
|
39
43
|
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def verify_required_options!(flags,options)
|
48
|
+
missing_required_options = flags.values.
|
49
|
+
select(&:required?).
|
50
|
+
reject { |option|
|
51
|
+
options[option.name] != nil
|
52
|
+
}
|
53
|
+
unless missing_required_options.empty?
|
54
|
+
raise BadCommandLine, missing_required_options.map { |option|
|
55
|
+
"#{option.name} is required"
|
56
|
+
}.join(' ')
|
57
|
+
end
|
58
|
+
end
|
40
59
|
end
|
41
60
|
|
42
|
-
class NormalCommandOptionParser
|
61
|
+
class NormalCommandOptionParser < GlobalOptionParser
|
43
62
|
def initialize(accepts)
|
44
63
|
@accepts = accepts
|
45
64
|
end
|
@@ -67,6 +86,8 @@ module GLI
|
|
67
86
|
command_finder = CommandFinder.new(command.commands,command.get_default_command)
|
68
87
|
next_command_name = arguments.shift
|
69
88
|
|
89
|
+
verify_required_options!(command.flags,parsed_command_options[command])
|
90
|
+
|
70
91
|
begin
|
71
92
|
command = command_finder.find_command(next_command_name)
|
72
93
|
rescue AmbiguousCommand
|
@@ -101,6 +122,7 @@ module GLI
|
|
101
122
|
parsing_result.arguments = Array(arguments.compact)
|
102
123
|
parsing_result
|
103
124
|
end
|
125
|
+
|
104
126
|
end
|
105
127
|
|
106
128
|
class LegacyCommandOptionParser < NormalCommandOptionParser
|
@@ -116,6 +138,7 @@ module GLI
|
|
116
138
|
subcommand,args = find_subcommand(command,parsing_result.arguments)
|
117
139
|
parsing_result.command = subcommand
|
118
140
|
parsing_result.arguments = args
|
141
|
+
verify_required_options!(command.flags,parsing_result.command_options)
|
119
142
|
end
|
120
143
|
|
121
144
|
private
|
data/lib/gli/version.rb
CHANGED
data/test/apps/todo/Rakefile
CHANGED
data/test/apps/todo/bin/todo
CHANGED
data/test/tc_gli.rb
CHANGED
@@ -71,6 +71,61 @@ class TC_testGLI < Clean::Test::TestCase
|
|
71
71
|
assert @called, "Expected default command to be executed"
|
72
72
|
end
|
73
73
|
|
74
|
+
def test_command_options_can_be_required
|
75
|
+
@app.reset
|
76
|
+
@called = false
|
77
|
+
@app.command :foo do |c|
|
78
|
+
c.flag :flag, :required => true
|
79
|
+
c.flag :other_flag, :required => true
|
80
|
+
c.action do |global, options, arguments|
|
81
|
+
@called = true
|
82
|
+
end
|
83
|
+
end
|
84
|
+
assert_equal 64, @app.run(['foo']), "Expected exit status to be 64"
|
85
|
+
assert @fake_stderr.contained?(/flag is required/), @fake_stderr.strings.inspect
|
86
|
+
assert @fake_stderr.contained?(/other_flag is required/), @fake_stderr.strings.inspect
|
87
|
+
assert !@called
|
88
|
+
|
89
|
+
assert_equal 0, @app.run(['foo','--flag=bar','--other_flag=blah']), "Expected exit status to be 0 #{@fake_stderr.strings.join(',')}"
|
90
|
+
assert @called
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_global_options_can_be_required
|
95
|
+
@app.reset
|
96
|
+
@called = false
|
97
|
+
@app.flag :flag, :required => true
|
98
|
+
@app.flag :other_flag, :required => true
|
99
|
+
@app.command :foo do |c|
|
100
|
+
c.action do |global, options, arguments|
|
101
|
+
@called = true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
assert_equal 64, @app.run(['foo']), "Expected exit status to be 64"
|
105
|
+
assert @fake_stderr.contained?(/flag is required/), @fake_stderr.strings.inspect
|
106
|
+
assert @fake_stderr.contained?(/other_flag is required/), @fake_stderr.strings.inspect
|
107
|
+
assert !@called
|
108
|
+
|
109
|
+
assert_equal 0, @app.run(['--flag=bar','--other_flag=blah','foo']), "Expected exit status to be 0 #{@fake_stderr.strings.join(',')}"
|
110
|
+
assert @called
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_global_required_options_are_ignored_on_help
|
115
|
+
@app.reset
|
116
|
+
@called = false
|
117
|
+
@app.flag :flag, :required => true
|
118
|
+
@app.flag :other_flag, :required => true
|
119
|
+
@app.command :foo do |c|
|
120
|
+
c.action do |global, options, arguments|
|
121
|
+
@called = true
|
122
|
+
end
|
123
|
+
end
|
124
|
+
assert_equal 0, @app.run(['help']), "Expected exit status to be 64"
|
125
|
+
assert !@fake_stderr.contained?(/flag is required/), @fake_stderr.strings.inspect
|
126
|
+
assert !@fake_stderr.contained?(/other_flag is required/), @fake_stderr.strings.inspect
|
127
|
+
end
|
128
|
+
|
74
129
|
def test_flag_with_space_barfs
|
75
130
|
@app.reset
|
76
131
|
assert_raises(ArgumentError) { @app.flag ['some flag'] }
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Copeland
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-01-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -185,11 +185,14 @@ files:
|
|
185
185
|
- lib/gli/commands/help_modules/arg_name_formatter.rb
|
186
186
|
- lib/gli/commands/help_modules/command_finder.rb
|
187
187
|
- lib/gli/commands/help_modules/command_help_format.rb
|
188
|
+
- lib/gli/commands/help_modules/compact_synopsis_formatter.rb
|
189
|
+
- lib/gli/commands/help_modules/full_synopsis_formatter.rb
|
188
190
|
- lib/gli/commands/help_modules/global_help_format.rb
|
189
191
|
- lib/gli/commands/help_modules/help_completion_format.rb
|
190
192
|
- lib/gli/commands/help_modules/list_formatter.rb
|
191
193
|
- lib/gli/commands/help_modules/one_line_wrapper.rb
|
192
194
|
- lib/gli/commands/help_modules/options_formatter.rb
|
195
|
+
- lib/gli/commands/help_modules/terminal_synopsis_formatter.rb
|
193
196
|
- lib/gli/commands/help_modules/text_wrapper.rb
|
194
197
|
- lib/gli/commands/help_modules/tty_only_wrapper.rb
|
195
198
|
- lib/gli/commands/help_modules/verbatim_wrapper.rb
|
@@ -272,7 +275,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
272
275
|
version: '0'
|
273
276
|
requirements: []
|
274
277
|
rubyforge_project: gli
|
275
|
-
rubygems_version: 2.0.
|
278
|
+
rubygems_version: 2.0.14
|
276
279
|
signing_key:
|
277
280
|
specification_version: 4
|
278
281
|
summary: Build command-suite CLI apps that are awesome.
|