mini-cli 0.2.0 → 0.3.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 +4 -4
- data/lib/mini-cli.rb +140 -97
- data/lib/mini-cli/version.rb +1 -1
- data/lib/mini_cli.rb +1 -0
- data/test/mini-cli/main_test.rb +22 -2
- data/test/mini-cli/run_test.rb +6 -4
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6954edd798ee19570124490d3c139443e54cf9db86da1cbf5e1ecf053f5b4de6
|
4
|
+
data.tar.gz: e9c458ab08eb813875df22053ce3c5fd99c651bbe24f60d787a6d0968936d204
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ad9884fa0f18600b69212a8f3b3c36108838d510879cde3d2a4525009439a3574c8eae1d7d7d4b567647fce04aa5cfaeee3fdceec8529381defc2533c1166559
|
7
|
+
data.tar.gz: 287198992331abcd2fee44165775cad3a6905c15eb1fac71bee380d706ba5f2067b3b2d3498f9a7bc4f07fe92c447e417c854313b247f0b81db304f0810a582b
|
data/lib/mini-cli.rb
CHANGED
@@ -1,36 +1,41 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module MiniCli
|
4
|
-
def self.included(
|
5
|
-
|
6
|
-
const_set(:
|
4
|
+
def self.included(mod)
|
5
|
+
source = caller_locations(1, 1).first.absolute_path
|
6
|
+
mod.const_set(:MiniCli__, Instance.new(source))
|
7
7
|
end
|
8
8
|
|
9
9
|
def name(name = nil)
|
10
|
-
|
11
|
-
@__name = name || File.basename(MiniCli::SRC, '.*')
|
10
|
+
__minicli__.name(name)
|
12
11
|
end
|
13
12
|
|
14
13
|
def help(helptext, *args)
|
15
|
-
|
14
|
+
__minicli__.help(helptext, args)
|
16
15
|
end
|
17
16
|
|
18
17
|
def show_help
|
19
|
-
|
20
|
-
true
|
18
|
+
__minicli__.show_help
|
21
19
|
end
|
22
20
|
|
23
|
-
def
|
24
|
-
|
21
|
+
def show_errors?
|
22
|
+
__minicli__.show_errors
|
23
|
+
end
|
24
|
+
|
25
|
+
def show_errors=(value)
|
26
|
+
__minicli__.show_errors = value ? true : false
|
27
|
+
end
|
28
|
+
|
29
|
+
def error(code, message = nil)
|
30
|
+
$stderr.puts("#{name}: #{message}") if message && show_errors?
|
25
31
|
exit(code)
|
26
32
|
end
|
27
33
|
|
28
34
|
def parse_argv(argv = nil, &argv_converter)
|
29
|
-
return
|
35
|
+
return __minicli__.converter = argv_converter if argv_converter
|
30
36
|
argv ||= ARGV.dup
|
31
37
|
exit(show_help) if argv.index('--help') || argv.index('-h')
|
32
|
-
|
33
|
-
defined?(@__argv_converter) ? @__argv_converter.call(args) || args : args
|
38
|
+
__minicli__.convert(__minicli__.parse(argv, method(:error).to_proc))
|
34
39
|
end
|
35
40
|
|
36
41
|
def main(args = nil)
|
@@ -43,121 +48,159 @@ module MiniCli
|
|
43
48
|
|
44
49
|
private
|
45
50
|
|
46
|
-
def
|
47
|
-
|
51
|
+
def __minicli__
|
52
|
+
self.class::MiniCli__
|
48
53
|
end
|
49
54
|
|
50
|
-
class
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
+
class Instance
|
56
|
+
attr_reader :source
|
57
|
+
attr_writer :converter
|
58
|
+
attr_accessor :show_errors
|
59
|
+
|
60
|
+
def initialize(source)
|
61
|
+
@source = source
|
62
|
+
@name = File.basename(source, '.*')
|
63
|
+
@parser = @converter = nil
|
64
|
+
@show_errors = true
|
55
65
|
end
|
56
66
|
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
67
|
+
def name(name = nil)
|
68
|
+
name ? @name = name.to_s : @name
|
69
|
+
end
|
70
|
+
|
71
|
+
def help(helptext, args)
|
72
|
+
@parser = ArgvParser.new(helptext, args)
|
73
|
+
end
|
74
|
+
|
75
|
+
def show_help
|
76
|
+
parser.show_help(@name)
|
77
|
+
true
|
65
78
|
end
|
66
79
|
|
67
80
|
def parse(argv, error)
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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)
|
81
|
+
parser.parse(argv, error)
|
82
|
+
end
|
83
|
+
|
84
|
+
def convert(args)
|
85
|
+
@converter ? @converter.call(args) || args : args
|
87
86
|
end
|
88
87
|
|
89
88
|
private
|
90
89
|
|
91
|
-
def
|
92
|
-
@
|
90
|
+
def parser
|
91
|
+
@parser ||= ArgvParser.new(nil, [])
|
93
92
|
end
|
94
93
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
94
|
+
class ArgvParser
|
95
|
+
def initialize(helptext, args)
|
96
|
+
@helptext = helptext.to_s
|
97
|
+
@args = args.flatten.map!(&:to_s).uniq
|
98
|
+
@options = nil
|
99
|
+
end
|
100
|
+
|
101
|
+
def show_help(name)
|
102
|
+
parse_help! unless @options
|
103
|
+
print("Usage: #{name}")
|
104
|
+
print(' [OPTIONS]') unless @options.empty?
|
105
|
+
print(' ', @args.join(' ')) unless @args.empty?
|
106
|
+
puts
|
107
|
+
puts(nil, 'Options:') unless @options.empty?
|
108
|
+
puts(@helptext) unless @helptext.empty?
|
109
|
+
end
|
110
|
+
|
111
|
+
def parse(argv, error)
|
112
|
+
@error, @result = error, {}
|
113
|
+
parse_help! unless @options
|
114
|
+
process(parse_argv(argv))
|
115
|
+
end
|
99
116
|
|
117
|
+
private
|
118
|
+
|
119
|
+
def parse_argv(argv)
|
120
|
+
arguments = []
|
121
|
+
while (arg = argv.shift)
|
122
|
+
case arg
|
123
|
+
when '--'
|
124
|
+
arguments += argv
|
125
|
+
break
|
126
|
+
when /\A--([[[:alnum:]]-]+)\z/
|
127
|
+
handle_option(Regexp.last_match[1], argv)
|
128
|
+
when /\A-([[:alnum:]]+)\z/
|
129
|
+
parse_options(Regexp.last_match[1], argv)
|
130
|
+
else
|
131
|
+
arguments << arg
|
132
|
+
end
|
133
|
+
end
|
134
|
+
arguments
|
135
|
+
end
|
136
|
+
|
137
|
+
def error(msg)
|
138
|
+
@error[1, msg]
|
139
|
+
end
|
140
|
+
|
141
|
+
def process(arguments)
|
142
|
+
@args.each do |arg|
|
143
|
+
process_arg(arg, arguments.shift) unless arg.index('..')
|
144
|
+
end
|
145
|
+
arguments.unshift(@result['FILES']) if @result.key?('FILES')
|
146
|
+
@result['FILES'] = arguments
|
147
|
+
@result
|
148
|
+
end
|
149
|
+
|
150
|
+
def process_arg(arg, value)
|
100
151
|
if arg.start_with?('[')
|
101
152
|
@result[arg[1..-2]] = value if value
|
102
153
|
else
|
103
154
|
@result[arg] = value || error("parameter expected - #{arg}")
|
104
155
|
end
|
105
156
|
end
|
106
|
-
arguments.unshift(@result['FILES']) if @result.key?('FILES')
|
107
|
-
@result['FILES'] = arguments
|
108
|
-
@result
|
109
|
-
end
|
110
157
|
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
158
|
+
def handle_option(option, argv, test = ->(k) { option == k })
|
159
|
+
key = @options[option] || error("unknown option - #{option}")
|
160
|
+
@result[key] = test[key] and return
|
123
161
|
@result[key] = value = argv.shift
|
124
|
-
|
125
|
-
error("parameter #{key} expected -
|
162
|
+
return unless value.nil? || value.start_with?('-')
|
163
|
+
error("parameter #{key} expected - --#{option}")
|
126
164
|
end
|
127
|
-
end
|
128
165
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
166
|
+
def parse_options(options, argv)
|
167
|
+
test = ->(k) { k == k.downcase }
|
168
|
+
options.each_char { |opt| handle_option(opt, argv, test) }
|
169
|
+
end
|
170
|
+
|
171
|
+
def parse_help!
|
172
|
+
@options = {}
|
173
|
+
@helptext.each_line do |line|
|
174
|
+
case line
|
175
|
+
when /-([[:alnum:]]), --([[[:alnum:]]-]+) ([[:upper:]]+)\s+\S+/
|
176
|
+
option_with_argument(Regexp.last_match)
|
177
|
+
when /--([[[:alnum:]]-]+) ([[:upper:]]+)\s+\S+/
|
178
|
+
short_option_with_argument(Regexp.last_match)
|
179
|
+
when /-([[:alnum:]]), --([[[:alnum:]]-]+)\s+\S+/
|
180
|
+
option(Regexp.last_match)
|
181
|
+
when /--([[[:alnum:]]-]+)\s+\S+/
|
182
|
+
short_option(Regexp.last_match)
|
183
|
+
end
|
141
184
|
end
|
142
185
|
end
|
143
|
-
end
|
144
186
|
|
145
|
-
|
146
|
-
|
147
|
-
|
187
|
+
def option_with_argument(match)
|
188
|
+
@options[match[1]] = @options[match[2]] = match[3]
|
189
|
+
end
|
148
190
|
|
149
|
-
|
150
|
-
|
151
|
-
|
191
|
+
def short_option_with_argument(match)
|
192
|
+
@options[match[1]] = match[2]
|
193
|
+
end
|
152
194
|
|
153
|
-
|
154
|
-
|
155
|
-
|
195
|
+
def option(match)
|
196
|
+
@options[match[1]] = @options[match[2]] = match[2]
|
197
|
+
end
|
156
198
|
|
157
|
-
|
158
|
-
|
199
|
+
def short_option(match)
|
200
|
+
@options[match[1]] = match[1]
|
201
|
+
end
|
159
202
|
end
|
160
203
|
end
|
161
204
|
|
162
|
-
private_constant(:
|
205
|
+
private_constant(:Instance)
|
163
206
|
end
|
data/lib/mini-cli/version.rb
CHANGED
data/lib/mini_cli.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'mini-cli'
|
data/test/mini-cli/main_test.rb
CHANGED
@@ -1,16 +1,29 @@
|
|
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[
|
13
|
-
|
15
|
+
expected_methods = %i[
|
16
|
+
error
|
17
|
+
help
|
18
|
+
main
|
19
|
+
name
|
20
|
+
parse_argv
|
21
|
+
run
|
22
|
+
show_errors=
|
23
|
+
show_errors?
|
24
|
+
show_help
|
25
|
+
]
|
26
|
+
methods = (subject.methods - Object.instance_methods).sort!
|
14
27
|
assert_equal(expected_methods, methods)
|
15
28
|
end
|
16
29
|
|
@@ -22,6 +35,13 @@ class MainTest < Test
|
|
22
35
|
assert_stop_with_error(21, 'error') { subject.error(21, :error) }
|
23
36
|
end
|
24
37
|
|
38
|
+
def test_no_error
|
39
|
+
subject.show_errors = false
|
40
|
+
|
41
|
+
assert_output(nil, nil) { subject.error(666, 'some error text') }
|
42
|
+
assert_equal([666], subject.exit_args)
|
43
|
+
end
|
44
|
+
|
25
45
|
def test_help_simple
|
26
46
|
subject.help 'Some helptext'
|
27
47
|
expected_text = <<~EXPECTED
|
data/test/mini-cli/run_test.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative '../helper'
|
2
4
|
|
3
5
|
class RunTest < Test
|
4
6
|
def test_simple
|
5
7
|
result = subject.run('pwd')
|
6
|
-
assert_equal(Dir.pwd
|
8
|
+
assert_equal("#{Dir.pwd}\n", result)
|
7
9
|
end
|
8
10
|
|
9
11
|
def test_simple_error
|
@@ -15,14 +17,14 @@ class RunTest < Test
|
|
15
17
|
home = Dir.home
|
16
18
|
refute(home == Dir.pwd)
|
17
19
|
result = subject.run('pwd', chdir: home)
|
18
|
-
assert_equal(home
|
20
|
+
assert_equal("#{home}\n", result)
|
19
21
|
end
|
20
22
|
|
21
23
|
def test_status
|
22
24
|
status, result = subject.run('pwd', status: true)
|
23
25
|
assert_instance_of(Process::Status, status)
|
24
26
|
assert(status.success?)
|
25
|
-
assert_equal(Dir.pwd
|
27
|
+
assert_equal("#{Dir.pwd}\n", result)
|
26
28
|
end
|
27
29
|
|
28
30
|
def test_status_error
|
@@ -39,7 +41,7 @@ class RunTest < Test
|
|
39
41
|
end
|
40
42
|
|
41
43
|
def test_stdin_stream
|
42
|
-
stream = StringIO.new(
|
44
|
+
stream = StringIO.new('Hello World')
|
43
45
|
result = subject.run('cat', stdin_data: stream)
|
44
46
|
assert_equal(stream.string, result)
|
45
47
|
end
|
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.
|
4
|
+
version: 0.3.0
|
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-
|
11
|
+
date: 2020-11-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -68,6 +68,7 @@ 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
|