gli 1.6.0 → 2.0.0.rc3

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.
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