unimatrix-cli 2.2.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.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/README.md +12 -0
  6. data/Rakefile +6 -0
  7. data/VERSION +1 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +8 -0
  10. data/exe/unimatrix +11 -0
  11. data/lib/unimatrix_cli.rb +74 -0
  12. data/lib/unimatrix_cli/alchemist/rendition/list_command.rb +72 -0
  13. data/lib/unimatrix_cli/alchemist/rendition_profile/assign_command.rb +59 -0
  14. data/lib/unimatrix_cli/alchemist/rendition_profile/list_command.rb +36 -0
  15. data/lib/unimatrix_cli/alchemist/video/create_command.rb +40 -0
  16. data/lib/unimatrix_cli/alchemist/video/describe_command.rb +40 -0
  17. data/lib/unimatrix_cli/alchemist/video_encoder/create_command.rb +37 -0
  18. data/lib/unimatrix_cli/alchemist/video_encoder/describe_command.rb +39 -0
  19. data/lib/unimatrix_cli/alchemist/video_encoder/encode_command.rb +102 -0
  20. data/lib/unimatrix_cli/alchemist/video_encoder/list_command.rb +35 -0
  21. data/lib/unimatrix_cli/archivist/blueprint/create_command.rb +94 -0
  22. data/lib/unimatrix_cli/citadel/app/build_command.rb +28 -0
  23. data/lib/unimatrix_cli/citadel/app/console_command.rb +17 -0
  24. data/lib/unimatrix_cli/citadel/app/db_setup_command.rb +17 -0
  25. data/lib/unimatrix_cli/citadel/app/environment_command.rb +30 -0
  26. data/lib/unimatrix_cli/citadel/app/logs_command.rb +17 -0
  27. data/lib/unimatrix_cli/citadel/app/migrate_command.rb +17 -0
  28. data/lib/unimatrix_cli/citadel/app/migrate_status_command.rb +17 -0
  29. data/lib/unimatrix_cli/citadel/app/rake_command.rb +18 -0
  30. data/lib/unimatrix_cli/citadel/app/routes_command.rb +17 -0
  31. data/lib/unimatrix_cli/citadel/citadel_command.rb +285 -0
  32. data/lib/unimatrix_cli/citadel/instance/details_command.rb +18 -0
  33. data/lib/unimatrix_cli/citadel/instance/scp_command.rb +48 -0
  34. data/lib/unimatrix_cli/citadel/instance/ssh_command.rb +29 -0
  35. data/lib/unimatrix_cli/citadel/instance/status_command.rb +17 -0
  36. data/lib/unimatrix_cli/citadel/instance/user_data_command.rb +17 -0
  37. data/lib/unimatrix_cli/citadel/instance/user_data_logs_command.rb +17 -0
  38. data/lib/unimatrix_cli/cli.rb +30 -0
  39. data/lib/unimatrix_cli/command.rb +138 -0
  40. data/lib/unimatrix_cli/config/acceptance.yml +23 -0
  41. data/lib/unimatrix_cli/config/configuration.rb +66 -0
  42. data/lib/unimatrix_cli/config/production.yml +15 -0
  43. data/lib/unimatrix_cli/config/staging.yml +15 -0
  44. data/lib/unimatrix_cli/config/unimatrix.rb +4 -0
  45. data/lib/unimatrix_cli/iris/stream/create_command.rb +43 -0
  46. data/lib/unimatrix_cli/iris/stream/describe_command.rb +35 -0
  47. data/lib/unimatrix_cli/iris/stream_encoder/create_command.rb +43 -0
  48. data/lib/unimatrix_cli/iris/stream_encoder/describe_command.rb +37 -0
  49. data/lib/unimatrix_cli/iris/stream_input/create_command.rb +43 -0
  50. data/lib/unimatrix_cli/iris/stream_input/describe_command.rb +37 -0
  51. data/lib/unimatrix_cli/iris/stream_output/create_command.rb +55 -0
  52. data/lib/unimatrix_cli/iris/stream_output/describe_command.rb +37 -0
  53. data/lib/unimatrix_cli/iris/stream_recorder/create_command.rb +49 -0
  54. data/lib/unimatrix_cli/iris/stream_recorder/describe_command.rb +37 -0
  55. data/lib/unimatrix_cli/iris/stream_transcriber/create_command.rb +54 -0
  56. data/lib/unimatrix_cli/iris/stream_transcriber/describe_command.rb +37 -0
  57. data/lib/unimatrix_cli/iris/stream_transmutator/create_command.rb +65 -0
  58. data/lib/unimatrix_cli/iris/stream_transmutator/describe_command.rb +38 -0
  59. data/lib/unimatrix_cli/login_command.rb +91 -0
  60. data/lib/unimatrix_cli/logout_command.rb +21 -0
  61. data/lib/unimatrix_cli/version.rb +3 -0
  62. data/lib/unimatrix_cli/zephyrus/input/create_command.rb +40 -0
  63. data/lib/unimatrix_cli/zephyrus/input/describe_command.rb +35 -0
  64. data/lib/unimatrix_cli/zephyrus/input/list_command.rb +38 -0
  65. data/lib/unimatrix_cli/zephyrus/output/list_command.rb +44 -0
  66. data/lib/unimatrix_cli/zephyrus/recording/configuration_command.rb +43 -0
  67. data/lib/unimatrix_cli/zephyrus/rendition/list_command.rb +40 -0
  68. data/lib/unimatrix_cli/zephyrus/routing/configuration_command.rb +43 -0
  69. data/lib/unimatrix_cli/zephyrus/transcoding/configuration_command.rb +55 -0
  70. data/lib/unimatrix_cli/zephyrus/transcribing/configuration_command.rb +55 -0
  71. data/lib/unimatrix_cli/zephyrus/transmutation/configuration_command.rb +55 -0
  72. data/lib/unimatrix_parser.rb +796 -0
  73. data/unimatrix-cli.gemspec +30 -0
  74. metadata +247 -0
