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