nub 0.0.43 → 0.0.44
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/nub/commander.rb +92 -51
- data/lib/nub/log.rb +38 -26
- data/lib/nub/sys.rb +2 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e625dfb52c0c38897732f0cf25ff675663f18c6db457485f7e8714badb74f9e1
|
4
|
+
data.tar.gz: 89b640564741b907642fe67aa50ff36a003f7963a4401d5964ced50933f06178
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78e202387517087c8c2daee6d44616fd8dbb507a352663f60c3e4d8975513c4ea0ed1778748fa10b233bebb7ea185c722a8b2edca808fccbc5e205bee4f9b445
|
7
|
+
data.tar.gz: eb7d0bbf9bc53ec9d4fdd5941a854c1a9160ffb37253db98124ac8989522468d6418b889087e82c94f64a6b8b2bc4f7e4e9ffe4ec0d51a497d81ce0757e03c9b
|
data/lib/nub/commander.rb
CHANGED
@@ -20,6 +20,7 @@
|
|
20
20
|
#SOFTWARE.
|
21
21
|
|
22
22
|
require 'colorize'
|
23
|
+
require_relative 'log'
|
23
24
|
require_relative 'sys'
|
24
25
|
require_relative 'string'
|
25
26
|
|
@@ -52,9 +53,8 @@ class Option
|
|
52
53
|
#https://bneijt.nl/pr/ruby-regular-expressions/
|
53
54
|
# Valid forms to look for with chars [a-zA-Z0-9-_=|]
|
54
55
|
# --help, --help=HINT, -h|--help, -h|--help=HINT
|
55
|
-
|
56
|
-
|
57
|
-
key[/(^--[a-zA-Z0-9\-_]+$)|(^--[a-zA-Z\-_]+=\w+$)|(^-[a-zA-Z]\|--[a-zA-Z0-9\-_]+$)|(^-[a-zA-Z]\|--[a-zA-Z0-9\-_]+=\w+$)/].nil?)
|
56
|
+
Log.die("invalid option key #{key}") if key && (key.count('=') > 1 or key.count('|') > 1 or !key[/[^\w\-=|]/].nil? or
|
57
|
+
key[/(^--[a-zA-Z0-9\-_]+$)|(^--[a-zA-Z\-_]+=\w+$)|(^-[a-zA-Z]\|--[a-zA-Z0-9\-_]+$)|(^-[a-zA-Z]\|--[a-zA-Z0-9\-_]+=\w+$)/].nil?)
|
58
58
|
@key = key
|
59
59
|
if key
|
60
60
|
@hint = key[/.*=(.*)$/, 1]
|
@@ -66,10 +66,8 @@ class Option
|
|
66
66
|
end
|
67
67
|
|
68
68
|
# Validate and set type
|
69
|
-
|
70
|
-
|
71
|
-
!puts("Error: option type must be set".colorize(:red)) and
|
72
|
-
exit if @hint && !type
|
69
|
+
Log.die("invalid option type #{type}") if ![String, Integer, Array, nil].any?{|x| type == x}
|
70
|
+
Log.die("option type must be set") if @hint && !type
|
73
71
|
@type = String if !key && !type
|
74
72
|
@type = FalseClass if key and !type
|
75
73
|
@type = type if type
|
@@ -77,8 +75,7 @@ class Option
|
|
77
75
|
# Validate allowed
|
78
76
|
if @allowed.any?
|
79
77
|
allowed_type = @allowed.first.class
|
80
|
-
|
81
|
-
exit if @allowed.any?{|x| x.class != allowed_type}
|
78
|
+
Log.die("mixed allowed types") if @allowed.any?{|x| x.class != allowed_type}
|
82
79
|
end
|
83
80
|
end
|
84
81
|
end
|
@@ -101,15 +98,22 @@ class Commander
|
|
101
98
|
@app_default = Sys.caller_filename
|
102
99
|
@version = version
|
103
100
|
@examples = examples
|
104
|
-
@help_opt = Option.new('-h|--help', 'Print command/options help')
|
105
101
|
@just = 40
|
106
102
|
|
107
|
-
#
|
108
|
-
@
|
103
|
+
# Regexps
|
104
|
+
@short_regex = /^(-\w).*$/
|
105
|
+
@long_regex = /(--[\w\-]+)(=.+)*$/
|
106
|
+
@value_regex = /.*=(.*)$/
|
109
107
|
|
110
108
|
# Incoming user set commands/options
|
111
109
|
# {command_name => {}}
|
112
110
|
@cmds = {}
|
111
|
+
|
112
|
+
# Configuration - ordered list of commands
|
113
|
+
@config = []
|
114
|
+
|
115
|
+
# Configure default global options
|
116
|
+
add_global(Option.new('-h|--help', 'Print command/options help'))
|
113
117
|
end
|
114
118
|
|
115
119
|
# Hash like accessor for checking if a command or option is set
|
@@ -122,30 +126,25 @@ class Commander
|
|
122
126
|
# @param desc [String] description of the command
|
123
127
|
# @param opts [List] list of command options
|
124
128
|
def add(cmd, desc, options:[])
|
125
|
-
|
126
|
-
|
129
|
+
Log.die("'global' is a reserved command name") if cmd == 'global'
|
130
|
+
Log.die("'help' is a reserved option name") if options.any?{|x| !x.key.nil? && x.key.include?('help')}
|
127
131
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
help = "#{banner}\n#{help}" if @app
|
132
|
-
options << @help_opt
|
132
|
+
cmd = add_cmd(cmd, desc, options)
|
133
|
+
@config << cmd
|
134
|
+
end
|
133
135
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
required = x.required ? ", Required" : ""
|
140
|
-
allowed = x.allowed.empty? ? "" : " (#{x.allowed * ','})"
|
141
|
-
positional_index += 1 if x.key.nil?
|
142
|
-
key = x.key.nil? ? "#{cmd}#{positional_index}" : x.key
|
143
|
-
type = x.type == FalseClass ? "Flag" : x.type
|
144
|
-
help += " #{key.ljust(@just)}#{x.desc}#{allowed}: #{type}#{required}\n"
|
145
|
-
}
|
136
|
+
# Add global options
|
137
|
+
# @param option/s [Array/Option] array or single option/s
|
138
|
+
def add_global(options)
|
139
|
+
options = [options] if options.class == Option
|
140
|
+
Log.die("only named global options are allowed") if options.any?{|x| x.key.nil?}
|
146
141
|
|
147
|
-
#
|
148
|
-
@config
|
142
|
+
# Process the global options, removed the old ones and add new ones
|
143
|
+
if (global = @config.find{|x| x.name == 'global'})
|
144
|
+
global.opts.each{|x| options << x}
|
145
|
+
@config.reject!{|x| x.name == 'global'}
|
146
|
+
end
|
147
|
+
@config << add_cmd('global', 'Global options:', options)
|
149
148
|
end
|
150
149
|
|
151
150
|
# Returns banner string
|
@@ -166,9 +165,9 @@ class Commander
|
|
166
165
|
end
|
167
166
|
app = @app || @app_default
|
168
167
|
help += "Usage: ./#{app} [commands] [options]\n"
|
169
|
-
help +=
|
168
|
+
help += @config.find{|x| x.name == 'global'}.help
|
170
169
|
help += "COMMANDS:\n"
|
171
|
-
@config.each{|x| help += " #{x.name.ljust(@just)}#{x.desc}\n" }
|
170
|
+
@config.select{|x| x.name != 'global'}.each{|x| help += " #{x.name.ljust(@just)}#{x.desc}\n" }
|
172
171
|
help += "\nsee './#{app} COMMAND --help' for specific command help\n"
|
173
172
|
|
174
173
|
return help
|
@@ -183,8 +182,7 @@ class Commander
|
|
183
182
|
# Process global options
|
184
183
|
#---------------------------------------------------------------------------
|
185
184
|
cmd_names = @config.map{|x| x.name }
|
186
|
-
|
187
|
-
!puts(help) and exit if globals.any?
|
185
|
+
ARGV.unshift('global') if ARGV.take_while{|x| !cmd_names.include?(x)}.any?
|
188
186
|
|
189
187
|
# Process command options
|
190
188
|
#---------------------------------------------------------------------------
|
@@ -219,23 +217,23 @@ class Commander
|
|
219
217
|
other = @config.find{|x| x.name == ARGV[i-1]}
|
220
218
|
other_required = other.opts.select{|x| x.key.nil? || x.required}
|
221
219
|
|
222
|
-
!puts("Error: chained commands must have equal numbers of required
|
220
|
+
!puts("Error: chained commands must have equal numbers of required options!".colorize(:red)) && !puts(cmd.help) and
|
223
221
|
exit if cmd_required.size != other_required.size
|
224
222
|
cmd_required.each_with_index{|x,i|
|
225
|
-
!puts("Error: chained command options are not type consistent".colorize(:red)) && !puts(cmd.help) and
|
223
|
+
!puts("Error: chained command options are not type consistent!".colorize(:red)) && !puts(cmd.help) and
|
226
224
|
exit if x.type != other_required[i].type || x.key != other_required[i].key
|
227
225
|
}
|
228
226
|
end
|
229
227
|
end
|
230
228
|
|
231
229
|
# Check that all positional options were given
|
232
|
-
!puts("Error: positional option required".colorize(:red)) && !puts(cmd.help) and
|
230
|
+
!puts("Error: positional option required!".colorize(:red)) && !puts(cmd.help) and
|
233
231
|
exit if opts.size < cmd_pos_opts.size
|
234
232
|
|
235
233
|
# Check that all required named options where given
|
236
234
|
named_opts = opts.select{|x| x.start_with?('-')}
|
237
235
|
cmd_named_opts.select{|x| x.required}.each{|x|
|
238
|
-
!puts("Error: required option #{x.key} not given".colorize(:red)) && !puts(cmd.help) and
|
236
|
+
!puts("Error: required option #{x.key} not given!".colorize(:red)) && !puts(cmd.help) and
|
239
237
|
exit if !named_opts.find{|y| y.start_with?(x.short) || y.start_with?(x.long)}
|
240
238
|
}
|
241
239
|
|
@@ -252,14 +250,18 @@ class Commander
|
|
252
250
|
# --------------------------------------------------------------------
|
253
251
|
# e.g. -s, --skip, --skip=VALUE
|
254
252
|
if opt.start_with?('-')
|
255
|
-
short = opt[
|
256
|
-
long = opt[
|
257
|
-
value = opt[
|
253
|
+
short = opt[@short_regex, 1]
|
254
|
+
long = opt[@long_regex, 1]
|
255
|
+
value = opt[@value_regex, 1]
|
258
256
|
|
259
257
|
# Set symbol converting dashes to underscores for named options
|
260
258
|
if (cmd_opt = cmd_named_opts.find{|x| x.short == short || x.long == long})
|
261
259
|
sym = cmd_opt.long[2..-1].gsub("-", "_").to_sym
|
262
260
|
|
261
|
+
# Handle help for the command
|
262
|
+
!puts(help) and exit if cmd.name == 'global' && sym == :help
|
263
|
+
!puts(cmd.help) and exit if sym == :help
|
264
|
+
|
263
265
|
# Collect value
|
264
266
|
if cmd_opt.type == FalseClass
|
265
267
|
value = true if !value
|
@@ -273,8 +275,8 @@ class Commander
|
|
273
275
|
else
|
274
276
|
pos += 1
|
275
277
|
cmd_opt = cmd_pos_opts.shift
|
276
|
-
!puts("Error: invalid positional option '#{opt}'".colorize(:red)) && !puts(cmd.help) and
|
277
|
-
exit if cmd_opt.nil?
|
278
|
+
!puts("Error: invalid positional option '#{opt}'!".colorize(:red)) && !puts(cmd.help) and
|
279
|
+
!puts("START:#{ARGV}:END") and exit if cmd_opt.nil?
|
278
280
|
value = opt
|
279
281
|
sym = "#{cmd.name}#{pos}".to_sym
|
280
282
|
end
|
@@ -284,20 +286,20 @@ class Commander
|
|
284
286
|
if value
|
285
287
|
if cmd_opt.type == String
|
286
288
|
if cmd_opt.allowed.any?
|
287
|
-
!puts("Error: invalid string value '#{value}'".colorize(:red)) && !puts(cmd.help) and
|
289
|
+
!puts("Error: invalid string value '#{value}'!".colorize(:red)) && !puts(cmd.help) and
|
288
290
|
exit if !cmd_opt.allowed.include?(value)
|
289
291
|
end
|
290
292
|
elsif cmd_opt.type == Integer
|
291
293
|
value = value.to_i
|
292
294
|
if cmd_opt.allowed.any?
|
293
|
-
!puts("Error: invalid integer value '#{value}'".colorize(:red)) && !puts(cmd.help) and
|
295
|
+
!puts("Error: invalid integer value '#{value}'!".colorize(:red)) && !puts(cmd.help) and
|
294
296
|
exit if !cmd_opt.allowed.include?(value)
|
295
297
|
end
|
296
298
|
elsif cmd_opt.type == Array
|
297
299
|
value = value.split(',')
|
298
300
|
if cmd_opt.allowed.any?
|
299
301
|
value.each{|x|
|
300
|
-
!puts("Error: invalid array value '#{x}'".colorize(:red)) && !puts(cmd.help) and
|
302
|
+
!puts("Error: invalid array value '#{x}'!".colorize(:red)) && !puts(cmd.help) and
|
301
303
|
exit if !cmd_opt.allowed.include?(x)
|
302
304
|
}
|
303
305
|
end
|
@@ -306,18 +308,57 @@ class Commander
|
|
306
308
|
|
307
309
|
# Set option with value
|
308
310
|
# --------------------------------------------------------------------
|
309
|
-
!puts("Error: unknown named option '#{opt}' given".colorize(:red)) && !puts(cmd.help) and exit if !sym
|
311
|
+
!puts("Error: unknown named option '#{opt}' given!".colorize(:red)) && !puts(cmd.help) and exit if !sym
|
310
312
|
@cmds[cmd.name.to_sym][sym] = value
|
311
313
|
}
|
312
314
|
end
|
313
315
|
}
|
314
316
|
|
315
317
|
# Ensure all options were consumed
|
316
|
-
|
318
|
+
Log.die("invalid options #{ARGV}") if ARGV.any?
|
317
319
|
|
318
320
|
# Print banner on success
|
319
321
|
puts(banner) if @app
|
320
322
|
end
|
323
|
+
|
324
|
+
#-----------------------------------------------------------------------------
|
325
|
+
# Private methods
|
326
|
+
#-----------------------------------------------------------------------------
|
327
|
+
private
|
328
|
+
|
329
|
+
# Add a command to the command list
|
330
|
+
# @param cmd [String] name of the command
|
331
|
+
# @param desc [String] description of the command
|
332
|
+
# @param opts [List] list of command options
|
333
|
+
# @returns cmd [Command] new command
|
334
|
+
def add_cmd(cmd, desc, options)
|
335
|
+
Log.die("command names must be pure lowercase letters") if cmd =~ /[^a-z]/
|
336
|
+
|
337
|
+
# Build help for command
|
338
|
+
app = @app || @app_default
|
339
|
+
help = "#{desc}\n"
|
340
|
+
help += "\nUsage: ./#{app} #{cmd} [options]\n" if cmd != 'global'
|
341
|
+
help = "#{banner}\n#{help}" if @app && cmd != 'global'
|
342
|
+
|
343
|
+
# Add help option if not global command
|
344
|
+
options << @config.find{|x| x.name == 'global'}.opts.find{|x| x.long == '--help'} if cmd != 'global'
|
345
|
+
|
346
|
+
# Add positional options first
|
347
|
+
sorted_options = options.select{|x| x.key.nil?}
|
348
|
+
sorted_options += options.select{|x| !x.key.nil?}.sort{|x,y| x.key <=> y.key}
|
349
|
+
positional_index = -1
|
350
|
+
sorted_options.each{|x|
|
351
|
+
required = x.required ? ", Required" : ""
|
352
|
+
allowed = x.allowed.empty? ? "" : " (#{x.allowed * ','})"
|
353
|
+
positional_index += 1 if x.key.nil?
|
354
|
+
key = x.key.nil? ? "#{cmd}#{positional_index}" : x.key
|
355
|
+
type = x.type == FalseClass ? "Flag" : x.type
|
356
|
+
help += " #{key.ljust(@just)}#{x.desc}#{allowed}: #{type}#{required}\n"
|
357
|
+
}
|
358
|
+
|
359
|
+
# Create the command in the command config
|
360
|
+
return Command.new(cmd, desc, sorted_options, help)
|
361
|
+
end
|
321
362
|
end
|
322
363
|
|
323
364
|
# vim: ft=ruby:ts=2:sw=2:sts=2
|
data/lib/nub/log.rb
CHANGED
@@ -60,6 +60,7 @@ module Log
|
|
60
60
|
@@_level = level
|
61
61
|
@@_queue = queue ? Queue.new : nil
|
62
62
|
@@_stdout = stdout
|
63
|
+
$stdout.sync = true
|
63
64
|
|
64
65
|
# Open log file creating as needed
|
65
66
|
if @path
|
@@ -162,47 +163,58 @@ module Log
|
|
162
163
|
end
|
163
164
|
|
164
165
|
def error(*args)
|
165
|
-
|
166
|
-
|
167
|
-
|
166
|
+
@@_monitor.synchronize{
|
167
|
+
opts = args.find{|x| x.is_a?(Hash)}
|
168
|
+
opts[:loc] = true and opts[:type] = 'E' if opts
|
169
|
+
args << {:loc => true, :type => 'E'} if !opts
|
168
170
|
|
169
|
-
|
171
|
+
return self.puts(*args)
|
172
|
+
}
|
170
173
|
end
|
171
174
|
|
172
175
|
def warn(*args)
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
176
|
+
@@_monitor.synchronize{
|
177
|
+
if LogLevel.warn <= @@_level
|
178
|
+
opts = args.find{|x| x.is_a?(Hash)}
|
179
|
+
opts[:type] = 'W' if opts
|
180
|
+
args << {:type => 'W'} if !opts
|
181
|
+
return self.puts(*args)
|
182
|
+
end
|
183
|
+
return true
|
184
|
+
}
|
180
185
|
end
|
181
186
|
|
182
187
|
def info(*args)
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
188
|
+
@@_monitor.synchronize{
|
189
|
+
if LogLevel.info <= @@_level
|
190
|
+
opts = args.find{|x| x.is_a?(Hash)}
|
191
|
+
opts[:type] = 'I' if opts
|
192
|
+
args << {:type => 'I'} if !opts
|
193
|
+
return self.puts(*args)
|
194
|
+
end
|
195
|
+
return true
|
196
|
+
}
|
190
197
|
end
|
191
198
|
|
192
199
|
def debug(*args)
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
+
@@_monitor.synchronize{
|
201
|
+
if LogLevel.debug <= @@_level
|
202
|
+
opts = args.find{|x| x.is_a?(Hash)}
|
203
|
+
opts[:type] = 'D' if opts
|
204
|
+
args << {:type => 'D'} if !opts
|
205
|
+
return self.puts(*args)
|
206
|
+
end
|
207
|
+
return true
|
208
|
+
}
|
200
209
|
end
|
201
210
|
|
202
211
|
# Log the given message in red and exit
|
203
212
|
# @param msg [String] message to log
|
204
213
|
def die(msg)
|
205
|
-
|
214
|
+
@@_monitor.synchronize{
|
215
|
+
msg += "!" if msg.is_a?(String) && msg[-1] != "!"
|
216
|
+
self.puts("Error: #{msg}".colorize(:red), stamp: false) and exit
|
217
|
+
}
|
206
218
|
end
|
207
219
|
|
208
220
|
# Remove an item from the queue, block until one exists
|
data/lib/nub/sys.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nub
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.44
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Patrick Crummett
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-04-
|
11
|
+
date: 2018-04-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|