consoler 1.0.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/lib/consoler/application.rb +164 -0
- data/lib/consoler/arguments.rb +26 -0
- data/lib/consoler/command.rb +29 -0
- data/lib/consoler/matcher.rb +259 -0
- data/lib/consoler/option.rb +181 -0
- data/lib/consoler/options.rb +117 -0
- data/lib/consoler/version.rb +7 -0
- data/lib/consoler.rb +12 -0
- metadata +80 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a3db645fb1a61eee615bf593a5a078320c098081
|
4
|
+
data.tar.gz: 675713207b19a2b6685be32447a79783738bb78d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9e021691c53d383d0a9b778116269d58b3b1357abd7dc51bb9e23150498f1046a62b4e0da21cd64a4e2466cc383d86f3ee5db3b3de571b8b2e94b3aca16cc69b
|
7
|
+
data.tar.gz: 410a8cc86d20818a59908a0de6f7e2561e7f8716820b1a198599c2304abae5fe99206d979af336f4527db6141035eef1f9f9b35c5d74d187aa9a81859d987c59
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'options'
|
4
|
+
require_relative 'arguments'
|
5
|
+
|
6
|
+
module Consoler
|
7
|
+
|
8
|
+
# Consoler application
|
9
|
+
#
|
10
|
+
# @example A simple application
|
11
|
+
# # create a application
|
12
|
+
# app = Consoler::Application.new description: 'A simple app'
|
13
|
+
#
|
14
|
+
# # define a command
|
15
|
+
# app.build 'target [--clean]' do |target, clean|
|
16
|
+
# # clean contains a boolean
|
17
|
+
# clean_up if clean
|
18
|
+
#
|
19
|
+
# # target contains a string
|
20
|
+
# build_project target
|
21
|
+
# end
|
22
|
+
# app.run(['build', 'production', '--clean'])
|
23
|
+
#
|
24
|
+
# # this does not match, nothing is executed and the usage message is printed
|
25
|
+
# app.run(['deploy', 'production'])
|
26
|
+
class Application
|
27
|
+
|
28
|
+
# Create a consoler application
|
29
|
+
#
|
30
|
+
# @param options [Hash] Options for the application
|
31
|
+
# @option options [String] :description The description for the application (optional)
|
32
|
+
def initialize(options={})
|
33
|
+
@description = options[:description]
|
34
|
+
@commands = []
|
35
|
+
end
|
36
|
+
|
37
|
+
# Register a command for this app
|
38
|
+
#
|
39
|
+
# @param command_name [Symbol] Name of the command
|
40
|
+
# @param input [String, Consoler::Application] Options definition or a complete subapp
|
41
|
+
# @yield [...] Executed when the action is matched with parameters based on your options
|
42
|
+
# @return [nil]
|
43
|
+
def method_missing(command_name, input = nil, &block)
|
44
|
+
action = nil
|
45
|
+
options_def = ''
|
46
|
+
|
47
|
+
unless block.nil? then
|
48
|
+
action = block
|
49
|
+
options_def = input
|
50
|
+
|
51
|
+
if not options_def.nil? and not options_def.instance_of? String then
|
52
|
+
raise 'Invalid options'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
if input.instance_of? Consoler::Application then
|
57
|
+
action = input
|
58
|
+
options_def = ''
|
59
|
+
end
|
60
|
+
|
61
|
+
if action.nil? then
|
62
|
+
raise 'Invalid subapp/block'
|
63
|
+
end
|
64
|
+
|
65
|
+
command = command_name.to_s
|
66
|
+
|
67
|
+
_add_command(command, options_def, action)
|
68
|
+
|
69
|
+
return nil
|
70
|
+
end
|
71
|
+
|
72
|
+
# Run the application with a list of arguments
|
73
|
+
#
|
74
|
+
# @param args [Array] Arguments
|
75
|
+
# @param disable_usage_message [Boolean] Disable the usage message when nothing it matched
|
76
|
+
# @return [mixed] Result of your matched command, <tt>nil</tt> otherwise
|
77
|
+
def run(args = ARGV, disable_usage_message = false)
|
78
|
+
# TODO signal handling of some kind?
|
79
|
+
|
80
|
+
result, matched = _run(args)
|
81
|
+
|
82
|
+
if not matched and not disable_usage_message
|
83
|
+
usage
|
84
|
+
end
|
85
|
+
|
86
|
+
return result
|
87
|
+
end
|
88
|
+
|
89
|
+
# Show the usage message
|
90
|
+
#
|
91
|
+
# Contains all commands and options, including subapps
|
92
|
+
def usage
|
93
|
+
puts "#{@description}\n\n" unless @description.nil?
|
94
|
+
puts 'Usage:'
|
95
|
+
|
96
|
+
_commands_usage $0
|
97
|
+
end
|
98
|
+
|
99
|
+
protected
|
100
|
+
|
101
|
+
def _run(args)
|
102
|
+
arg = args.shift
|
103
|
+
arguments = Consoler::Arguments.new args
|
104
|
+
|
105
|
+
@commands.each do |command|
|
106
|
+
if command.command == arg then
|
107
|
+
if command.action.instance_of? Consoler::Application then
|
108
|
+
result, matched = command.action._run(args)
|
109
|
+
|
110
|
+
if matched then
|
111
|
+
return result, true
|
112
|
+
end
|
113
|
+
else
|
114
|
+
match = arguments.match command.options
|
115
|
+
|
116
|
+
next if match.nil?
|
117
|
+
|
118
|
+
return _dispatch(command.action, match), true
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
return nil, false
|
124
|
+
end
|
125
|
+
|
126
|
+
def _commands_usage(prefix='')
|
127
|
+
@commands.each do |command|
|
128
|
+
if command.action.instance_of? Consoler::Application then
|
129
|
+
command.action._commands_usage "#{prefix} #{command.command}"
|
130
|
+
else
|
131
|
+
print " #{prefix} #{command.command}"
|
132
|
+
|
133
|
+
if command.options.size then
|
134
|
+
print " #{command.options.to_definition}"
|
135
|
+
end
|
136
|
+
|
137
|
+
unless command.options.description.nil? then
|
138
|
+
print " -- #{command.options.description}"
|
139
|
+
end
|
140
|
+
|
141
|
+
print "\n"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def _add_command(command, options_def, action)
|
149
|
+
@commands.push(Consoler::Command.new(
|
150
|
+
command: command,
|
151
|
+
options: Consoler::Options.new(options_def),
|
152
|
+
action: action,
|
153
|
+
))
|
154
|
+
end
|
155
|
+
|
156
|
+
def _dispatch(action, match)
|
157
|
+
arguments = action.parameters.map do |parameter|
|
158
|
+
match[parameter[1].to_s]
|
159
|
+
end
|
160
|
+
|
161
|
+
action.call(*arguments)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'matcher'
|
4
|
+
|
5
|
+
module Consoler
|
6
|
+
|
7
|
+
# Arguments
|
8
|
+
#
|
9
|
+
# @attr_reader [Array<String>] args Raw arguments
|
10
|
+
class Arguments
|
11
|
+
attr_reader :args
|
12
|
+
|
13
|
+
def initialize(args)
|
14
|
+
@args = args
|
15
|
+
end
|
16
|
+
|
17
|
+
# Match arguments against options
|
18
|
+
#
|
19
|
+
# @see Consoler::Matcher#match
|
20
|
+
# @return [Hash, nil] Matched information, or <tt>nil</tt> is returned when there was no match
|
21
|
+
def match(options)
|
22
|
+
matcher = Consoler::Matcher.new self, options
|
23
|
+
matcher.match
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Consoler
|
4
|
+
|
5
|
+
# Consoler command
|
6
|
+
#
|
7
|
+
# Basically a named hash
|
8
|
+
#
|
9
|
+
# @attr_reader [String] command Name of the command
|
10
|
+
# @attr_reader [Consoler::Options] options List of all options
|
11
|
+
# @attr_reader [Proc] action Action for this command
|
12
|
+
class Command
|
13
|
+
attr_reader :command
|
14
|
+
attr_reader :options
|
15
|
+
attr_reader :action
|
16
|
+
|
17
|
+
# Create a command
|
18
|
+
#
|
19
|
+
# @param [Hash] options
|
20
|
+
# @option options [String] :command Name of the command
|
21
|
+
# @option options [Consoler::Options] :options List of all options
|
22
|
+
# @option options [Proc] :action Action for this command
|
23
|
+
def initialize(options)
|
24
|
+
@command = options[:command]
|
25
|
+
@options = options[:options]
|
26
|
+
@action = options[:action]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,259 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Consoler
|
4
|
+
|
5
|
+
# Argument/Options matcher
|
6
|
+
#
|
7
|
+
# Given a list of arguments and a list option try to match them
|
8
|
+
class Matcher
|
9
|
+
|
10
|
+
# Create a matcher
|
11
|
+
#
|
12
|
+
# @param [Consoler::Arguments] arguments List of arguments
|
13
|
+
# @param [Consoler::Options] options List of options
|
14
|
+
def initialize(arguments, options)
|
15
|
+
@arguments = arguments
|
16
|
+
@options = options
|
17
|
+
|
18
|
+
@index = 0
|
19
|
+
@matched_options = {}
|
20
|
+
@argument_values = []
|
21
|
+
end
|
22
|
+
|
23
|
+
# Match arguments against options
|
24
|
+
#
|
25
|
+
# @return [Hash, nil] Matched information, or <tt>nil</tt> is returned when there was no match
|
26
|
+
def match
|
27
|
+
parse_options = true
|
28
|
+
|
29
|
+
loop_args do |arg|
|
30
|
+
unless parse_options then
|
31
|
+
@argument_values.push arg
|
32
|
+
next
|
33
|
+
end
|
34
|
+
|
35
|
+
if arg == '--' then
|
36
|
+
parse_options = false
|
37
|
+
next
|
38
|
+
end
|
39
|
+
|
40
|
+
analyzed = _analyze arg
|
41
|
+
|
42
|
+
if analyzed.nil?
|
43
|
+
return nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
remaining = _match_arguments
|
48
|
+
_fill_defaults
|
49
|
+
|
50
|
+
if @matched_options.size == @options.size then
|
51
|
+
@matched_options['remaining'] = remaining
|
52
|
+
return @matched_options
|
53
|
+
end
|
54
|
+
|
55
|
+
return nil
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def _analyze(arg)
|
61
|
+
is_long = false
|
62
|
+
is_short = false
|
63
|
+
name = nil
|
64
|
+
|
65
|
+
if arg[0..1] == '--' then
|
66
|
+
is_long = true
|
67
|
+
name = arg[2..-1]
|
68
|
+
elsif arg[0] == '-' then
|
69
|
+
is_short = true
|
70
|
+
name = arg[1..-1]
|
71
|
+
end
|
72
|
+
|
73
|
+
if name.nil?
|
74
|
+
@argument_values.push arg
|
75
|
+
return true
|
76
|
+
end
|
77
|
+
|
78
|
+
unless name.nil? then
|
79
|
+
option_name = if is_short then
|
80
|
+
name[0]
|
81
|
+
else
|
82
|
+
name
|
83
|
+
end
|
84
|
+
|
85
|
+
option = @options.get option_name
|
86
|
+
|
87
|
+
return nil if option.nil?
|
88
|
+
|
89
|
+
needs_short = option.is_short
|
90
|
+
needs_long = option.is_long
|
91
|
+
|
92
|
+
if needs_long and not is_long then
|
93
|
+
return nil
|
94
|
+
elsif needs_short and not is_short then
|
95
|
+
return nil
|
96
|
+
end
|
97
|
+
|
98
|
+
if is_long then
|
99
|
+
if option.is_value then
|
100
|
+
return nil if peek_next.nil?
|
101
|
+
@matched_options[name] = peek_next
|
102
|
+
skip
|
103
|
+
else
|
104
|
+
@matched_options[name] = true
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
if is_short then
|
109
|
+
if name.size == 1 and option.is_value then
|
110
|
+
return nil if peek_next.nil?
|
111
|
+
@matched_options[name] = peek_next
|
112
|
+
skip
|
113
|
+
else
|
114
|
+
name.split('').each do |n|
|
115
|
+
if @matched_options[n].nil? then
|
116
|
+
@matched_options[n] = 0
|
117
|
+
end
|
118
|
+
|
119
|
+
@matched_options[n] += 1
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
return true
|
126
|
+
end
|
127
|
+
|
128
|
+
def current
|
129
|
+
@arguments.args[@index]
|
130
|
+
end
|
131
|
+
|
132
|
+
def peek_next
|
133
|
+
@arguments.args[@index + 1]
|
134
|
+
end
|
135
|
+
|
136
|
+
def loop_args
|
137
|
+
@index = 0
|
138
|
+
size = @arguments.args.size
|
139
|
+
|
140
|
+
while @index < size do
|
141
|
+
yield current
|
142
|
+
|
143
|
+
skip
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def skip
|
148
|
+
@index += 1
|
149
|
+
end
|
150
|
+
|
151
|
+
def _match_arguments
|
152
|
+
@optionals_before = {}
|
153
|
+
@optionals_before_has_remaining = false
|
154
|
+
|
155
|
+
argument_values_index = 0
|
156
|
+
|
157
|
+
_match_arguments_optionals_before
|
158
|
+
|
159
|
+
@optionals_before.each do |mandatory_arg_name, optionals|
|
160
|
+
optionals.each do |_, optional|
|
161
|
+
optional.each do |before|
|
162
|
+
if before[:included] then
|
163
|
+
@matched_options[before[:name]] = @argument_values[argument_values_index]
|
164
|
+
argument_values_index += 1
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
if mandatory_arg_name != :REMAINING then
|
170
|
+
@matched_options[mandatory_arg_name] = @argument_values[argument_values_index]
|
171
|
+
argument_values_index += 1
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
remaining = []
|
176
|
+
|
177
|
+
while argument_values_index < @argument_values.size do
|
178
|
+
remaining.push @argument_values[argument_values_index]
|
179
|
+
argument_values_index += 1
|
180
|
+
end
|
181
|
+
|
182
|
+
remaining
|
183
|
+
end
|
184
|
+
|
185
|
+
def _match_arguments_optionals_before
|
186
|
+
@optionals_before = {}
|
187
|
+
tracker = {}
|
188
|
+
|
189
|
+
@options.each do |option, key|
|
190
|
+
next unless option.is_argument
|
191
|
+
|
192
|
+
if option.is_optional then
|
193
|
+
tracker[option.is_optional] = [] if tracker[option.is_optional].nil?
|
194
|
+
|
195
|
+
tracker[option.is_optional].push({
|
196
|
+
included: false,
|
197
|
+
name: option.name,
|
198
|
+
})
|
199
|
+
else
|
200
|
+
@optionals_before[option.name] = tracker
|
201
|
+
tracker = {}
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
if tracker != {} then
|
206
|
+
@optionals_before[:REMAINING] = tracker
|
207
|
+
@optionals_before_has_remaining = true
|
208
|
+
end
|
209
|
+
|
210
|
+
_match_arguments_optoins_before_matcher
|
211
|
+
end
|
212
|
+
|
213
|
+
def _match_arguments_optoins_before_matcher
|
214
|
+
mandatories_matched = @optionals_before.size
|
215
|
+
|
216
|
+
if @optionals_before_has_remaining then
|
217
|
+
mandatories_matched -= 1
|
218
|
+
end
|
219
|
+
|
220
|
+
total = 0
|
221
|
+
|
222
|
+
_each_optional_before_sorted do |before|
|
223
|
+
if (total + before.size + mandatories_matched) <= @argument_values.size then
|
224
|
+
total += before.size
|
225
|
+
|
226
|
+
before.each do |val|
|
227
|
+
val[:included] = true;
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def _fill_defaults
|
234
|
+
@options.each do |option|
|
235
|
+
if option.is_optional then
|
236
|
+
unless @matched_options.has_key? option.name then
|
237
|
+
@matched_options[option.name] = option.default_value
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def _each_optional_before_sorted
|
244
|
+
@optionals_before.each do |_, optionals|
|
245
|
+
tmp = []
|
246
|
+
optionals.each do |optional_index, before|
|
247
|
+
tmp.push({
|
248
|
+
count: before.size,
|
249
|
+
index: optional_index,
|
250
|
+
})
|
251
|
+
end
|
252
|
+
|
253
|
+
tmp.sort! { |a, b| b[:count] - a[:count] }.each do |item|
|
254
|
+
yield optionals[item[:index]]
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Consoler
|
4
|
+
|
5
|
+
# Represents an option
|
6
|
+
#
|
7
|
+
# @attr_reader [String] name Name of the options
|
8
|
+
# @attr_reader [Boolean] is_long Is the option long (<tt>--option</tt>)
|
9
|
+
# @attr_reader [Boolean] is_short Is the option short (<tt>-o</tt>)
|
10
|
+
# @attr_reader [Boolean] is_argument Is the option an argument
|
11
|
+
# @attr_reader [Boolean] is_value Does the option need a value (<tt>--option=</tt>)
|
12
|
+
# @attr_reader [Integer] is_optional Is the option optional (> 0) (<tt>[option]</tt>)
|
13
|
+
class Option
|
14
|
+
attr_reader :name
|
15
|
+
attr_reader :is_long
|
16
|
+
attr_reader :is_short
|
17
|
+
attr_reader :is_argument
|
18
|
+
attr_reader :is_value
|
19
|
+
attr_reader :is_optional
|
20
|
+
|
21
|
+
# Create a option
|
22
|
+
#
|
23
|
+
# Yields an option for every option detected
|
24
|
+
#
|
25
|
+
# @param option_def [String] Definition of the option
|
26
|
+
# @param tracker [Consoler::OptionalsTracker] optionals tracker
|
27
|
+
def self.create(option_def, tracker)
|
28
|
+
option = Option.new option_def, tracker
|
29
|
+
|
30
|
+
if option.is_short and option.name.size > 1 then
|
31
|
+
old_tracking = tracker.is_tracking
|
32
|
+
old_is_value = option.is_value
|
33
|
+
|
34
|
+
if option.is_optional then
|
35
|
+
tracker.is_tracking = true
|
36
|
+
end
|
37
|
+
|
38
|
+
names = option.name.split('')
|
39
|
+
|
40
|
+
names.each_with_index do |name, i|
|
41
|
+
new_name = "-#{name}"
|
42
|
+
|
43
|
+
if old_is_value and i == names.count - 1 then
|
44
|
+
new_name = "#{new_name}="
|
45
|
+
end
|
46
|
+
|
47
|
+
yield Option.new new_name, tracker
|
48
|
+
end
|
49
|
+
|
50
|
+
tracker.is_tracking = old_tracking
|
51
|
+
else
|
52
|
+
yield option
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Get the definition of the option
|
57
|
+
#
|
58
|
+
# Does not include the optional information, as that is linked to other
|
59
|
+
# options
|
60
|
+
#
|
61
|
+
# @return [String]
|
62
|
+
def to_definition
|
63
|
+
definition = name
|
64
|
+
|
65
|
+
if is_long then
|
66
|
+
definition = "--#{definition}"
|
67
|
+
elsif is_short then
|
68
|
+
definition = "-#{definition}"
|
69
|
+
end
|
70
|
+
|
71
|
+
if is_value then
|
72
|
+
definition = "#{definition}="
|
73
|
+
end
|
74
|
+
|
75
|
+
definition
|
76
|
+
end
|
77
|
+
|
78
|
+
# Get the default value of this option
|
79
|
+
#
|
80
|
+
# @return [nil | 0 | false]
|
81
|
+
def default_value
|
82
|
+
return nil if is_value
|
83
|
+
return 0 if is_short
|
84
|
+
return false if is_long
|
85
|
+
|
86
|
+
return nil
|
87
|
+
end
|
88
|
+
|
89
|
+
protected
|
90
|
+
|
91
|
+
# Create a option
|
92
|
+
#
|
93
|
+
# @param [String] option_def Definition of the option
|
94
|
+
# @param [Consoler::Tracker] tracker tracker
|
95
|
+
def initialize(option_def, tracker)
|
96
|
+
option, @is_optional = _is_optional option_def, tracker
|
97
|
+
option, @is_long = _is_long option
|
98
|
+
option, @is_short = _is_short option
|
99
|
+
@is_argument = (not @is_long and not @is_short)
|
100
|
+
option, @is_value = _value option, @is_argument
|
101
|
+
|
102
|
+
@name = option
|
103
|
+
|
104
|
+
if @name.empty? then
|
105
|
+
raise 'Option must have a name'
|
106
|
+
end
|
107
|
+
|
108
|
+
if @is_long and @is_short
|
109
|
+
raise 'Option can not be a long and a short option'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def _is_optional(option, tracker)
|
116
|
+
if option[0] == '[' then
|
117
|
+
if !tracker.is_tracking then
|
118
|
+
tracker.is_tracking = true
|
119
|
+
tracker.index += 1
|
120
|
+
option = option[1..-1]
|
121
|
+
else
|
122
|
+
raise 'Nested optionals are not allowed'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
optional = if tracker.is_tracking then
|
127
|
+
tracker.index
|
128
|
+
else
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
|
132
|
+
if option[-1] == ']' then
|
133
|
+
if tracker.is_tracking then
|
134
|
+
tracker.is_tracking = false
|
135
|
+
option = option[0..-2]
|
136
|
+
else
|
137
|
+
raise 'Unopened optional'
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
return option, optional
|
142
|
+
end
|
143
|
+
|
144
|
+
def _is_long(option)
|
145
|
+
if option[0..1] == '--' then
|
146
|
+
long = true
|
147
|
+
option = option[2..-1]
|
148
|
+
else
|
149
|
+
long = false
|
150
|
+
end
|
151
|
+
|
152
|
+
return option, long
|
153
|
+
end
|
154
|
+
|
155
|
+
def _is_short(option)
|
156
|
+
if option[0] == '-' then
|
157
|
+
short = true
|
158
|
+
option = option[1..-1]
|
159
|
+
else
|
160
|
+
short = false
|
161
|
+
end
|
162
|
+
|
163
|
+
return option, short
|
164
|
+
end
|
165
|
+
|
166
|
+
def _value(option, argument)
|
167
|
+
if option[-1] == '=' then
|
168
|
+
if argument then
|
169
|
+
raise 'Arguments can\'t have a value'
|
170
|
+
end
|
171
|
+
|
172
|
+
value = true
|
173
|
+
option = option[0..-2]
|
174
|
+
else
|
175
|
+
value = false
|
176
|
+
end
|
177
|
+
|
178
|
+
return option, value
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'option'
|
4
|
+
|
5
|
+
module Consoler
|
6
|
+
|
7
|
+
# List of options
|
8
|
+
#
|
9
|
+
# @attr_reader [String] description Description of the options
|
10
|
+
class Options
|
11
|
+
attr_reader :description
|
12
|
+
|
13
|
+
# Create a list of option based on a string definition
|
14
|
+
#
|
15
|
+
# @param options_def [String] A string definition of the desired options
|
16
|
+
def initialize(options_def)
|
17
|
+
@options = []
|
18
|
+
@description = nil
|
19
|
+
|
20
|
+
return if options_def.nil?
|
21
|
+
|
22
|
+
if match = /(^|\s+)-- (?<description>.*)$/.match(options_def) then
|
23
|
+
@description = match[:description]
|
24
|
+
options_def = options_def[0...-match[0].size]
|
25
|
+
end
|
26
|
+
|
27
|
+
options = options_def.split ' '
|
28
|
+
tracker = Consoler::OptionalsTracker.new
|
29
|
+
|
30
|
+
option_names = []
|
31
|
+
|
32
|
+
while option_def = options.shift do
|
33
|
+
Consoler::Option.create option_def, tracker do |option|
|
34
|
+
raise "Duplicate option name: #{option.name}" if option_names.include? option.name
|
35
|
+
|
36
|
+
@options.push option
|
37
|
+
option_names.push option.name
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get a options by its name
|
43
|
+
#
|
44
|
+
# @param name [String] Name of the option
|
45
|
+
# @return [Consoler::Option, nil]
|
46
|
+
def get(name)
|
47
|
+
each do |option|
|
48
|
+
if option.name == name then
|
49
|
+
return option
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
return nil
|
54
|
+
end
|
55
|
+
|
56
|
+
# Loop through all options
|
57
|
+
#
|
58
|
+
# @yield [Consoler::Option, Integer] An option
|
59
|
+
# @return [Consoler::Options]
|
60
|
+
def each
|
61
|
+
@options.each_with_index do |option, i|
|
62
|
+
yield option, i
|
63
|
+
end
|
64
|
+
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get the number of options
|
69
|
+
#
|
70
|
+
# @return [Integer]
|
71
|
+
def size
|
72
|
+
@options.size
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_definition
|
76
|
+
definition = ''
|
77
|
+
optional = nil
|
78
|
+
|
79
|
+
each do |option, i|
|
80
|
+
definition += ' '
|
81
|
+
|
82
|
+
if optional.nil? and option.is_optional then
|
83
|
+
definition += '['
|
84
|
+
optional = option.is_optional
|
85
|
+
end
|
86
|
+
|
87
|
+
definition += option.to_definition
|
88
|
+
|
89
|
+
if option.is_optional then
|
90
|
+
if @options[i + 1].nil? or optional != @options[i + 1].is_optional then
|
91
|
+
definition += ']'
|
92
|
+
optional = nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
definition.strip
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
# Optionals tracker
|
104
|
+
#
|
105
|
+
# @attr [Boolean] is_tracking Is inside optional options
|
106
|
+
# @attr [Integer] index Optional group
|
107
|
+
class OptionalsTracker
|
108
|
+
attr_accessor :is_tracking
|
109
|
+
attr_accessor :index
|
110
|
+
|
111
|
+
# Create an optionals tracker
|
112
|
+
def initialize
|
113
|
+
@is_tracking = nil
|
114
|
+
@index = 0
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/lib/consoler.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'consoler/application'
|
4
|
+
require_relative 'consoler/command'
|
5
|
+
|
6
|
+
# Consoler
|
7
|
+
#
|
8
|
+
# Sinatra-like application builder for the console
|
9
|
+
#
|
10
|
+
# See {Consoler::Application} for usage information
|
11
|
+
module Consoler
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: consoler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tim
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-03-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: yard
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.9.12
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.9.12
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: simplecov
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.15.1
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.15.1
|
41
|
+
description: Sinatra-like application builder for the console
|
42
|
+
email: me@justim.net
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- lib/consoler.rb
|
48
|
+
- lib/consoler/application.rb
|
49
|
+
- lib/consoler/arguments.rb
|
50
|
+
- lib/consoler/command.rb
|
51
|
+
- lib/consoler/matcher.rb
|
52
|
+
- lib/consoler/option.rb
|
53
|
+
- lib/consoler/options.rb
|
54
|
+
- lib/consoler/version.rb
|
55
|
+
homepage: https://github.com/justim/consoler-rb
|
56
|
+
licenses:
|
57
|
+
- MIT
|
58
|
+
metadata:
|
59
|
+
yard.run: yri
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 2.6.11
|
77
|
+
signing_key:
|
78
|
+
specification_version: 4
|
79
|
+
summary: Consoler
|
80
|
+
test_files: []
|