mini-cli 0.1.1 → 0.5.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: b99c5619db31b2250da1515981cf62e897939e57399e72b44c3b48d22d4c9840
4
- data.tar.gz: 9ea02f0d1077a03ca5284ef20619a9c38370d9bcece25ba106e0b834733660c1
3
+ metadata.gz: b366ee44d4b95a00a977cbfdcf3e7cc7aff2cc1d3f72c03e0df2c2858aaf22e8
4
+ data.tar.gz: 8a622678016ca76e554a6b80d40dbea7dd7d6db5a494b5d7dd83944e68d66921
5
5
  SHA512:
6
- metadata.gz: 5877c7746653b3999ec6337e86e108276f26ba9a2142bdee0892033491cce2f788ee8dea594742cc5fb0554a1496923dfe5b80eba03409e9b6b4c4f2255d2d7d
7
- data.tar.gz: 7c7d1e36a5dd1ae1f139826ad9063e68e38a864416744e383e3e308fb8d172ab6efc58c26c5e4875422884deb6e2fa820ec10cc7371d028fcdfa53b6e06523ba
6
+ metadata.gz: 73a9bee03f689d2f1e7334b6b927ba8cd45efb8422729c1abe93bf9062e26de47886ff0e70dc518ed01eeab7330e8777cd25a2c6fa270d6f16d32c0979de8d8d
7
+ data.tar.gz: cf6679d1245593b36ab18481ea4c83a14da4953cf41e6e849b92951a5dc5301c3603ef42476fe1cab0995d008b5356233f50dc2135f9f3d2530c5a61683560cd
data/gems.rb CHANGED
@@ -1 +1,2 @@
1
+ source 'https://rubygems.org'
1
2
  gemspec
data/lib/mini-cli.rb CHANGED
@@ -1,162 +1,243 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniCli
4
- def self.included(_)
5
- return if const_defined?(:SRC)
6
- const_set(:SRC, caller_locations(1, 1).first.absolute_path)
4
+ def self.included(mod)
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)
10
- return name ? @__name = name : @__name if defined?(@__name)
11
- @__name = name || File.basename(MiniCli::SRC, '.*')
13
+ __minicli__.name(name)
12
14
  end
13
15
 
14
16
  def help(helptext, *args)
15
- @__argv_parser = ArgvParser.new(helptext, args)
17
+ __minicli__.help(helptext, args)
16
18
  end
17
19
 
18
20
  def show_help
19
- __argv_parser.show_help(name)
20
- true
21
+ __minicli__.show_help
21
22
  end
22
23
 
23
- def error(code, message)
24
- $stderr.puts("#{name}: #{message}")
25
- exit(code)
24
+ def show_errors?
25
+ __minicli__.show_errors
26
26
  end
27
27
 
28
- def parse_argv(argv = nil, &block)
29
- return @__argv_converter = block if block
30
- argv ||= ARGV.dup
28
+ def show_errors=(value)
29
+ __minicli__.show_errors = value ? true : false
30
+ end
31
+
32
+ def error_code
33
+ __minicli__.error_code
34
+ end
35
+
36
+ def error(code, message = nil)
37
+ $stderr.puts("#{name}: #{message}") if message && show_errors?
38
+ exit(__minicli__.error_code = code)
39
+ end
40
+
41
+ def parse_argv(argv = nil, &argv_converter)
42
+ return __minicli__.converter = argv_converter if argv_converter
43
+ argv = Array.new(argv || ARGV)
31
44
  exit(show_help) if argv.index('--help') || argv.index('-h')
32
- args = __argv_parser.parse(argv, method(:error).to_proc)
33
- defined?(@__argv_converter) ? @__argv_converter.call(args) || args : args
45
+ __minicli__.main(argv, method(:error).to_proc)
34
46
  end
35
47
 
36
- def main(args = nil)
37
- at_exit do
38
- yield(args || parse_argv)
48
+ def main
49
+ __minicli__.run do
50
+ yield(parse_argv)
39
51
  rescue Interrupt
40
52
  error(130, 'aborted')
41
53
  end
42
54
  end
43
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
+
44
64
  private
45
65
 
46
- def __argv_parser
47
- @__argv_parser ||= ArgvParser.new(nil, [])
66
+ def __minicli__
67
+ self.class.const_get(:MiniCli__)
48
68
  end
49
69
 
