mini-cli 0.1.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 +7 -0
- data/.gitignore +4 -0
- data/README.md +64 -0
- data/gems.rb +1 -0
- data/lib/mini-cli.rb +162 -0
- data/lib/mini-cli/run.rb +32 -0
- data/lib/mini-cli/version.rb +5 -0
- data/mini-cli.gemspec +31 -0
- data/rakefile.rb +18 -0
- data/samples/custom_args.rb +31 -0
- data/samples/demo.rb +22 -0
- data/samples/simple.rb +14 -0
- data/test/helper.rb +32 -0
- data/test/mini-cli/main_test.rb +178 -0
- data/test/mini-cli/run_test.rb +48 -0
- metadata +105 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3f71b96747e645a7e4ab1131c3e52840f63f22af4035d06b98f2d21172716d9b
|
4
|
+
data.tar.gz: a8ec28b1326a313f16ec8986f85a56b60f1b8c9832583324b79fd6a55c6a6228
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 72575dc73beedd924a2080b8cb436dcb11b78d9897e4f9eb87836dff6af028845d077c069540b06a5b7b93c6a2719c2de3a53a4e19996476a8c5871332fe97bb
|
7
|
+
data.tar.gz: 6848f5981ef7ce5bb4545a015b309f549450796a5d6530f4fd0f56fd3c315e7347da2afa318048facdb077db0eeee21676cd305b5cd17a863cb2037f5323cf32
|
data/.gitignore
ADDED
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# Mini Cli
|
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.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
To use Mini CLI just install the gem with
|
8
|
+
|
9
|
+
```shell
|
10
|
+
gem install mini-cli
|
11
|
+
```
|
12
|
+
|
13
|
+
or include it to you project's gemspec:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'mini-cli'
|
17
|
+
```
|
18
|
+
|
19
|
+
## Sample
|
20
|
+
|
21
|
+
A very minimalistic program may look like this sample program:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
require 'mini-cli'
|
25
|
+
|
26
|
+
include MiniCli
|
27
|
+
|
28
|
+
help <<~HELP, %w[TARGET [SOURCE]]
|
29
|
+
-n, --name NAME option requires NAME argument, has shortcut
|
30
|
+
--url URL option requires URL argument
|
31
|
+
-s, --switch option without any argument, has shortcut
|
32
|
+
--opt option without any argument
|
33
|
+
|
34
|
+
This is a sample application only.
|
35
|
+
HELP
|
36
|
+
|
37
|
+
main do |args|
|
38
|
+
puts "TARGET: #{args['TARGET']}"
|
39
|
+
puts "SOURCE: #{args['SOURCE']}" if args.key?('SOURCE')
|
40
|
+
puts "NAME: #{args['NAME']}" if args.key?('NAME')
|
41
|
+
puts "URL: #{args['URL']}" if args.key?('URL')
|
42
|
+
puts "FILES: #{args['FILES']}" unless args['FILES'].empty?
|
43
|
+
puts '--switch was given' if args.key?('switch')
|
44
|
+
puts '--opt was given' if args.key?('opt')
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
The sample uses the powerful `#help` method to generate an argument parser which handles the command line for you. You only need to handle the given `Hash` parameter (named `args` in the sample) in the body of your `#main` block.
|
49
|
+
|
50
|
+
Executing the sample with `--help` or `-h` will provide following help screen:
|
51
|
+
|
52
|
+
```
|
53
|
+
Usage: sample [OPTIONS] TARGET [SOURCE]
|
54
|
+
|
55
|
+
Valid Options:
|
56
|
+
-n, --name NAME option requires NAME argument, has shortcut
|
57
|
+
--url URL option requires URL argument
|
58
|
+
-s, --switch option without any argument, has shortcut
|
59
|
+
--opt option without any argument
|
60
|
+
|
61
|
+
This is a sample application only.
|
62
|
+
```
|
63
|
+
|
64
|
+
See the `./samples` directory for more sample programs…
|
data/gems.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
gemspec
|
data/lib/mini-cli.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MiniCli
|
4
|
+
def self.included(_)
|
5
|
+
return if const_defined?(:SRC)
|
6
|
+
const_set(:SRC, caller_locations(1, 1).first.absolute_path)
|
7
|
+
end
|
8
|
+
|
9
|
+
def name(name = nil)
|
10
|
+
return name ? @__name = name : @__name if defined?(@__name)
|
11
|
+
@__name = name || File.basename(MiniCli::SRC, '.*')
|
12
|
+
end
|
13
|
+
|
14
|
+
def help(helptext, *args)
|
15
|
+
@__argv_parser = ArgvParser.new(helptext, args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def show_help
|
19
|
+
__argv_parser.show_help(name)
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def error(code, message)
|
24
|
+
$stderr.puts("#{name}: #{message}")
|
25
|
+
exit(code)
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_argv(argv = nil, &block)
|
29
|
+
return @__argv_converter = block if block
|
30
|
+
argv ||= ARGV.dup
|
31
|
+
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
|
34
|
+
end
|
35
|
+
|
36
|
+
def main(args = nil)
|
37
|
+
at_exit do
|
38
|
+
yield(args || parse_argv)
|
39
|
+
rescue Interrupt
|
40
|
+
error(130, 'aborted')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def __argv_parser
|
47
|
+
@__argv_parser ||= ArgvParser.new(nil, [])
|
48
|
+
end
|
49
|
+
|
50
|
+
class ArgvParser
|
51
|
+
def initialize(helptext, args)
|
52
|
+
@helptext = helptext.to_s
|
53
|
+
@args = args.flatten.map!(&:to_s).uniq
|
54
|
+
@options = nil
|
55
|
+
end
|
56
|
+
|
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?
|
65
|
+
end
|
66
|
+
|
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)
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def error(msg)
|
92
|
+
@error.call(1, msg)
|
93
|
+
end
|
94
|
+
|
95
|
+
def process(arguments)
|
96
|
+
@args.each do |arg|
|
97
|
+
next if arg.index('..')
|
98
|
+
value = arguments.shift
|
99
|
+
if arg.start_with?('[')
|
100
|
+
@result[arg[1..-2]] = value if value
|
101
|
+
else
|
102
|
+
@result[arg] = value || error("parameter expected - #{arg}")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
arguments.unshift(@result['FILES']) if @result.key?('FILES')
|
106
|
+
@result['FILES'] = arguments
|
107
|
+
@result
|
108
|
+
end
|
109
|
+
|
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
|
122
|
+
@result[key] = value = argv.shift
|
123
|
+
next unless value.nil? || value.start_with?('-')
|
124
|
+
error("parameter #{key} expected - -#{opt}")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
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)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def named_option(match)
|
145
|
+
@options[match[1]] = @options[match[2]] = match[3]
|
146
|
+
end
|
147
|
+
|
148
|
+
def named_option_short(match)
|
149
|
+
@options[match[1]] = match[2]
|
150
|
+
end
|
151
|
+
|
152
|
+
def option(match)
|
153
|
+
@options[match[1]] = @options[match[2]] = match[2]
|
154
|
+
end
|
155
|
+
|
156
|
+
def option_short(match)
|
157
|
+
@options[match[1]] = match[1]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
private_constant(:ArgvParser)
|
162
|
+
end
|
data/lib/mini-cli/run.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
|
5
|
+
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
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
|
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
|
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) }
|
31
|
+
end
|
32
|
+
end
|
data/mini-cli.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './lib/mini-cli/version'
|
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
|
12
|
+
minimalistic error handling.
|
13
|
+
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
|
+
|
21
|
+
gem.required_ruby_version = '>= 2.5.0'
|
22
|
+
|
23
|
+
gem.add_development_dependency 'bundler'
|
24
|
+
gem.add_development_dependency 'minitest'
|
25
|
+
gem.add_development_dependency 'rake'
|
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]
|
31
|
+
end
|
data/rakefile.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'bundler/gem_tasks'
|
5
|
+
|
6
|
+
STDOUT.sync = STDERR.sync = true
|
7
|
+
|
8
|
+
CLOBBER << 'prj'
|
9
|
+
|
10
|
+
task :default do
|
11
|
+
exec "#{$PROGRAM_NAME} --tasks"
|
12
|
+
end
|
13
|
+
|
14
|
+
Rake::TestTask.new(:test) do |t|
|
15
|
+
t.ruby_opts = %w[-w]
|
16
|
+
t.verbose = true
|
17
|
+
t.test_files = FileList['test/**/*_test.rb']
|
18
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../lib/mini-cli'
|
4
|
+
|
5
|
+
include MiniCli
|
6
|
+
|
7
|
+
help <<~HELP, %w[TARGET [SOURCE]]
|
8
|
+
-n, --name NAME option requires NAME argument, has shortcut
|
9
|
+
--url URL option requires URL argument
|
10
|
+
-s, --switch option without any argument, has shortcut
|
11
|
+
--opt option without any argument
|
12
|
+
HELP
|
13
|
+
|
14
|
+
main do |cfg|
|
15
|
+
cfg.each_pair{ |key, value| puts("#{key}: #{value}") }
|
16
|
+
end
|
17
|
+
|
18
|
+
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
|
31
|
+
end
|
data/samples/demo.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../lib/mini-cli'
|
4
|
+
|
5
|
+
include MiniCli
|
6
|
+
|
7
|
+
help <<~HELP, %w[TARGET [SOURCE]]
|
8
|
+
-n, --name NAME option requires NAME argument, has shortcut
|
9
|
+
--url URL option requires URL argument
|
10
|
+
-s, --switch option without any argument, has shortcut
|
11
|
+
--opt option without any argument
|
12
|
+
HELP
|
13
|
+
|
14
|
+
main do |args|
|
15
|
+
puts("TARGET: #{args['TARGET']}")
|
16
|
+
puts("SOURCE: #{args['SOURCE']}") if args.key?('SOURCE')
|
17
|
+
puts("NAME: #{args['NAME']}") if args.key?('NAME')
|
18
|
+
puts("URL: #{args['URL']}") if args.key?('URL')
|
19
|
+
puts("FILES: #{args['FILES']}") unless args['FILES'].empty?
|
20
|
+
puts('--switch was given') if args.key?('switch')
|
21
|
+
puts('--opt was given') if args.key?('opt')
|
22
|
+
end
|
data/samples/simple.rb
ADDED
data/test/helper.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'minitest/parallel'
|
5
|
+
|
6
|
+
require_relative '../lib/mini-cli'
|
7
|
+
require_relative '../lib/mini-cli/run'
|
8
|
+
|
9
|
+
class Test < Minitest::Test
|
10
|
+
parallelize_me!
|
11
|
+
attr_reader :subject
|
12
|
+
|
13
|
+
def setup
|
14
|
+
@subject = Class.new do
|
15
|
+
include MiniCli
|
16
|
+
attr_reader :exit_args
|
17
|
+
|
18
|
+
def exit(*args)
|
19
|
+
@exit_args = args
|
20
|
+
end
|
21
|
+
end.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def assert_stop_with_error(code, text, &block)
|
25
|
+
assert_output(nil, "helper: #{text}\n", &block)
|
26
|
+
assert_equal([code], subject.exit_args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def as_argv(str)
|
30
|
+
str.split(' ')
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require_relative '../helper'
|
2
|
+
|
3
|
+
class MainTest < Test
|
4
|
+
def test_defaults
|
5
|
+
assert_equal('helper', subject.name)
|
6
|
+
assert_output("Usage: helper\n"){ subject.show_help }
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_methods
|
10
|
+
subject = Class.new{ include MiniCli }.new
|
11
|
+
|
12
|
+
expected_methods = %i[
|
13
|
+
error
|
14
|
+
help
|
15
|
+
main
|
16
|
+
name
|
17
|
+
parse_argv
|
18
|
+
run
|
19
|
+
show_help
|
20
|
+
].sort!
|
21
|
+
methods = (subject.methods - Object.new.methods).sort!
|
22
|
+
assert_equal(expected_methods, methods)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_error
|
26
|
+
assert_stop_with_error(42, 'some error text') do
|
27
|
+
subject.error(42, 'some error text')
|
28
|
+
end
|
29
|
+
|
30
|
+
assert_stop_with_error(21, 'error') do
|
31
|
+
subject.error(21, :error)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_help_simple
|
36
|
+
subject.help 'Some helptext'
|
37
|
+
expected_text = <<~EXPECTED
|
38
|
+
Usage: helper
|
39
|
+
Some helptext
|
40
|
+
EXPECTED
|
41
|
+
|
42
|
+
assert_output(expected_text){ subject.show_help }
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_help_with_args
|
46
|
+
subject.help <<~HELP, 'ARG1', :ARG2
|
47
|
+
Some helptext comes
|
48
|
+
here
|
49
|
+
HELP
|
50
|
+
|
51
|
+
expected_text = <<~EXPECTED
|
52
|
+
Usage: helper ARG1 ARG2
|
53
|
+
Some helptext comes
|
54
|
+
here
|
55
|
+
EXPECTED
|
56
|
+
|
57
|
+
assert_output(expected_text){ subject.show_help }
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_argument_required
|
61
|
+
subject.help :text, :ARG
|
62
|
+
|
63
|
+
assert_stop_with_error(1, 'parameter expected - ARG') do
|
64
|
+
subject.parse_argv(as_argv(''))
|
65
|
+
end
|
66
|
+
|
67
|
+
expected = {'ARG' => 'arg', 'FILES' => []}
|
68
|
+
assert_equal(expected, subject.parse_argv(as_argv('arg')))
|
69
|
+
|
70
|
+
expected = {'ARG' => 'arg1', 'FILES' => %w[arg2 arg3]}
|
71
|
+
assert_equal(expected, subject.parse_argv(as_argv('arg1 arg2 arg3')))
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_name
|
75
|
+
subject.name 'test-42'
|
76
|
+
assert_equal('test-42', subject.name)
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_argument_optional
|
80
|
+
subject.help :text, 'ARG1', '[ARG2]'
|
81
|
+
|
82
|
+
assert_stop_with_error(1, 'parameter expected - ARG1') do
|
83
|
+
subject.parse_argv(as_argv(''))
|
84
|
+
end
|
85
|
+
|
86
|
+
expected = {'ARG1' => 'arg1', 'ARG2' => 'arg2', 'FILES' => []}
|
87
|
+
assert_equal(expected, subject.parse_argv(as_argv('arg1 arg2')))
|
88
|
+
|
89
|
+
expected = {'ARG1' => 'arg', 'FILES' => []}
|
90
|
+
assert_equal(expected, subject.parse_argv(as_argv('arg')))
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_options
|
94
|
+
subject.help <<~HELP
|
95
|
+
-n, --named NAME option NAME with shortcut
|
96
|
+
--named-long LNAME option LNAME without shortcut
|
97
|
+
-u, --unnamed option unnamed with shortcut
|
98
|
+
--un-named option un-named without shortcut
|
99
|
+
|
100
|
+
Some additional explaination can be here
|
101
|
+
HELP
|
102
|
+
|
103
|
+
expected = {'FILES' => []}
|
104
|
+
assert_equal(expected, subject.parse_argv(as_argv('')))
|
105
|
+
|
106
|
+
expected = {'NAME' => 'name', 'FILES' => []}
|
107
|
+
assert_equal(expected, subject.parse_argv(as_argv('--named name')))
|
108
|
+
assert_equal(expected, subject.parse_argv(as_argv('-n name')))
|
109
|
+
|
110
|
+
expected = {'LNAME' => 'long', 'FILES' => []}
|
111
|
+
assert_equal(expected, subject.parse_argv(as_argv('--named-long long')))
|
112
|
+
|
113
|
+
expected = {'unnamed' => true, 'FILES' => []}
|
114
|
+
assert_equal(expected, subject.parse_argv(as_argv('--unnamed')))
|
115
|
+
assert_equal(expected, subject.parse_argv(as_argv('-u')))
|
116
|
+
|
117
|
+
expected = {'un-named' => true, 'FILES' => []}
|
118
|
+
assert_equal(expected, subject.parse_argv(as_argv('--un-named')))
|
119
|
+
|
120
|
+
expected = {
|
121
|
+
'NAME' => 'name',
|
122
|
+
'LNAME' => 'long',
|
123
|
+
'unnamed' => true,
|
124
|
+
'un-named' => true,
|
125
|
+
'FILES' => %w[FILE1 FILE2]
|
126
|
+
}
|
127
|
+
|
128
|
+
result = subject.parse_argv(%w[
|
129
|
+
--named name
|
130
|
+
--named-long long
|
131
|
+
--unnamed
|
132
|
+
--un-named
|
133
|
+
FILE1
|
134
|
+
FILE2
|
135
|
+
])
|
136
|
+
assert_equal(expected, result)
|
137
|
+
|
138
|
+
result = subject.parse_argv(%w[
|
139
|
+
-nu name
|
140
|
+
--named-long long
|
141
|
+
--un-named
|
142
|
+
FILE1
|
143
|
+
FILE2
|
144
|
+
])
|
145
|
+
assert_equal(expected, result)
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_complex_options
|
149
|
+
subject.help <<~HELP, %w[INFILE OUTFILE [OPTFILE]]
|
150
|
+
-n, --named NAME key 'NAME'
|
151
|
+
-p, --port PORT key 'PORT'
|
152
|
+
-u, --un-named key 'unnamed'
|
153
|
+
HELP
|
154
|
+
|
155
|
+
expected = {
|
156
|
+
'INFILE' => 'in',
|
157
|
+
'OUTFILE' => 'out',
|
158
|
+
'NAME' => 'name',
|
159
|
+
'PORT' => 'port',
|
160
|
+
'un-named' => true,
|
161
|
+
'FILES' => []
|
162
|
+
}
|
163
|
+
result = subject.parse_argv(as_argv('-nup name port in out'))
|
164
|
+
assert_equal(expected, result)
|
165
|
+
|
166
|
+
expected = {
|
167
|
+
'INFILE' => 'in',
|
168
|
+
'OUTFILE' => 'out',
|
169
|
+
'OPTFILE' => 'opt',
|
170
|
+
'NAME' => 'name',
|
171
|
+
'PORT' => 'port',
|
172
|
+
'un-named' => true,
|
173
|
+
'FILES' => %w[file]
|
174
|
+
}
|
175
|
+
result = subject.parse_argv(as_argv('-nup name port in out opt file'))
|
176
|
+
assert_equal(expected, result)
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative '../helper'
|
2
|
+
|
3
|
+
class RunTest < Test
|
4
|
+
def setup
|
5
|
+
super
|
6
|
+
@pwd = Dir.pwd + "\n"
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_std_out_only
|
10
|
+
out = subject.run('pwd')
|
11
|
+
assert_equal(@pwd, out)
|
12
|
+
|
13
|
+
out = subject.run('pwd', stdout: true)
|
14
|
+
assert_equal(@pwd, out)
|
15
|
+
end
|
16
|
+
|
17
|
+
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)
|
26
|
+
assert_instance_of(Process::Status, status)
|
27
|
+
assert(status.success?)
|
28
|
+
end
|
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)
|
40
|
+
assert_instance_of(Process::Status, status)
|
41
|
+
refute(status.success?)
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_failure
|
45
|
+
result = subject.run('this-is-not-a-valid-command')
|
46
|
+
assert_instance_of(NilClass, result)
|
47
|
+
end
|
48
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mini-cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mike Blumtritt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-03-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
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
|
58
|
+
minimalistic error handling.
|
59
|
+
email:
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- ".gitignore"
|
65
|
+
- README.md
|
66
|
+
- gems.rb
|
67
|
+
- lib/mini-cli.rb
|
68
|
+
- lib/mini-cli/run.rb
|
69
|
+
- lib/mini-cli/version.rb
|
70
|
+
- mini-cli.gemspec
|
71
|
+
- rakefile.rb
|
72
|
+
- samples/custom_args.rb
|
73
|
+
- samples/demo.rb
|
74
|
+
- samples/simple.rb
|
75
|
+
- test/helper.rb
|
76
|
+
- test/mini-cli/main_test.rb
|
77
|
+
- test/mini-cli/run_test.rb
|
78
|
+
homepage: https://github.com/mblumtritt/mini-cli
|
79
|
+
licenses: []
|
80
|
+
metadata:
|
81
|
+
source_code_uri: https://github.com/mblumtritt/mini-cli
|
82
|
+
bug_tracker_uri: https://github.com/mblumtritt/mini-cli/issues
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: 2.5.0
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubygems_version: 3.1.2
|
99
|
+
signing_key:
|
100
|
+
specification_version: 4
|
101
|
+
summary: The minimalistic CLI framework for Ruby
|
102
|
+
test_files:
|
103
|
+
- test/helper.rb
|
104
|
+
- test/mini-cli/main_test.rb
|
105
|
+
- test/mini-cli/run_test.rb
|