cri 2.2.1 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/NEWS.md +6 -0
- data/cri.gemspec +2 -0
- data/lib/cri.rb +1 -1
- data/lib/cri/command.rb +56 -24
- data/lib/cri/command_dsl.rb +10 -0
- data/lib/cri/commands/basic_help.rb +6 -2
- data/lib/cri/commands/basic_root.rb +2 -2
- data/lib/cri/core_ext/string.rb +19 -0
- data/test/helper.rb +4 -1
- data/test/test_basic_root.rb +17 -0
- data/test/test_command.rb +51 -2
- metadata +21 -4
data/NEWS.md
CHANGED
data/cri.gemspec
CHANGED
@@ -18,6 +18,8 @@ Gem::Specification.new do |s|
|
|
18
18
|
[ 'cri.gemspec', '.gemtest' ]
|
19
19
|
s.require_paths = [ 'lib' ]
|
20
20
|
|
21
|
+
s.add_dependency('colored', '>= 1.2')
|
22
|
+
|
21
23
|
s.rdoc_options = [ '--main', 'README.md' ]
|
22
24
|
s.extra_rdoc_files = [ 'ChangeLog', 'LICENSE', 'README.md', 'NEWS.md' ]
|
23
25
|
end
|
data/lib/cri.rb
CHANGED
data/lib/cri/command.rb
CHANGED
@@ -70,6 +70,11 @@ module Cri
|
|
70
70
|
# supercommands’ names.
|
71
71
|
attr_accessor :usage
|
72
72
|
|
73
|
+
# @return [Boolean] true if the command is hidden (e.g. because it is
|
74
|
+
# deprecated), false otherwise
|
75
|
+
attr_accessor :hidden
|
76
|
+
alias_method :hidden?, :hidden
|
77
|
+
|
73
78
|
# @return [Array<Hash>] The list of option definitions
|
74
79
|
attr_accessor :option_definitions
|
75
80
|
|
@@ -292,46 +297,73 @@ module Cri
|
|
292
297
|
end
|
293
298
|
|
294
299
|
# @return [String] The help text for this command
|
295
|
-
def help
|
300
|
+
def help(params={})
|
301
|
+
is_verbose = params.fetch(:verbose, false)
|
302
|
+
|
296
303
|
text = ''
|
297
304
|
|
305
|
+
# Append name and summary
|
306
|
+
if summary
|
307
|
+
text << "name".formatted_as_title << "\n"
|
308
|
+
text << " #{name.formatted_as_command} - #{summary}" << "\n"
|
309
|
+
unless aliases.empty?
|
310
|
+
text << " aliases: " << aliases.map { |a| a.formatted_as_command }.join(' ') << "\n"
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
298
314
|
# Append usage
|
299
315
|
if usage
|
300
316
|
path = [ self.supercommand ]
|
301
317
|
path.unshift(path[0].supercommand) until path[0].nil?
|
302
|
-
|
303
|
-
|
304
|
-
end
|
318
|
+
formatted_usage = usage.gsub(/^([^\s]+)/) { |m| m.formatted_as_command }
|
319
|
+
full_usage = path[1..-1].map { |c| c.name.formatted_as_command + ' ' }.join + formatted_usage
|
305
320
|
|
306
|
-
# Append aliases
|
307
|
-
unless aliases.empty?
|
308
321
|
text << "\n"
|
309
|
-
text << "
|
310
|
-
|
311
|
-
|
312
|
-
# Append short description
|
313
|
-
if summary
|
314
|
-
text << "\n"
|
315
|
-
text << summary + "\n"
|
322
|
+
text << "usage".formatted_as_title << "\n"
|
323
|
+
text << full_usage.wrap_and_indent(78, 4) << "\n"
|
316
324
|
end
|
317
325
|
|
318
326
|
# Append long description
|
319
327
|
if description
|
320
328
|
text << "\n"
|
329
|
+
text << "description".formatted_as_title << "\n"
|
321
330
|
text << description.wrap_and_indent(78, 4) + "\n"
|
322
331
|
end
|
323
332
|
|
324
333
|
# Append subcommands
|
325
|
-
unless self.
|
334
|
+
unless self.subcommands.empty?
|
326
335
|
text << "\n"
|
327
|
-
text << (self.supercommand ? 'subcommands' : 'commands')
|
336
|
+
text << (self.supercommand ? 'subcommands' : 'commands').formatted_as_title
|
328
337
|
text << "\n"
|
329
|
-
|
330
|
-
self.
|
338
|
+
|
339
|
+
visible_cmds, invisible_cmds = self.subcommands.partition { |c| !c.hidden? }
|
340
|
+
|
341
|
+
commands_for_length = is_verbose ? self.subcommands : visible_cmds
|
342
|
+
length = commands_for_length.map { |c| c.name.formatted_as_command.size }.max
|
343
|
+
|
344
|
+
# Visible
|
345
|
+
visible_cmds.sort_by { |cmd| cmd.name }.each do |cmd|
|
331
346
|
text << sprintf(" %-#{length+4}s %s\n",
|
332
|
-
cmd.name,
|
347
|
+
cmd.name.formatted_as_command,
|
333
348
|
cmd.summary)
|
334
349
|
end
|
350
|
+
|
351
|
+
# Invisible
|
352
|
+
if is_verbose
|
353
|
+
invisible_cmds.sort_by { |cmd| cmd.name }.each do |cmd|
|
354
|
+
text << sprintf(" %-#{length+4}s %s\n",
|
355
|
+
cmd.name.formatted_as_command,
|
356
|
+
cmd.summary)
|
357
|
+
end
|
358
|
+
else
|
359
|
+
case invisible_cmds.size
|
360
|
+
when 0
|
361
|
+
when 1
|
362
|
+
text << " (1 hidden command ommitted; show it with --verbose)\n"
|
363
|
+
else
|
364
|
+
text << " (#{invisible_cmds.size} hidden commands ommitted; show them with --verbose)\n"
|
365
|
+
end
|
366
|
+
end
|
335
367
|
end
|
336
368
|
|
337
369
|
# Append options
|
@@ -339,23 +371,23 @@ module Cri
|
|
339
371
|
if self.supercommand
|
340
372
|
groups["options for #{self.supercommand.name}"] = self.supercommand.global_option_definitions
|
341
373
|
end
|
342
|
-
length = groups.values.inject(&:+).
|
374
|
+
length = groups.values.inject(&:+).map { |o| o[:long].size }.max
|
343
375
|
groups.each_pair do |name, defs|
|
344
376
|
unless defs.empty?
|
345
377
|
text << "\n"
|
346
|
-
text << "#{name}
|
378
|
+
text << "#{name}".formatted_as_title
|
347
379
|
text << "\n"
|
348
380
|
defs.sort { |x,y| x[:long] <=> y[:long] }.each do |opt_def|
|
349
381
|
text << sprintf(
|
350
|
-
" -%1s --%-#{length+4}s
|
382
|
+
" -%1s --%-#{length+4}s",
|
351
383
|
opt_def[:short],
|
352
|
-
opt_def[:long]
|
353
|
-
|
384
|
+
opt_def[:long]).formatted_as_option
|
385
|
+
|
386
|
+
text << opt_def[:desc] << "\n"
|
354
387
|
end
|
355
388
|
end
|
356
389
|
end
|
357
390
|
|
358
|
-
# Return text
|
359
391
|
text
|
360
392
|
end
|
361
393
|
|
data/lib/cri/command_dsl.rb
CHANGED
@@ -79,6 +79,16 @@ module Cri
|
|
79
79
|
@command.usage = arg
|
80
80
|
end
|
81
81
|
|
82
|
+
# Marks the command as hidden. Hidden commands do not show up in the list of
|
83
|
+
# subcommands of the parent command, unless --verbose is passed (or
|
84
|
+
# `:verbose => true` is passed to the {Cri::Command#help} method). This can
|
85
|
+
# be used to mark commands as deprecated.
|
86
|
+
#
|
87
|
+
# @return [void]
|
88
|
+
def be_hidden
|
89
|
+
@command.hidden = true
|
90
|
+
end
|
91
|
+
|
82
92
|
# Adds a new option to the command. If a block is given, it will be
|
83
93
|
# executed when the option is successfully parsed.
|
84
94
|
#
|
@@ -10,16 +10,20 @@ commandline options. When a command is given, a command description as well as
|
|
10
10
|
command-specific commandline options are shown.
|
11
11
|
EOS
|
12
12
|
|
13
|
+
flag :v, :verbose, 'show more detailed help'
|
14
|
+
|
13
15
|
run do |opts, args, cmd|
|
14
16
|
if cmd.supercommand.nil?
|
15
17
|
raise NoHelpAvailableError,
|
16
18
|
"No help available because the help command has no supercommand"
|
17
19
|
end
|
18
20
|
|
21
|
+
is_verbose = opts.fetch(:verbose, false)
|
22
|
+
|
19
23
|
if args.empty?
|
20
|
-
puts cmd.supercommand.help
|
24
|
+
puts cmd.supercommand.help(:verbose => is_verbose)
|
21
25
|
elsif args.size == 1
|
22
|
-
puts cmd.supercommand.command_named(args[0]).help
|
26
|
+
puts cmd.supercommand.command_named(args[0]).help(:verbose => is_verbose)
|
23
27
|
else
|
24
28
|
$stderr.puts cmd.usage
|
25
29
|
exit 1
|
data/lib/cri/core_ext/string.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
require 'colored'
|
2
3
|
|
3
4
|
module Cri::CoreExtensions
|
4
5
|
|
@@ -59,6 +60,24 @@ module Cri::CoreExtensions
|
|
59
60
|
end.join("\n\n")
|
60
61
|
end
|
61
62
|
|
63
|
+
# @return [String] The string, formatted to be used as a title in a section
|
64
|
+
# in the help
|
65
|
+
def formatted_as_title
|
66
|
+
self.upcase.red.bold
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [String] The string, formatted to be used as the name of a command
|
70
|
+
# in the help
|
71
|
+
def formatted_as_command
|
72
|
+
self.green
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [String] The string, formatted to be used as an option definition
|
76
|
+
# of a command in the help
|
77
|
+
def formatted_as_option
|
78
|
+
self.yellow
|
79
|
+
end
|
80
|
+
|
62
81
|
end
|
63
82
|
|
64
83
|
end
|
data/test/helper.rb
CHANGED
@@ -13,7 +13,7 @@ class Cri::TestCase < MiniTest::Unit::TestCase
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def capture_io_while(&block)
|
16
|
-
|
16
|
+
orig_io = capture_io
|
17
17
|
block.call
|
18
18
|
[ $stdout.string, $stderr.string ]
|
19
19
|
ensure
|
@@ -42,3 +42,6 @@ private
|
|
42
42
|
end
|
43
43
|
|
44
44
|
end
|
45
|
+
|
46
|
+
# Unexpected system exit is unexpected
|
47
|
+
::MiniTest::Unit::TestCase::PASSTHROUGH_EXCEPTIONS.delete(SystemExit)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Cri::BasicRootTestCase < Cri::TestCase
|
4
|
+
|
5
|
+
def test_run_with_help
|
6
|
+
cmd = Cri::Command.new_basic_root
|
7
|
+
|
8
|
+
stdout, stderr = capture_io_while do
|
9
|
+
assert_raises SystemExit do
|
10
|
+
cmd.run(%w( -h ))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
assert stdout =~ /COMMANDS.*\n.*help.*show help/
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/test/test_command.rb
CHANGED
@@ -5,7 +5,7 @@ class Cri::CommandTestCase < Cri::TestCase
|
|
5
5
|
def simple_cmd
|
6
6
|
Cri::Command.define do
|
7
7
|
name 'moo'
|
8
|
-
usage '
|
8
|
+
usage 'moo [options] arg1 arg2 ...'
|
9
9
|
summary 'does stuff'
|
10
10
|
description 'This command does a lot of stuff.'
|
11
11
|
|
@@ -256,7 +256,7 @@ class Cri::CommandTestCase < Cri::TestCase
|
|
256
256
|
def test_help_nested
|
257
257
|
help = nested_cmd.subcommands.find { |cmd| cmd.name == 'sub' }.help
|
258
258
|
|
259
|
-
|
259
|
+
assert help.include?("USAGE\e[0m\e[0m\n \e[32msuper\e[0m \e[32msub\e[0m [options]\n")
|
260
260
|
end
|
261
261
|
|
262
262
|
def test_help_for_bare_cmd
|
@@ -346,4 +346,53 @@ class Cri::CommandTestCase < Cri::TestCase
|
|
346
346
|
assert_match /mycommand.rb/, error.backtrace.join("\n")
|
347
347
|
end
|
348
348
|
|
349
|
+
def test_hidden_commands_single
|
350
|
+
cmd = nested_cmd
|
351
|
+
subcmd = simple_cmd
|
352
|
+
cmd.add_command subcmd
|
353
|
+
subcmd.modify do |c|
|
354
|
+
c.name 'old-and-deprecated'
|
355
|
+
c.summary 'does stuff the ancient, totally deprecated way'
|
356
|
+
c.be_hidden
|
357
|
+
end
|
358
|
+
|
359
|
+
refute cmd.help.include?('hidden commands ommitted')
|
360
|
+
assert cmd.help.include?('hidden command ommitted')
|
361
|
+
refute cmd.help.include?('old-and-deprecated')
|
362
|
+
|
363
|
+
refute cmd.help(:verbose => true).include?('hidden commands ommitted')
|
364
|
+
refute cmd.help(:verbose => true).include?('hidden command ommitted')
|
365
|
+
assert cmd.help(:verbose => true).include?('old-and-deprecated')
|
366
|
+
end
|
367
|
+
|
368
|
+
def test_hidden_commands_multiple
|
369
|
+
cmd = nested_cmd
|
370
|
+
|
371
|
+
subcmd = simple_cmd
|
372
|
+
cmd.add_command subcmd
|
373
|
+
subcmd.modify do |c|
|
374
|
+
c.name 'old-and-deprecated'
|
375
|
+
c.summary 'does stuff the old, deprecated way'
|
376
|
+
c.be_hidden
|
377
|
+
end
|
378
|
+
|
379
|
+
subcmd = simple_cmd
|
380
|
+
cmd.add_command subcmd
|
381
|
+
subcmd.modify do |c|
|
382
|
+
c.name 'ancient-and-deprecated'
|
383
|
+
c.summary 'does stuff the ancient, reallydeprecated way'
|
384
|
+
c.be_hidden
|
385
|
+
end
|
386
|
+
|
387
|
+
assert cmd.help.include?('hidden commands ommitted')
|
388
|
+
refute cmd.help.include?('hidden command ommitted')
|
389
|
+
refute cmd.help.include?('old-and-deprecated')
|
390
|
+
refute cmd.help.include?('ancient-and-deprecated')
|
391
|
+
|
392
|
+
refute cmd.help(:verbose => true).include?('hidden commands ommitted')
|
393
|
+
refute cmd.help(:verbose => true).include?('hidden command ommitted')
|
394
|
+
assert cmd.help(:verbose => true).include?('old-and-deprecated')
|
395
|
+
assert cmd.help(:verbose => true).include?('ancient-and-deprecated')
|
396
|
+
end
|
397
|
+
|
349
398
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cri
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,24 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
13
|
-
dependencies:
|
12
|
+
date: 2012-06-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: colored
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.2'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.2'
|
14
30
|
description: Cri allows building easy-to-use commandline interfaces with support for
|
15
31
|
subcommands.
|
16
32
|
email: denis.defreyne@stoneship.org
|
@@ -39,6 +55,7 @@ files:
|
|
39
55
|
- test/helper.rb
|
40
56
|
- test/test_base.rb
|
41
57
|
- test/test_basic_help.rb
|
58
|
+
- test/test_basic_root.rb
|
42
59
|
- test/test_command.rb
|
43
60
|
- test/test_command_dsl.rb
|
44
61
|
- test/test_core_ext.rb
|
@@ -67,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
67
84
|
version: '0'
|
68
85
|
requirements: []
|
69
86
|
rubyforge_project:
|
70
|
-
rubygems_version: 1.8.
|
87
|
+
rubygems_version: 1.8.24
|
71
88
|
signing_key:
|
72
89
|
specification_version: 3
|
73
90
|
summary: a library for building easy-to-use commandline tools
|