50
- class ArgvParser
51
- def initialize(helptext, args)
52
- @helptext = helptext.to_s
53
- @args = args.flatten.map!(&:to_s).uniq
54
- @options = nil
70
+ class Instance
71
+ attr_reader :source
72
+ attr_writer :converter
73
+ attr_accessor :show_errors, :before, :after, :error_code
74
+
75
+ def initialize(source)
76
+ @source = source
77
+ @name = File.basename(source, '.*')
78
+ @parser = @converter = @main_proc = nil
79
+ @before, @after = [], []
80
+ @show_errors = true
81
+ @before_ok = false
82
+ @error_code = 0
83
+ init_main
55
84
  end
56
85
 
57
- def show_help(name)
58
- parse_help! unless @options
59
- print("Usage: #{name}")
60
- print(' [OPTIONS]') unless @options.empty?
61
- print(' ', @args.join(' ')) unless @args.empty?
62
- puts
63
- puts(nil, 'Valid Options:') unless @options.empty?
64
- puts(@helptext) unless @helptext.empty?
86
+ def name(name = nil)
87
+ name ? @name = name.to_s : @name
65
88
  end
66
89
 
67
- def parse(argv, error)
68
- @error = error
69
- parse_help! unless @options
70
- @result, arguments = {}, []
71
- loop do
72
- case arg = argv.shift
73
- when nil
74
- break
75
- when '--'
76
- arguments += argv
77
- break
78
- when /\A--([[[:alnum:]]-]+)\z/
79
- handle_option(Regexp.last_match[1], argv)
80
- when /\A-([[:alnum:]]+)\z/
81
- parse_options(Regexp.last_match[1], argv)
82
- else
83
- arguments << arg
84
- end
85
- end
86
- process(arguments)
90
+ def help(helptext, args)
91
+ @parser = ArgvParser.new(helptext, args)
92
+ end
93
+
94
+ def show_help
95
+ parser.show_help(@name)
96
+ true
97
+ end
98
+
99
+ def run(&main_proc)
100
+ @main_proc = main_proc
101
+ end
102
+
103
+ def main(argv, error)
104
+ @before.each(&:call)
105
+ @before_ok = true
106
+ args = parser.parse(argv, error)
107
+ @converter ? @converter.call(args) || args : args
87
108
  end
88
109
 
89
110
  private
90
111
 
91
- def error(msg)
92
- @error.call(1, msg)
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
93
118
  end
94
119
 
95
- def process(arguments)
96
- @args.each do |arg|
97
- next if arg.index('..')
98
- value = arguments.shift
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
+
128
+ def parser
129
+ @parser ||= ArgvParser.new(nil, [])
130
+ end
131
+
132
+ class ArgvParser
133
+ def initialize(helptext, args)
134
+ @helptext = helptext.to_s
135
+ @args = args.flatten.map!(&:to_s).uniq
136
+ @options = nil
137
+ end
138
+
139
+ def show_help(name)
140
+ parse_help! unless @options
141
+ print("Usage: #{name}")
142
+ print(' [OPTIONS]') unless @options.empty?
143
+ print(' ', @args.join(' ')) unless @args.empty?
144
+ puts
145
+ puts(nil, 'Options:') unless @options.empty?
146
+ puts(@helptext) unless @helptext.empty?
147
+ end
148
+
149
+ def parse(argv, error)
150
+ @error, @result = error, {}
151
+ parse_help! unless @options
152
+ process(parse_argv(argv))
153
+ end
154
+
155
+ private
156
+
157
+ def parse_argv(argv)
158
+ arguments = []
159
+ while (arg = argv.shift)
160
+ case arg
161
+ when '--'
162
+ break arguments += argv
163
+ when /\A--([[[:alnum:]]-]+)\z/
164
+ handle_option(Regexp.last_match[1], argv)
165
+ when /\A-([[:alnum:]]+)\z/
166
+ parse_options(Regexp.last_match[1], argv)
167
+ else
168
+ arguments << arg
169
+ end
170
+ end
171
+ arguments
172
+ end
173
+
174
+ def error(msg)
175
+ @error[1, msg]
176
+ end
177
+
178
+ def process(arguments)
179
+ @args.each do |arg|
180
+ process_arg(arg, arguments.shift) unless arg.index('..')
181
+ end
182
+ arguments.unshift(@result['FILES']) if @result.key?('FILES')
183
+ @result['FILES'] = arguments
184
+ @result
185
+ end
186
+
187
+ def process_arg(arg, value)
99
188
  if arg.start_with?('[')
