unimatrix-cli 2.2.0

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