@@ -0,0 +1,55 @@
1
+ module UnimatrixCLI
2
+ module Zephyrus
3
+ module Transcribing
4
+ class ConfigurationCommand < Command
5
+
6
+ option :key, "The key for an input", type: :string, required: true
7
+
8
+ synopsis "Display transcribing configuration for an input"
9
+
10
+ def execute
11
+ response = transcribing_configuration_request
12
+ if response[ 'outputs' ].nil?
13
+ write(
14
+ message: "Configuration could not be found: #{ response.inspect }",
15
+ error: true
16
+ )
17
+ else
18
+ response[ 'outputs' ].each do | output |
19
+ write( message: parse_configuration_output( output ) )
20
+ end
21
+ end
22
+ end
23
+
24
+ #----------------------------------------------------------------------------
25
+
26
+ private; def transcribing_configuration_request
27
+ endpoint = "#{ Configuration.default_config[ 'zephyrus_url' ] }/" +
28
+ "transcribing/configuration?key=#{ @options[ :key ] }"
29
+
30
+ # Unimatrix SDK cannot handle Zephyrus configuration responses
31
+ make_request( endpoint, 'Get' )
32
+ end
33
+
34
+ private; def parse_configuration_output( output )
35
+ output = output.map do | key, value |
36
+ if key == "rendition"
37
+ renditions = value.map { | rendition | parse_rendition( rendition ) }.join
38
+ "rendition:\n*\n#{ renditions }"
39
+ else
40
+ "#{ key }: #{ value }\n"
41
+ end
42
+ end
43
+ output << "\n"
44
+ end
45
+
46
+ private; def parse_rendition( rendition )
47
+ rendition = rendition.map do | key, value |
48
+ "#{ key }: #{ value }\n"
49
+ end
50
+ rendition << "*\n"
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,55 @@
1
+ module UnimatrixCLI
2
+ module Zephyrus
3
+ module Transmutation
4
+ class ConfigurationCommand < Command
5
+
6
+ option :key, "The key for an input", type: :string, required: true
7
+
8
+ synopsis "Display transmutation configuration for an input"
9
+
10
+ def execute
11
+ response = transmutation_configuration_request
12
+ if response[ 'outputs' ].nil?
13
+ write(
14
+ message: "Configuration could not be found: #{ response.inspect }",
15
+ error: true
16
+ )
17
+ else
18
+ response[ 'outputs' ].each do | output |
19
+ write( message: parse_configuration_output( output ) )
20
+ end
21
+ end
22
+ end
23
+
24
+ #----------------------------------------------------------------------------
25
+
26
+ private; def transmutation_configuration_request
27
+ endpoint = "#{ Configuration.default_config[ 'zephyrus_url' ] }/" +
28
+ "transmutation/onfiguration?key=#{ @options[ :key ] }"
29
+
30
+ # Unimatrix SDK cannot handle Zephyrus configuration responses
31
+ make_request( endpoint, 'Get' )
32
+ end
33
+
34
+ private; def parse_configuration_output( output )
35
+ output = output.map do | key, value |
36
+ if key == "rendition"
37
+ renditions = value.map { | rendition | parse_rendition( rendition ) }.join
38
+ "rendition:\n*\n#{ renditions }"
39
+ else
40
+ "#{ key }: #{ value }\n"
41
+ end
42
+ end
43
+ output << "\n"
44
+ end
45
+
46
+ private; def parse_rendition( rendition )
47
+ rendition = rendition.map do | key, value |
48
+ "#{ key }: #{ value }\n"
49
+ end
50
+ rendition << "*\n"
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,796 @@
1
+ # Modified from trollop commandline processing library 2.1.2
2
+ # https://github.com/ManageIQ/trollop
3
+
4
+ require 'date'
5
+
6
+ module UnimatrixParser
7
+
8
+ #----------------------------------------------------------------------------
9
+ # Class Methods
10
+
11
+ module ClassMethods
12
+ def option( name, description = "", options = {} )
13
+ option = Option.new( name, description, options )
14
+ @options = [] if @options.nil?
15
+ @options << option
16
+ end
17
+
18
+ def synopsis( synopsis = nil )
19
+ @synopsis = synopsis
20
+ end
21
+
22
+ def stop_on_unknown
23
+ @stop_on_unknown = true
24
+ end
25
+
26
+ def educate_if_empty
27
+ if ARGV.empty?
28
+ parser = initialize_parser
29
+ parser.educate
30
+ exit
31
+ end
32
+ end
33
+
34
+ def with_standard_exception_handling( parser )
35
+ yield
36
+ rescue CommandlineError => error
37
+ parser.die( error.message, nil, error.error_code )
38
+ rescue HelpNeeded
39
+ parser.educate
40
+ exit
41
+ end
42
+
43
+ def options
44
+ parser = initialize_parser
45
+ with_standard_exception_handling( parser ) do
46
+ parser.parse( ARGV )
47
+ end
48
+ end
49
+
50
+ private; def initialize_parser
51
+ option :help, "Display this help message", type: :string
52
+ parser = Parser.new
53
+ parser.options( @options )
54
+ parser.synopsis( @synopsis )
55
+ parser.stop_on_unknown if @stop_on_unknown
56
+ parser
57
+ end
58
+ end
59
+
60
+ def self.included( base )
61
+ base.extend( ClassMethods )
62
+ end
63
+
64
+ #----------------------------------------------------------------------------
65
+ # Error Classes
66
+
67
+ class CommandlineError < StandardError
68
+ attr_reader :error_code
69
+
70
+ def initialize( message, error_code = nil )
71
+ super( message )
72
+ @error_code = error_code
73
+ end
74
+ end
75
+
76
+ class HelpNeeded < StandardError; end
77
+
78
+ #----------------------------------------------------------------------------
79
+ # Option Class
80
+
81
+ class Option
82
+ FLAG_TYPES = [ :flag, :bool, :boolean ]
83
+ SINGLE_ARG_TYPES = [ :int, :integer, :string, :double, :float, :io, :date ]
84
+ MULTI_ARG_TYPES = [ :ints, :integers, :strings, :doubles, :floats, :ios, :dates ]
85
+ TYPES = FLAG_TYPES + SINGLE_ARG_TYPES + MULTI_ARG_TYPES
86
+ INVALID_SHORT_ARG_REGEX = /[\d-]/
87
+
88
+ attr_accessor :name, :options
89
+
90
+ def initialize( name, description, options )
91
+ options[ :description ] = description
92
+ options[ :type ] = normalize_type( options[ :type ] )
93
+
94
+ type_from_default = disambiguate_and_set_type_from_default( options )
95
+
96
+ if options[ :type ] && type_from_default && options[ :type ] != type_from_default
97
+ raise ArgumentError, ":type specification and default type don't match " +
98
+ "(default type is #{ type_from_default })"
99
+ end
100
+
101
+ options[ :type ] = options[ :type ] || type_from_default || :flag
102
+ options[ :long ] = set_long( options[ :long ], name )
103
+ options[ :short ] = set_short( options[ :short ] )
104
+
105
+ if options[ :short ] && options[ :short ] =~ INVALID_SHORT_ARG_REGEX
106
+ raise ArgumentError, "a short option name can't be a number or a dash"
107
+ end
108
+
109
+ options[ :default ] = false if options[ :type ] == :flag && options[ :default ].nil?
110
+
111
+ if options[ :default ] && options[ :multi ] && !options[ :default ].kind_of?( Array )
112
+ options[ :default ] = [ options[ :default ] ]
113
+ end
114
+
115
+ options[ :multi ] ||= false
116
+
117
+ self.name = name
118
+ self.options = options
119
+ end
120
+
121
+ #----------------------------------------------------------------------------
122
+ # Option Methods
123
+
124
+ def key?( name )
125
+ options.key?( name )
126
+ end
127
+
128
+ def type
129
+ options[ :type ]
130
+ end
131
+
132
+ def flag?
133
+ type == :flag
134
+ end
135
+
136
+ def single_arg?
137
+ SINGLE_ARG_TYPES.include?( type )
138
+ end
139
+
140
+ def multi
141
+ options[ :multi ]
142
+ end
143
+
144
+ alias multi? multi
145
+
146
+ def multi_arg?
147
+ MULTI_ARG_TYPES.include?( type )
148
+ end
149
+
150
+ def default
151
+ options[ :default ]
152
+ end
153
+
154
+ def array_default?
155
+ options[ :default ].kind_of?( Array )
156
+ end
157
+
158
+ def short
159
+ options[ :short ]
160
+ end
161
+
162
+ def short?
163
+ short && short != :none
164
+ end
165
+
166
+ def short=( value )
167
+ options[ :short ] = value
168
+ end
169
+
170
+ def long
171
+ options[ :long ]
172
+ end
173
+
174
+ def description
175
+ options[ :description ]
176
+ end
177
+
178
+ def required?
179
+ options[ :required ]
180
+ end
181
+
182
+ #----------------------------------------------------------------------------
183
+ # Private Option Methods
184
+
185
+ private; def normalize_type( type )
186
+ case type
187
+ when :boolean, :bool then :flag
188
+ when :integer then :int
189
+ when :integers then :ints
190
+ when :double then :float
191
+ when :doubles then :floats
192
+ when Class
193
+ case type.name
194
+ when 'TrueClass',
195
+ 'FalseClass' then :flag
196
+ when 'String' then :string
197
+ when 'Integer' then :int
198
+ when 'Float' then :float
199
+ when 'IO' then :io
200
+ when 'Date' then :date
201
+ else
202
+ raise ArgumentError, "unsupported argument type '#{ type.class.name }'"
203
+ end
204
+ when nil then nil
205
+ else
206
+ raise ArgumentError, "unsupported argument type " +
207
+ "'#{ type }'" unless TYPES.include?( type )
208
+ type
209
+ end
210
+ end
211
+
212
+ private; def disambiguate_and_set_type_from_default( options )
213
+ disambiguated_default = if options[ :multi ] &&
214
+ options[ :default ].kind_of?( Array ) &&
215
+ !options[ :type ]
216
+ options[ :default ].first
217
+ else
218
+ options[ :default ]
219
+ end
220
+
221
+ type_from_default =
222
+ case disambiguated_default
223
+ when Integer then :int
224
+ when Numeric then :float
225
+ when TrueClass,
226
+ FalseClass then :flag
227
+ when String then :string
228
+ when IO then :io
229
+ when Date then :date
230
+ when Array
231
+ if options[ :default ].empty?
232
+ if options[ :type ]
233
+ unless MULTI_ARG_TYPES.include?( options[ :type ] )
234
+ raise ArgumentError, "multiple argument type must be plural"
235
+ end
236
+ nil
237
+ else
238
+ raise ArgumentError, "multiple argument type cannot be deduced " +
239
+ "from an empty array for '#{ options[ :default ][ 0 ].class.name }'"
240
+ end
241
+ else
242
+ case options[:default][0]
243
+ when Integer then :ints
244
+ when Numeric then :floats
245
+ when String then :strings
246
+ when IO then :ios
247
+ when Date then :dates
248
+ else
249
+ raise ArgumentError, "unsupported multiple argument type " +
250
+ "'#{ options[ :default ][ 0 ].class.name }'"
251
+ end
252
+ end
253
+ when nil then nil
254
+ else
255
+ raise ArgumentError, "unsupported argument type " +
256
+ "'#{ options[ :default ].class.name }'"
257
+ end
258
+ end
259
+
260
+ private; def set_long( long, name )
261
+ long = long ? long.to_s : name.to_s.gsub( "_", "-" )
262
+ case long
263
+ when /^--([^-].*)$/ then $1
264
+ when /^[^-]/ then long
265
+ else
266
+ raise ArgumentError, "invalid long option name #{ long.inspect }"
267
+ end
268
+ end
269
+
270
+ private; def set_short( short )
271
+ short = short.to_s if short && short != :none
272
+ case short
273
+ when /^-(.)$/ then $1
274
+ when nil, :none, /^.$/ then short
275
+ else
276
+ raise ArgumentError, "invalid short option name '#{ short.inspect }'"
277
+ end
278
+ end
279
+ end
280
+
281
+ #----------------------------------------------------------------------------
282
+ # Parser Class
283
+
284
+ class Parser
285
+ INVALID_SHORT_ARG_REGEX = /[\d-]/
286
+ FLOAT_REGEX = /^-?((\d+(\.\d+)?)|(\.\d+))([eE][-+]?[\d]+)?$/
287
+ PARAM_REGEX = /^-(-|\.$|[^\d\.])/
288
+
289
+ attr_reader :leftovers
290
+ attr_reader :specs
291
+ attr_accessor :ignore_invalid_options
292
+
293
+ def initialize( *a, &b )
294
+ @leftovers = []
295
+ @specs = {}
296
+ @long = {}
297
+ @short = {}
298
+ @order = []
299
+ @constraints = []
300
+ @stop_on_unknown = false
301
+ @synopsis = nil
302
+
303
+ cloaker( &b ).bind( self ).call( *a ) if b
304
+ end
305
+
306
+ def options( options = [] )
307
+ options.each do | option |
308
+ if @specs.member?( option.name )
309
+ # raise ArgumentError, "you already have an argument named '#{ option.name }'"
310
+ options.delete( option ) # ignore duplicate option
311
+ elsif @long[ option.long ]
312
+ raise ArgumentError, "long option name #{ option.long.inspect } " +
313
+ "is already taken; please specify a (different) :long"
314
+ elsif @short[ option.short ]
315
+ raise ArgumentError, "short option name #{ option.short.inspect } " +
316
+ "is already taken; please specify a (different) :short"
317
+ else
318
+ @long[ option.long ] = option.name
319
+ @short[ option.short ] = option.name if option.short?
320
+ @specs[ option.name ] = option
321
+ @order << [ :option, option.name ]
322
+ end
323
+ end
324
+ end
325
+
326
+ def synopsis( synopsis = nil )
327
+ synopsis ? @synopsis = synopsis : @synopsis
328
+ end
329
+
330
+ def stop_on_unknown
331
+ @stop_on_unknown = true
332
+ end
333
+
334
+ def depends( *keys )
335
+ keys.each do | key |
336
+ raise ArgumentError, "unknown option '#{ key }'" unless @specs[ key ]
337
+ end
338
+ @constraints << [ :depends, keys ]
339
+ end
340
+
341
+ def conflicts( *keys )
342
+ keys.each do | key |
343
+ raise ArgumentError, "unknown option '#{ key }'" unless @specs[ key ]
344
+ end
345
+ @constraints << [ :conflicts, keys ]
346
+ end
347
+
348
+ def parse( cmdline = ARGV )
349
+ values = {}
350
+ required = {}
351
+
352
+ @specs.each do | key, option |
353
+ required[ key ] = true if option.required?
354
+ values[ key ] = option.default
355
+ values[ key ] = [] if option.multi && !option.default
356
+ end
357
+
358
+ resolve_default_short_options!
359
+
360
+ given_args = resolve_symbols( cmdline )
361
+
362
+ raise HelpNeeded if given_args.include? :help
363
+
364
+ @constraints.each do | type, syms |
365
+ constraint_sym = syms.find { | sym | given_args[ sym ] }
366
+ next unless constraint_sym
367
+
368
+ case type
369
+ when :depends
370
+ syms.each do | sym |
371
+ unless given_args.include? sym
372
+ raise CommandlineError, "--#{ @specs[ constraint_sym ].long } " +
373
+ "requires --#{ @specs[ sym ].long }"
374
+ end
375
+ end
376
+ when :conflicts
377
+ syms.each do | sym |
378
+ if given_args.include?( sym ) && ( sym != constraint_sym )
379
+ raise CommandlineError, "--#{ @specs[ constraint_sym ].long } " +
380
+ "conflicts with --#{ @specs[ sym ].long }"
381
+ end
382
+ end
383
+ end
384
+ end
385
+
386
+ required.each do | key, val |
387
+ unless given_args.include? key
388
+ raise CommandlineError, "option --#{ @specs[ key ].long } must be specified"
389
+ end
390
+ end
391
+
392
+ parse_parameters( given_args, values )
393
+
394
+ cmdline.clear
395
+ @leftovers.each { | leftover | cmdline << leftover }
396
+
397
+ # allow openstruct-style accessors
398
+ class << values
399
+ def method_missing( m, *_args )
400
+ self[ m ] || self[ m.to_s ]
401
+ end
402
+ end
403
+ values
404
+ end
405
+
406
+ def parse_date_parameter( param, arg )
407
+ begin
408
+ require 'chronic'
409
+ time = Chronic.parse(param)
410
+ rescue LoadError
411
+ # chronic is not available
412
+ end
413
+ time ? Date.new( time.year, time.month, time.day ) : Date.parse( param )
414
+ rescue ArgumentError
415
+ raise CommandlineError, "option '#{ arg }' needs a date"
416
+ end
417
+
418
+ def educate( stream = $stdout )
419
+ width # hack: calculate it now; otherwise we have to be careful not to
420
+ # call this unless the cursor's at the beginning of a line.
421
+ left = {}
422
+ @specs.each do | name, spec |
423
+ left[ name ] =
424
+ ( spec.short? ? "-#{ spec.short }, " : "" ) + "--#{ spec.long }" +
425
+ case spec.type
426
+ when :flag then ""
427
+ when :int then "=<i>"
428
+ when :ints then "=<i+>"
429
+ when :string then "=<s>"
430
+ when :strings then "=<s+>"
431
+ when :float then "=<f>"
432
+ when :floats then "=<f+>"
433
+ when :io then "=<filename/uri>"
434
+ when :ios then "=<filename/uri+>"
435
+ when :date then "=<date>"
436
+ when :dates then "=<date+>"
437
+ end +
438
+ ( spec.flag? && spec.default ? ", --no-#{ spec.long }" : "" )
439
+ end
440
+
441
+ leftcol_width = left.values.map( &:length ).max || 0
442
+ rightcol_start = leftcol_width + 6
443
+
444
+ unless @order.size > 0 && @order.first.first == :text
445
+ command_name = File.basename( $0 ).gsub( /\.[^.]+$/, '' )
446
+ stream.puts "#{ @synopsis }\n" if @synopsis
447
+ stream.puts "Options:"
448
+ end
449
+
450
+ @order.each do | type, option |
451
+ if type == :text
452
+ stream.puts wrap( option )
453
+ next
454
+ end
455
+
456
+ spec = @specs[ option ]
457
+ stream.printf " %-#{ leftcol_width }s ", left[ option ]
458
+ description = spec.description + begin
459
+ default_s = case spec.default
460
+ when $stdout then "<stdout>"
461
+ when $stdin then "<stdin>"
462
+ when $stderr then "<stderr>"
463
+ when Array
464
+ spec.default.join( ", " )
465
+ else
466
+ spec.default.to_s
467
+ end
468
+
469
+ if spec.default
470
+ if spec.description =~ /\.$/
471
+ " (Default: #{ default_s })"
472
+ else
473
+ " (default: #{ default_s })"
474
+ end
475
+ else
476
+ ""
477
+ end
478
+ end
479
+ stream.puts wrap(
480
+ description,
481
+ :width => width - rightcol_start - 1,
482
+ :prefix => rightcol_start
483
+ )
484
+ end
485
+ end
486
+
487
+ def width
488
+ @width ||= if $stdout.tty?
489
+ begin
490
+ require 'io/console'
491
+ w = IO.console.winsize.last
492
+ w.to_i > 0 ? w : 80
493
+ rescue LoadError, NoMethodError, Errno::ENOTTY, Errno::EBADF, Errno::EINVAL
494
+ legacy_width
495
+ end
496
+ else
497
+ 80
498
+ end
499
+ end
500
+
501
+ def wrap( str, opts = {} )
502
+ if str == ""
503
+ [ "" ]
504
+ else
505
+ inner = false
506
+ str.split( "\n" ).map do | s |
507
+ line = wrap_line s, opts.merge( :inner => inner )
508
+ inner = true
509
+ line
510
+ end.flatten
511
+ end
512
+ end
513
+
514
+ def die( arg, message = nil, error_code = nil )
515
+ if message
516
+ $stderr.puts "Error: argument --#{ @specs[ arg ].long } #{ message }."
517
+ else
518
+ $stderr.puts "Error: #{ arg }."
519
+ end
520
+ $stderr.puts
521
+ educate $stderr
522
+ exit( error_code || -1 )
523
+ end
524
+
525
+ #----------------------------------------------------------------------------
526
+ # Private Parser Methods
527
+
528
+ private; def resolve_symbols( cmdline )
529
+ given_args = {}
530
+ @leftovers = each_arg cmdline do | arg, params |
531
+ # handle --no- forms
532
+ arg, negative_given = if arg =~ /^--no-([^-]\S*)$/
533
+ [ "--#{ $1 }", true ]
534
+ else
535
+ [ arg, false ]
536
+ end
537
+
538
+ key = case arg
539
+ when /^-([^-])$/ then @short[ $1 ]
540
+ when /^--([^-]\S*)$/ then @long[ $1 ] || @long[ "no-#{ $1 }" ]
541
+ else raise CommandlineError, "invalid argument syntax: '#{ arg }'"
542
+ end
543
+
544
+ key = nil if arg =~ /--no-/ # explicitly invalidate --no-no- arguments
545
+
546
+ next nil if ignore_invalid_options && !key
547
+ raise CommandlineError, "unknown argument '#{ arg }'" unless key
548
+
549
+ if given_args.include?( key ) && !@specs[ key ].multi?
550
+ raise CommandlineError, "option '#{ arg }' specified multiple times"
551
+ end
552
+
553
+ given_args[ key ] ||= {}
554
+ given_args[ key ][ :arg ] = arg
555
+ given_args[ key ][ :negative_given ] = negative_given
556
+ given_args[ key ][ :params ] ||= []
557
+
558
+ # the block returns the number of parameters taken
559
+ num_params_taken = 0
560
+
561
+ unless params.empty?
562
+ if @specs[ key ].single_arg?
563
+ given_args[ key ][ :params ] << params[ 0, 1 ] # take the first parameter
564
+ num_params_taken = 1
565
+ elsif @specs[ key ].multi_arg?
566
+ given_args[ key ][ :params ] << params # take all the parameters
567
+ num_params_taken = params.size
568
+ end
569
+ end
570
+
571
+ num_params_taken
572
+ end
573
+ given_args
574
+ end
575
+
576
+ private; def parse_parameters( given_args, values )
577
+ given_args.each do | key, given_data |
578
+ arg, params, negative_given = given_data.values_at :arg, :params, :negative_given
579
+
580
+ option = @specs[ key ]
581
+ if params.empty? && !option.flag?
582
+ unless option.default
583
+ raise CommandlineError, "option '#{ arg }' needs a parameter"
584
+ end
585
+ params << ( option.array_default? ? option.default.clone : [ option.default ] )
586
+ end
587
+
588
+ # mark argument as specified on the commandline
589
+ values[ "#{ key }_given".intern ] = true
590
+
591
+ case option.type
592
+ when :flag
593
+ values[ key ] = ( key.to_s =~ /^no_/ ? negative_given : !negative_given )
594
+ when :int, :ints
595
+ values[ key ] = params.map do | param |
596
+ param.map { | p | parse_integer_parameter p, arg }
597
+ end
598
+ when :float, :floats
599
+ values[ key ] = params.map do | param |
600
+ param.map { | p | parse_float_parameter p, arg }
601
+ end
602
+ when :string, :strings
603
+ values[ key ] = params.map do | param |
604
+ param.map( &:to_s )
605
+ end
606
+ when :io, :ios
607
+ values[ key ] = params.map do | param |
608
+ param.map { | p | parse_io_parameter p, arg }
609
+ end
610
+ when :date, :dates
611
+ values[ key ] = params.map do | param |
612
+ param.map { | p | parse_date_parameter p, arg }
613
+ end
614
+ end
615
+
616
+ if option.single_arg?
617
+ if option.multi? # multiple options, each with a single parameter
618
+ values[ key ] = values[ key ].map { | p | p[ 0 ] }
619
+ else # single parameter
620
+ values[ key ] = values[ key ][ 0 ][ 0 ]
621
+ end
622
+ elsif option.multi_arg? && !option.multi?
623
+ values[ key ] = values[ key ][ 0 ] # single option, with multiple parameters
624
+ end
625
+ # else: multiple options, with multiple parameters
626
+ end
627
+ end
628
+
629
+ private; def legacy_width
630
+ # Support for older Rubies where io/console is not available
631
+ `tput cols`.to_i
632
+ rescue Errno::ENOENT
633
+ 80
634
+ end
635
+
636
+ private; def each_arg( args )
637
+ remains = []
638
+ i = 0
639
+
640
+ until i >= args.length
641
+ # return remains += args[ i..-1 ] if @stop_words.member? args[ i ]
642
+ case args[ i ]
643
+ when /^--$/ # arg terminator
644
+ return remains += args[ ( i + 1 )..-1 ]
645
+ when /^--(\S+?)=(.*)$/ # long argument with equals
646
+ num_params_taken = yield "--#{ $1 }", [ $2 ]
647
+ if num_params_taken.nil?
648
+ remains << args[ i ]
649
+ if @stop_on_unknown
650
+ return remains += args[ i + 1..-1 ]
651
+ end
652
+ end
653
+ i += 1
654
+ when /^--(\S+)$/ # long argument
655
+ params = collect_argument_parameters( args, i + 1 )
656
+ num_params_taken = yield args[ i ], params
657
+
658
+ if num_params_taken.nil?
659
+ remains << args[ i ]
660
+ if @stop_on_unknown
661
+ return remains += args[ i + 1..-1 ]
662
+ end
663
+ else
664
+ i += num_params_taken
665
+ end
666
+ i += 1
667
+ when /^-(\S+)$/ # one or more short arguments
668
+ short_remaining = ""
669
+ shortargs = $1.split( // )
670
+ shortargs.each_with_index do | a, j |
671
+ if j == ( shortargs.length - 1 )
672
+ params = collect_argument_parameters( args, i + 1 )
673
+
674
+ num_params_taken = yield "-#{ a }", params
675
+ unless num_params_taken
676
+ short_remaining << a
677
+ if @stop_on_unknown
678
+ remains << "-#{ short_remaining }"
679
+ return remains += args[ i + 1..-1 ]
680
+ end
681
+ else
682
+ i += num_params_taken
683
+ end
684
+ else
685
+ unless yield "-#{ a }", []
686
+ short_remaining << a
687
+ if @stop_on_unknown
688
+ short_remaining += shortargs[ j + 1..-1 ].join
689
+ remains << "-#{ short_remaining }"
690
+ return remains += args[ i + 1..-1 ]
691
+ end
692
+ end
693
+ end
694
+ end
695
+
696
+ unless short_remaining.empty?
697
+ remains << "-#{ short_remaining }"
698
+ end
699
+ i += 1
700
+ else
701
+ if @stop_on_unknown
702
+ return remains += args[ i..-1 ]
703
+ else
704
+ remains << args[ i ]
705
+ i += 1
706
+ end
707
+ end
708
+ end
709
+
710
+ remains
711
+ end
712
+
713
+ private; def parse_integer_parameter( param, arg )
714
+ unless param =~ /^-?[\d_]+$/
715
+ raise CommandlineError, "option '#{ arg }' needs an integer"
716
+ end
717
+ param.to_i
718
+ end
719
+
720
+ private; def parse_float_parameter( param, arg )
721
+ unless param =~ FLOAT_REGEX
722
+ raise CommandlineError, "option '#{ arg }' needs a floating-point number"
723
+ end
724
+ param.to_f
725
+ end
726
+
727
+ private; def parse_io_parameter( param, arg )
728
+ if param =~ /^(stdin|-)$/i
729
+ $stdin
730
+ else
731
+ require 'open-uri'
732
+ begin
733
+ open param
734
+ rescue SystemCallError => e
735
+ raise CommandlineError, "file or url for option '#{ arg }' " +
736
+ "cannot be opened: #{ e.message }"
737
+ end
738
+ end
739
+ end
740
+
741
+ private; def collect_argument_parameters( args, start_at )
742
+ params = []
743
+ pos = start_at
744
+ while args[ pos ] && args[ pos ] !~ PARAM_REGEX do
745
+ params << args[ pos ]
746
+ pos += 1
747
+ end
748
+ params
749
+ end
750
+
751
+ private; def resolve_default_short_options!
752
+ @order.each do | type, name |
753
+ option = @specs[ name ]
754
+ next if type != :option || option.short
755
+
756
+ c = option.long.split( // ).find do | d |
757
+ d !~ INVALID_SHORT_ARG_REGEX && !@short.member?( d )
758
+ end
759
+
760
+ if c
761
+ option.short = c
762
+ @short[ c ] = name
763
+ end
764
+ end
765
+ end
766
+
767
+ private; def wrap_line( str, opts = {} )
768
+ prefix = opts[ :prefix ] || 0
769
+ width = opts[ :width ] || ( self.width - 1 )
770
+ start = 0
771
+ ret = []
772
+ until start > str.length
773
+ nextt =
774
+ if start + width >= str.length
775
+ str.length
776
+ else
777
+ x = str.rindex( /\s/, start + width )
778
+ x = str.index( /\s/, start ) if x && x < start
779
+ x || str.length
780
+ end
781
+ ret << ( ( ret.empty? && !opts[ :inner ] ) ? "" : " " * prefix ) + str[ start...nextt ]
782
+ start = nextt + 1
783
+ end
784
+ ret
785
+ end
786
+
787
+ private; def cloaker( &b )
788
+ ( class << self; self; end ).class_eval do
789
+ define_method :cloaker_, &b
790
+ meth = instance_method :cloaker_
791
+ remove_method :cloaker_
792
+ meth
793
+ end
794
+ end
795
+ end
796
+ end