cliapp 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b398e8d768022a0d3ecaf12d9e63f1d5ada3a18e14f7fa0dcb737f2ec4b55aaf
4
+ data.tar.gz: d1ec659812a404e055a0b11b873d118012e4090cd51bffa0f3a9639c3f0478b4
5
+ SHA512:
6
+ metadata.gz: 98e18468f041be66b58e3ee92523a35b999dc9396fa4d60d6125a9007830dc410bc7cc1d2d3bae69992cc05de0ff7c2a24512ac3d52a642ef1abbacaf04b6a3e
7
+ data.tar.gz: 220c43cf0dbf9a2db65bdd5d48a0fd02d6f44c9a3379b05673c670d9116bba780f05ac73e06ef9ac1f883a598f417b2ad6f8450159070e627c7b1c4a5ef9f351
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 kwatch@gmail.com
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,131 @@
1
+ CLIApp
2
+ ======
3
+
4
+ ($Version: 0.1.0 $)
5
+
6
+ CLIApp is a small framework for command-line application.
7
+ If you need to create a CLI app such as Git or Docker, CLIApp is one of the solutions.
8
+
9
+ * GitHub: https://github.com/kwatch/cliapp-ruby/
10
+
11
+
12
+ Quick Start
13
+ -----------
14
+
15
+ ```console
16
+ $ gem install cliapp
17
+ $ ruby -r cliapp -e 'puts CLIApp.skeleton' > sample
18
+ $ chmod a+x sample
19
+ $ ./sample --help | less
20
+ $ ./sample hello
21
+ Hello, world!
22
+ $ ./sample hello Alice --lang=fr
23
+ Bonjour, Alice!
24
+ ```
25
+
26
+
27
+ Sample Code
28
+ -----------
29
+
30
+ File: sample
31
+
32
+ ```ruby
33
+ #!/usr/bin/env ruby
34
+ # coding: utf-8
35
+ # frozen_string_literal: true
36
+
37
+ require 'cliapp'
38
+
39
+ ## create an application object
40
+ app = CLIApp.new("Sample", "Sample Application",
41
+ #command: "sample", # default: File.basename($0)
42
+ version: "1.0.0")
43
+ app.global_options({
44
+ :help => ["-h", "--help" , "print help message"],
45
+ :version => [ "--version" , "print version number"],
46
+ :list => ["-l", "--list" , "list action names"],
47
+ })
48
+ APP = app
49
+
50
+ ## 'hello' action
51
+ app.action("hello", "greeting message", {
52
+ :lang => ["-l", "--lang=<en|fr|it>", "language", ["en", "it", "fr"]],
53
+ }) do |name="world", lang: "en"|
54
+ case lang
55
+ when "en" ; puts "Hello, #{name}!"
56
+ when "fr" ; puts "Bonjour, #{name}!"
57
+ when "it" ; puts "Chao, #{name}!"
58
+ else raise "** internal error: lang=#{lang.inspect}"
59
+ end
60
+ end
61
+
62
+ ## 'clean' action
63
+ app.action("clean", "delete garbage files (& product files too if '-a')", {
64
+ :all => ["-a", "--all", "delete product files, too"],
65
+ }) do |all: false|
66
+ require 'fileutils' unless defined?(FileUtils)
67
+ FileUtils.rm_r(Dir.glob(GARBAGE_FILES), verbose: true)
68
+ FileUtils.rm_r(Dir.glob(PRODUCT_FILES), verbose: true) if all
69
+ end
70
+ GARBAGE_FILES = []
71
+ PRODUCT_FILES = []
72
+
73
+ ## main
74
+ def main(argv=ARGV)
75
+ APP.run(*argv)
76
+ return 0
77
+ rescue OptionParser::ParseError, CLIApp::ActionError => exc
78
+ $stderr.puts "[ERROR] #{exc.message}"
79
+ return 1
80
+ end
81
+
82
+ if __FILE__ == $0
83
+ status_code = main(ARGV)
84
+ exit status_code
85
+ end
86
+ ```
87
+
88
+ Output example:
89
+
90
+ ```console
91
+ $ chmod a+x sample
92
+
93
+ $ ./sample -l
94
+ clean : delete garbage files (& product files too if '-a')
95
+ hello : greeting message
96
+
97
+ $ ./sample hello --help
98
+ sample hello --- greeting message
99
+
100
+ Usage:
101
+ $ sample hello [<options>] [<name>]
102
+
103
+ Options:
104
+ -l, --lang=<en|fr|it> language
105
+
106
+ $ ./sample hello
107
+ Hello, world!
108
+
109
+ $ ./sample hello Alice --lang=fr
110
+ Bonjour, Alice!
111
+
112
+ $ ./sample hello Alice Bob
113
+ [ERROR] Too many arguments.
114
+
115
+ $ ./sample hello --lang
116
+ [ERROR] missing argument: --lang
117
+
118
+ $ ./sample hello --lang=ja
119
+ [ERROR] invalid argument: --lang=ja
120
+
121
+ $ ./sample hello --language=en
122
+ [ERROR] invalid option: --language=en
123
+ ```
124
+
125
+
126
+ License and Copyright
127
+ ---------------------
128
+
129
+ $License: MIT License $
130
+
131
+ $Copyright: copyright(c) 2024 kwatch@gmail.com $
data/Rakefile.rb ADDED
@@ -0,0 +1,9 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+
4
+ PROJECT = "cliapp"
5
+ SPECFILE = PROJECT + ".gemspec"
6
+
7
+ RUBY_VERSIONS = %w[3.3 3.2 3.1 3.0 2.7 2.6 2.5 2.4]
8
+
9
+ Dir.glob("./task/*-task.rb").each {|x| require x }
data/cliapp.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'cliapp'
5
+ spec.version = '$Version: 0.1.0 $'.split()[1]
6
+ spec.author = 'kwatch'
7
+ spec.email = 'kwatch@gmail.com'
8
+ spec.platform = Gem::Platform::RUBY
9
+ spec.homepage = 'https://github.com/kwatch/cliapp-ruby'
10
+ spec.summary = "CLI Application Framework"
11
+ spec.description = <<-'END'
12
+ A small framework for CLI Applications such as Git, Docker, NPM, etc.
13
+ END
14
+ spec.license = 'MIT'
15
+ spec.files = Dir[*%w[
16
+ README.md MIT-LICENSE Rakefile.rb cliapp.gemspec
17
+ lib/**/*.rb
18
+ test/**/*.rb
19
+ task/**/*.rb
20
+ ]]
21
+ spec.executables = []
22
+ spec.bindir = 'bin'
23
+ spec.require_path = 'lib'
24
+ spec.test_files = Dir['test/**/*_test.rb']
25
+ #spec.extra_rdoc_files = ['README.md', 'CHANGES.md']
26
+
27
+ spec.required_ruby_version = ">= 2.4"
28
+ spec.add_development_dependency 'oktest', '~> 1.4'
29
+ end
data/lib/cliapp.rb ADDED
@@ -0,0 +1,430 @@
1
+ # -*- coding: utf-8 -*-
2
+ # frozen_string_literal: true
3
+
4
+ ##
5
+ ## Command-Line Application Framework
6
+ ##
7
+ ## $Version: 0.1.0 $
8
+ ## $Copyright: copyright (c)2014 kwatch@gmail.com $
9
+ ## $License: MIT License $
10
+ ##
11
+
12
+ require 'optparse'
13
+
14
+
15
+ module CLIApp
16
+
17
+
18
+ class ActionError < StandardError; end
19
+ class ActionNotFoundError < ActionError; end
20
+ class ActionTooFewArgsError < ActionError; end
21
+ class ActionTooManyArgsError < ActionError; end
22
+
23
+
24
+ class Action
25
+
26
+ def initialize(name, desc=nil, option_schema={}, &block)
27
+ @name = name
28
+ @desc = desc
29
+ @option_schema = option_schema
30
+ @block = block
31
+ end
32
+
33
+ attr_reader :name, :desc, :option_schema, :block
34
+
35
+ def call(*args, **opts)
36
+ #; [!pc2hw] raises error when fewer arguments.
37
+ n_min, n_max = Util.arity_of_proc(@block)
38
+ args.length >= n_min or
39
+ raise ActionTooFewArgsError, "Too few arguments."
40
+ #; [!6vdhh] raises error when too many arguments.
41
+ n_max == nil || args.length <= n_max or
42
+ raise ActionTooManyArgsError, "Too many arguments."
43
+ #; [!7n4hs] invokes action block with args and kwargs.
44
+ if opts.empty? # for Ruby < 2.7
45
+ @block.call(*args) # for Ruby < 2.7
46
+ else
47
+ @block.call(*args, **opts)
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+
54
+ class Config
55
+
56
+ def initialize(name: nil, desc: nil, version: nil, command: nil,
57
+ help_indent: nil, help_option_width: nil, help_action_width: nil,
58
+ actionlist_width: nil, actionlist_format: nil)
59
+ command ||= File.basename($0)
60
+ @name = name || command # ex: "FooBar"
61
+ @desc = desc # ex: "Foo Bar application"
62
+ @command = command # ex: "foobar"
63
+ @version = version # ex: "1.0.0"
64
+ @help_indent = help_indent || " "
65
+ @help_option_width = help_option_width || 22
66
+ @help_action_width = help_action_width || 22
67
+ @actionlist_width = actionlist_width || 16
68
+ @actionlist_format = actionlist_format || nil # ex: "%-#{@actionlist_width}s : %s"
69
+ end
70
+
71
+ attr_accessor :name, :desc, :command, :version
72
+ attr_accessor :help_indent, :help_option_width, :help_action_width
73
+ attr_accessor :actionlist_width, :actionlist_format
74
+
75
+ end
76
+
77
+
78
+ def self.new(name, desc, version: nil, command: nil, **kws, &gopts_handler)
79
+ #; [!gpvqe] creates new Config object internally.
80
+ config = Config.new(name: name, desc: desc, version: version, command: command, **kws)
81
+ #; [!qyunk] creates new Application object with config object created.
82
+ return Application.new(config, &gopts_handler)
83
+ end
84
+
85
+
86
+ class Application
87
+
88
+ def initialize(config, &gopts_handler)
89
+ @config = config
90
+ @gopts_handler = gopts_handler
91
+ @gopts_schema = {} # ex: {:help => ["-h", "--hel", "help message"}
92
+ @actions = {} # ex: {"clean" => Action.new(:clean, "delete files", {:all=>["-a", "all"]})}
93
+ end
94
+
95
+ attr_reader :config
96
+
97
+ def global_options(option_schema={})
98
+ #; [!2kq26] accepts global option schema.
99
+ @gopts_schema = option_schema
100
+ nil
101
+ end
102
+
103
+ def action(name, desc, schema_dict={}, &block)
104
+ #; [!i1jjg] converts action name into string.
105
+ name = name.to_s
106
+ #; [!kculn] registers an action.
107
+ action = Action.new(name, desc, schema_dict, &block)
108
+ @actions[name] = action
109
+ #; [!82n8q] returns an action object.
110
+ return action
111
+ end
112
+
113
+ def get_action(name)
114
+ #; [!hop4z] returns action object if found, nil if else.
115
+ return @actions[name.to_s]
116
+ end
117
+
118
+ def each_action(sort: false, &b)
119
+ #; [!u46wo] returns Enumerator object if block not given.
120
+ return to_enum(:each_action, sort: sort) unless block_given?()
121
+ #; [!yorp6] if `sort: true` passed, sort actions by name.
122
+ names = @actions.keys
123
+ names = names.sort if sort
124
+ #; [!ealgm] yields each action object.
125
+ names.each do |name|
126
+ yield @actions[name]
127
+ end
128
+ nil
129
+ end
130
+
131
+ def run(*args)
132
+ #; [!qv5fz] parses global options (not parses action options).
133
+ global_opts = parse_global_options(args)
134
+ #; [!kveua] handles global options such as '--help'.
135
+ done = handle_global_options(global_opts)
136
+ return if done
137
+ #; [!j029i] prints help message if no action name specified.
138
+ if args.empty?
139
+ done = do_when_action_not_specified(global_opts)
140
+ return if done
141
+ end
142
+ #; [!43u4y] raises error if action name is unknown.
143
+ action_name = args.shift()
144
+ action = get_action(action_name) or
145
+ raise ActionNotFoundError, "#{action_name}: Action not found."
146
+ #; [!lm0ir] parses all action options even after action args.
147
+ action_opts = parse_action_options(action, args)
148
+ #; [!ehshp] prints action help if action option contains help option.
149
+ if action_opts[:help]
150
+ print action_help_message(action)
151
+ return
152
+ end
153
+ #; [!0nwwe] invokes an action with action args and options.
154
+ action.call(*args, **action_opts)
155
+ end
156
+
157
+ def handle_global_options(global_opts)
158
+ #; [!6n0w0] when '-h' or '--help' specified, prints help message and returns true.
159
+ if global_opts[:help]
160
+ print application_help_message()
161
+ return true
162
+ end
163
+ #; [!zii8c] when '-V' or '--version' specified, prints version number and returns true.
164
+ if global_opts[:version]
165
+ puts @config.version
166
+ return true
167
+ end
168
+ #; [!csw5l] when '-l' or '--list' specified, prints action list and returns true.
169
+ if global_opts[:list]
170
+ print list_actions()
171
+ return true
172
+ end
173
+ #; [!5y8ph] if global option handler block specified, call it.
174
+ if (handler = @gopts_handler)
175
+ return !! handler.call(global_opts)
176
+ end
177
+ #; [!s816x] returns nil if global options are not handled.
178
+ return nil
179
+ end
180
+
181
+ def application_help_message(width: nil, indent: nil)
182
+ #; [!p02s2] builds application help message.
183
+ #; [!41l2g] includes version number if it is specified.
184
+ #; [!2eycw] includes 'Options:' section if any global options exist.
185
+ #; [!x3dim] includes 'Actions:' section if any actions defined.
186
+ #; [!vxcin] help message will be affcted by config.
187
+ c = @config
188
+ indent ||= c.help_indent
189
+ options_str = option_help_message(@gopts_schema, width: width, indent: indent)
190
+ format = "#{indent}%-#{width || c.help_action_width}s %s\n"
191
+ actions_str = each_action(sort: true).collect {|action|
192
+ format % [action.name, action.desc]
193
+ }.join()
194
+ optstr = options_str.empty? ? "" : " [<options>]"
195
+ actstr = actions_str.empty? ? "" : " <action> [<arguments>...]"
196
+ ver = c.version ? " (#{c.version})" : nil
197
+ sb = []
198
+ sb << <<"END"
199
+ #{c.name}#{ver} --- #{c.desc}
200
+
201
+ Usage:
202
+ #{indent}$ #{c.command}#{optstr}#{actstr}
203
+ END
204
+ sb << (options_str.empty? ? "" : <<"END")
205
+
206
+ Options:
207
+ #{options_str.chomp()}
208
+ END
209
+ sb << (actions_str.empty? ? "" : <<"END")
210
+
211
+ Actions:
212
+ #{actions_str.chomp()}
213
+ END
214
+ return sb.join()
215
+ end
216
+
217
+ def action_help_message(action, width: nil, indent: nil)
218
+ #; [!ny72g] build action help message.
219
+ #; [!pr2vy] includes 'Options:' section if any options exist.
220
+ #; [!1xggx] help message will be affcted by config.
221
+ options_str = option_help_message(action.option_schema, width: width, indent: indent)
222
+ optstr = options_str.empty? ? "" : " [<options>]"
223
+ argstr = Util.argstr_of_proc(action.block)
224
+ c = @config
225
+ sb = []
226
+ sb << <<"END"
227
+ #{c.command} #{action.name} --- #{action.desc}
228
+
229
+ Usage:
230
+ #{c.help_indent}$ #{c.command} #{action.name}#{optstr}#{argstr}
231
+ END
232
+ sb << (options_str.empty? ? "" : <<"END")
233
+
234
+ Options:
235
+ #{options_str.chomp()}
236
+ END
237
+ return sb.join()
238
+ end
239
+
240
+ def parse_global_options(args)
241
+ #; [!o83ty] parses global options and returns it.
242
+ parser = new_parser()
243
+ global_opts = prepare_parser(parser, @gopts_schema)
244
+ parser.order!(args) # not parse options after arguments
245
+ return global_opts
246
+ end
247
+
248
+ def parse_action_options(action, args)
249
+ #; [!5m767] parses action options and returns it.
250
+ parser = new_parser()
251
+ action_opts = prepare_parser(parser, action.option_schema)
252
+ #; [!k2cto] adds '-h, --help' option automatically.
253
+ parser.on("-h", "--help", "print help message") {|v| action_opts[:help] = v }
254
+ parser.permute!(args) # parse all options even after arguments
255
+ return action_opts
256
+ end
257
+
258
+ protected
259
+
260
+ def prepare_parser(parser, schema_dict, opts={})
261
+ #; [!vcgq0] adds all option schema into parser.
262
+ ## ex: schema_dict == {:help => ["-h", "--help", "help msg"]}
263
+ schema_dict.each do |key, arr|
264
+ parser.on(*arr) {|v| opts[key] = v }
265
+ end
266
+ #; [!lcpvw] returns hash object which stores options.
267
+ return opts
268
+ end
269
+
270
+ def new_parser(*args)
271
+ #; [!lnbpm] creates new parser object.
272
+ parser = OptionParser.new(*args)
273
+ #parser.require_exact = true
274
+ return parser
275
+ end
276
+
277
+ def option_help_message(option_schema, width: nil, indent: nil)
278
+ #; [!lfnlq] builds help message of options.
279
+ c = @config
280
+ width ||= c.help_option_width
281
+ indent ||= c.help_indent
282
+ parser = new_parser(nil, width, indent)
283
+ prepare_parser(parser, option_schema)
284
+ return parser.summarize().join()
285
+ end
286
+
287
+ def list_actions()
288
+ #; [!1xggx] output will be affcted by config.
289
+ c = @config
290
+ format = c.actionlist_format || "%-#{c.actionlist_width}s : %s"
291
+ format += "\n" unless format.end_with?("\n")
292
+ #; [!g99qx] returns list of action names and descriptions as a string.
293
+ #; [!rl5hs] sorts actions by name.
294
+ return each_action(sort: true).collect {|action|
295
+ #; [!rlak5] print only the first line of multiline description.
296
+ desc = (action.desc || "").each_line.take(1).first.chomp()
297
+ format % [action.name, desc]
298
+ }.join()
299
+ end
300
+
301
+ def do_when_action_not_specified(global_opts)
302
+ #; [!w5lq9] prints application help message.
303
+ print application_help_message()
304
+ #; [!txqnr] returns true which means 'done'.
305
+ return true
306
+ end
307
+
308
+ end
309
+
310
+
311
+ module Util
312
+ module_function
313
+
314
+ def arity_of_proc(proc_)
315
+ #; [!get6i] returns min and max arity of proc object.
316
+ #; [!ghrxo] returns nil as max arity if proc has variable param.
317
+ n_max = 0
318
+ n_min = proc_.arity
319
+ n_min = - (n_min + 1) if n_min < 0
320
+ has_rest = false
321
+ proc_.parameters.each do |ptype, _|
322
+ case ptype
323
+ when :req, :opt ; n_max += 1
324
+ when :rest ; has_rest = true
325
+ end
326
+ end
327
+ return n_min, (has_rest ? nil : n_max)
328
+ end
329
+
330
+ def argstr_of_proc(proc_)
331
+ #; [!gbk7b] generates argument string of proc object.
332
+ n = proc_.arity
333
+ n = - (n + 1) if n < 0
334
+ sb = []; cnt = 0
335
+ proc_.parameters.each do |(ptype, pname)|
336
+ aname = param2argname(pname)
337
+ case ptype
338
+ when :req, :opt
339
+ #; [!b6gzp] required param should be '<param>'.
340
+ #; [!q1030] optional param should be '[<param>]'.
341
+ n -= 1
342
+ if n >= 0 ; sb << " <#{aname}>"
343
+ else ; sb << " [<#{aname}>" ; cnt += 1
344
+ end
345
+ when :rest
346
+ #; [!osxwq] variable param should be '[<param>...]'.
347
+ sb << " [<#{aname}>...]"
348
+ end
349
+ end
350
+ sb << ("]" * cnt)
351
+ return sb.join()
352
+ end
353
+
354
+ def param2argname(name)
355
+ #; [!52dzl] converts 'yes_or_no' to 'yes|no'.
356
+ #; [!6qkk6] converts 'file__html' to 'file.html'.
357
+ #; [!2kbhe] converts 'aa_bb_cc' to 'aa-bb-cc'.
358
+ name = name.to_s
359
+ name = name.gsub('_or_', '|') # ex: 'yes_or_no' -> 'yes|no'
360
+ name = name.gsub('__', '.') # ex: 'file__html' -> 'file.html'
361
+ name = name.gsub('_', '-') # ex: 'src_dir' -> 'src-dir'
362
+ return name
363
+ end
364
+
365
+ end
366
+
367
+
368
+ def self.skeleton()
369
+ #; [!zls9g] returns example code.
370
+ return File.read(__FILE__).split(/^__END__\n/, 2)[1]
371
+ end
372
+
373
+
374
+ end
375
+
376
+
377
+ __END__
378
+ #!/usr/bin/env ruby
379
+ # coding: utf-8
380
+ # frozen_string_literal: true
381
+
382
+ require 'cliapp'
383
+
384
+ ## create an application object
385
+ app = CLIApp.new("Sample", "Sample Application",
386
+ #command: "sample", # default: File.basename($0)
387
+ version: "1.0.0")
388
+ app.global_options({
389
+ :help => ["-h", "--help" , "print help message"],
390
+ :version => [ "--version" , "print version number"],
391
+ :list => ["-l", "--list" , "list action names"],
392
+ })
393
+ APP = app
394
+
395
+ ## 'hello' action
396
+ app.action("hello", "greeting message", {
397
+ :lang => ["-l", "--lang=<en|fr|it>", "language", ["en", "it", "fr"]],
398
+ }) do |name="world", lang: "en"|
399
+ case lang
400
+ when "en" ; puts "Hello, #{name}!"
401
+ when "fr" ; puts "Bonjour, #{name}!"
402
+ when "it" ; puts "Chao, #{name}!"
403
+ else raise "** internal error: lang=#{lang.inspect}"
404
+ end
405
+ end
406
+
407
+ ## 'clean' action
408
+ app.action("clean", "delete garbage files (& product files too if '-a')", {
409
+ :all => ["-a", "--all", "delete product files, too"],
410
+ }) do |all: false|
411
+ require 'fileutils' unless defined?(FileUtils)
412
+ FileUtils.rm_r(Dir.glob(GARBAGE_FILES), verbose: true)
413
+ FileUtils.rm_r(Dir.glob(PRODUCT_FILES), verbose: true) if all
414
+ end
415
+ GARBAGE_FILES = []
416
+ PRODUCT_FILES = []
417
+
418
+ ## main
419
+ def main(argv=ARGV)
420
+ APP.run(*argv)
421
+ return 0
422
+ rescue OptionParser::ParseError, CLIApp::ActionError => exc
423
+ $stderr.puts "[ERROR] #{exc.message}"
424
+ return 1
425
+ end
426
+
427
+ if __FILE__ == $0
428
+ status_code = main(ARGV)
429
+ exit status_code
430
+ end
@@ -0,0 +1,67 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+
4
+ task :default => :help # or :test if you like
5
+
6
+
7
+ desc "list task names"
8
+ task :help do
9
+ system "rake -T"
10
+ end
11
+
12
+
13
+ desc "show how to release"
14
+ task :howto, [:version] do |t, args|
15
+ ver = args[:version] || ENV['version'] || "0.0.0"
16
+ zero_p = ver.end_with?('.0')
17
+ opt_b = zero_p ? " -b" : ""
18
+ puts <<"END"
19
+ How to release:
20
+
21
+ $ git diff # confirm that there is no changes
22
+ $ rake test
23
+ $ rake test:all # test on Ruby 2.x ~ 3.x
24
+ $ git checkout#{opt_b} rel-#{ver[0..-3]} # create or switch to release branch
25
+ $ vi CHANGES.md # if necessary
26
+ $ git add CHANGES.md # if necessary
27
+ $ git commit -m "Update 'CHANGES.md'" # if necessary
28
+ $ git log -1 # if necessary
29
+ $ cid=$(git log -1 | awk 'NR==1{print $2}') # if necessary
30
+ $ rake prepare[#{ver}] # update release number
31
+ $ git add -u . # add changes
32
+ $ git status -sb . # list files in staging area
33
+ $ git commit -m "Preparation for release #{ver}"
34
+ $ rake package # create a gem package
35
+ $ rake release[#{ver}] # upload to rubygems.org
36
+ $ git push -u origin
37
+ $ git tag | fgrep #{ver} # confirm release tag
38
+ $ git push --tags
39
+ $ git checkout - # back to main branch
40
+ $ git log -1 $cid # if necessary
41
+ $ git cherry-pick $cid # if necessary
42
+
43
+ END
44
+ end
45
+
46
+
47
+ desc "run test scripts"
48
+ task :test do
49
+ $LOAD_PATH << File.join(File.dirname(__FILE__), "lib")
50
+ sh "oktest test -sp"
51
+ end
52
+
53
+
54
+ desc "run test scripts on Ruby 2.x and 3.x"
55
+ task :'test:all' do
56
+ vs_home = ENV['VS_HOME'] or raise "$VS_HOME should be set."
57
+ defined?(RUBY_VERSIONS) or raise "RUBY_VERSIONS should be defined."
58
+ $LOAD_PATH << File.join(File.dirname(__FILE__), "lib")
59
+ RUBY_VERSIONS.each do |ver|
60
+ path_pat = "#{vs_home}/ruby/#{ver}.*/bin/ruby"
61
+ ruby_path = Dir.glob(path_pat).sort.last() or
62
+ raise "#{path_pat}: Not exist."
63
+ puts "\e[33m======== Ruby #{ver} ========\e[0m"
64
+ sh "#{ruby_path} -r oktest -e 'Oktest.main' -- test -sp" do end
65
+ puts ""
66
+ end
67
+ end