mini-cli 0.1.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: 3f71b96747e645a7e4ab1131c3e52840f63f22af4035d06b98f2d21172716d9b
4
- data.tar.gz: a8ec28b1326a313f16ec8986f85a56b60f1b8c9832583324b79fd6a55c6a6228
3
+ metadata.gz: 969a4421172e3cf6a3b6c6ecc294932e825a170de914ac5f19926fcc4e3da674
4
+ data.tar.gz: '039fc6a1d177834a3999468142a71a985ef417d179011cbba6b7b57777cb05c8'
5
5
  SHA512:
6
- metadata.gz: 72575dc73beedd924a2080b8cb436dcb11b78d9897e4f9eb87836dff6af028845d077c069540b06a5b7b93c6a2719c2de3a53a4e19996476a8c5871332fe97bb
7
- data.tar.gz: 6848f5981ef7ce5bb4545a015b309f549450796a5d6530f4fd0f56fd3c315e7347da2afa318048facdb077db0eeee21676cd305b5cd17a863cb2037f5323cf32
6
+ metadata.gz: e8fc8a932a4e508327570bb3c4e062950d78b19cbaf87594456e2f5d340a22bcbe2db32bbffd55c76e7e57e2fdb1ca63118d2f20a67976bb60fb3795e9cf372a
7
+ data.tar.gz: 3a2d8fa7af6ec17ae701ab0a6505ce03084646540c3cab5a54facb76c5434ad5b29acdbcb9aea35d19e7daa60bdabdb79cf143a8f8a22a2d6af0b6c7d4aeb98e
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Mini Cli
2
2
 
3
- This gem is a minimalistic, easy to use CLI framework with a very small footprint. I provides an easy to use argument parsing, help displaying, minimalistic error handling and some tools like executing external programs and gather their output.
3
+ This gem is a lean, easy to use CLI framework with a very small footprint. It provides an easy to use argument parsing, help displaying, minimalistic error handling and some tools like executing external programs and gather their output.
4
4
 
5
5
  ## Installation
6
6
 
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,32 @@
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
17
  private
14
18
 
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
20
- end
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
27
- end
28
-
29
- def __run_opt(opts)
30
- %i[stdout stderr status].keep_if{ |s| opts.delete(s) }
19
+ def __run_proc(stdin_data, in_write)
20
+ return :read unless stdin_data
21
+ proc do |out|
22
+ in_write.sync = true
23
+ if stdin_data.respond_to?(:readpartial)
24
+ IO.copy_stream(stdin_data, in_write)
25
+ else
26
+ in_write.write(stdin_data)
27
+ end
28
+ in_write.close
29
+ out.read
30
+ end
31
31
  end
32
32
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniCli
4
- VERSION = '0.1.0'
4
+ VERSION = '0.4.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 minimalistic CLI framework for Ruby'
9
- gem.description = <<~DESC
10
- This gem is a minimalistic, easy to use CLI framework with a very small
11
- footprint. I provides an easy to use argument parsing, help displaying and
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
12
+ This gem is a lean, easy to use CLI framework with a very small footprint.
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 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
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,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
@@ -1,24 +1,32 @@
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
+ show_errors=
26
+ show_errors?
19
27
  show_help
20
- ].sort!
21
- methods = (subject.methods - Object.new.methods).sort!
28
+ ]
29
+ methods = (subject.methods - Object.instance_methods).sort!
22
30
  assert_equal(expected_methods, methods)
23
31
  end
24
32
 
@@ -27,9 +35,14 @@ class MainTest < Test
27
35
  subject.error(42, 'some error text')
28
36
  end
29
37
 
30
- assert_stop_with_error(21, 'error') do
31
- subject.error(21, :error)
32
- end
38
+ assert_stop_with_error(21, 'error') { subject.error(21, :error) }
39
+ end
40
+
41
+ def test_no_error
42
+ subject.show_errors = false
43
+
44
+ assert_output(nil, nil) { subject.error(666, 'some error text') }
45
+ assert_equal([666], subject.exit_args)
33
46
  end
34
47
 
35
48
  def test_help_simple
@@ -39,7 +52,7 @@ class MainTest < Test
39
52
  Some helptext
40
53
  EXPECTED
41
54
 
42
- assert_output(expected_text){ subject.show_help }
55
+ assert_output(expected_text) { subject.show_help }
43
56
  end
44
57
 
45
58
  def test_help_with_args
@@ -54,7 +67,7 @@ class MainTest < Test
54
67
  here
55
68
  EXPECTED
56
69
 
57
- assert_output(expected_text){ subject.show_help }
70
+ assert_output(expected_text) { subject.show_help }
58
71
  end
59
72
 
60
73
  def test_argument_required
@@ -64,10 +77,10 @@ class MainTest < Test
64
77
  subject.parse_argv(as_argv(''))
65
78
  end
66
79
 
67
- expected = {'ARG' => 'arg', 'FILES' => []}
80
+ expected = { 'ARG' => 'arg', 'FILES' => [] }
68
81
  assert_equal(expected, subject.parse_argv(as_argv('arg')))
69
82
 
70
- expected = {'ARG' => 'arg1', 'FILES' => %w[arg2 arg3]}
83
+ expected = { 'ARG' => 'arg1', 'FILES' => %w[arg2 arg3] }
71
84
  assert_equal(expected, subject.parse_argv(as_argv('arg1 arg2 arg3')))
