gli 1.6.0 → 2.0.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/.gitignore +11 -0
  2. data/.rvmrc +1 -0
  3. data/.travis.yml +10 -0
  4. data/Gemfile +8 -0
  5. data/LICENSE.txt +201 -0
  6. data/ObjectModel.graffle +1191 -0
  7. data/README.rdoc +60 -10
  8. data/Rakefile +145 -0
  9. data/bin/gli +12 -30
  10. data/bin/report_on_rake_results +10 -0
  11. data/bin/test_all_rubies.sh +6 -0
  12. data/features/gli_executable.feature +84 -0
  13. data/features/gli_init.feature +219 -0
  14. data/features/step_definitions/gli_executable_steps.rb +12 -0
  15. data/features/step_definitions/gli_init_steps.rb +11 -0
  16. data/features/step_definitions/todo_steps.rb +69 -0
  17. data/features/support/env.rb +49 -0
  18. data/features/todo.feature +182 -0
  19. data/gli.cheat +95 -0
  20. data/gli.gemspec +34 -0
  21. data/lib/gli.rb +11 -571
  22. data/lib/gli/app.rb +184 -0
  23. data/lib/gli/app_support.rb +226 -0
  24. data/lib/gli/command.rb +107 -95
  25. data/lib/gli/command_line_option.rb +34 -0
  26. data/lib/gli/command_line_token.rb +13 -9
  27. data/lib/gli/command_support.rb +200 -0
  28. data/lib/gli/commands/compound_command.rb +42 -0
  29. data/lib/gli/commands/help.rb +63 -0
  30. data/lib/gli/commands/help_modules/command_help_format.rb +134 -0
  31. data/lib/gli/commands/help_modules/global_help_format.rb +61 -0
  32. data/lib/gli/commands/help_modules/list_formatter.rb +22 -0
  33. data/lib/gli/commands/help_modules/options_formatter.rb +50 -0
  34. data/lib/gli/commands/help_modules/text_wrapper.rb +53 -0
  35. data/lib/gli/commands/initconfig.rb +67 -0
  36. data/lib/{support → gli/commands}/scaffold.rb +150 -34
  37. data/lib/gli/dsl.rb +194 -0
  38. data/lib/gli/exceptions.rb +13 -4
  39. data/lib/gli/flag.rb +30 -41
  40. data/lib/gli/gli_option_parser.rb +98 -0
  41. data/lib/gli/option_parser_factory.rb +44 -0
  42. data/lib/gli/options.rb +2 -1
  43. data/lib/gli/switch.rb +19 -51
  44. data/lib/gli/terminal.rb +30 -20
  45. data/lib/gli/version.rb +5 -0
  46. data/test/apps/README.md +2 -0
  47. data/test/apps/todo/Gemfile +2 -0
  48. data/test/apps/todo/README.rdoc +6 -0
  49. data/test/apps/todo/Rakefile +23 -0
  50. data/test/apps/todo/bin/todo +52 -0
  51. data/test/apps/todo/lib/todo/commands/create.rb +22 -0
  52. data/test/apps/todo/lib/todo/commands/list.rb +53 -0
  53. data/test/apps/todo/lib/todo/commands/ls.rb +47 -0
  54. data/test/apps/todo/lib/todo/version.rb +3 -0
  55. data/test/apps/todo/test/tc_nothing.rb +14 -0
  56. data/test/apps/todo/todo.gemspec +23 -0
  57. data/test/apps/todo/todo.rdoc +5 -0
  58. data/test/config.yaml +10 -0
  59. data/test/fake_std_out.rb +30 -0
  60. data/test/gli.reek +122 -0
  61. data/test/init_simplecov.rb +8 -0
  62. data/test/option_test_helper.rb +13 -0
  63. data/test/roodi.yaml +18 -0
  64. data/test/tc_command.rb +260 -0
  65. data/test/tc_compount_command.rb +22 -0
  66. data/test/tc_flag.rb +56 -0
  67. data/test/tc_gli.rb +611 -0
  68. data/test/tc_help.rb +223 -0
  69. data/test/tc_options.rb +31 -0
  70. data/test/tc_subcommands.rb +162 -0
  71. data/test/tc_switch.rb +57 -0
  72. data/test/tc_terminal.rb +97 -0
  73. data/test/test_helper.rb +13 -0
  74. metadata +318 -49
  75. data/lib/gli_version.rb +0 -3
  76. data/lib/support/help.rb +0 -179
  77. data/lib/support/initconfig.rb +0 -34
  78. data/lib/support/rdoc.rb +0 -119
