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