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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 18e9121a0f51826a76f7c76172ad12a543fdc1ed177e2faafd226c4748ee1c55
4
- data.tar.gz: b2d67cc335778e825742b6d9778026e47c07a166243b0a0909b6c5318f740812
3
+ metadata.gz: e625dfb52c0c38897732f0cf25ff675663f18c6db457485f7e8714badb74f9e1
4
+ data.tar.gz: 89b640564741b907642fe67aa50ff36a003f7963a4401d5964ced50933f06178
5
5
  SHA512:
6
- metadata.gz: 1cc75cd89bf288b25b600f4a1f922dd4e8af61cc12c77c61ae798bfb0d20ca2dccc6712cb7ba6d3ca70cea4fd8685519e254f539ef177d893c12a1c3350ae6e0
7
- data.tar.gz: b910fb8b96b7475db9d7d1b691eb118ee27220f78a0647cecea89c221ba3b29c92630f3058cf435abc70a586e6fc472aef73866890f643af9598533fda6f39b1
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
- !puts("Error: invalid option key #{key}".colorize(:red)) and
56
- exit 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?)
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
- !puts("Error: invalid option type #{type}".colorize(:red)) and
70
- exit if ![String, Integer, Array, nil].any?{|x| type == x}
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
- !puts("Error: mixed allowed types".colorize(:red)) and
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
- # Configuration - ordered list of commands
108
- @config = []
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
- !puts("Error: command names must be pure lowercase letters".colorize(:red)) and
126
- exit if cmd =~ /[^a-z]/
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
- # Build help for command
129
- app = @app || @app_default
130
- help = "#{desc}\n\nUsage: ./#{app} #{cmd} [options]\n"
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
- # Add positional options first
135
- sorted_options = options.select{|x| x.key.nil?}
136
- sorted_options += options.select{|x| !x.key.nil?}.sort{|x,y| x.key <=> y.key}
137
- positional_index = -1
138
- sorted_options.each{|x|
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
- # Create the command in the command config
148
- @config << Command.new(cmd, desc, sorted_options, help)
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 += " #{'-h|--help'.ljust(@just)}Print command/options help: Flag\n"
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
- globals = ARGV.take_while{|x| !cmd_names.include?(x)}
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 optiosn".colorize(:red)) && !puts(cmd.help) and
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[/^(-\w).*$/, 1]
256
- long = opt[/(--[\w\-]+)(=.+)*$/, 1]
257
- value = opt[/.*=(.*)$/, 1]
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
- !puts("Error: invalid options #{ARGV}".colorize(:red)) and exit if ARGV.any?
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
- opts = args.find{|x| x.is_a?(Hash)}
166
- opts[:loc] = true and opts[:type] = 'E' if opts
167
- args << {:loc => true, :type => 'E'} if !opts
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
- return self.puts(*args)
171
+ return self.puts(*args)
172
+ }
170
173
  end
171
174
 
172
175
  def warn(*args)
173
- if LogLevel.warn <= @@_level
174
- opts = args.find{|x| x.is_a?(Hash)}
175
- opts[:type] = 'W' if opts
176
- args << {:type => 'W'} if !opts
177
- return self.puts(*args)
178
- end
179
- return true
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
- if LogLevel.info <= @@_level
184
- opts = args.find{|x| x.is_a?(Hash)}
185
- opts[:type] = 'I' if opts
186
- args << {:type => 'I'} if !opts
187
- return self.puts(*args)
188
- end
189
- return true
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
- if LogLevel.debug <= @@_level
194
- opts = args.find{|x| x.is_a?(Hash)}
195
- opts[:type] = 'D' if opts
196
- args << {:type => 'D'} if !opts
197
- return self.puts(*args)
198
- end
199
- return true
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
- self.puts("Error: #{msg}".colorize(:red), stamp: false) and exit
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
@@ -64,6 +64,8 @@ module Sys
64
64
  result = block.call
65
65
 
66
66
  $stdout, $stderr = STDOUT, STDERR
67
+ $stdout.flush
68
+ $stderr.flush
67
69
 
68
70
  return OpenStruct.new(result: result, stdout: stdout.string, stderr: stderr.string)
69
71
  end
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.43
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-05 00:00:00.000000000 Z
11
+ date: 2018-04-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize