mini-cli 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6954edd798ee19570124490d3c139443e54cf9db86da1cbf5e1ecf053f5b4de6
4
- data.tar.gz: e9c458ab08eb813875df22053ce3c5fd99c651bbe24f60d787a6d0968936d204
3
+ metadata.gz: 969a4421172e3cf6a3b6c6ecc294932e825a170de914ac5f19926fcc4e3da674
4
+ data.tar.gz: '039fc6a1d177834a3999468142a71a985ef417d179011cbba6b7b57777cb05c8'
5
5
  SHA512:
6
- metadata.gz: ad9884fa0f18600b69212a8f3b3c36108838d510879cde3d2a4525009439a3574c8eae1d7d7d4b567647fce04aa5cfaeee3fdceec8529381defc2533c1166559
7
- data.tar.gz: 287198992331abcd2fee44165775cad3a6905c15eb1fac71bee380d706ba5f2067b3b2d3498f9a7bc4f07fe92c447e417c854313b247f0b81db304f0810a582b
6
+ metadata.gz: e8fc8a932a4e508327570bb3c4e062950d78b19cbaf87594456e2f5d340a22bcbe2db32bbffd55c76e7e57e2fdb1ca63118d2f20a67976bb60fb3795e9cf372a
7
+ data.tar.gz: 3a2d8fa7af6ec17ae701ab0a6505ce03084646540c3cab5a54facb76c5434ad5b29acdbcb9aea35d19e7daa60bdabdb79cf143a8f8a22a2d6af0b6c7d4aeb98e
data/lib/mini-cli.rb CHANGED
@@ -2,8 +2,11 @@
2
2
 
3
3
  module MiniCli
4
4
  def self.included(mod)
5
- source = caller_locations(1, 1).first.absolute_path
6
- mod.const_set(:MiniCli__, Instance.new(source))
5
+ mod.const_set(
6
+ :MiniCli__,
7
+ Instance.new(caller_locations(1, 1).first.absolute_path)
8
+ )
9
+ mod.private_constant(:MiniCli__)
7
10
  end
8
11
 
9
12
  def name(name = nil)
@@ -26,42 +29,58 @@ module MiniCli
26
29
  __minicli__.show_errors = value ? true : false
27
30
  end
28
31
 
32
+ def error_code
33
+ __minicli__.error_code
34
+ end
35
+
29
36
  def error(code, message = nil)
30
37
  $stderr.puts("#{name}: #{message}") if message && show_errors?
31
- exit(code)
38
+ exit(__minicli__.error_code = code)
32
39
  end
33
40
 
34
41
  def parse_argv(argv = nil, &argv_converter)
35
42
  return __minicli__.converter = argv_converter if argv_converter
36
- argv ||= ARGV.dup
43
+ argv = Array.new(argv || ARGV)
37
44
  exit(show_help) if argv.index('--help') || argv.index('-h')
38
- __minicli__.convert(__minicli__.parse(argv, method(:error).to_proc))
45
+ __minicli__.main(argv, method(:error).to_proc)
39
46
  end
40
47
 
41
- def main(args = nil)
42
- at_exit do
43
- yield(args || parse_argv)
48
+ def main
49
+ __minicli__.run do
50
+ yield(parse_argv)
44
51
  rescue Interrupt
45
52
  error(130, 'aborted')
46
53
  end
47
54
  end
48
55
 
56
+ def before(&callback)
57
+ callback and __minicli__.before << callback
58
+ end
59
+
60
+ def after(&callback)
61
+ callback and __minicli__.after << callback
62
+ end
63
+
49
64
  private
50
65
 
51
66
  def __minicli__
52
- self.class::MiniCli__
67
+ self.class.const_get(:MiniCli__)
53
68
  end
54
69
 
55
70
  class Instance
56
71
  attr_reader :source
57
72
  attr_writer :converter
58
- attr_accessor :show_errors
73
+ attr_accessor :show_errors, :before, :after, :error_code
59
74
 
60
75
  def initialize(source)
61
76
  @source = source
62
77
  @name = File.basename(source, '.*')
63
- @parser = @converter = nil
78
+ @parser = @converter = @main_proc = nil
79
+ @before, @after = [], []
64
80
  @show_errors = true
