mini-cli 0.3.0 → 0.4.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 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