mini-cli 0.1.2 → 0.5.1

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: 1e6c0c096fa96ac3c5687b159637ae5762c1dfa9d18201b0253476bfb96050ca
4
- data.tar.gz: 21eb69f322117c7eb8cc2c810292b1b38a3a3f268356cdd9c5cd2f2d0d876a9b
3
+ metadata.gz: 299da5a35fccbc561453ee2dc9584f7aec510b1fe9dd34e8c7ae3221b6253dbb
4
+ data.tar.gz: 242f37afb8733342cafd7694d73de4247f2887a027cb4f33da210eff1dc81380
5
5
  SHA512:
6
- metadata.gz: 5c975c9bff22c0841ac32ac6340d74ed908babb282f5791a7ed0951ad7f59fd0f4e9bcb3226a83f4dd1548ccfe340c2071072a94f4ac27e8937cae234fa584ac
7
- data.tar.gz: c8544fba204f56ad6e2359d37ffe9e76ce9997ad34653270d1241d7c82d1153d8826e260f2f40d4840065dc70bd7133139d5cb835fbcee7394696245e825e512
6
+ metadata.gz: da79647fb91708e556151c22296aa0febe509c9502bbbe327e6fb10cfd3cc22b851f402cede01e25d1df6b9e42382200cc7d34c28f8dab7fc1fac9984dda6d47
7
+ data.tar.gz: bb7bfb271ea6cbb2e30e4a0833746a30869e484ddc4e7d13c6f1c5f44acdca10d29081f0b52fe9c045685974d6ef6c188ce0e6147cd57f331deb020d74abeacb
data/lib/mini-cli.rb CHANGED
@@ -1,163 +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
99
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)
100
188
  if arg.start_with?('[')
101
189
  @result[arg[1..-2]] = value if value
102
190
  else
103
191
  @result[arg] = value || error("parameter expected - #{arg}")
104
192
  end
105
193
  end
106
- arguments.unshift(@result['FILES']) if @result.key?('FILES')
107
- @result['FILES'] = arguments
108
- @result
109
- end
110
194
 
111
- def handle_option(option, argv)
112
- key = @options[option] || error("unknown option - #{option}")
113
- return @result[key] = true if option == key
114
- @result[key] = value = argv.shift
115
- return unless value.nil? || value.start_with?('-')
116
- error("parameter #{key} expected - --#{option}")
117
- end
118
-
119
- def parse_options(options, argv)
120
- options.each_char do |opt|
121
- key = @options[opt] || error("unknown option - #{opt}")
122
- 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
123
198
  @result[key] = value = argv.shift
124
- next unless value.nil? || value.start_with?('-')
125
- error("parameter #{key} expected - -#{opt}")
199
+ return unless value.nil? || value.start_with?('-')
200
+ error("parameter #{key} expected - --#{option}")
126
201
  end
127
- end
128
202
 
129
- def parse_help!
130
- @options = {}
131
- @helptext.each_line do |line|
132
- case line
133
- when /-([[:alnum:]]), --([[[:alnum:]]-]+) ([[:upper:]]+)\s+\S+/
134
- named_option(Regexp.last_match)
135
- when /--([[[:alnum:]]-]+) ([[:upper:]]+)\s+\S+/
136
- named_option_short(Regexp.last_match)
137
- when /-([[:alnum:]]), --([[[:alnum:]]-]+)\s+\S+/
138
- option(Regexp.last_match)
139
- when /--([[[:alnum:]]-]+)\s+\S+/
140
- 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
141
221
  end
142
222
  end
143
- end
144
223
 
145
- def named_option(match)
146
- @options[match[1]] = @options[match[2]] = match[3]
147
- end
224
+ def option_with_argument(match)
225
+ @options[match[1]] = @options[match[2]] = match[3]
226
+ end
148
227
 
149
- def named_option_short(match)
150
- @options[match[1]] = match[2]
151
- end
228
+ def short_option_with_argument(match)
229
+ @options[match[1]] = match[2]
230
+ end
152
231
 
153
- def option(match)
154
- @options[match[1]] = @options[match[2]] = match[2]
155
- end
232
+ def option(match)
233
+ @options[match[1]] = @options[match[2]] = match[2]
234
+ end
156
235
 
