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