benry-cmdapp 0.2.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +6 -0
- data/README.md +1693 -852
- data/benry-cmdapp.gemspec +3 -3
- data/doc/benry-cmdapp.html +1582 -906
- data/lib/benry/cmdapp.rb +1894 -1060
- data/test/app_test.rb +882 -1078
- data/test/config_test.rb +71 -0
- data/test/context_test.rb +382 -0
- data/test/func_test.rb +302 -82
- data/test/help_test.rb +1054 -553
- data/test/metadata_test.rb +191 -0
- data/test/misc_test.rb +175 -0
- data/test/registry_test.rb +402 -0
- data/test/run_all.rb +4 -3
- data/test/scope_test.rb +1210 -0
- data/test/shared.rb +112 -49
- data/test/util_test.rb +154 -99
- metadata +17 -9
- data/test/action_test.rb +0 -1038
- data/test/index_test.rb +0 -185
data/test/config_test.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
|
5
|
+
require 'oktest'
|
6
|
+
|
7
|
+
require 'benry/cmdapp'
|
8
|
+
|
9
|
+
|
10
|
+
Oktest.scope do
|
11
|
+
|
12
|
+
|
13
|
+
topic Benry::CmdApp::Config do
|
14
|
+
|
15
|
+
before do
|
16
|
+
@config = Benry::CmdApp::Config.new("testapp", "1.2.3")
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
topic '#initialize()' do
|
21
|
+
|
22
|
+
spec "[!pzp34] if `option_version` is not specified, then set true if `app_version` is provided." do
|
23
|
+
c = Benry::CmdApp::Config.new("x", "1.0.0", option_version: nil)
|
24
|
+
ok {c.option_version} == true
|
25
|
+
c = Benry::CmdApp::Config.new("x", option_version: nil)
|
26
|
+
ok {c.option_version} == false
|
27
|
+
#
|
28
|
+
c = Benry::CmdApp::Config.new("x", "1.0.0", option_version: true)
|
29
|
+
ok {c.option_version} == true
|
30
|
+
c = Benry::CmdApp::Config.new("x", option_version: true)
|
31
|
+
ok {c.option_version} == true
|
32
|
+
#
|
33
|
+
c = Benry::CmdApp::Config.new("x", "1.0.0", option_version: false)
|
34
|
+
ok {c.option_version} == false
|
35
|
+
c = Benry::CmdApp::Config.new("x", option_version: false)
|
36
|
+
ok {c.option_version} == false
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
topic '#each()' do
|
43
|
+
|
44
|
+
spec "[!yxi7r] returns Enumerator object if block not given." do
|
45
|
+
ok {@config.each()}.is_a?(Enumerator)
|
46
|
+
end
|
47
|
+
|
48
|
+
spec "[!64zkf] yields each config name and value." do
|
49
|
+
d = {}
|
50
|
+
@config.each do |k, v|
|
51
|
+
ok {k}.is_a?(Symbol)
|
52
|
+
d[k] = v
|
53
|
+
end
|
54
|
+
ok {d}.NOT.empty?
|
55
|
+
end
|
56
|
+
|
57
|
+
spec "[!0zatj] sorts key names if `sort: true` passed." do
|
58
|
+
keys1 = []; keys2 = []
|
59
|
+
@config.each {|k, v| keys1 << k }
|
60
|
+
@config.each(sort: true) {|k, v| keys2 << k }
|
61
|
+
ok {keys1} != keys2
|
62
|
+
ok {keys1.sort} == keys2
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,382 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
|
5
|
+
require_relative "shared"
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
class FooBarAction < Benry::CmdApp::Action
|
10
|
+
category "foo:"
|
11
|
+
|
12
|
+
@action.("prep for foo")
|
13
|
+
def prep; puts "foo:prep"; end
|
14
|
+
|
15
|
+
category "bar:" do
|
16
|
+
|
17
|
+
@action.("prep for foo:bar")
|
18
|
+
def prep; puts "foo:bar:prep"; end
|
19
|
+
|
20
|
+
category "baz:" do
|
21
|
+
|
22
|
+
@action.("prep for foo:bar:baz")
|
23
|
+
def prep; puts "foo:bar:baz:prep"; end
|
24
|
+
|
25
|
+
@action.("aaa")
|
26
|
+
def aaa(); run_action("prep"); puts "foo:bar:baz:aaa"; end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
@action.("bbb")
|
31
|
+
def bbb(); run_action("prep"); puts "foo:bar:bbb"; end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
@action.("ccc")
|
36
|
+
def ccc(); run_action("prep"); puts "foo:ccc"; end
|
37
|
+
|
38
|
+
@action.("ddd")
|
39
|
+
def ddd(); run_action("prep"); puts "foo:ddd"; end
|
40
|
+
|
41
|
+
### looped action
|
42
|
+
|
43
|
+
@action.("looped")
|
44
|
+
def loop1()
|
45
|
+
run_once("loop2")
|
46
|
+
end
|
47
|
+
|
48
|
+
@action.("looped")
|
49
|
+
def loop2()
|
50
|
+
run_once("loop3")
|
51
|
+
end
|
52
|
+
|
53
|
+
@action.("looped")
|
54
|
+
def loop3()
|
55
|
+
run_once("loop1")
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
|
60
|
+
@action.("err1")
|
61
|
+
@option.(:z, "-z", "option z")
|
62
|
+
def err1(x, y=0, z: nil)
|
63
|
+
puts "x=#{x.inspect}, y=#{y.inspect}, z=#{z.inspect}"
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
|
68
|
+
@action.("nest1")
|
69
|
+
def nest1(); run_action("nest2"); end
|
70
|
+
|
71
|
+
@action.("nest2")
|
72
|
+
def nest2(); run_action("nest3"); end
|
73
|
+
|
74
|
+
@action.("nest3")
|
75
|
+
def nest3(); puts "nest3"; end
|
76
|
+
|
77
|
+
##
|
78
|
+
|
79
|
+
@action.("takes any arguments")
|
80
|
+
def anyargs(*args, **kwargs)
|
81
|
+
puts "args=#{args.inspect}, kwargs=#{kwargs.inspect}"
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
Oktest.scope do
|
88
|
+
|
89
|
+
|
90
|
+
topic Benry::CmdApp::ApplicationContext do
|
91
|
+
|
92
|
+
|
93
|
+
before do
|
94
|
+
@config = Benry::CmdApp::Config.new("test app", "1.2.3", app_command: "testapp")
|
95
|
+
@context = Benry::CmdApp::ApplicationContext.new(@config)
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
topic '#teardown()' do
|
100
|
+
|
101
|
+
before do
|
102
|
+
@scope = MyAction.new(@config, @context)
|
103
|
+
@result = []
|
104
|
+
@scope.at_end { @result << "A" }
|
105
|
+
@scope.at_end { @result << "B" }
|
106
|
+
@scope.at_end { @result << "C" }
|
107
|
+
end
|
108
|
+
|
109
|
+
spec "[!4df2f] invokes end blocks in reverse order of registration." do
|
110
|
+
ok {@result} == []
|
111
|
+
@context.__send__(:teardown)
|
112
|
+
ok {@result} == ["C", "B", "A"]
|
113
|
+
end
|
114
|
+
|
115
|
+
spec "[!vskre] end block list should be cleared." do
|
116
|
+
ok {@context.instance_variable_get(:@end_blocks)}.length(3)
|
117
|
+
@context.__send__(:teardown)
|
118
|
+
ok {@context.instance_variable_get(:@end_blocks)}.length(0)
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
topic '#start_action()' do
|
125
|
+
|
126
|
+
spec "[!2mnh7] looks up action metadata with action or alias name." do
|
127
|
+
metadata = Benry::CmdApp::REGISTRY.metadata_get("foo:anyargs")
|
128
|
+
ok {metadata} != nil
|
129
|
+
Benry::CmdApp.define_alias("ali21", "foo:anyargs")
|
130
|
+
Benry::CmdApp.define_alias!("ali22", "ali21")
|
131
|
+
Benry::CmdApp.define_alias!("ali23", "ali22")
|
132
|
+
#
|
133
|
+
sout, serr = capture_sio() { @context.start_action("ali23", []) }
|
134
|
+
ok {sout} == "args=[], kwargs={}\n"
|
135
|
+
#
|
136
|
+
r = recorder()
|
137
|
+
r.fake_method(@context, :_invoke_action => nil)
|
138
|
+
@context.start_action("ali23", [])
|
139
|
+
ok {r[0].args} == [metadata, [], {}, {:once=>false}]
|
140
|
+
end
|
141
|
+
|
142
|
+
spec "[!0ukvb] raises CommandError if action nor alias not found." do
|
143
|
+
pr = proc { @context.start_action("hello99", []) }
|
144
|
+
ok {pr}.raise?(Benry::CmdApp::CommandError,
|
145
|
+
"hello99: Action nor alias not found.")
|
146
|
+
end
|
147
|
+
|
148
|
+
spec "[!9n46s] if alias has its own args, combines them with command-line args." do
|
149
|
+
metadata = Benry::CmdApp::REGISTRY.metadata_get("foo:anyargs")
|
150
|
+
ok {metadata} != nil
|
151
|
+
Benry::CmdApp.define_alias("ali31", ["foo:anyargs", "aa"])
|
152
|
+
Benry::CmdApp.define_alias!("ali32", "ali31")
|
153
|
+
Benry::CmdApp.define_alias!("ali33", ["ali32" , "bb", "cc"])
|
154
|
+
#
|
155
|
+
sout, serr = capture_sio() { @context.start_action("ali33", ["xx", "yy"]) }
|
156
|
+
ok {sout} == "args=[\"aa\", \"bb\", \"cc\", \"xx\", \"yy\"], kwargs={}\n"
|
157
|
+
#
|
158
|
+
r = recorder()
|
159
|
+
r.fake_method(@context, :_invoke_action => nil)
|
160
|
+
@context.start_action("ali33", [])
|
161
|
+
ok {r[0].args} == [metadata, ["aa", "bb", "cc"], {}, {:once=>false}]
|
162
|
+
end
|
163
|
+
|
164
|
+
spec "[!5ru31] options in alias args are also parsed as well as command-line options." do
|
165
|
+
metadata = Benry::CmdApp::REGISTRY.metadata_get("hello")
|
166
|
+
ok {metadata} != nil
|
167
|
+
Benry::CmdApp.define_alias("ali41", ["hello", "-l", "it"])
|
168
|
+
Benry::CmdApp.define_alias!("ali42", ["ali41"])
|
169
|
+
#
|
170
|
+
sout, serr = capture_sio() { @context.start_action("ali42", ["-l", "fr", "Alice"]) }
|
171
|
+
ok {sout} == "Bonjour, Alice!\n"
|
172
|
+
#
|
173
|
+
r = recorder()
|
174
|
+
r.fake_method(@context, :_invoke_action => nil)
|
175
|
+
@context.start_action("ali42", ["Alice"])
|
176
|
+
ok {r[0].args} == [metadata, ["Alice"], {:lang=>"it"}, {:once=>false}]
|
177
|
+
end
|
178
|
+
|
179
|
+
spec "[!r3gfv] raises OptionError if invalid action options specified." do
|
180
|
+
pr = proc { @context.start_action("hello", ["-x"]) }
|
181
|
+
ok {pr}.raise?(Benry::CmdApp::OptionError,
|
182
|
+
"-x: Unknown option.")
|
183
|
+
end
|
184
|
+
|
185
|
+
spec "[!lg6br] runs action with command-line arguments." do
|
186
|
+
sout, serr = capture_sio do
|
187
|
+
@context.start_action("hello", ["-lit", "Alice"])
|
188
|
+
end
|
189
|
+
ok {sout} == "Chao, Alice!\n"
|
190
|
+
ok {serr} == ""
|
191
|
+
end
|
192
|
+
|
193
|
+
spec "[!jcguj] clears instance variables." do
|
194
|
+
@context.instance_eval { @status_dict[:x] = :done }
|
195
|
+
ok {@context.instance_eval { @status_dict } }.NOT.empty?
|
196
|
+
r = recorder()
|
197
|
+
r.record_method(@context, :teardown)
|
198
|
+
sout, serr = capture_sio do
|
199
|
+
@context.start_action("hello", ["-lit", "Alice"])
|
200
|
+
end
|
201
|
+
ok {r[0].name} == :teardown
|
202
|
+
ok {r[0].args} == []
|
203
|
+
ok {@context.instance_eval { @status_dict } }.empty?
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
topic '#invoke_action()' do
|
210
|
+
|
211
|
+
spec "[!uw6rq] raises ActionError if action name is not a string." do
|
212
|
+
pr = proc { @context.invoke_action(:hello, [], {}) }
|
213
|
+
ok {pr}.raise?(Benry::CmdApp::ActionError,
|
214
|
+
"`:hello`: Action name should be a string, but got Symbol object.")
|
215
|
+
end
|
216
|
+
|
217
|
+
spec "[!dri6e] if called from other action containing prefix, looks up action with the prefix firstly." do
|
218
|
+
sout, serr = capture_sio { @context.invoke_action("foo:ccc", [], {}) }
|
219
|
+
ok {sout} == "foo:prep\nfoo:ccc\n"
|
220
|
+
#
|
221
|
+
sout, serr = capture_sio { @context.invoke_action("foo:bar:bbb", [], {}) }
|
222
|
+
ok {sout} == "foo:bar:prep\nfoo:bar:bbb\n"
|
223
|
+
#
|
224
|
+
sout, serr = capture_sio { @context.invoke_action("foo:bar:baz:aaa", [], {}) }
|
225
|
+
ok {sout} == "foo:bar:baz:prep\nfoo:bar:baz:aaa\n"
|
226
|
+
end
|
227
|
+
|
228
|
+
spec "[!ygpsw] raises ActionError if action not found." do
|
229
|
+
pr = proc { @context.invoke_action("foo:xxx", [], {}) }
|
230
|
+
ok {pr}.raise?(Benry::CmdApp::ActionError,
|
231
|
+
"foo:xxx: Action not found.")
|
232
|
+
end
|
233
|
+
|
234
|
+
spec "[!de6a9] raises ActionError if alias name specified." do
|
235
|
+
Benry::CmdApp.define_alias("a0469", "hello")
|
236
|
+
pr = proc { @context.invoke_action("a0469", [], {}) }
|
237
|
+
ok {pr}.raise?(Benry::CmdApp::ActionError,
|
238
|
+
"a0469: Action expected, but it is an alias.")
|
239
|
+
end
|
240
|
+
|
241
|
+
spec "[!ev3qh] handles help option firstly if specified." do
|
242
|
+
config = Benry::CmdApp::Config.new("x", app_command: "testapp")
|
243
|
+
app = Benry::CmdApp::Application.new(config)
|
244
|
+
Benry::CmdApp._set_current_app(app)
|
245
|
+
at_end { Benry::CmdApp._set_current_app(nil) }
|
246
|
+
#
|
247
|
+
alias_names = []
|
248
|
+
Benry::CmdApp::REGISTRY.metadata_each(all: true) do |md|
|
249
|
+
alias_names << md.name if md.alias? && md.action == "hello"
|
250
|
+
end
|
251
|
+
alias_names.each {|alias_name| Benry::CmdApp.undef_alias(alias_name) }
|
252
|
+
#
|
253
|
+
expected = <<"END"
|
254
|
+
\e[1mtestapp hello\e[0m --- greeting message
|
255
|
+
|
256
|
+
\e[1;34mUsage:\e[0m
|
257
|
+
$ \e[1mtestapp hello\e[0m [<options>] [<name>]
|
258
|
+
|
259
|
+
\e[1;34mOptions:\e[0m
|
260
|
+
-l, --lang=<lang> : language name (en/fr/it)
|
261
|
+
END
|
262
|
+
#
|
263
|
+
sout, serr = capture_sio(tty: true) do
|
264
|
+
@context.invoke_action("hello", [], {:help=>true})
|
265
|
+
end
|
266
|
+
ok {sout} == expected
|
267
|
+
#
|
268
|
+
sout, serr = capture_sio(tty: true) { app.run("hello", "--help", "--lang=en") }
|
269
|
+
ok {sout} == expected
|
270
|
+
#
|
271
|
+
sout, serr = capture_sio(tty: true) { app.run("hello", "-h", "-lfr") }
|
272
|
+
ok {sout} == expected
|
273
|
+
end
|
274
|
+
|
275
|
+
spec "[!6hoir] don't run action and returns false if `once: true` specified and the action already done." do
|
276
|
+
ret1 = ret2 = ret3 = nil
|
277
|
+
sout, serr = capture_sio do
|
278
|
+
ret1 = @context.invoke_action("foo:prep", [], {}, once: true)
|
279
|
+
ret2 = @context.invoke_action("foo:prep", [], {}, once: true)
|
280
|
+
ret3 = @context.invoke_action("foo:prep", [], {}, once: true)
|
281
|
+
end
|
282
|
+
ok {sout} == "foo:prep\n"
|
283
|
+
ok {ret1} == true
|
284
|
+
ok {ret2} == false
|
285
|
+
ok {ret3} == false
|
286
|
+
#
|
287
|
+
sout, serr = capture_sio do
|
288
|
+
ret1 = @context.invoke_action("foo:prep", [], {}, once: false)
|
289
|
+
ret2 = @context.invoke_action("foo:prep", [], {}, once: false)
|
290
|
+
ret3 = @context.invoke_action("foo:prep", [], {}, once: false)
|
291
|
+
end
|
292
|
+
ok {sout} == "foo:prep\n" * 3
|
293
|
+
ok {ret1} == true
|
294
|
+
ok {ret2} == true
|
295
|
+
ok {ret3} == true
|
296
|
+
end
|
297
|
+
|
298
|
+
spec "[!xwlou] raises ActionError if looped aciton detected." do
|
299
|
+
pr = proc { @context.invoke_action("foo:loop1", [], {}) }
|
300
|
+
ok {pr}.raise?(Benry::CmdApp::ActionError,
|
301
|
+
"foo:loop1: Looped action detected.")
|
302
|
+
end
|
303
|
+
|
304
|
+
spec "[!peqk8] raises ActionError if args and opts not matched to action method." do
|
305
|
+
pr = proc { @context.invoke_action("foo:err1", [], {}) }
|
306
|
+
ok {pr}.raise?(Benry::CmdApp::ActionError,
|
307
|
+
"foo:err1: Argument required (but nothing specified).")
|
308
|
+
pr = proc { @context.invoke_action("foo:err1", ["X", "Y", "Z"], {}) }
|
309
|
+
ok {pr}.raise?(Benry::CmdApp::ActionError,
|
310
|
+
"foo:err1: Too much arguments (at most 2 args).")
|
311
|
+
end
|
312
|
+
|
313
|
+
spec "[!kao97] action invocation is nestable." do
|
314
|
+
sout, serr = capture_sio do
|
315
|
+
@context.invoke_action("foo:nest1", [], {})
|
316
|
+
end
|
317
|
+
ok {sout} == "nest3\n"
|
318
|
+
end
|
319
|
+
|
320
|
+
spec "[!5jdlh] runs action method with scope object." do
|
321
|
+
sout, serr = capture_sio do
|
322
|
+
@context.invoke_action("foo:prep", [], {})
|
323
|
+
end
|
324
|
+
ok {sout} == "foo:prep\n"
|
325
|
+
end
|
326
|
+
|
327
|
+
spec "[!9uue9] reports enter into and exit from action if global '-T' option specified." do
|
328
|
+
@config.trace_mode = true
|
329
|
+
sout, serr = capture_sio(tty: true) do
|
330
|
+
@context.invoke_action("foo:nest1", [], {})
|
331
|
+
end
|
332
|
+
ok {sout} == <<"END"
|
333
|
+
\e[33m### enter: foo:nest1\e[0m
|
334
|
+
\e[33m### enter: foo:nest2\e[0m
|
335
|
+
\e[33m### enter: foo:nest3\e[0m
|
336
|
+
nest3
|
337
|
+
\e[33m### exit: foo:nest3\e[0m
|
338
|
+
\e[33m### exit: foo:nest2\e[0m
|
339
|
+
\e[33m### exit: foo:nest1\e[0m
|
340
|
+
END
|
341
|
+
#
|
342
|
+
sout, serr = capture_sio(tty: false) do
|
343
|
+
@context.invoke_action("foo:nest1", [], {})
|
344
|
+
end
|
345
|
+
ok {sout} == <<"END"
|
346
|
+
### enter: foo:nest1
|
347
|
+
### enter: foo:nest2
|
348
|
+
### enter: foo:nest3
|
349
|
+
nest3
|
350
|
+
### exit: foo:nest3
|
351
|
+
### exit: foo:nest2
|
352
|
+
### exit: foo:nest1
|
353
|
+
END
|
354
|
+
end
|
355
|
+
|
356
|
+
spec "[!ndxc3] returns true if action invoked." do
|
357
|
+
ret = nil
|
358
|
+
capture_sio() do
|
359
|
+
ret = @context.invoke_action("foo:bar:prep", [], {})
|
360
|
+
end
|
361
|
+
ok {ret} == true
|
362
|
+
end
|
363
|
+
|
364
|
+
end
|
365
|
+
|
366
|
+
|
367
|
+
topic '#new_scope_object()' do
|
368
|
+
|
369
|
+
spec "[!1uzs3] creates new scope object." do
|
370
|
+
md = Benry::CmdApp::REGISTRY.metadata_get("hello")
|
371
|
+
x = @context.__send__(:new_scope_object, md)
|
372
|
+
ok {x}.is_a?(md.klass)
|
373
|
+
ok {x}.is_a?(MyAction)
|
374
|
+
ok {x.instance_variable_get(:@__context__)} == @context
|
375
|
+
end
|
376
|
+
|
377
|
+
end
|
378
|
+
|
379
|
+
end
|
380
|
+
|
381
|
+
|
382
|
+
end
|