72
85
  end
73
86
 
@@ -83,10 +96,10 @@ class MainTest < Test
83
96
  subject.parse_argv(as_argv(''))
84
97
  end
85
98
 
86
- expected = {'ARG1' => 'arg1', 'ARG2' => 'arg2', 'FILES' => []}
99
+ expected = { 'ARG1' => 'arg1', 'ARG2' => 'arg2', 'FILES' => [] }
87
100
  assert_equal(expected, subject.parse_argv(as_argv('arg1 arg2')))
88
101
 
89
- expected = {'ARG1' => 'arg', 'FILES' => []}
102
+ expected = { 'ARG1' => 'arg', 'FILES' => [] }
90
103
  assert_equal(expected, subject.parse_argv(as_argv('arg')))
91
104
  end
92
105
 
@@ -100,21 +113,21 @@ class MainTest < Test
100
113
  Some additional explaination can be here
101
114
  HELP
102
115
 
103
- expected = {'FILES' => []}
116
+ expected = { 'FILES' => [] }
104
117
  assert_equal(expected, subject.parse_argv(as_argv('')))
105
118
 
106
- expected = {'NAME' => 'name', 'FILES' => []}
119
+ expected = { 'NAME' => 'name', 'FILES' => [] }
107
120
  assert_equal(expected, subject.parse_argv(as_argv('--named name')))
108
121
  assert_equal(expected, subject.parse_argv(as_argv('-n name')))
109
122
 
110
- expected = {'LNAME' => 'long', 'FILES' => []}
123
+ expected = { 'LNAME' => 'long', 'FILES' => [] }
111
124
  assert_equal(expected, subject.parse_argv(as_argv('--named-long long')))
112
125
 
113
- expected = {'unnamed' => true, 'FILES' => []}
126
+ expected = { 'unnamed' => true, 'FILES' => [] }
114
127
  assert_equal(expected, subject.parse_argv(as_argv('--unnamed')))
115
128
  assert_equal(expected, subject.parse_argv(as_argv('-u')))
116
129
 
117
- expected = {'un-named' => true, 'FILES' => []}
130
+ expected = { 'un-named' => true, 'FILES' => [] }
118
131
  assert_equal(expected, subject.parse_argv(as_argv('--un-named')))
119
132
 
120
133
  expected = {
@@ -125,23 +138,14 @@ class MainTest < Test
125
138
  'FILES' => %w[FILE1 FILE2]
126
139
  }
127
140
 
128
- result = subject.parse_argv(%w[
129
- --named name
130
- --named-long long
131
- --unnamed
132
- --un-named
133
- FILE1
134
- FILE2
135
- ])
141
+ result =
142
+ subject.parse_argv(
143
+ %w[--named name --named-long long --unnamed --un-named FILE1 FILE2]
144
+ )
136
145
  assert_equal(expected, result)
137
146
 
138
- result = subject.parse_argv(%w[
139
- -nu name
140
- --named-long long
141
- --un-named
142
- FILE1
143
- FILE2
144
- ])
147
+ result =
148
+ subject.parse_argv(%w[-nu name --named-long long --un-named FILE1 FILE2])
145
149
  assert_equal(expected, result)
146
150
  end
147
151
 
@@ -175,4 +179,13 @@ class MainTest < Test
175
179
  result = subject.parse_argv(as_argv('-nup name port in out opt file'))
176
180
  assert_equal(expected, result)
177
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
178
191
  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.0
4
+ version: 0.4.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-08 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
@@ -53,13 +53,14 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  description: |
56
- This gem is a minimalistic, easy to use CLI framework with a very small
57
- footprint. I provides an easy to use argument parsing, help displaying and
56
+ This gem is a lean, easy to use CLI framework with a very small footprint.
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,12 +68,16 @@ 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
78
+ - test/apps/sequence
75
79
  - test/helper.rb
80
+ - test/mini-cli/exec_test.rb
76
81
  - test/mini-cli/main_test.rb
77
82
  - test/mini-cli/run_test.rb
78
83
  homepage: https://github.com/mblumtritt/mini-cli
@@ -80,7 +85,7 @@ licenses: []
80
85
  metadata:
81
86
  source_code_uri: https://github.com/mblumtritt/mini-cli
82
87
  bug_tracker_uri: https://github.com/mblumtritt/mini-cli/issues
83
- post_install_message:
88
+ post_install_message:
84
89
  rdoc_options: []
85
90
  require_paths:
86
91
  - lib
@@ -95,11 +100,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
100
  - !ruby/object:Gem::Version
96
101
  version: '0'
97
102
  requirements: []
98
- rubygems_version: 3.1.2
99
- signing_key:
103
+ rubygems_version: 3.2.9
104
+ signing_key:
100
105
  specification_version: 4
101
- summary: The minimalistic CLI framework for Ruby
106
+ summary: The lean CLI framework for Ruby
102
107
  test_files:
108
+ - test/apps/noop
109
+ - test/apps/sequence
103
110
  - test/helper.rb
111
+ - test/mini-cli/exec_test.rb
104
112
  - test/mini-cli/main_test.rb
105
113
  - test/mini-cli/run_test.rb