157
- def option_short(match)
158
- @options[match[1]] = match[1]
236
+ def short_option(match)
237
+ @options[match[1]] = match[1]
238
+ end
159
239
  end
160
240
  end
161
241
 
162
- private_constant(:ArgvParser)
242
+ private_constant(:Instance)
163
243
  end
data/lib/mini-cli/run.rb CHANGED
@@ -1,32 +1,48 @@
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) : %i[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
+ def __run_proc(stdin_data, in_write)
29
+ return :read unless stdin_data
30
+ proc do |out|
31
+ in_write.sync = true
32
+ if stdin_data.respond_to?(:readpartial)
33
+ IO.copy_stream(stdin_data, in_write)
34
+ else
35
+ in_write.write(stdin_data)
36
+ end
37
+ in_write.close
38
+ out.read
39
+ end
31
40
  end
41
+
42
+ RUBY_CMD_ =
43
+ %w[--disable gems --disable did_you_mean --disable rubyopt].unshift(
44
+ RbConfig.ruby
45
+ ).freeze
46
+
47
+ private_constant(:RUBY_CMD_)
32
48
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniCli
4
- VERSION = '0.1.2'
4
+ VERSION = '0.5.1'
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]
@@ -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.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
@@ -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,16 +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)
8
+ assert(subject.show_errors?)
6
9
  assert_output("Usage: helper\n") { subject.show_help }
7
10
  end
8
11
 
9
12
  def test_methods
10
13
  subject = Class.new { include MiniCli }.new
11
14
 
12
- expected_methods = %i[error help main name parse_argv run show_help].sort!
13
- methods = (subject.methods - Object.new.methods).sort!
15
+ expected_methods = %i[
16
+ after
17
+ before
18
+ error
19
+ error_code
20
+ help
21
+ main
22
+ name
23
+ parse_argv
24
+ run
25
+ run_ruby
26
+ run_script
27
+ show_errors=
28
+ show_errors?
29
+ show_help
30
+ ]
31
+ methods = (subject.methods - Object.instance_methods).sort!
14
32
  assert_equal(expected_methods, methods)
15
33
  end
16
34
 
@@ -22,6 +40,13 @@ class MainTest < Test
22
40
  assert_stop_with_error(21, 'error') { subject.error(21, :error) }
23
41
  end
24
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)
48
+ end
49
+
25
50
  def test_help_simple
26
51
  subject.help 'Some helptext'
27
52
  expected_text = <<~EXPECTED
@@ -156,4 +181,13 @@ class MainTest < Test
156
181
  result = subject.parse_argv(as_argv('-nup name port in out opt file'))
157
182
  assert_equal(expected, result)
158
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
159
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.2
4
+ version: 0.5.1
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-06-19 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
@@ -68,13 +68,18 @@ files:
68
68
  - lib/mini-cli.rb
69
69
  - lib/mini-cli/run.rb
70
70
  - lib/mini-cli/version.rb
71
+ - lib/mini_cli.rb
71
72
  - mini-cli.gemspec
72
73
  - rakefile.rb
73
74
  - samples/custom_args.rb
74
75
  - samples/demo.rb
75
76
  - samples/simple.rb
77
+ - test/apps/noop.rb
78
+ - test/apps/sequence.rb
76
79
  - test/helper.rb
80
+ - test/mini-cli/exec_test.rb
77
81
  - test/mini-cli/main_test.rb
82
+ - test/mini-cli/ruby_run_test.rb
78
83
  - test/mini-cli/run_test.rb
79
84
  homepage: https://github.com/mblumtritt/mini-cli
80
85
  licenses: []
@@ -96,11 +101,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
96
101
  - !ruby/object:Gem::Version
97
102
  version: '0'
98
103
  requirements: []
99
- rubygems_version: 3.1.4
104
+ rubygems_version: 3.2.15
100
105
  signing_key:
101
106
  specification_version: 4
102
107
  summary: The lean CLI framework for Ruby
103
108
  test_files:
109
+ - test/apps/noop.rb
110
+ - test/apps/sequence.rb
104
111
  - test/helper.rb
112
+ - test/mini-cli/exec_test.rb
105
113
  - test/mini-cli/main_test.rb
114
+ - test/mini-cli/ruby_run_test.rb
106
115
  - test/mini-cli/run_test.rb