100
189
  @result[arg[1..-2]] = value if value
101
190
  else
102
191
  @result[arg] = value || error("parameter expected - #{arg}")
103
192
  end
104
193
  end
105
- arguments.unshift(@result['FILES']) if @result.key?('FILES')
106
- @result['FILES'] = arguments
107
- @result
108
- end
109
194
 
110
- def handle_option(option, argv)
111
- key = @options[option] || error("unknown option - #{option}")
112
- return @result[key] = true if option == key
113
- @result[key] = value = argv.shift
114
- return unless value.nil? || value.start_with?('-')
115
- error("parameter #{key} expected - --#{option}")
116
- end
117
-
118
- def parse_options(options, argv)
119
- options.each_char do |opt|
120
- key = @options[opt] || error("unknown option - #{opt}")
121
- next @result[key] = true if key == key.downcase
195
+ def handle_option(option, argv, test = ->(k) { option == k })
196
+ key = @options[option] || error("unknown option - #{option}")
197
+ @result[key] = test[key] and return
122
198
  @result[key] = value = argv.shift
123
- next unless value.nil? || value.start_with?('-')
124
- error("parameter #{key} expected - -#{opt}")
199
+ return unless value.nil? || value.start_with?('-')
200
+ error("parameter #{key} expected - --#{option}")
125
201
  end
126
- end
127
202
 
128
- def parse_help!
129
- @options = {}
130
- @helptext.each_line do |line|
131
- case line
132
- when /-([[:alnum:]]), --([[[:alnum:]]-]+) ([[:upper:]]+)\s+\S+/
133
- named_option(Regexp.last_match)
134
- when /--([[[:alnum:]]-]+) ([[:upper:]]+)\s+\S+/
135
- named_option_short(Regexp.last_match)
136
- when /-([[:alnum:]]), --([[[:alnum:]]-]+)\s+\S+/
137
- option(Regexp.last_match)
138
- when /--([[[:alnum:]]-]+)\s+\S+/
139
- option_short(Regexp.last_match)
203
+ def parse_options(options, argv)
204
+ test = ->(k) { k == k.downcase }
205
+ options.each_char { |opt| handle_option(opt, argv, test) }
206
+ end
207
+
208
+ def parse_help!
209
+ @options = {}
210
+ @helptext.each_line do |line|
211
+ case line
212
+ when /-([[:alnum:]]), --([[[:alnum:]]-]+) ([[:upper:]]+)\s+\S+/
213
+ option_with_argument(Regexp.last_match)
214
+ when /--([[[:alnum:]]-]+) ([[:upper:]]+)\s+\S+/
215
+ short_option_with_argument(Regexp.last_match)
216
+ when /-([[:alnum:]]), --([[[:alnum:]]-]+)\s+\S+/
217
+ option(Regexp.last_match)
218
+ when /--([[[:alnum:]]-]+)\s+\S+/
219
+ short_option(Regexp.last_match)
220
+ end
140
221
  end
141
222
  end
142
- end
143
223
 
144
- def named_option(match)
145
- @options[match[1]] = @options[match[2]] = match[3]
146
- end
224
+ def option_with_argument(match)
225
+ @options[match[1]] = @options[match[2]] = match[3]
226
+ end
147
227
 
148
- def named_option_short(match)
149
- @options[match[1]] = match[2]
150
- end
228
+ def short_option_with_argument(match)
229
+ @options[match[1]] = match[2]
230
+ end
151
231
 
152
- def option(match)
153
- @options[match[1]] = @options[match[2]] = match[2]
154
- end
232
+ def option(match)
233
+ @options[match[1]] = @options[match[2]] = match[2]
234
+ end
155
235
 
156
- def option_short(match)
157
- @options[match[1]] = match[1]
236
+ def short_option(match)
237
+ @options[match[1]] = match[1]
238
+ end
158
239
  end
159
240
  end
160
241
 
161
- private_constant(:ArgvParser)
242
+ private_constant(:Instance)
162
243
  end
data/lib/mini-cli/run.rb CHANGED
@@ -1,32 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'open3'
4
-
5
3
  module MiniCli
