cliapp 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 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