data/test/tc_help.rb ADDED
@@ -0,0 +1,223 @@
1
+ require 'test_helper'
2
+
3
+ class TC_testHelp < Clean::Test::TestCase
4
+ include TestHelper
5
+
6
+ def setup
7
+ @option_index = 0
8
+ @real_columns = ENV['COLUMNS']
9
+ ENV['COLUMNS'] = '1024'
10
+ @output = StringIO.new
11
+ @error = StringIO.new
12
+ end
13
+
14
+ def teardown
15
+ ENV['COLUMNS'] = @real_columns
16
+ end
17
+
18
+ class TestApp
19
+ include GLI::App
20
+ end
21
+
22
+ test_that "the help command is configured properly when created" do
23
+ Given {
24
+ @command = GLI::Commands::Help.new(TestApp.new,@output,@error)
25
+ }
26
+ Then {
27
+ assert_equal 'help',@command.name.to_s
28
+ assert_nil @command.aliases
29
+ assert_equal 'command',@command.arguments_description
30
+ assert_not_nil @command.description
31
+ assert_not_nil @command.long_description
32
+ assert @command.skips_pre
33
+ assert @command.skips_post
34
+ }
35
+ end
36
+
37
+ test_that "invoking help with no arguments results in listing all commands and global options" do
38
+ Given a_GLI_app
39
+ And {
40
+ @command = GLI::Commands::Help.new(@app,@output,@error)
41
+ }
42
+ When {
43
+ @command.execute({},{},[])
44
+ }
45
+ Then {
46
+ assert_top_level_help_output
47
+ }
48
+ end
49
+
50
+ test_that "invoking help with a command that doesn't exist shows an error" do
51
+ Given a_GLI_app
52
+ And {
53
+ @command = GLI::Commands::Help.new(@app,@output,@error)
54
+ @unknown_command_name = any_command_name
55
+ }
56
+ When {
57
+ @command.execute({},{},[@unknown_command_name])
58
+ }
59
+ Then {
60
+ assert_error_contained(/error: Unknown command '#{@unknown_command_name}'. Use 'gli help' for a list of commands/)
61
+ }
62
+ end
63
+
64
+ test_that "invoking help with a known command shows help for that command" do
65
+ Given a_GLI_app
66
+ And {
67
+ @command_name = cm = any_command_name
68
+ @desc = d = any_desc
69
+ @long_desc = ld = any_desc
70
+ @switch = s = any_option
71
+ @switch_desc = sd = any_desc
72
+ @flag = f = any_option
73
+ @flag_desc = fd = any_desc
74
+
75
+ @app.instance_eval do
76
+ desc d
77
+ long_desc ld
78
+ command cm do |c|
79
+
80
+ c.desc sd
81
+ c.switch s
82
+
83
+ c.desc fd
84
+ c.flag f
85
+
86
+ c.action {}
87
+ end
88
+ end
89
+ @command = GLI::Commands::Help.new(@app,@output,@error)
90
+ }
91
+ When {
92
+ @command.execute({},{},[@command_name])
93
+ }
94
+ Then {
95
+ assert_output_contained(@command_name,"Name of the command")
96
+ assert_output_contained(@desc,"Short description")
97
+ assert_output_contained(@long_desc,"Long description")
98
+ assert_output_contained("-" + @switch,"command switch")
99
+ assert_output_contained(@switch_desc,"switch description")
100
+ assert_output_contained("-" + @flag,"command flag")
101
+ assert_output_contained(@flag_desc,"flag description")
102
+ }
103
+ end
104
+
105
+ test_that 'invoking help for an app with no global options omits [global options] from the usage string' do
106
+ Given a_GLI_app(:no_options)
107
+ And {
108
+ @command = GLI::Commands::Help.new(@app,@output,@error)
109
+ }
110
+ When {
111
+ @command.execute({},{},[])
112
+ }
113
+ Then {
114
+ refute_output_contained(/\[global options\] command \[command options\] \[arguments\.\.\.\]/)
115
+ refute_output_contained('GLOBAL OPTIONS')
116
+ assert_output_contained(/command \[command options\] \[arguments\.\.\.\]/)
117
+ }
118
+ end
119
+
120
+ private
121
+
122
+ def a_GLI_app(omit_options=false)
123
+ lambda {
124
+ @program_description = program_description = any_desc
125
+ @flags = flags = [
126
+ [any_desc.strip,any_arg_name,[any_option]],
127
+ [any_desc.strip,any_arg_name,[any_option,any_long_option]],
128
+ ]
129
+ @switches = switches = [
130
+ [any_desc.strip,[any_option]],
131
+ [any_desc.strip,[any_option,any_long_option]],
132
+ ]
133
+
134
+ @commands = commands = [
135
+ [any_desc.strip,[any_command_name]],
136
+ [any_desc.strip,[any_command_name,any_command_name]],
137
+ ]
138
+
139
+ @app = TestApp.new
140
+ @app.instance_eval do
141
+ program_desc program_description
142
+
143
+ unless omit_options
144
+ flags.each do |(description,arg,flag_names)|
145
+ desc description
146
+ arg_name arg
147
+ flag flag_names
148
+ end
149
+
150
+ switches.each do |(description,switch_names)|
151
+ desc description
152
+ switch switch_names
153
+ end
154
+ end
155
+
156
+ commands.each do |(description,command_names)|
157
+ desc description
158
+ command command_names do |c|
159
+ c.action {}
160
+ end
161
+ end
162
+ end
163
+ }
164
+ end
165
+
166
+ def assert_top_level_help_output
167
+ assert_output_contained(@program_description)
168
+
169
+ @commands.each do |(description,command_names)|
170
+ assert_output_contained(/#{command_names.join(', ')}\s+-\s+#{description}/,"For command #{command_names.join(',')}")
171
+ end
172
+ assert_output_contained(/help\s+-\s+#{@command.description}/)
173
+
174
+ @switches.each do |(description,switch_names)|
175
+ expected_switch_names = switch_names.map { |_| _.length == 1 ? "-#{_}" : "--\\[no-\\]#{_}" }.join(', ')
176
+ assert_output_contained(/#{expected_switch_names}\s+-\s+#{description}/,"For switch #{switch_names.join(',')}")
177
+ end
178
+
179
+ @flags.each do |(description,arg,flag_names)|
180
+ expected_flag_names = flag_names.map { |_| _.length == 1 ? "-#{_}" : "--#{_}" }.join(', ')
181
+ assert_output_contained(/#{expected_flag_names}[ =]#{arg}\s+-\s+#{description}/,"For flag #{flag_names.join(',')}")
182
+ end
183
+
184
+ assert_output_contained('GLOBAL OPTIONS')
185
+ assert_output_contained('COMMANDS')
186
+ assert_output_contained(/\[global options\] command \[command options\] \[arguments\.\.\.\]/)
187
+ end
188
+
189
+ def assert_error_contained(string_or_regexp,desc='')
190
+ string_or_regexp = /#{string_or_regexp}/ if string_or_regexp.kind_of?(String)
191
+ assert_match string_or_regexp,@error.string,desc
192
+ end
193
+
194
+ def assert_output_contained(string_or_regexp,desc='')
195
+ string_or_regexp = /#{string_or_regexp}/ if string_or_regexp.kind_of?(String)
196
+ assert_match string_or_regexp,@output.string,desc
197
+ end
198
+
199
+ def refute_output_contained(string_or_regexp,desc='')
200
+ string_or_regexp = /#{string_or_regexp}/ if string_or_regexp.kind_of?(String)
201
+ assert_no_match string_or_regexp,@output.string,desc
202
+ end
203
+
204
+ def any_option
205
+ ('a'..'z').to_a[@option_index].tap { @option_index += 1 }
206
+ end
207
+
208
+ def any_long_option
209
+ Faker::Lorem.words(10)[rand(10)]
210
+ end
211
+
212
+ def any_arg_name
213
+ any_string :max => 20
214
+ end
215
+
216
+ def any_desc
217
+ Faker::Lorem.words(10).join(' ')[0..30].gsub(/\s*$/,'')
218
+ end
219
+
220
+ def any_command_name
221
+ Faker::Lorem.words(10)[rand(10)]
222
+ end
223
+ end
@@ -0,0 +1,31 @@
1
+ require 'test_helper'
2
+
3
+ class TC_testOptions < Clean::Test::TestCase
4
+ include TestHelper
5
+ include GLI
6
+
7
+ def test_by_method
8
+ o = Options.new
9
+ o.name = 'verbose'
10
+ assert_equal 'verbose', o.name
11
+ assert_equal 'verbose', o[:name]
12
+ assert_equal 'verbose', o['name']
13
+ end
14
+
15
+ def test_by_string
16
+ o = Options.new
17
+ o['name'] = 'verbose'
18
+ assert_equal 'verbose', o.name
19
+ assert_equal 'verbose', o[:name]
20
+ assert_equal 'verbose', o['name']
21
+ end
22
+
23
+ def test_by_symbol
24
+ o = Options.new
25
+ o[:name] = 'verbose'
26
+ assert_equal 'verbose', o.name
27
+ assert_equal 'verbose', o[:name]
28
+ assert_equal 'verbose', o['name']
29
+ end
30
+
31
+ end
@@ -0,0 +1,162 @@
1
+ require 'test_helper'
2
+
3
+ class TC_testSubCommand < Clean::Test::TestCase
4
+ include TestHelper
5
+
6
+ def setup
7
+ @app = CLIApp.new
8
+ @app.reset
9
+ @fake_stderr = FakeStdOut.new
10
+ @app.error_device=@fake_stderr
11
+ ENV.delete('GLI_DEBUG')
12
+ end
13
+
14
+ ['add','new'].each do |name|
15
+ test_that "We run the 'add' subcommand using '#{name}'" do
16
+ Given we_have_a_command_with_two_subcommands
17
+ When run_app('remote',name,'-f','foo','bar')
18
+ Then assert_command_ran_with(:add, :command_options => {:f => true}, :args => %w(foo bar))
19
+ end
20
+ end
21
+
22
+ test_that "with subcommands, but not using one on the command-line, we run the base action" do
23
+ Given we_have_a_command_with_two_subcommands
24
+ When run_app('remote','foo','bar')
25
+ Then assert_command_ran_with(:base, :command_options => {:f => false}, :args => %w(foo bar))
26
+ end
27
+
28
+ test_that "switches and flags defined on a subcommand are available" do
29
+ Given we_have_a_command_with_two_subcommands(:switches => [:addswitch], :flags => [:addflag])
30
+ When run_app('remote','add','--addswitch','--addflag','foo','bar')
31
+ Then assert_command_ran_with(:add,:command_options => { :addswitch => true, :addflag => 'foo', :f => false },
32
+ :args => ['bar'])
33
+ end
34
+
35
+ test_that "we can nest subcommands very deep" do
36
+ Given {
37
+ @run_results = { :add => nil, :rename => nil, :base => nil }
38
+ @app.command :remote do |c|
39
+
40
+ c.switch :f
41
+ c.command :add do |add|
42
+ add.command :some do |some|
43
+ some.command :cmd do |cmd|
44
+ cmd.switch :s
45
+ cmd.action do |global_options,command_options,args|
46
+ @run_results[:cmd] = [global_options,command_options,args]
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ ENV['GLI_DEBUG'] = 'true'
53
+ }
54
+ When run_app('remote','add','some','cmd','-s','blah')
55
+ Then assert_command_ran_with(:cmd, :command_options => {:s => true, :f => false},:args => ['blah'])
56
+ end
57
+
58
+ test_that "when any command in the chain has no action, but there's still arguments, indicate we have an unknown command" do
59
+ Given a_very_deeply_nested_command_structure
60
+ Then {
61
+ assert_raises GLI::UnknownCommand do
62
+ When run_app('remote','add','some','foo')
63
+ end
64
+ assert_match /Unknown command 'foo'/,@fake_stderr.to_s
65
+ }
66
+ end
67
+
68
+ test_that "when a command in the chain has no action, but there's NO additional arguments, indicate we need a subcommand" do
69
+ Given a_very_deeply_nested_command_structure
70
+ Then {
71
+ assert_raises GLI::BadCommandLine do
72
+ When run_app('remote','add','some')
73
+ end
74
+ assert_match /Command 'some' requires a subcommand/,@fake_stderr.to_s
75
+ }
76
+ end
77
+
78
+ private
79
+
80
+ def run_app(*args)
81
+ lambda { @exit_code = @app.run(args) }
82
+ end
83
+
84
+ def a_very_deeply_nested_command_structure
85
+ lambda {
86
+ @run_results = { :add => nil, :rename => nil, :base => nil }
87
+ @app.command :remote do |c|
88
+
89
+ c.switch :f
90
+ c.command :add do |add|
91
+ add.command :some do |some|
92
+ some.command :cmd do |cmd|
93
+ cmd.switch :s
94
+ cmd.action do |global_options,command_options,args|
95
+ @run_results[:cmd] = [global_options,command_options,args]
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ ENV['GLI_DEBUG'] = 'true'
102
+ }
103
+ end
104
+
105
+ # expected_command - name of command exepcted to have been run
106
+ # options:
107
+ # - global_options => hash of expected options
108
+ # - command_options => hash of expected command options
109
+ # - args => array of expected args
110
+ def assert_command_ran_with(expected_command,options)
111
+ lambda {
112
+ @run_results.each do |command,results|
113
+ if command == expected_command
114
+ assert_equal(Hash(options[:global_options]),results[0])
115
+ assert_equal(Hash(options[:command_options]),results[1])
116
+ assert_equal(options[:args],results[2])
117
+ else
118
+ assert_nil results
119
+ end
120
+ end
121
+ }
122
+ end
123
+
124
+ def Hash(possibly_nil_hash)
125
+ possibly_nil_hash || {}
126
+ end
127
+
128
+ # options -
129
+ # :flags => flags to add to :add
130
+ # :switiches => switiches to add to :add
131
+ def we_have_a_command_with_two_subcommands(options = {})
132
+ lambda {
133
+ @run_results = { :add => nil, :rename => nil, :base => nil }
134
+ @app.command :remote do |c|
135
+
136
+ c.switch :f
137
+
138
+ c.desc "add a remote"
139
+ c.command [:add,:new] do |add|
140
+
141
+ Array(options[:flags]).each { |_| add.flag _ }
142
+ Array(options[:switches]).each { |_| add.switch _ }
143
+ add.action do |global_options,command_options,args|
144
+ @run_results[:add] = [global_options,command_options,args]
145
+ end
146
+ end
147
+
148
+ c.desc "rename a remote"
149
+ c.command :rename do |rename|
150
+ rename.action do |global_options,command_options,args|
151
+ @run_results[:rename] = [global_options,command_options,args]
152
+ end
153
+ end
154
+
155
+ c.action do |global_options,command_options,args|
156
+ @run_results[:base] = [global_options,command_options,args]
157
+ end
158
+ end
159
+ ENV['GLI_DEBUG'] = 'true'
160
+ }
161
+ end
162
+ end
data/test/tc_switch.rb ADDED
@@ -0,0 +1,57 @@
1
+ require 'test_helper'
2
+
3
+ class TC_testSwitch < Clean::Test::TestCase
4
+ include TestHelper
5
+ include GLI
6
+
7
+ def test_basics_simple
8
+ Given switch_with_names(:filename)
9
+ Then attributes_should_be_set
10
+ And name_should_be(:filename)
11
+ And aliases_should_be(nil)
12
+ end
13
+
14
+ def test_basics_kinda_complex
15
+ Given switch_with_names([:f])
16
+ Then attributes_should_be_set
17
+ And name_should_be(:f)
18
+ And aliases_should_be(nil)
19
+ end
20
+
21
+ def test_basics_complex
22
+ Given switch_with_names([:f,:file,:filename])
23
+ Then attributes_should_be_set
24
+ And name_should_be(:f)
25
+ And aliases_should_be([:file,:filename])
26
+ And {
27
+ assert_equal ["-f","--[no-]file","--[no-]filename"],@switch.arguments_for_option_parser
28
+ }
29
+ end
30
+
31
+ def test_includes_negatable
32
+ assert_equal '-a',Switch.name_as_string('a')
33
+ assert_equal '--[no-]foo',Switch.name_as_string('foo')
34
+ end
35
+
36
+ private
37
+
38
+ def switch_with_names(names)
39
+ lambda do
40
+ @options = {
41
+ :desc => 'Filename',
42
+ :long_desc => 'The Filename',
43
+ }
44
+ @switch = Switch.new(names,@options)
45
+ @cli_option = @switch
46
+ end
47
+ end
48
+
49
+ def attributes_should_be_set
50
+ lambda {
51
+ assert_equal(@options[:desc],@switch.description)
52
+ assert_equal(@options[:long_desc],@switch.long_description)
53
+ assert(@switch.usage != nil)
54
+ }
55
+ end
56
+
57
+ end