6
- def run(*cmd)
7
- opts = Hash === cmd.last ? __run_opt(cmd.last) : [:stdout]
8
- opts.delete(:stderr) ? __run3(opts, cmd) : __run2(opts, cmd)
9
- rescue SystemCallError
4
+ def run(*cmd, stdin_data: nil, status: false, chdir: nil)
5
+ in_read, in_write = IO.pipe
6
+ opts = { err: %i[child out], in: in_read }
7
+ opts[:chdir] = chdir if chdir
8
+ ret = IO.popen(*cmd, opts, &__run_proc(stdin_data, in_write))
9
+ status ? [Process.last_status, ret] : ret
10
+ rescue Errno::ENOENT
10
11
  nil
12
+ ensure
13
+ in_read&.close
14
+ in_write&.close
11
15
  end
12
16
 
13
- private
14
-
15
- def __run3(opts, cmd)
16
- result = Open3.capture3(*cmd)
17
- result.shift unless opts.first == :stdout
18
- result.pop unless opts.last == :status
19
- result.size == 1 ? result.first : result
17
+ def run_ruby(*cmd, **run_options)
18
+ require 'shellwords'
19
+ run(Shellwords.join(RUBY_CMD + cmd), **run_options)
20
20
  end
21
21
 
22
- def __run2(opts, cmd)
23
- result = Open3.capture2e(*cmd)
24
- return result.last.success? if opts.empty?
25
- return result if opts.size == 2
26
- opts.first == :status ? result.last : result.first
22
+ def run_script(script, status: false, chdir: nil)
23
+ run_ruby(stdin_data: script, status: status, chdir: chdir)
27
24
  end
28
25
 
29
- def __run_opt(opts)
30
- %i[stdout stderr status].keep_if{ |s| opts.delete(s) }
26
+ private
27
+
28
+ RUBY_CMD =
29
+ %w[--disable gems --disable did_you_mean --disable rubyopt].unshift(
30
+ RbConfig.ruby
31
+ ).freeze
32
+
33
+ def __run_proc(stdin_data, in_write)
34
+ return :read unless stdin_data
35
+ proc do |out|
36
+ in_write.sync = true
37
+ if stdin_data.respond_to?(:readpartial)
38
+ IO.copy_stream(stdin_data, in_write)
39
+ else
40
+ in_write.write(stdin_data)
41
+ end
42
+ in_write.close
43
+ out.read
44
+ end
31
45
  end
32
46
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniCli
4
- VERSION = '0.1.1'
4
+ VERSION = '0.5.0'
5
5
  end
data/lib/mini_cli.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative 'mini-cli'
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]
@@ -11,21 +11,21 @@ help <<~HELP, %w[TARGET [SOURCE]]
11
11
  --opt option without any argument
12
12
  HELP
13
13
 
14
- main do |cfg|
15
- cfg.each_pair{ |key, value| puts("#{key}: #{value}") }
16
- end
14
+ main { |cfg| cfg.each_pair { |key, value| puts("#{key}: #{value}") } }
17
15
 
18
16
  parse_argv do |args|
19
- Struct.new(:target, :sources, :name, :url, :switch, :opt).new.tap do |cfg|
20
- cfg.target = args['TARGET']
21
- # args['FILES'] is an array containing all surplus arguments
22
- cfg.sources = args['FILES']
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
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
31
31
  end
data/samples/simple.rb CHANGED
@@ -9,6 +9,4 @@ help <<~HELP
9
9
  This is a very simple sample without any required parameter.
10
10
  HELP
11
11
 
12
- main do |args|
13
- puts("given files: #{args['FILES']}")
14
- end
12
+ main { |args| puts("given files: #{args['FILES']}") }
data/test/apps/noop.rb ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ # completely empty command
3
+
4
+ require_relative '../../lib/mini-cli'
5
+ include(MiniCli)
6
+ main {}
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+ # demonstrates the call sequence
3
+
4
+ require_relative '../../lib/mini-cli'
5
+ include(MiniCli)
6
+
7
+ help <<~HELP
8
+ -x, --exit early exit
9
+ -e, --error exit with error
10
+ HELP
11
+
12
+ main do |*args|
13
+ puts('main' + args.inspect)
14
+ exit if args.first['exit']
15
+ error(42, '!error!') if args.first['error']
16
+ end
17
+
18
+ before { |*args| puts('before_1' + args.inspect) }
19
+ after { |*args| puts('after_1' + args.inspect) }
20
+ before { |*args| puts('before_2' + args.inspect) }
21
+ after { |*args| puts('after_2' + args.inspect) }
22
+
23
+ parse_argv do |*args|
24
+ puts('parse_argv' + args.inspect)
25
+ args.first
26
+ end
data/test/helper.rb CHANGED
@@ -11,14 +11,15 @@ class Test < Minitest::Test
11
11
  attr_reader :subject
