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.
- checksums.yaml +7 -0
- data/README.md +428 -0
- data/Rakefile +108 -0
- data/benry-cli.gemspec +30 -0
- data/lib/benry/cli.rb +595 -0
- data/test/cli_test.rb +1025 -0
- metadata +79 -0
checksums.yaml
ADDED
@@ -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
|
data/README.md
ADDED
@@ -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 $
|
data/Rakefile
ADDED
@@ -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
|