benry-cli 0.1.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.
Files changed (7) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +428 -0
  3. data/Rakefile +108 -0
  4. data/benry-cli.gemspec +30 -0
  5. data/lib/benry/cli.rb +595 -0
  6. data/test/cli_test.rb +1025 -0
  7. metadata +79 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d4b05886752db49150fab5e3d6236f9e0ecdaf69
4
+ data.tar.gz: c91a2f5d46702753c3da2eb67e195e0310713d2a
5
+ SHA512:
6
+ metadata.gz: 71182c543f6ece58bfba085a6fe7afcf3a0ccc3f8302641143cd62d2b61dc71f8a0b6825e0a6998baf9f075e05e2ac1a1ac093a380c3ef1599453b56db52bd33
7
+ data.tar.gz: 91cfa77e7706d50d414bf1be9024a6a66d5548dc075d1a61bb29e247cbaf5a12f38ffca2cc221a1da46dfe56697fcd0b8144ccf443ef23383544fb85353e7aa4
@@ -0,0 +1,428 @@
1
+ Benry::CLI README
2
+ =================
3
+
4
+ ($Release: 0.1.0 $)
5
+
6
+ Benry::CLI is a MVC-like framework for command-line application.
7
+ It is suitable for command-line application such as `git`, `gem` or `rails`.
8
+
9
+ Compared with Rake, Benry::CLI have a distinct advantage:
10
+
11
+ * Benry::CLI can define sub-command which can take arguments and options.
12
+ * Rake can't define such sub-command.
13
+
14
+ If you use Rake as command-line application framework instead of build tool,
15
+ take account of Benry::CLI as an alternative of Rake.
16
+
17
+ (Benry::CLI requires Ruby >= 2.0)
18
+
19
+
20
+ Basic Example
21
+ -------------
22
+
23
+ ex1.rb:
24
+ ```ruby
25
+ require 'benry/cli'
26
+
27
+ ##
28
+ ## Define subclass of Benry::CLI::Action (= controller class).
29
+ ##
30
+ class HelloAction < Benry::CLI::Action
31
+
32
+ ##
33
+ ## Define action (= sub-command) with @action.()
34
+ ##
35
+ @action.(:hello, "print hello message")
36
+ def do_hello(name='World')
37
+ puts "Hello, #{name}!"
38
+ end
39
+
40
+ ##
41
+ ## When action name is nil then method name is used as action name instead.
42
+ ##
43
+ @action.(nil, "print goodbye message")
44
+ def goodbye(name='World')
45
+ puts "Goodbye, #{name}!"
46
+ end
47
+
48
+ end
49
+
50
+ VERSION = "1.0"
51
+
52
+ def main()
53
+ ##
54
+ ## Create application object with desription, version and action classes.
55
+ ## When 'action_classes' is nil, then all subclasses of Benry::CLI::Action
56
+ ## are used instead.
57
+ ##
58
+ classes = [
59
+ HelloAction,
60
+ ]
61
+ app = Benry::CLI::Application.new("example script",
62
+ version: VERSION,
63
+ action_classes: classes)
64
+ app.main()
65
+ end
66
+
67
+
68
+ if __FILE__ == $0
69
+ main()
70
+ end
71
+ ```
72
+
73
+ Example:
74
+ ```console
75
+ $ ruby ex1.rb hello
76
+ Hello, World!
77
+
78
+ $ ruby ex1.rb hello ruby
79
+ Hello, ruby!
80
+
81
+ $ ruby ex1.rb goodbye
82
+ Goodbye, World!
83
+
84
+ $ ruby ex1.rb goodbye sekai
85
+ Goodbye, sekai!
86
+ ```
87
+
88
+ Help mesage:
89
+ ```console
90
+ $ ruby ex1.rb --help # or: ruby ex1.rb help
91
+ example script
92
+
93
+ Usage:
94
+ ex1.rb [<options>] <action> [<args>...]
95
+
96
+ Options:
97
+ -h, --help : print help message
98
+ --version : print version
99
+
100
+ Actions:
101
+ goodbye : print goodbye message
102
+ hello : print hello message
103
+
104
+ (Run `ex1.rb help <action>' to show help message of each action.)
105
+ ```
106
+
107
+
108
+ Command-line Options
109
+ --------------------
110
+
111
+ ex2.rb:
112
+ ```ruby
113
+ require 'benry/cli'
114
+
115
+ class OptionTestAction < Benry::CLI::Action
116
+
117
+ ##
118
+ ## Define command-line options with @option.()
119
+ ##
120
+ @action.(:hello1, "say hello")
121
+ @option.('-q, --quiet' , "quiet mode") # no argument
122
+ @option.('-f, --format=TYPE' , "'text' or 'html'") # required arg
123
+ @option.('-d, --debug[=LEVEL]', "debug level") # optional arg
124
+ def do_hello1(name='World', quiet: nil, format: nil, debug: nil)
125
+ puts "name=%p, quiet=%p, format=%p, debug=%p" % \
126
+ [name, quiet, format, debug]
127
+ end
128
+
129
+ ##
130
+ ## Long-only version
131
+ ##
132
+ @action.(:hello2, "say hello")
133
+ @option.('--quiet' , "quiet mode") # no argument
134
+ @option.('--format=TYPE' , "'text' or 'html'") # required arg
135
+ @option.('--debug[=LEVEL]', "debug level") # optional arg
136
+ def do_hello2(name='World', quiet: nil, format: nil, debug: nil)
137
+ puts "name=%p, quiet=%p, format=%p, debug=%p" % \
138
+ [name, quiet, format, debug]
139
+ end
140
+
141
+ ##
142
+ ## Short-only version
143
+ ##
144
+ @action.(:hello3, "say hello")
145
+ @option.('-q' , "quiet mode") # no argument
146
+ @option.('-f TYPE' , "'text' or 'html'") # required arg
147
+ @option.('-d[=LEVEL]', "debug level") # optional arg
148
+ def do_hello3(name='World', q: nil, f: nil, d: nil)
149
+ puts "name=%p, q=%p, f=%p, d=%p" % \
150
+ [name, q, f, d]
151
+ end
152
+
153
+ ##
154
+ ## Change keyword arg name without '--long' option
155
+ ##
156
+ @action.(:hello4, "say hello")
157
+ @option.(:quiet, '-q ' , "quiet mode") # no argument
158
+ @option.(:format, '-f TYPE ' , "'text' or 'html'") # required arg
159
+ @option.(:debug, '-d[=LEVEL]' , "debug level") # optional arg
160
+ def do_hello4(name='World', quiet: nil, format: nil, debug: nil)
161
+ puts "name=%p, quit=%p, format=%p, debug=%p" % \
162
+ [name, quiet, format, debug]
163
+ end
164
+
165
+ end
166
+
167
+ def main()
168
+ app = Benry::CLI::Application.new(version: '1.0')
169
+ app.main()
170
+ end
171
+
172
+
173
+ if __FILE__ == $0
174
+ main()
175
+ end
176
+ ```
177
+
178
+ Example:
179
+ ```console
180
+ ## no options
181
+ $ ruby ex2.rb hello world
182
+ name="world", quiet=nil, format=nil, debug=nil
183
+
184
+ ## with some options
185
+ $ ruby ex2.rb hello -d -f foo.txt -d2 world
186
+ name="world", quiet=nil, format="foo.txt", debug="2"
187
+
188
+ ## notice that argument of '-d' is optional.
189
+ $ ruby ex2.rb hello -d
190
+ name="World", quiet=nil, format=nil, debug=true
191
+ $ ruby ex2.rb hello -d2
192
+ name="World", quiet=nil, format=nil, debug="2"
193
+ ```
194
+
195
+ Help message:
196
+ ```console
197
+ $ ruby ex2.rb hello -h
198
+ Usage:
199
+ ex2.rb [<options>] <action> [<args>...]
200
+
201
+ Options:
202
+ -h, --help : print help message
203
+ --version : print version
204
+
205
+ Actions:
206
+ hello1 : say hello
207
+ hello2 : say hello
208
+ hello3 : say hello
209
+ hello4 : say hello
210
+
211
+ (Run `ex2.rb help <action>' to show help message of each action.)
212
+ ```
213
+
214
+
215
+ Validation and Type Conversion
216
+ ------------------------------
217
+
218
+ ex3.rb:
219
+ ```ruby
220
+ require 'benry/cli'
221
+
222
+ class ValidationTestAction < Benry::CLI::Action
223
+
224
+ ##
225
+ ## @option.() can take a block argument which does both validation
226
+ ## and data conversion.
227
+ ##
228
+ @action.(:hello, "say hello")
229
+ @option.('-L, --log-level=N', "log level (1~5)") {|val|
230
+ val =~ /\A\d+\z/ or raise "positive integer expected."
231
+ val.to_i # convert string into integer
232
+ }
233
+ def do_hello(log_level: 1)
234
+ puts "log_level=#{log_level.inspect}"
235
+ end
236
+
237
+ end
238
+
239
+
240
+ if __FILE__ == $0
241
+ Benry::CLI::Application.new(version: '1.0').main()
242
+ end
243
+ ```
244
+
245
+ Example:
246
+ ```console
247
+ $ ruby ex3.rb hello -L abc
248
+ ERROR: -L abc: positive integer expected.
249
+ ```
250
+
251
+
252
+ Other Topics
253
+ ------------
254
+
255
+
256
+ ### Name Conversion Rule in Help Message
257
+
258
+ * Action name 'foo_bar' is printed as 'foo-bar' in help message.
259
+ * Long option '--foo-bar' corresponds to 'foo_bar' keyword argument.
260
+ * Parameter 'foo_bar' is printed as '<foo-bar>' in help message.
261
+ * Parameter 'foo_or_bar' is printed as '<foo|bar>' in help message (!!).
262
+
263
+ ex-argname.rb:
264
+ ```ruby
265
+ require 'benry/cli'
266
+
267
+ class FooAction < Benry::CLI::Action
268
+
269
+ ##
270
+ ## parameter name -> in help message
271
+ ## ----------------------------------------
272
+ ## file_name -> <file-name>
273
+ ## file_or_dir -> <file|dir>
274
+ ## file_name=nil -> [<file-name>]
275
+ ## *fil_namee -> [<file-name>...]
276
+ ##
277
+ @action.(:bla_bla_bla, "test")
278
+ @option.('-v, --verbose-mode', "enable verbose mode")
279
+ def do_something(file_name, file_or_dir=nil, verbose_mode: nil)
280
+ # ...
281
+ end
282
+
283
+ end
284
+
285
+ if __FILE__ == $0
286
+ Benry::CLI::Application.new().main()
287
+ end
288
+ ```
289
+
290
+ Example:
291
+ ```console
292
+ $ ruby ex-argname.rb help bla-bla-bla
293
+ test
294
+
295
+ Usage:
296
+ argname.rb bla-bla-bla [<options>] <file-name> [<file|dir>]
297
+
298
+ Options:
299
+ -h, --help : print help message
300
+ -v, --verbose-mode : enable verbose mode
301
+ ```
302
+
303
+
304
+ ### Prefix of Action Name
305
+
306
+ `self.prefix = "..."` sets prefix name of action.
307
+
308
+ mygit.rb:
309
+ ```ruby
310
+ require 'benry/cli'
311
+
312
+ class GitAction < Benry::CLI::Action
313
+
314
+ ##
315
+ ## add prefix 'git:' to each actions
316
+ ##
317
+ self.prefix = 'git'
318
+
319
+ @action.(:stage, "same as 'git add -p'")
320
+ def do_stage(*filenames)
321
+ system 'git', 'add', '-p', *filenames
322
+ end
323
+
324
+ @action.(:staged, "same as 'git diff --cached'")
325
+ def do_staged(*filenames)
326
+ system 'git', 'diff', '--cached', *filenames
327
+ end
328
+
329
+ @action.(:unstaged, "same as 'git reset HEAD'")
330
+ def do_unstage(*filenames)
331
+ system 'git', 'reset', 'HEAD', *filenames
332
+ end
333
+
334
+ end
335
+
336
+ if __FILE__ == $0
337
+ Benry::CLI::Application.new("git wrapper", version: '1.0').main()
338
+ end
339
+ ```
340
+
341
+ Example (prefix 'git:' is added to actions):
342
+ ```console
343
+ $ ruby mygit.rb --help
344
+ git wrapper
345
+
346
+ Usage:
347
+ mygit.rb [<options>] <action> [<args>...]
348
+
349
+ Options:
350
+ -h, --help : print help message
351
+ --version : print version
352
+
353
+ Actions:
354
+ git:stage : same as 'git add -p'
355
+ git:staged : same as 'git diff --cached'
356
+ git:unstaged : same as 'git reset HEAD'
357
+
358
+ (Run `mygit.rb help <action>' to show help message of each action.)
359
+ ```
360
+
361
+ Of course, it is possible to specify prefix name with `@action.(:'git:stage')`.
362
+
363
+
364
+ #### Multiple-Value Option
365
+
366
+ Validator callback can take option values as second argument.
367
+ Using it, you can define options which can be specified more than once
368
+ (such as `-I` option of gcc).
369
+
370
+ ex-multival.rb:
371
+ ```ruby
372
+ require 'benry/cli'
373
+
374
+ class TestAction < Benry::CLI::Action
375
+
376
+ @action.(nil, "example of multi-value option")
377
+ @option.("-I, --include=path", "include path") {|val, opt|
378
+ opt['include'] ||= []
379
+ opt['include'] << val
380
+ opt['include']
381
+ }
382
+ def multival(include: nil)
383
+ p include
384
+ end
385
+
386
+ end
387
+
388
+ if __FILE__ == $0
389
+ Benry::CLI::Application.new("multi-value example", version: '1.0').main()
390
+ end
391
+ ```
392
+
393
+ Example:
394
+ ```console
395
+ $ ruby ex-multival.rb multival -I /path1 -I /path2 -I /path3
396
+ ["/path1", "/path2", "/path3"]
397
+ ```
398
+
399
+
400
+ #### Parse Command Options without Application
401
+
402
+ If you need just command-line option parser instead of application,
403
+ use `Benry::CLI::OptionParser` class.
404
+
405
+ ex-parser.rb:
406
+ ```ruby
407
+ require 'benry/cli'
408
+
409
+ parser = Benry::CLI::OptionParser.new
410
+ parser.option("-h, --help", "print help")
411
+ parser.option("-f, --file=FILE", "load filename")
412
+ parser.option("-i, --indent=N", "indent") {|val|
413
+ val =~ /\A\d+\z/ or raise "integer expected." # validation
414
+ val.to_i # convertion (string -> integer)
415
+ }
416
+ args = ['-hftest.txt', '-i', '2', "x", "y"]
417
+ options = parser.parse(args)
418
+ puts options #=> {"help"=>true, "file"=>"test.txt", "indent"=>2}
419
+ puts args.inspect #=> ["x", "y"]
420
+ ```
421
+
422
+
423
+ License and Copyright
424
+ ---------------------
425
+
426
+ $License: MIT License $
427
+
428
+ $Copyright: copyright(c) 2016 kuwata-lab.com all rights reserved $
@@ -0,0 +1,108 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+
4
+ project = "benry-cli"
5
+ release = ENV['RELEASE'] || "0.0.0"
6
+ copyright = "copyright(c) 2016 kuwata-lab.com all rights reserved"
7
+ license = "MIT License"
8
+
9
+ target_files = Dir[*%W[
10
+ README.md MIT-LICENSE.txt Rakefile
11
+ lib/**/*.rb
12
+ test/**/*_test.rb
13
+ #{project}.gemspec
14
+ ]]
15
+
16
+
17
+ task :default => :help
18
+
19
+
20
+ desc "show help"
21
+ task :help do
22
+ puts "rake help # help"
23
+ puts "rake test # run test"
24
+ puts "rake package RELEASE=X.X.X # create gem file"
25
+ puts "rake publish RELEASE=X.X.X # upload gem file"
26
+ puts "rake clean # remove files"
27
+ end
28
+
29
+
30
+ desc "do test"
31
+ task :test do
32
+ sh "ruby", *Dir.glob("test/*.rb")
33
+ end
34
+
35
+
36
+ desc "create package"
37
+ task :package do
38
+ release != "0.0.0" or
39
+ raise "specify $RELEASE"
40
+ ## copy
41
+ dir = "build"
42
+ rm_rf dir if File.exist?(dir)
43
+ mkdir dir
44
+ target_files.each do |file|
45
+ dest = File.join(dir, File.dirname(file))
46
+ mkdir_p dest, :verbose=>false unless File.exist?(dest)
47
+ cp file, "#{dir}/#{file}"
48
+ end
49
+ ## edit
50
+ Dir.glob("#{dir}/**/*").each do |file|
51
+ next unless File.file?(file)
52
+ File.open(file, 'rb+') do |f|
53
+ s = f.read()
54
+ s = s.gsub(/\$Release\:.*?\$/, "$"+"Release: #{release} $")
55
+ s = s.gsub(/\$Copyright\:.*?\$/, "$"+"Copyright: #{copyright} $")
56
+ s = s.gsub(/\$License\:.*?\$/, "$"+"License: #{license} $")
57
+ #
58
+ f.rewind()
59
+ f.truncate(0)
60
+ f.write(s)
61
+ end
62
+ end
63
+ ## build
64
+ chdir dir do
65
+ sh "gem build #{project}.gemspec"
66
+ end
67
+ mv "#{dir}/#{project}-#{release}.gem", "."
68
+ end
69
+
70
+
71
+ desc "upload gem file to rubygems.org"
72
+ task :publish do
73
+ release != "0.0.0" or
74
+ raise "specify $RELEASE"
75
+ #
76
+ gemfile = "#{project}-#{release}.gem"
77
+ print "** Are you sure to publish #{gemfile}? [y/N]: "
78
+ ans = $stdin.gets().strip()
79
+ if ans.downcase.start_with?("y")
80
+ sh "gem push #{gemfile}"
81
+ sh "git tag ruby-#{project}-#{release}"
82
+ sh "git push --tags"
83
+ end
84
+ end
85
+
86
+
87
+ desc "remove build files"
88
+ task :clean do
89
+ rm_rf "build"
90
+ #
91
+ filenames = []
92
+ content = File.read('README.md')
93
+ content.scan(/^(\w[-\w]+.rb):\n```ruby\n(.*?)```/m) do
94
+ filename, code = $1, $2
95
+ filenames << filename
96
+ end
97
+ rm_f filenames
98
+ end
99
+
100
+
101
+ desc "retrieve sample codes from README file"
102
+ task :'readme:examples' do
103
+ content = File.read('README.md')
104
+ content.scan(/^(\w[-\w]+.rb):\n```ruby\n(.*?)```/m) do
105
+ filename, code = $1, $2
106
+ File.open(filename, 'w') {|f| f.write(code) }
107
+ end
108
+ end