12
12
 
13
13
  def setup
14
- @subject = Class.new do
15
- include MiniCli
16
- attr_reader :exit_args
14
+ @subject =
15
+ Class.new do
16
+ include MiniCli
17
+ attr_reader :exit_args
17
18
 
18
- def exit(*args)
19
- @exit_args = args
20
- end
21
- end.new
19
+ def exit(*args)
20
+ @exit_args = args
21
+ end
22
+ end.new
22
23
  end
23
24
 
24
25
  def assert_stop_with_error(code, text, &block)
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+ require 'shellwords'
5
+ require_relative '../helper'
6
+
7
+ class ExecTest < Minitest::Test
8
+ def test_noop
9
+ assert_empty(assert_no_err('noop.rb'))
10
+ end
11
+
12
+ def test_noop_help
13
+ assert_equal("Usage: noop\n", assert_no_err('noop.rb', '--help'))
14
+ end
15
+
16
+ def test_sequence
17
+ expected = [
18
+ 'before_1[]',
19
+ 'before_2[]',
20
+ "parse_argv[{\"FILES\"=>[]}]",
21
+ "main[{\"FILES\"=>[]}]",
22
+ 'after_2[]',
23
+ 'after_1[]'
24
+ ]
25
+ assert_equal(expected, assert_no_err('sequence.rb').split("\n"))
26
+ end
27
+
28
+ def test_sequence_help
29
+ expected = [
30
+ 'Usage: sequence [OPTIONS]',
31
+ '',
32
+ 'Options:',
33
+ '-x, --exit early exit',
34
+ '-e, --error exit with error'
35
+ ]
36
+ assert_equal(expected, assert_no_err('sequence.rb', '--help').split("\n"))
37
+ end
38
+
39
+ def test_sequence_early_exit
40
+ expected = [
41
+ 'before_1[]',
42
+ 'before_2[]',
43
+ "parse_argv[{\"exit\"=>true, \"FILES\"=>[]}]",
44
+ "main[{\"exit\"=>true, \"FILES\"=>[]}]",
45
+ 'after_2[]',
46
+ 'after_1[]'
47
+ ]
48
+ assert_equal(expected, assert_no_err('sequence.rb', '-x').split("\n"))
49
+ end
50
+
51
+ def test_sequence_error
52
+ expected = [
53
+ 'before_1[]',
54
+ 'before_2[]',
55
+ "parse_argv[{\"error\"=>true, \"FILES\"=>[]}]",
56
+ "main[{\"error\"=>true, \"FILES\"=>[]}]",
57
+ 'after_2[]',
58
+ 'after_1[]'
59
+ ]
60
+ std, err, status = invoke('sequence.rb', '-e')
61
+ assert_same(42, status.exitstatus)
62
+ assert_equal("sequence: !error!\n", err)
63
+ assert_equal(expected, std.split("\n"))
64
+ end
65
+
66
+ def assert_no_err(...)
67
+ std, err = assert_success(...)
68
+ assert_empty(err)
69
+ std
70
+ end
71
+
72
+ def assert_success(...)
73
+ std, err, status = invoke(...)
74
+ assert(status.success?)
75
+ return std, err
76
+ end
77
+
78
+ def invoke(name, *args)
79
+ Open3.capture3(
80
+ Shellwords.join(
81
+ [
82
+ RbConfig.ruby,
83
+ '--disable',
84
+ 'gems',
85
+ '--disable',
86
+ 'did_you_mean',
87
+ '--disable',
88
+ 'rubyopt',
89
+ File.expand_path("../apps/#{name}", __dir__)
90
+ ] + args
91
+ )
92
+ )
93
+ end
94
+ end
@@ -1,24 +1,34 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../helper'
2
4
 
3
5
  class MainTest < Test
4
6
  def test_defaults
5
7
  assert_equal('helper', subject.name)
6
- assert_output("Usage: helper\n"){ subject.show_help }
8
+ assert(subject.show_errors?)
9
+ assert_output("Usage: helper\n") { subject.show_help }
7
10
  end