81
+ @before_ok = false
82
+ @error_code = 0
83
+ init_main
65
84
  end
66
85
 
67
86
  def name(name = nil)
@@ -77,16 +96,35 @@ module MiniCli
77
96
  true
78
97
  end
79
98
 
80
- def parse(argv, error)
81
- parser.parse(argv, error)
99
+ def run(&main_proc)
100
+ @main_proc = main_proc
82
101
  end
83
102
 
84
- def convert(args)
103
+ def main(argv, error)
104
+ @before.each(&:call)
105
+ @before_ok = true
106
+ args = parser.parse(argv, error)
85
107
  @converter ? @converter.call(args) || args : args
86
108
  end
87
109
 
88
110
  private
89
111
 
112
+ def init_main
113
+ at_exit do
114
+ next if $! and not ($!.kind_of?(SystemExit) and $!.success?)
115
+ shutdown unless @after.empty?
116
+ @main_proc&.call
117
+ end
118
+ end
119
+
120
+ def shutdown(pid = Process.pid)
121
+ at_exit do
122
+ next if Process.pid != pid
123
+ @after.reverse_each(&:call) if @before_ok
124
+ exit(@error_code)
125
+ end
126
+ end
127
+
90
128
  def parser
91
129
  @parser ||= ArgvParser.new(nil, [])
92
130
  end
@@ -121,8 +159,7 @@ module MiniCli
121
159
  while (arg = argv.shift)
122
160
  case arg
123
161
  when '--'
124
- arguments += argv
125
- break
162
+ break arguments += argv
126
163
  when /\A--([[[:alnum:]]-]+)\z/
127
164
  handle_option(Regexp.last_match[1], argv)
128
165
  when /\A-([[:alnum:]]+)\z/
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniCli
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.0'
5
5
  end
data/mini-cli.gemspec CHANGED
@@ -2,30 +2,30 @@
2
2
 
3
3
  require_relative './lib/mini-cli/version'
4
4
 
5
- GemSpec = Gem::Specification.new do |gem|
6
- gem.name = 'mini-cli'
7
- gem.version = MiniCli::VERSION
8
- gem.summary = 'The lean CLI framework for Ruby'
9
- gem.description = <<~DESC
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'mini-cli'
7
+ spec.version = MiniCli::VERSION
8
+ spec.required_ruby_version = '>= 2.5.0'
9
+
10
+ spec.summary = 'The lean CLI framework for Ruby'
11
+ spec.description = <<~DESC
10
12
  This gem is a lean, easy to use CLI framework with a very small footprint.
11
13
  It provides an easy to use argument parsing, help displaying and
12
14
  minimalistic error handling.
13
15
  DESC
14
- gem.author = 'Mike Blumtritt'
15
- gem.homepage = 'https://github.com/mblumtritt/mini-cli'
16
- gem.metadata = {
17
- 'source_code_uri' => 'https://github.com/mblumtritt/mini-cli',
18
- 'bug_tracker_uri' => 'https://github.com/mblumtritt/mini-cli/issues'
19
- }
20
16
 
21
- gem.required_ruby_version = '>= 2.5.0'
17
+ spec.author = 'Mike Blumtritt'
18
+ spec.homepage = 'https://github.com/mblumtritt/mini-cli'
19
+ spec.metadata['source_code_uri'] = 'https://github.com/mblumtritt/mini-cli'
20
+ spec.metadata['bug_tracker_uri'] =
21
+ 'https://github.com/mblumtritt/mini-cli/issues'
22
22
 
23
- gem.add_development_dependency 'bundler'
24
- gem.add_development_dependency 'minitest'
25
- gem.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'bundler'
24
+ spec.add_development_dependency 'minitest'
25
+ spec.add_development_dependency 'rake'
26
26
 
27
- all_files = %x(git ls-files -z).split(0.chr)
28
- gem.test_files = all_files.grep(%r{^(spec|test)/})
29
- gem.files = all_files - gem.test_files
30
- gem.extra_rdoc_files = %w[README.md]
27
+ all_files = Dir.chdir(__dir__) { `git ls-files -z`.split(0.chr) }
28
+ spec.test_files = all_files.grep(%r{^(spec|test)/})
29
+ spec.files = all_files - spec.test_files
30
+ spec.extra_rdoc_files = %w[README.md]
31
31
  end
