consoler 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|