8
11
 
9
12
  def test_methods
10
- subject = Class.new{ include MiniCli }.new
13
+ subject = Class.new { include MiniCli }.new
11
14
 
12
15
  expected_methods = %i[
16
+ after
17
+ before
13
18
  error
19
+ error_code
14
20
  help
15
21
  main
16
22
  name
17
23
  parse_argv
18
24
  run
25
+ run_ruby
26
+ run_script
27
+ show_errors=
28
+ show_errors?
19
29
  show_help
20
- ].sort!
21
- methods = (subject.methods - Object.new.methods).sort!
30
+ ]
31
+ methods = (subject.methods - Object.instance_methods).sort!
22
32
  assert_equal(expected_methods, methods)
23
33
  end
24
34
 
@@ -27,9 +37,14 @@ class MainTest < Test
27
37
  subject.error(42, 'some error text')
28
38
  end
29
39
 
30
- assert_stop_with_error(21, 'error') do
31
- subject.error(21, :error)
32
- end
40
+ assert_stop_with_error(21, 'error') { subject.error(21, :error) }
41
+ end
42
+
43
+ def test_no_error
44
+ subject.show_errors = false
45
+
46
+ assert_output(nil, nil) { subject.error(666, 'some error text') }
47
+ assert_equal([666], subject.exit_args)
33
48
  end
34
49
 
35
50
  def test_help_simple
@@ -39,7 +54,7 @@ class MainTest < Test
39
54
  Some helptext
40
55
  EXPECTED
41
56
 
42
- assert_output(expected_text){ subject.show_help }
57
+ assert_output(expected_text) { subject.show_help }
43
58
  end
44
59
 
45
60
  def test_help_with_args
@@ -54,7 +69,7 @@ class MainTest < Test
54
69
  here
55
70
  EXPECTED
56
71
 
57
- assert_output(expected_text){ subject.show_help }
72
+ assert_output(expected_text) { subject.show_help }
58
73
  end
59
74
 
60
75
  def test_argument_required
@@ -64,10 +79,10 @@ class MainTest < Test
64
79
  subject.parse_argv(as_argv(''))
65
80
  end
66
81
 
67
- expected = {'ARG' => 'arg', 'FILES' => []}
82
+ expected = { 'ARG' => 'arg', 'FILES' => [] }
68
83
  assert_equal(expected, subject.parse_argv(as_argv('arg')))
69
84
 
70
- expected = {'ARG' => 'arg1', 'FILES' => %w[arg2 arg3]}
85
+ expected = { 'ARG' => 'arg1', 'FILES' => %w[arg2 arg3] }
71
86
  assert_equal(expected, subject.parse_argv(as_argv('arg1 arg2 arg3')))
72
87
  end
73
88
 
@@ -83,10 +98,10 @@ class MainTest < Test
83
98
  subject.parse_argv(as_argv(''))
84
99
  end
85
100
 
86
- expected = {'ARG1' => 'arg1', 'ARG2' => 'arg2', 'FILES' => []}
101
+ expected = { 'ARG1' => 'arg1', 'ARG2' => 'arg2', 'FILES' => [] }
87
102
  assert_equal(expected, subject.parse_argv(as_argv('arg1 arg2')))
88
103
 
89
- expected = {'ARG1' => 'arg', 'FILES' => []}
104
+ expected = { 'ARG1' => 'arg', 'FILES' => [] }
90
105
  assert_equal(expected, subject.parse_argv(as_argv('arg')))
91
106
  end
92
107
 
@@ -100,21 +115,21 @@ class MainTest < Test
100
115
  Some additional explaination can be here
101
116
  HELP
102
117
 
103
- expected = {'FILES' => []}
118
+ expected = { 'FILES' => [] }
104
119
  assert_equal(expected, subject.parse_argv(as_argv('')))
105
120
 
106
- expected = {'NAME' => 'name', 'FILES' => []}
121
+ expected = { 'NAME' => 'name', 'FILES' => [] }
107
122
  assert_equal(expected, subject.parse_argv(as_argv('--named name')))
108
123
  assert_equal(expected, subject.parse_argv(as_argv('-n name')))
109
124
 
110
- expected = {'LNAME' => 'long', 'FILES' => []}
125
+ expected = { 'LNAME' => 'long', 'FILES' => [] }
111
126
  assert_equal(expected, subject.parse_argv(as_argv('--named-long long')))