data/rakefile.rb CHANGED
@@ -3,13 +3,11 @@
3
3
  require 'rake/testtask'
4
4
  require 'bundler/gem_tasks'
5
5
 
6
- STDOUT.sync = STDERR.sync = true
6
+ $stdout.sync = $stderr.sync = true
7
7
 
8
- CLOBBER << 'prj'
8
+ task(:default) { exec('rake --tasks') }
9
9
 
10
- task :default do
11
- exec "#{$PROGRAM_NAME} --tasks"
12
- end
10
+ CLOBBER << 'prj'
13
11
 
14
12
  Rake::TestTask.new(:test) do |t|
15
13
  t.ruby_opts = %w[-w]
@@ -14,15 +14,18 @@ HELP
14
14
  main { |cfg| cfg.each_pair { |key, value| puts("#{key}: #{value}") } }
15
15
 
16
16
  parse_argv do |args|
17
- Struct.new(:target, :sources, :name, :url, :switch, :opt).new.tap do |cfg|
18
- cfg.target = args['TARGET']
19
- cfg.sources = args['FILES'] # args['FILES'] is an array containing all surplus arguments
20
- source = args['SOURCE'] || ENV['SOURCE']
21
- cfg.sources.unshift(source) if source
22
- cfg.sources << 'STDIN' if cfg.sources.empty?
23
- cfg.name = args['NAME'] || 'default_name'
24
- cfg.url = args['URL'] || 'www.url.test'
25
- cfg.switch = args.key?('switch')
26
- cfg.opt = args.key?('opt')
27
- end
17
+ Struct
18
+ .new(:target, :sources, :name, :url, :switch, :opt)
19
+ .new
20
+ .tap do |cfg|
21
+ cfg.target = args['TARGET']
22
+ cfg.sources = args['FILES'] # args['FILES'] is an array containing all surplus arguments
23
+ source = args['SOURCE'] || ENV['SOURCE']
24
+ cfg.sources.unshift(source) if source
25
+ cfg.sources << 'STDIN' if cfg.sources.empty?
26
+ cfg.name = args['NAME'] || 'default_name'
27
+ cfg.url = args['URL'] || 'www.url.test'
28
+ cfg.switch = args.key?('switch')
29
+ cfg.opt = args.key?('opt')
30
+ end
28
31
  end
