benry-cmdapp 0.2.0 → 1.0.0
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.
- 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
|