112
127
 
113
- expected = {'unnamed' => true, 'FILES' => []}
128
+ expected = { 'unnamed' => true, 'FILES' => [] }
114
129
  assert_equal(expected, subject.parse_argv(as_argv('--unnamed')))
115
130
  assert_equal(expected, subject.parse_argv(as_argv('-u')))
116
131
 
117
- expected = {'un-named' => true, 'FILES' => []}
132
+ expected = { 'un-named' => true, 'FILES' => [] }
118
133
  assert_equal(expected, subject.parse_argv(as_argv('--un-named')))
119
134
 
120
135
  expected = {
@@ -125,23 +140,14 @@ class MainTest < Test
125
140
  'FILES' => %w[FILE1 FILE2]
126
141
  }
127
142
 
128
- result = subject.parse_argv(%w[
129
- --named name
130
- --named-long long
131
- --unnamed
132
- --un-named
133
- FILE1
134
- FILE2
135
- ])
143
+ result =
144
+ subject.parse_argv(
145
+ %w[--named name --named-long long --unnamed --un-named FILE1 FILE2]
146
+ )
136
147
  assert_equal(expected, result)
137
148
 
138
- result = subject.parse_argv(%w[
139
- -nu name
140
- --named-long long
141
- --un-named
142
- FILE1
143
- FILE2
144
- ])
149
+ result =
150
+ subject.parse_argv(%w[-nu name --named-long long --un-named FILE1 FILE2])
145
151
  assert_equal(expected, result)
146
152
  end
147
153
 
@@ -175,4 +181,13 @@ class MainTest < Test
175
181
  result = subject.parse_argv(as_argv('-nup name port in out opt file'))
176
182
  assert_equal(expected, result)
177
183
  end
184
+
185
+ def test_run
186
+ call_sequence = []
187
+ subject.main { call_sequence << :main }
188
+ subject.before { call_sequence << :start_1 }
189
+ subject.after { call_sequence << :end_1 }
190
+ subject.before { call_sequence << :start_2 }
191
+ subject.after { call_sequence << :end_2 }
192
+ end
178
193
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../helper'
4
+
5
+ class RubyRunTest < Test
6
+ def test_simple
7
+ result = subject.run_script('puts("Hello World")')
8
+ assert_equal("Hello World\n", result)
9
+ end
10
+
11
+ def test_error
12
+ result = subject.run_script('UNDEFINED')
13
+ assert_match(/NameError/, result)
14
+ end
15
+
16
+ def test_chdir
17
+ home = Dir.home
18
+ refute(home == Dir.pwd)
19
+ result = subject.run_script('print Dir.pwd', chdir: home)
20
+ assert_equal(home, result)
21
+ end
22
+
23
+ def test_status
24
+ status, result = subject.run_script('print :Ok', status: true)
25
+ assert_instance_of(Process::Status, status)
26
+ assert(status.success?)
27
+ assert_equal('Ok', result)
28
+ end
29
+
30
+ def test_status_error
31
+ status, result = subject.run_script('print :Err;exit 42', status: true)
32
+ assert_instance_of(Process::Status, status)
33
+ refute(status.success?)
34
+ assert_same(42, status.exitstatus)
35
+ assert_equal('Err', result)
36
+ end
37
+
38
+ def test_stream
39
+ stream = StringIO.new('puts "Hello World"')
40
+ result = subject.run_script(stream)
41
+ assert_equal("Hello World\n", result)
42
+ end
43
+ end
@@ -1,44 +1,49 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../helper'
2
4
 
3
5
  class RunTest < Test
4
- def setup
5
- super
6
- @pwd = Dir.pwd + "\n"
6
+ def test_simple
7
+ result = subject.run('pwd')
8
+ assert_equal("#{Dir.pwd}\n", result)
7
9
  end
8
10
 
9
- def test_std_out_only
10
- out = subject.run('pwd')
11
- assert_equal(@pwd, out)
11
+ def test_simple_error
12
+ result = subject.run('ls /no-valid-dir')
13
+ assert_match(/No such file or directory/, result)
14
+ end
12
15
 
13
- out = subject.run('pwd', stdout: true)
14
- assert_equal(@pwd, out)
16
+ def test_chdir
17
+ home = Dir.home
18
+ refute(home == Dir.pwd)
19
+ result = subject.run('pwd', chdir: home)
20
+ assert_equal("#{home}\n", result)
15
21
  end