data/test/apps/noop ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # completely empty command
5
+
6
+ require_relative '../../lib/mini-cli'
7
+ include(MiniCli)
8
+ main {}
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # demonstrates the call sequence
5
+
6
+ require_relative '../../lib/mini-cli'
7
+ include(MiniCli)
8
+
9
+ help <<~HELP
10
+ -x, --exit early exit
11
+ -e, --error exit with error
12
+ HELP
13
+
14
+ main do |*args|
15
+ puts('main' + args.inspect)
16
+ exit if args.first['exit']
17
+ error(42, '!error!') if args.first['error']
18
+ end
19
+
20
+ before { |*args| puts('before_1' + args.inspect) }
21
+ after { |*args| puts('after_1' + args.inspect) }
22
+ before { |*args| puts('before_2' + args.inspect) }
23
+ after { |*args| puts('after_2' + args.inspect) }
24
+
25
+ parse_argv do |*args|
26
+ puts('parse_argv' + args.inspect)
27
+ args.first
28
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require 'shellwords'
5
+ require_relative '../helper'
6
+
7
+ class ExecTest < Minitest::Test
8
+ parallelize_me!
9
+
10
+ def test_noop
11
+ assert_empty(assert_no_err('noop'))
12
+ end
13
+
14
+ def test_noop_help
15
+ assert_equal("Usage: noop\n", assert_no_err('noop', '--help'))
16
+ end
17
+
18
+ def test_sequence
19
+ expected = [
20
+ 'before_1[]',
21
+ 'before_2[]',
22
+ "parse_argv[{\"FILES\"=>[]}]",
23
+ "main[{\"FILES\"=>[]}]",
24
+ 'after_2[]',
25
+ 'after_1[]'
26
+ ]
27
+ assert_equal(expected, assert_no_err('sequence').split("\n"))
28
+ end
29
+
30
+ def test_sequence_help
31
+ expected = [
32
+ 'Usage: sequence [OPTIONS]',
33
+ '',
34
+ 'Options:',
35
+ '-x, --exit early exit',
36
+ '-e, --error exit with error'
37
+ ]
38
+ assert_equal(expected, assert_no_err('sequence', '--help').split("\n"))
39
+ end
40
+
41
+ def test_sequence_early_exit
42
+ expected = [
43
+ 'before_1[]',
44
+ 'before_2[]',
45
+ "parse_argv[{\"exit\"=>true, \"FILES\"=>[]}]",
46
+ "main[{\"exit\"=>true, \"FILES\"=>[]}]",
47
+ 'after_2[]',
48
+ 'after_1[]'
49
+ ]
50
+ assert_equal(expected, assert_no_err('sequence', '-x').split("\n"))
51
+ end
52
+
53
+ def test_sequence_error
54
+ expected = [
55
+ 'before_1[]',
56
+ 'before_2[]',
57
+ "parse_argv[{\"error\"=>true, \"FILES\"=>[]}]",
58
+ "main[{\"error\"=>true, \"FILES\"=>[]}]",
59
+ 'after_2[]',
60
+ 'after_1[]'
61
+ ]
62
+ std, err, status = invoke('sequence', '-e')
63
+ assert_same(42, status.exitstatus)
64
+ assert_equal("sequence: !error!\n", err)
65
+ assert_equal(expected, std.split("\n"))
66
+ end
67
+
68
+ def assert_no_err(...)
69
+ std, err = assert_success(...)
70
+ assert_empty(err)
71
+ std
72
+ end
73
+
74
+ def assert_success(...)
75
+ std, err, status = invoke(...)
76
+ assert(status.success?)
77
+ return std, err
78
+ end
79
+
80
+ def invoke(name, *args)
81
+ Open3.capture3(
82
+ Shellwords.join(
83
+ [
84
+ RbConfig.ruby,
85
+ '--disable',
86
+ 'gems',
87
+ '--disable',
88
+ 'did_you_mean',
89
+ '--disable',
90
+ 'rubyopt',
91
+ File.expand_path("../apps/#{name}", __dir__)
92
+ ] + args
93
+ )
94
+ )
95
+ end
96
+ end
@@ -13,7 +13,10 @@ class MainTest < Test
13
13
  subject = Class.new { include MiniCli }.new
14
14
 
15
15
  expected_methods = %i[
16
+ after
17
+ before
16
18
  error
19
+ error_code
17
20
  help
18
21
  main
19
22
  name
@@ -176,4 +179,13 @@ class MainTest < Test
176
179
  result = subject.parse_argv(as_argv('-nup name port in out opt file'))
177
180
  assert_equal(expected, result)
178
181
  end
182
+
183
+ def test_run
184
+ call_sequence = []
185
+ subject.main { call_sequence << :main }
186
+ subject.before { call_sequence << :start_1 }
187
+ subject.after { call_sequence << :end_1 }
188
+ subject.before { call_sequence << :start_2 }
189
+ subject.after { call_sequence << :end_2 }
190
+ end
179
191
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mini-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-20 00:00:00.000000000 Z
11
+ date: 2021-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -74,7 +74,10 @@ files:
74
74
  - samples/custom_args.rb
75
75
  - samples/demo.rb
76
76
  - samples/simple.rb
77
+ - test/apps/noop
78
+ - test/apps/sequence
77
79
  - test/helper.rb
80
+ - test/mini-cli/exec_test.rb
78
81
  - test/mini-cli/main_test.rb
79
82
  - test/mini-cli/run_test.rb
80
83
  homepage: https://github.com/mblumtritt/mini-cli
@@ -97,11 +100,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
100
  - !ruby/object:Gem::Version
98
101
  version: '0'
99
102
  requirements: []
100
- rubygems_version: 3.1.4
103
+ rubygems_version: 3.2.9
101
104
  signing_key:
102
105
  specification_version: 4
103
106
  summary: The lean CLI framework for Ruby
104
107
  test_files:
108
+ - test/apps/noop
109
+ - test/apps/sequence
105
110
  - test/helper.rb
111
+ - test/mini-cli/exec_test.rb
106
112
  - test/mini-cli/main_test.rb
107
113
  - test/mini-cli/run_test.rb