mini-cli 0.1.2 → 0.5.1

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