16
22
 
17
23
  def test_status
18
- result = subject.run('pwd', stdout: false)
19
- assert_instance_of(TrueClass, result)
20
-
21
- status = subject.run('pwd', status: true)
22
- assert_instance_of(Process::Status, status)
23
-
24
- out, status = subject.run('pwd', stdout: true, status: true)
25
- assert_equal(@pwd, out)
24
+ status, result = subject.run('pwd', status: true)
26
25
  assert_instance_of(Process::Status, status)
27
26
  assert(status.success?)
27
+ assert_equal("#{Dir.pwd}\n", result)
28
28
  end
29
29
 
30
- def test_std_error
31
- err = subject.run('ls /no-valid-dir', stderr: true)
32
- assert_match(/No such file or directory/, err)
33
-
34
- out, err = subject.run('ls /no-valid-dir', stdout: true, stderr: true)
35
- assert_empty(out)
36
- assert_match(/No such file or directory/, err)
37
-
38
- err, status = subject.run('ls /no-valid-dir', stderr: true, status: true)
39
- assert_match(/No such file or directory/, err)
30
+ def test_status_error
31
+ status, result = subject.run('ls /no-valid-dir', status: true)
40
32
  assert_instance_of(Process::Status, status)
41
33
  refute(status.success?)
34
+ assert_match(/No such file or directory/, result)
35
+ end
36
+
37
+ def test_stdin
38
+ string = 'Hello World'
39
+ result = subject.run('cat', stdin_data: string)
40
+ assert_equal(string, result)
41
+ end
42
+
43
+ def test_stdin_stream
44
+ stream = StringIO.new('Hello World')
45
+ result = subject.run('cat', stdin_data: stream)
46
+ assert_equal(stream.string, result)
42
47
  end
43
48
 
44
49
  def test_failure
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.1.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-09 00:00:00.000000000 Z
11
+ date: 2021-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -56,10 +56,11 @@ description: |
56
56
  This gem is a lean, easy to use CLI framework with a very small footprint.
57
57
  It provides an easy to use argument parsing, help displaying and
58
58
  minimalistic error handling.
59
- email:
59
+ email:
60
60
  executables: []
61
61
  extensions: []
62
- extra_rdoc_files: []
62
+ extra_rdoc_files:
63
+ - README.md
63
64
  files:
64
65
  - ".gitignore"
65
66
  - README.md
@@ -67,20 +68,25 @@ files:
67
68
  - lib/mini-cli.rb
68
69
  - lib/mini-cli/run.rb
69
70
  - lib/mini-cli/version.rb
71
+ - lib/mini_cli.rb
70
72
  - mini-cli.gemspec
71
73
  - rakefile.rb
72
74
  - samples/custom_args.rb
73
75
  - samples/demo.rb
74
76
  - samples/simple.rb
77
+ - test/apps/noop.rb
78
+ - test/apps/sequence.rb
75
79
  - test/helper.rb
80
+ - test/mini-cli/exec_test.rb
76
81
  - test/mini-cli/main_test.rb
82
+ - test/mini-cli/ruby_run_test.rb
77
83
  - test/mini-cli/run_test.rb
78
84
  homepage: https://github.com/mblumtritt/mini-cli
79
85
  licenses: []
80
86
  metadata:
81
87
  source_code_uri: https://github.com/mblumtritt/mini-cli
82
88
  bug_tracker_uri: https://github.com/mblumtritt/mini-cli/issues
83
- post_install_message:
89
+ post_install_message:
84
90
  rdoc_options: []
85
91
  require_paths:
86
92
  - lib
@@ -95,11 +101,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
101
  - !ruby/object:Gem::Version
96
102
  version: '0'
97
103
  requirements: []
98
- rubygems_version: 3.1.2
99
- signing_key:
104
+ rubygems_version: 3.2.15
105
+ signing_key:
100
106
  specification_version: 4
101
107
  summary: The lean CLI framework for Ruby
102
108
  test_files:
109
+ - test/apps/noop.rb
110
+ - test/apps/sequence.rb
103
111
  - test/helper.rb
112
+ - test/mini-cli/exec_test.rb
104
113
  - test/mini-cli/main_test.rb
114
+ - test/mini-cli/ruby_run_test.rb
105
115
  - test/mini-cli/run_test.rb