MuranoCLI 2.2.4 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (159) hide show
  1. checksums.yaml +4 -4
  2. data/.agignore +3 -0
  3. data/.gitignore +18 -1
  4. data/.rubocop.yml +222 -0
  5. data/.trustme.sh +185 -0
  6. data/.trustme.vim +24 -0
  7. data/Gemfile +23 -4
  8. data/LICENSE.txt +1 -1
  9. data/MuranoCLI.gemspec +43 -8
  10. data/README.markdown +9 -11
  11. data/Rakefile +187 -143
  12. data/TODO.taskpaper +2 -2
  13. data/bin/murano +51 -52
  14. data/docs/basic_example.rst +436 -0
  15. data/docs/completions/murano_completion-bash +3484 -0
  16. data/docs/demo.md +32 -32
  17. data/docs/develop.rst +391 -0
  18. data/lib/MrMurano.rb +21 -7
  19. data/lib/MrMurano/Account.rb +159 -174
  20. data/lib/MrMurano/Business.rb +381 -0
  21. data/lib/MrMurano/Config-Migrate.rb +32 -26
  22. data/lib/MrMurano/Config.rb +407 -128
  23. data/lib/MrMurano/Content.rb +191 -0
  24. data/lib/MrMurano/Gateway.rb +489 -0
  25. data/lib/MrMurano/Keystore.rb +48 -0
  26. data/lib/MrMurano/Passwords.rb +103 -0
  27. data/lib/MrMurano/ProjectFile.rb +121 -79
  28. data/lib/MrMurano/ReCommander.rb +114 -10
  29. data/lib/MrMurano/Setting.rb +90 -0
  30. data/lib/MrMurano/Solution-ServiceConfig.rb +89 -45
  31. data/lib/MrMurano/Solution-Services.rb +461 -166
  32. data/lib/MrMurano/Solution-Users.rb +70 -31
  33. data/lib/MrMurano/Solution.rb +372 -13
  34. data/lib/MrMurano/SolutionId.rb +73 -0
  35. data/lib/MrMurano/SyncRoot.rb +137 -0
  36. data/lib/MrMurano/SyncUpDown.rb +594 -284
  37. data/lib/MrMurano/Webservice-Cors.rb +71 -0
  38. data/lib/MrMurano/Webservice-Endpoint.rb +234 -0
  39. data/lib/MrMurano/Webservice-File.rb +193 -0
  40. data/lib/MrMurano/Webservice.rb +51 -0
  41. data/lib/MrMurano/commands.rb +18 -15
  42. data/lib/MrMurano/commands/business.rb +300 -6
  43. data/lib/MrMurano/commands/completion-bash.erb +166 -0
  44. data/lib/MrMurano/commands/{zshcomplete.erb → completion-zsh.erb} +0 -0
  45. data/lib/MrMurano/commands/completion.rb +76 -39
  46. data/lib/MrMurano/commands/config.rb +108 -44
  47. data/lib/MrMurano/commands/content.rb +115 -72
  48. data/lib/MrMurano/commands/cors.rb +29 -14
  49. data/lib/MrMurano/commands/devices.rb +286 -0
  50. data/lib/MrMurano/commands/domain.rb +52 -12
  51. data/lib/MrMurano/commands/gb.rb +24 -9
  52. data/lib/MrMurano/commands/globals.rb +64 -0
  53. data/lib/MrMurano/commands/init.rb +377 -155
  54. data/lib/MrMurano/commands/keystore.rb +92 -82
  55. data/lib/MrMurano/commands/link.rb +300 -0
  56. data/lib/MrMurano/commands/login.rb +74 -11
  57. data/lib/MrMurano/commands/logs.rb +63 -32
  58. data/lib/MrMurano/commands/mock.rb +57 -29
  59. data/lib/MrMurano/commands/password.rb +57 -39
  60. data/lib/MrMurano/commands/postgresql.rb +127 -94
  61. data/lib/MrMurano/commands/settings.rb +203 -0
  62. data/lib/MrMurano/commands/show.rb +79 -38
  63. data/lib/MrMurano/commands/solution.rb +423 -5
  64. data/lib/MrMurano/commands/solution_picker.rb +547 -0
  65. data/lib/MrMurano/commands/status.rb +195 -61
  66. data/lib/MrMurano/commands/sync.rb +78 -39
  67. data/lib/MrMurano/commands/timeseries.rb +71 -55
  68. data/lib/MrMurano/commands/tsdb.rb +113 -87
  69. data/lib/MrMurano/commands/usage.rb +57 -15
  70. data/lib/MrMurano/hash.rb +100 -10
  71. data/lib/MrMurano/http.rb +187 -43
  72. data/lib/MrMurano/makePretty.rb +16 -14
  73. data/lib/MrMurano/optparse.rb +2178 -0
  74. data/lib/MrMurano/progress.rb +138 -0
  75. data/lib/MrMurano/schema/resource-v1.0.0.yaml +32 -0
  76. data/lib/MrMurano/template/projectFile.murano.erb +16 -13
  77. data/lib/MrMurano/verbosing.rb +166 -29
  78. data/lib/MrMurano/version.rb +30 -1
  79. data/spec/Account-Passwords_spec.rb +21 -4
  80. data/spec/Account_spec.rb +69 -146
  81. data/spec/Business_spec.rb +290 -0
  82. data/spec/ConfigFile_spec.rb +1 -0
  83. data/spec/ConfigMigrate_spec.rb +12 -8
  84. data/spec/Config_spec.rb +40 -34
  85. data/spec/Content_spec.rb +363 -0
  86. data/spec/GatewayBase_spec.rb +54 -0
  87. data/spec/GatewayDevice_spec.rb +321 -0
  88. data/spec/GatewayResource_spec.rb +266 -0
  89. data/spec/GatewaySettings_spec.rb +120 -0
  90. data/spec/Http_spec.rb +18 -8
  91. data/spec/Mock_spec.rb +2 -2
  92. data/spec/ProjectFile_spec.rb +25 -14
  93. data/spec/Setting_spec.rb +110 -0
  94. data/spec/Solution-ServiceConfig_spec.rb +44 -5
  95. data/spec/Solution-ServiceEventHandler_spec.rb +23 -14
  96. data/spec/Solution-ServiceModules_spec.rb +47 -37
  97. data/spec/Solution-UsersRoles_spec.rb +10 -8
  98. data/spec/Solution_spec.rb +17 -8
  99. data/spec/SyncRoot_spec.rb +46 -20
  100. data/spec/SyncUpDown_spec.rb +437 -201
  101. data/spec/Verbosing_spec.rb +12 -4
  102. data/spec/{Solution-Cors_spec.rb → Webservice-Cors_spec.rb} +23 -20
  103. data/spec/{Solution-Endpoint_spec.rb → Webservice-Endpoint_spec.rb} +43 -41
  104. data/spec/{Solution-File_spec.rb → Webservice-File_spec.rb} +44 -33
  105. data/spec/Webservice-Setting_spec.rb +89 -0
  106. data/spec/_workspace.rb +4 -4
  107. data/spec/cmd_business_spec.rb +9 -4
  108. data/spec/cmd_common.rb +44 -1
  109. data/spec/cmd_content_spec.rb +43 -17
  110. data/spec/cmd_cors_spec.rb +4 -4
  111. data/spec/cmd_device_spec.rb +61 -16
  112. data/spec/cmd_domain_spec.rb +29 -6
  113. data/spec/cmd_init_spec.rb +281 -126
  114. data/spec/cmd_keystore_spec.rb +3 -3
  115. data/spec/cmd_link_spec.rb +98 -0
  116. data/spec/cmd_password_spec.rb +1 -1
  117. data/spec/cmd_setting_application_spec.rb +260 -0
  118. data/spec/cmd_setting_product_spec.rb +220 -0
  119. data/spec/cmd_status_spec.rb +223 -114
  120. data/spec/cmd_syncdown_spec.rb +115 -35
  121. data/spec/cmd_syncup_spec.rb +68 -15
  122. data/spec/cmd_usage_spec.rb +35 -8
  123. data/spec/fixtures/dumped_config +6 -4
  124. data/spec/fixtures/gateway_resource_files/resources.notyaml +12 -0
  125. data/spec/fixtures/gateway_resource_files/resources.yaml +13 -0
  126. data/spec/fixtures/gateway_resource_files/resources_invalid.yaml +13 -0
  127. data/spec/fixtures/mrmuranorc_deleted_bob +0 -2
  128. data/spec/fixtures/product_spec_files/lightbulb.yaml +20 -13
  129. data/spec/fixtures/{syncable_content → syncable_conflict}/services/devdata.lua +1 -1
  130. data/spec/fixtures/{syncable_content → syncable_conflict}/services/timers.lua +0 -0
  131. data/spec/spec_helper.rb +5 -0
  132. metadata +262 -171
  133. data/bin/mr +0 -8
  134. data/lib/MrMurano/Product-1P-Device.rb +0 -145
  135. data/lib/MrMurano/Product-Resources.rb +0 -205
  136. data/lib/MrMurano/Product.rb +0 -358
  137. data/lib/MrMurano/Solution-Cors.rb +0 -47
  138. data/lib/MrMurano/Solution-Endpoint.rb +0 -191
  139. data/lib/MrMurano/Solution-File.rb +0 -166
  140. data/lib/MrMurano/commands/assign.rb +0 -57
  141. data/lib/MrMurano/commands/businessList.rb +0 -45
  142. data/lib/MrMurano/commands/product.rb +0 -14
  143. data/lib/MrMurano/commands/productCreate.rb +0 -39
  144. data/lib/MrMurano/commands/productDelete.rb +0 -33
  145. data/lib/MrMurano/commands/productDevice.rb +0 -87
  146. data/lib/MrMurano/commands/productDeviceIdCmds.rb +0 -89
  147. data/lib/MrMurano/commands/productList.rb +0 -45
  148. data/lib/MrMurano/commands/productWrite.rb +0 -27
  149. data/lib/MrMurano/commands/solutionCreate.rb +0 -41
  150. data/lib/MrMurano/commands/solutionDelete.rb +0 -34
  151. data/lib/MrMurano/commands/solutionList.rb +0 -45
  152. data/spec/ProductBase_spec.rb +0 -113
  153. data/spec/ProductContent_spec.rb +0 -162
  154. data/spec/ProductResources_spec.rb +0 -329
  155. data/spec/Product_1P_Device_spec.rb +0 -202
  156. data/spec/Product_1P_RPC_spec.rb +0 -175
  157. data/spec/Product_spec.rb +0 -153
  158. data/spec/Solution-ServiceDevice_spec.rb +0 -176
  159. data/spec/cmd_assign_spec.rb +0 -51
@@ -1,18 +1,41 @@
1
- require 'uri'
2
- require 'net/http'
1
+ # Last Modified: 2017.08.14 /coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ # Copyright © 2016-2017 Exosite LLC.
5
+ # License: MIT. See LICENSE.txt.
6
+ # vim:tw=0:ts=2:sw=2:et:ai
7
+
8
+ require 'certified' if Gem.win_platform?
9
+ require 'date'
3
10
  require 'json'
4
- require('certified') if Gem.win_platform?
11
+ require 'net/http'
12
+ require 'uri'
13
+ # 2017-06-07: [lb] getting "execution expired (Net::OpenTimeout)" on http.start.
14
+ # Suggestions online say to load the pure-Ruby DNS implementation, resolv.rb.
15
+ require 'resolv-replace'
16
+ require 'MrMurano/progress'
5
17
 
6
18
  module MrMurano
7
19
  module Http
8
20
  def token
9
- return @token unless @token.nil?
10
- acc = Account.new
21
+ return @token if defined?(@token) && !@token.to_s.empty?
22
+ acc = MrMurano::Account.instance
11
23
  @token = acc.token
12
- raise "Not logged in!" if @token.nil?
24
+ #raise 'Not logged in!' if @token.nil?
25
+ ensure_token! @token
26
+ # MAYBE: Check that ADC is enabled on the business. If not, tell
27
+ # user to run Murano 2.x. See adc_compat_check for comments.
28
+ #acc.adc_compat_check
13
29
  @token
14
30
  end
15
31
 
32
+ def ensure_token!(tok)
33
+ if tok.nil?
34
+ error 'Not logged in!'
35
+ exit 1
36
+ end
37
+ end
38
+
16
39
  def json_opts
17
40
  return @json_opts unless not defined?(@json_opts) or @json_opts.nil?
18
41
  @json_opts = {
@@ -23,24 +46,44 @@ module MrMurano
23
46
  end
24
47
 
25
48
  def curldebug(request)
26
- if $cfg['tool.curldebug'] then
49
+ if $cfg['tool.curldebug']
50
+ formp = (request.content_type =~ %r{multipart/form-data})
27
51
  a = []
28
- a << %{curl -s }
29
- if request.key?('Authorization') then
52
+ a << %{curl -s}
53
+ if request.key?('Authorization')
30
54
  a << %{-H 'Authorization: #{request['Authorization']}'}
31
55
  end
32
56
  a << %{-H 'User-Agent: #{request['User-Agent']}'}
33
- a << %{-H 'Content-Type: #{request.content_type}'}
57
+ a << %{-H 'Content-Type: #{request.content_type}'} unless formp
34
58
  a << %{-X #{request.method}}
35
59
  a << %{'#{request.uri.to_s}'}
36
- a << %{-d '#{request.body}'} unless request.body.nil?
37
- puts a.join(' ')
60
+ unless request.body.nil?
61
+ if formp
62
+ m = request.body.match(
63
+ %r{form-data;\s+name="(?<name>[^"]+)";\s+filename="(?<filename>[^"]+)"}
64
+ )
65
+ a << %{-F #{m[:name]}=@#{m[:filename]}} unless m.nil?
66
+ else
67
+ a << %{-d '#{request.body}'}
68
+ end
69
+ end
70
+ if $cfg.curlfile_f.nil?
71
+ MrMurano::Progress.instance.whirly_interject { puts a.join(' ') }
72
+ else
73
+ $cfg.curlfile_f << a.join(' ') + "\n\n"
74
+ $cfg.curlfile_f.flush
75
+ end
38
76
  end
39
77
  end
40
78
 
79
+ # Default endpoint unless Class overrides it.
80
+ def endpoint(path)
81
+ URI('https://' + $cfg['net.host'] + '/api:1/' + path.to_s)
82
+ end
83
+
41
84
  def http
42
85
  uri = URI('https://' + $cfg['net.host'])
43
- if not defined?(@http) or @http.nil? then
86
+ if not defined?(@http) or @http.nil?
44
87
  @http = Net::HTTP.new(uri.host, uri.port)
45
88
  @http.use_ssl = true
46
89
  @http.start
@@ -53,6 +96,9 @@ module MrMurano
53
96
 
54
97
  def set_def_headers(request)
55
98
  request.content_type = 'application/json'
99
+ # 2017-08-14: MrMurano::Account overrides the token method, and
100
+ # it doesn't exit if no token, and then we end up here.
101
+ ensure_token! token
56
102
  request['Authorization'] = 'token ' + token
57
103
  request['User-Agent'] = "MrMurano/#{MrMurano::VERSION}"
58
104
  request
@@ -60,17 +106,17 @@ module MrMurano
60
106
 
61
107
  def isJSON(data)
62
108
  begin
63
- return true, JSON.parse(data, json_opts)
109
+ [true, JSON.parse(data, json_opts)]
64
110
  rescue
65
- return false, data
111
+ [false, data]
66
112
  end
67
113
  end
68
114
 
69
115
  def showHttpError(request, response)
70
- if $cfg['tool.debug'] then
116
+ if $cfg['tool.debug']
71
117
  puts "Sent #{request.method} #{request.uri.to_s}"
72
118
  request.each_capitalized{|k,v| puts "> #{k}: #{v}"}
73
- if request.body.nil? then
119
+ if request.body.nil?
74
120
  else
75
121
  puts ">> #{request.body[0..156]}"
76
122
  end
@@ -79,15 +125,19 @@ module MrMurano
79
125
  end
80
126
  isj, jsn = isJSON(response.body)
81
127
  resp = "Request Failed: #{response.code}: "
82
- if isj then
83
- if $cfg['tool.fullerror'] then
84
- resp << JSON.pretty_generate(jsn)
128
+ if isj
129
+ # 2017-07-02: Changing shovel operator << to +=
130
+ # to support Ruby 3.0 frozen string literals.
131
+ if $cfg['tool.fullerror']
132
+ resp += JSON.pretty_generate(jsn)
133
+ elsif jsn.kind_of? Hash
134
+ resp += "[#{jsn[:statusCode]}] " if jsn.has_key? :statusCode
135
+ resp += jsn[:message] if jsn.has_key? :message
85
136
  else
86
- resp << "[#{jsn[:statusCode]}] " if jsn.has_key? :statusCode
87
- resp << jsn[:message] if jsn.has_key? :message
137
+ resp += jsn.to_s
88
138
  end
89
139
  else
90
- resp << jsn
140
+ resp += (jsn or 'nil')
91
141
  end
92
142
  # assuming verbosing was included.
93
143
  error resp
@@ -95,38 +145,40 @@ module MrMurano
95
145
 
96
146
  def workit(request, &block)
97
147
  curldebug(request)
98
- if block_given? then
99
- return yield request, http()
148
+ if block_given?
149
+ yield request, http()
100
150
  else
101
151
  response = http().request(request)
102
152
  case response
103
153
  when Net::HTTPSuccess
104
- return {} if response.body.nil?
105
- begin
106
- return JSON.parse(response.body, json_opts)
107
- rescue
108
- return response.body
109
- end
154
+ workit_response(response)
110
155
  else
111
- showHttpError(request, response)
112
- return {} if response.body.nil?
113
- begin
114
- return JSON.parse(response.body, json_opts)
115
- rescue
116
- return response.body
156
+ # One problem with mixins is initialization...
157
+ unless defined?(@suppress_error) && @suppress_error
158
+ showHttpError(request, response)
117
159
  end
160
+ nil
118
161
  end
119
162
  end
120
163
  end
121
164
 
165
+ def workit_response(response)
166
+ return {} if response.body.nil?
167
+ begin
168
+ JSON.parse(response.body, json_opts)
169
+ rescue
170
+ response.body
171
+ end
172
+ end
173
+
122
174
  def get(path='', query=nil, &block)
123
- uri = endPoint(path)
175
+ uri = endpoint(path)
124
176
  uri.query = URI.encode_www_form(query) unless query.nil?
125
177
  workit(set_def_headers(Net::HTTP::Get.new(uri)), &block)
126
178
  end
127
179
 
128
180
  def post(path='', body={}, &block)
129
- uri = endPoint(path)
181
+ uri = endpoint(path)
130
182
  req = Net::HTTP::Post.new(uri)
131
183
  set_def_headers(req)
132
184
  req.body = JSON.generate(body)
@@ -134,7 +186,7 @@ module MrMurano
134
186
  end
135
187
 
136
188
  def postf(path='', form={}, &block)
137
- uri = endPoint(path)
189
+ uri = endpoint(path)
138
190
  req = Net::HTTP::Post.new(uri)
139
191
  set_def_headers(req)
140
192
  req.content_type = 'application/x-www-form-urlencoded; charset=utf-8'
@@ -143,18 +195,110 @@ module MrMurano
143
195
  end
144
196
 
145
197
  def put(path='', body={}, &block)
146
- uri = endPoint(path)
198
+ uri = endpoint(path)
147
199
  req = Net::HTTP::Put.new(uri)
148
200
  set_def_headers(req)
149
201
  req.body = JSON.generate(body)
150
202
  workit(req, &block)
151
203
  end
152
204
 
205
+ def patch(path='', body={}, &block)
206
+ uri = endpoint(path)
207
+ req = Net::HTTP::Patch.new(uri)
208
+ set_def_headers(req)
209
+ req.body = JSON.generate(body)
210
+ workit(req, &block)
211
+ end
212
+
153
213
  def delete(path='', &block)
154
- uri = endPoint(path)
214
+ uri = endpoint(path)
155
215
  workit(set_def_headers(Net::HTTP::Delete.new(uri)), &block)
156
216
  end
217
+ end
218
+ end
157
219
 
220
+ # There is a bug where having TCP_NODELAY disabled causes connection issues
221
+ # with Murano. While ultimately the bug is Murano's, we need to work around
222
+ # here. As for Ruby, setting TCP_NODELAY was added in 2.1. But since the
223
+ # default version installed on MacOS is 2.0.0, we oftentimes hit it.
224
+ #
225
+ # So, if the current version of Ruby is 2.0.0, then use this bit of code
226
+ # copied from Ruby 2.1 (lib/net/http.rb, at line 868).
227
+
228
+ if RUBY_VERSION == '2.0.0'
229
+ module Net
230
+ class HTTP
231
+ def connect
232
+ if proxy?
233
+ conn_address = proxy_address
234
+ conn_port = proxy_port
235
+ else
236
+ conn_address = address
237
+ conn_port = port
238
+ end
239
+
240
+ D "opening connection to #{conn_address}:#{conn_port}..."
241
+ s = Timeout.timeout(@open_timeout, Net::OpenTimeout) {
242
+ TCPSocket.open(conn_address, conn_port, @local_host, @local_port)
243
+ }
244
+ s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
245
+ D "opened"
246
+ if use_ssl?
247
+ ssl_parameters = Hash.new
248
+ iv_list = instance_variables
249
+ SSL_IVNAMES.each_with_index do |ivname, i|
250
+ if iv_list.include?(ivname) and
251
+ value = instance_variable_get(ivname)
252
+ ssl_parameters[SSL_ATTRIBUTES[i]] = value if value
253
+ end
254
+ end
255
+ @ssl_context = OpenSSL::SSL::SSLContext.new
256
+ @ssl_context.set_params(ssl_parameters)
257
+ D "starting SSL for #{conn_address}:#{conn_port}..."
258
+ s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context)
259
+ s.sync_close = true
260
+ D "SSL established"
261
+ end
262
+ @socket = BufferedIO.new(s)
263
+ @socket.read_timeout = @read_timeout
264
+ @socket.continue_timeout = @continue_timeout
265
+ @socket.debug_output = @debug_output
266
+ if use_ssl?
267
+ begin
268
+ if proxy?
269
+ # 2017-07-02: Changing shovel operator << to +=
270
+ # to support Ruby 3.0 frozen string literals.
271
+ buf = "CONNECT #{@address}:#{@port} HTTP/#{HTTPVersion}\r\n"
272
+ buf += "Host: #{@address}:#{@port}\r\n"
273
+ if proxy_user
274
+ credential = ["#{proxy_user}:#{proxy_pass}"].pack('m')
275
+ credential.delete!("\r\n")
276
+ buf += "Proxy-Authorization: Basic #{credential}\r\n"
277
+ end
278
+ buf += "\r\n"
279
+ @socket.write(buf)
280
+ HTTPResponse.read_new(@socket).value
281
+ end
282
+ # Server Name Indication (SNI) RFC 3546
283
+ s.hostname = @address if s.respond_to? :hostname=
284
+ if @ssl_session and
285
+ Time.now < @ssl_session.time + @ssl_session.timeout
286
+ s.session = @ssl_session if @ssl_session
287
+ end
288
+ Timeout.timeout(@open_timeout, Net::OpenTimeout) { s.connect }
289
+ if @ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE
290
+ s.post_connection_check(@address)
291
+ end
292
+ @ssl_session = s.session
293
+ rescue => exception
294
+ D "Conn close because of connect error #{exception}"
295
+ @socket.close if @socket and not @socket.closed?
296
+ raise exception
297
+ end
298
+ end
299
+ on_connect
300
+ end
301
+ end
158
302
  end
159
303
  end
160
- # vim: set ai et sw=2 ts=2 :
304
+
@@ -25,10 +25,12 @@ module MrMurano
25
25
  end
26
26
 
27
27
  def self.makePretty(line, options)
28
- out=''
29
- out << HighLine.color("#{line[:type] || '--'} ".upcase, :subject)
30
- out << HighLine.color("[#{line[:subject] || ''}]", :subject)
31
- out << " "
28
+ # 2017-07-02: Changing shovel operator << to +=
29
+ # to support Ruby 3.0 frozen string literals.
30
+ out = ''
31
+ out += HighLine.color("#{line[:type] || '--'} ".upcase, :subject)
32
+ out += HighLine.color("[#{line[:subject] || ''}]", :subject)
33
+ out += ' '
32
34
  if line.has_key?(:timestamp) then
33
35
  if line[:timestamp].kind_of? Numeric then
34
36
  if options.localtime then
@@ -40,32 +42,32 @@ module MrMurano
40
42
  curtime = line[:timestamp]
41
43
  end
42
44
  else
43
- curtime = "<no timestamp>"
45
+ curtime = '<no timestamp>'
44
46
  end
45
- out << HighLine.color(curtime, :timestamp)
46
- out << ":\n"
47
+ out += HighLine.color(curtime, :timestamp)
48
+ out += ":\n"
47
49
  if line.has_key?(:data) then
48
50
  data = line[:data]
49
51
 
50
52
  if data.kind_of?(Hash) then
51
53
  if data.has_key?(:request) and data.has_key?(:response) then
52
- out << "---------\nrequest:"
53
- out << makeJsonPretty(data[:request], options)
54
+ out += "---------\nrequest:"
55
+ out += makeJsonPretty(data[:request], options)
54
56
 
55
- out << "\n---------\nresponse:"
56
- out << makeJsonPretty(data[:response], options)
57
+ out += "\n---------\nresponse:"
58
+ out += makeJsonPretty(data[:response], options)
57
59
  else
58
- out << makeJsonPretty(data, options)
60
+ out += makeJsonPretty(data, options)
59
61
  end
60
62
  else
61
- out << data.to_s
63
+ out += data.to_s
62
64
  end
63
65
 
64
66
  else
65
67
  line.delete :type
66
68
  line.delete :timestamp
67
69
  line.delete :subject
68
- out << makeJsonPretty(line, options)
70
+ out += makeJsonPretty(line, options)
69
71
  end
70
72
  out
71
73
  end
@@ -0,0 +1,2178 @@
1
+ # frozen_string_literal: false
2
+ #
3
+ # optparse.rb - command-line option analysis with the OptionParser class.
4
+ #
5
+ # Author:: Nobu Nakada
6
+ # Documentation:: Nobu Nakada and Gavin Sinclair.
7
+ #
8
+ # See OptionParser for documentation.
9
+ #
10
+
11
+ # 2017-08-16: Copied by [lb] from Ruby 2.3.0:
12
+ #
13
+ # ~/.rubies/ruby-2.3.3/lib/ruby/2.3.0/optparse.rb
14
+ #
15
+ # to fix a problem with single character option abbreviations.
16
+ # Search herein for [lb] to see changes.
17
+
18
+ Object.send(:remove_const, :OptionParser) if defined?(OptionParser)
19
+ Object.send(:remove_const, :OptParse) if defined?(OptParse)
20
+
21
+ #--
22
+ # == Developer Documentation (not for RDoc output)
23
+ #
24
+ # === Class tree
25
+ #
26
+ # - OptionParser:: front end
27
+ # - OptionParser::Switch:: each switches
28
+ # - OptionParser::List:: options list
29
+ # - OptionParser::ParseError:: errors on parsing
30
+ # - OptionParser::AmbiguousOption
31
+ # - OptionParser::NeedlessArgument
32
+ # - OptionParser::MissingArgument
33
+ # - OptionParser::InvalidOption
34
+ # - OptionParser::InvalidArgument
35
+ # - OptionParser::AmbiguousArgument
36
+ #
37
+ # === Object relationship diagram
38
+ #
39
+ # +--------------+
40
+ # | OptionParser |<>-----+
41
+ # +--------------+ | +--------+
42
+ # | ,-| Switch |
43
+ # on_head -------->+---------------+ / +--------+
44
+ # accept/reject -->| List |<|>-
45
+ # | |<|>- +----------+
46
+ # on ------------->+---------------+ `-| argument |
47
+ # : : | class |
48
+ # +---------------+ |==========|
49
+ # on_tail -------->| | |pattern |
50
+ # +---------------+ |----------|
51
+ # OptionParser.accept ->| DefaultList | |converter |
52
+ # reject |(shared between| +----------+
53
+ # | all instances)|
54
+ # +---------------+
55
+ #
56
+ #++
57
+ #
58
+ # == OptionParser
59
+ #
60
+ # === Introduction
61
+ #
62
+ # OptionParser is a class for command-line option analysis. It is much more
63
+ # advanced, yet also easier to use, than GetoptLong, and is a more Ruby-oriented
64
+ # solution.
65
+ #
66
+ # === Features
67
+ #
68
+ # 1. The argument specification and the code to handle it are written in the
69
+ # same place.
70
+ # 2. It can output an option summary; you don't need to maintain this string
71
+ # separately.
72
+ # 3. Optional and mandatory arguments are specified very gracefully.
73
+ # 4. Arguments can be automatically converted to a specified class.
74
+ # 5. Arguments can be restricted to a certain set.
75
+ #
76
+ # All of these features are demonstrated in the examples below. See
77
+ # #make_switch for full documentation.
78
+ #
79
+ # === Minimal example
80
+ #
81
+ # require 'optparse'
82
+ #
83
+ # options = {}
84
+ # OptionParser.new do |opts|
85
+ # opts.banner = "Usage: example.rb [options]"
86
+ #
87
+ # opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
88
+ # options[:verbose] = v
89
+ # end
90
+ # end.parse!
91
+ #
92
+ # p options
93
+ # p ARGV
94
+ #
95
+ # === Generating Help
96
+ #
97
+ # OptionParser can be used to automatically generate help for the commands you
98
+ # write:
99
+ #
100
+ # require 'optparse'
101
+ #
102
+ # Options = Struct.new(:name)
103
+ #
104
+ # class Parser
105
+ # def self.parse(options)
106
+ # args = Options.new("world")
107
+ #
108
+ # opt_parser = OptionParser.new do |opts|
109
+ # opts.banner = "Usage: example.rb [options]"
110
+ #
111
+ # opts.on("-nNAME", "--name=NAME", "Name to say hello to") do |n|
112
+ # args.name = n
113
+ # end
114
+ #
115
+ # opts.on("-h", "--help", "Prints this help") do
116
+ # puts opts
117
+ # exit
118
+ # end
119
+ # end
120
+ #
121
+ # opt_parser.parse!(options)
122
+ # return args
123
+ # end
124
+ # end
125
+ # options = Parser.parse %w[--help]
126
+ #
127
+ # #=>
128
+ # # Usage: example.rb [options]
129
+ # # -n, --name=NAME Name to say hello to
130
+ # # -h, --help Prints this help
131
+ #
132
+ # === Required Arguments
133
+ #
134
+ # For options that require an argument, option specification strings may include an
135
+ # option name in all caps. If an option is used without the required argument,
136
+ # an exception will be raised.
137
+ # require 'optparse'
138
+ #
139
+ # options = {}
140
+ # OptionParser.new do |parser|
141
+ # parser.on("-r", "--require LIBRARY",
142
+ # "Require the LIBRARY before executing your script") do |lib|
143
+ # puts "You required #{lib}!"
144
+ # end
145
+ # end.parse!
146
+ #
147
+ # Used:
148
+ #
149
+ # bash-3.2$ ruby optparse-test.rb -r
150
+ # optparse-test.rb:9:in `<main>': missing argument: -r (OptionParser::MissingArgument)
151
+ # bash-3.2$ ruby optparse-test.rb -r my-library
152
+ # You required my-library!
153
+ #
154
+ # === Type Coercion
155
+ #
156
+ # OptionParser supports the ability to coerce command line arguments
157
+ # into objects for us.
158
+ #
159
+ # OptionParser comes with a few ready-to-use kinds of type
160
+ # coercion. They are:
161
+ #
162
+ # - Date -- Anything accepted by +Date.parse+
163
+ # - DateTime -- Anything accepted by +DateTime.parse+
164
+ # - Time -- Anything accepted by +Time.httpdate+ or +Time.parse+
165
+ # - URI -- Anything accepted by +URI.parse+
166
+ # - Shellwords -- Anything accepted by +Shellwords.shellwords+
167
+ # - String -- Any non-empty string
168
+ # - Integer -- Any integer. Will convert octal. (e.g. 124, -3, 040)
169
+ # - Float -- Any float. (e.g. 10, 3.14, -100E+13)
170
+ # - Numeric -- Any integer, float, or rational (1, 3.4, 1/3)
171
+ # - DecimalInteger -- Like +Integer+, but no octal format.
172
+ # - OctalInteger -- Like +Integer+, but no decimal format.
173
+ # - DecimalNumeric -- Decimal integer or float.
174
+ # - TrueClass -- Accepts '+, yes, true, -, no, false' and
175
+ # defaults as +true+
176
+ # - FalseClass -- Same as +TrueClass+, but defaults to +false+
177
+ # - Array -- Strings separated by ',' (e.g. 1,2,3)
178
+ # - Regexp -- Regular expressions. Also includes options.
179
+ #
180
+ # We can also add our own coercions, which we will cover soon.
181
+ #
182
+ # ==== Using Built-in Conversions
183
+ #
184
+ # As an example, the built-in +Time+ conversion is used. The other built-in
185
+ # conversions behave in the same way.
186
+ # OptionParser will attempt to parse the argument
187
+ # as a +Time+. If it succeeds, that time will be passed to the
188
+ # handler block. Otherwise, an exception will be raised.
189
+ #
190
+ # require 'optparse'
191
+ # require 'optparse/time'
192
+ # OptionParser.new do |parser|
193
+ # parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time|
194
+ # p time
195
+ # end
196
+ # end.parse!
197
+ #
198
+ # Used:
199
+ # bash-3.2$ ruby optparse-test.rb -t nonsense
200
+ # ... invalid argument: -t nonsense (OptionParser::InvalidArgument)
201
+ # from ... time.rb:5:in `block in <top (required)>'
202
+ # from optparse-test.rb:31:in `<main>'
203
+ # bash-3.2$ ruby optparse-test.rb -t 10-11-12
204
+ # 2010-11-12 00:00:00 -0500
205
+ # bash-3.2$ ruby optparse-test.rb -t 9:30
206
+ # 2014-08-13 09:30:00 -0400
207
+ #
208
+ # ==== Creating Custom Conversions
209
+ #
210
+ # The +accept+ method on OptionParser may be used to create converters.
211
+ # It specifies which conversion block to call whenever a class is specified.
212
+ # The example below uses it to fetch a +User+ object before the +on+ handler receives it.
213
+ #
214
+ # require 'optparse'
215
+ #
216
+ # User = Struct.new(:id, :name)
217
+ #
218
+ # def find_user id
219
+ # not_found = ->{ raise "No User Found for id #{id}" }
220
+ # [ User.new(1, "Sam"),
221
+ # User.new(2, "Gandalf") ].find(not_found) do |u|
222
+ # u.id == id
223
+ # end
224
+ # end
225
+ #
226
+ # op = OptionParser.new
227
+ # op.accept(User) do |user_id|
228
+ # find_user user_id.to_i
229
+ # end
230
+ #
231
+ # op.on("--user ID", User) do |user|
232
+ # puts user
233
+ # end
234
+ #
235
+ # op.parse!
236
+ #
237
+ # output:
238
+ # bash-3.2$ ruby optparse-test.rb --user 1
239
+ # #<struct User id=1, name="Sam">
240
+ # bash-3.2$ ruby optparse-test.rb --user 2
241
+ # #<struct User id=2, name="Gandalf">
242
+ # bash-3.2$ ruby optparse-test.rb --user 3
243
+ # optparse-test.rb:15:in `block in find_user': No User Found for id 3 (RuntimeError)
244
+ # === Complete example
245
+ #
246
+ # The following example is a complete Ruby program. You can run it and see the
247
+ # effect of specifying various options. This is probably the best way to learn
248
+ # the features of +optparse+.
249
+ #
250
+ # require 'optparse'
251
+ # require 'optparse/time'
252
+ # require 'ostruct'
253
+ # require 'pp'
254
+ #
255
+ # class OptparseExample
256
+ # Version = '1.0.0'
257
+ #
258
+ # CODES = %w[iso-2022-jp shift_jis euc-jp utf8 binary]
259
+ # CODE_ALIASES = { "jis" => "iso-2022-jp", "sjis" => "shift_jis" }
260
+ #
261
+ # class ScriptOptions
262
+ # attr_accessor :library, :inplace, :encoding, :transfer_type,
263
+ # :verbose, :extension, :delay, :time, :record_separator,
264
+ # :list
265
+ #
266
+ # def initialize
267
+ # self.library = []
268
+ # self.inplace = false
269
+ # self.encoding = "utf8"
270
+ # self.transfer_type = :auto
271
+ # self.verbose = false
272
+ # end
273
+ #
274
+ # def define_options(parser)
275
+ # parser.banner = "Usage: example.rb [options]"
276
+ # parser.separator ""
277
+ # parser.separator "Specific options:"
278
+ #
279
+ # # add additional options
280
+ # perform_inplace_option(parser)
281
+ # delay_execution_option(parser)
282
+ # execute_at_time_option(parser)
283
+ # specify_record_separator_option(parser)
284
+ # list_example_option(parser)
285
+ # specify_encoding_option(parser)
286
+ # optional_option_argument_with_keyword_completion_option(parser)
287
+ # boolean_verbose_option(parser)
288
+ #
289
+ # parser.separator ""
290
+ # parser.separator "Common options:"
291
+ # # No argument, shows at tail. This will print an options summary.
292
+ # # Try it and see!
293
+ # parser.on_tail("-h", "--help", "Show this message") do
294
+ # puts parser
295
+ # exit
296
+ # end
297
+ # # Another typical switch to print the version.
298
+ # parser.on_tail("--version", "Show version") do
299
+ # puts Version
300
+ # exit
301
+ # end
302
+ # end
303
+ #
304
+ # def perform_inplace_option(parser)
305
+ # # Specifies an optional option argument
306
+ # parser.on("-i", "--inplace [EXTENSION]",
307
+ # "Edit ARGV files in place",
308
+ # "(make backup if EXTENSION supplied)") do |ext|
309
+ # self.inplace = true
310
+ # self.extension = ext || ''
311
+ # self.extension.sub!(/\A\.?(?=.)/, ".") # Ensure extension begins with dot.
312
+ # end
313
+ # end
314
+ #
315
+ # def delay_execution_option(parser)
316
+ # # Cast 'delay' argument to a Float.
317
+ # parser.on("--delay N", Float, "Delay N seconds before executing") do |n|
318
+ # self.delay = n
319
+ # end
320
+ # end
321
+ #
322
+ # def execute_at_time_option(parser)
323
+ # # Cast 'time' argument to a Time object.
324
+ # parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time|
325
+ # self.time = time
326
+ # end
327
+ # end
328
+ #
329
+ # def specify_record_separator_option(parser)
330
+ # # Cast to octal integer.
331
+ # parser.on("-F", "--irs [OCTAL]", OptionParser::OctalInteger,
332
+ # "Specify record separator (default \\0)") do |rs|
333
+ # self.record_separator = rs
334
+ # end
335
+ # end
336
+ #
337
+ # def list_example_option(parser)
338
+ # # List of arguments.
339
+ # parser.on("--list x,y,z", Array, "Example 'list' of arguments") do |list|
340
+ # self.list = list
341
+ # end
342
+ # end
343
+ #
344
+ # def specify_encoding_option(parser)
345
+ # # Keyword completion. We are specifying a specific set of arguments (CODES
346
+ # # and CODE_ALIASES - notice the latter is a Hash), and the user may provide
347
+ # # the shortest unambiguous text.
348
+ # code_list = (CODE_ALIASES.keys + CODES).join(', ')
349
+ # parser.on("--code CODE", CODES, CODE_ALIASES, "Select encoding",
350
+ # "(#{code_list})") do |encoding|
351
+ # self.encoding = encoding
352
+ # end
353
+ # end
354
+ #
355
+ # def optional_option_argument_with_keyword_completion_option(parser)
356
+ # # Optional '--type' option argument with keyword completion.
357
+ # parser.on("--type [TYPE]", [:text, :binary, :auto],
358
+ # "Select transfer type (text, binary, auto)") do |t|
359
+ # self.transfer_type = t
360
+ # end
361
+ # end
362
+ #
363
+ # def boolean_verbose_option(parser)
364
+ # # Boolean switch.
365
+ # parser.on("-v", "--[no-]verbose", "Run verbosely") do |v|
366
+ # self.verbose = v
367
+ # end
368
+ # end
369
+ # end
370
+ #
371
+ # #
372
+ # # Return a structure describing the options.
373
+ # #
374
+ # def parse(args)
375
+ # # The options specified on the command line will be collected in
376
+ # # *options*.
377
+ #
378
+ # @options = ScriptOptions.new
379
+ # @args = OptionParser.new do |parser|
380
+ # @options.define_options(parser)
381
+ # parser.parse!(args)
382
+ # end
383
+ # @options
384
+ # end
385
+ #
386
+ # attr_reader :parser, :options
387
+ # end # class OptparseExample
388
+ #
389
+ # example = OptparseExample.new
390
+ # options = example.parse(ARGV)
391
+ # pp options # example.options
392
+ # pp ARGV
393
+ #
394
+ # === Shell Completion
395
+ #
396
+ # For modern shells (e.g. bash, zsh, etc.), you can use shell
397
+ # completion for command line options.
398
+ #
399
+ # === Further documentation
400
+ #
401
+ # The above examples should be enough to learn how to use this class. If you
402
+ # have any questions, file a ticket at http://bugs.ruby-lang.org.
403
+ #
404
+ class OptionParser
405
+ # :stopdoc:
406
+ NoArgument = [NO_ARGUMENT = :NONE, nil].freeze
407
+ RequiredArgument = [REQUIRED_ARGUMENT = :REQUIRED, true].freeze
408
+ OptionalArgument = [OPTIONAL_ARGUMENT = :OPTIONAL, false].freeze
409
+ # :startdoc:
410
+
411
+ #
412
+ # Keyword completion module. This allows partial arguments to be specified
413
+ # and resolved against a list of acceptable values.
414
+ #
415
+ module Completion
416
+ def self.regexp(key, icase)
417
+ Regexp.new('\A' + Regexp.quote(key).gsub(/\w+\b/, '\&\w*'), icase)
418
+ end
419
+
420
+ # [lb] added: typ param
421
+ def self.candidate(key, icase = false, pat = nil, typ = nil, &block)
422
+ pat ||= Completion.regexp(key, icase)
423
+ candidates = []
424
+ block.call do |k, *v|
425
+ (if Regexp === k
426
+ kn = "".freeze
427
+ k === key
428
+ else
429
+ kn = defined?(k.id2name) ? k.id2name : k
430
+ pat === kn
431
+ end) or next
432
+
433
+ # [lb]: Do not compare single character to first character of long option.
434
+ # E.g., if you define one option, ('-T', '--excellent', 'Awesome option'),
435
+ # and another option just ('-e', 'Another option'), the command `mycmd -e`
436
+ # will match against --excellent because long options are considered first.
437
+ # (2017-08-16: I saw this with github.com/commander-rb, where I used
438
+ # global_option to define a long option without a short abbreviation;
439
+ # and I used cmd.option to define an option with both a short and a long,
440
+ # where the short's letter is the first letter of the global_option long.)
441
+ # To see this in action, set a breakpoint here and run something like
442
+ # murano status -e
443
+ key.length > 1 || typ != :long || next
444
+
445
+ v << k if v.empty?
446
+ candidates << [k, v, kn]
447
+ end
448
+ candidates
449
+ end
450
+
451
+ # [lb] added: typ param
452
+ def candidate(key, icase = false, pat = nil, typ = nil)
453
+ # [lb] added: typ param
454
+ Completion.candidate(key, icase, pat, typ, &method(:each))
455
+ end
456
+
457
+ public
458
+ # [lb] added: typ param
459
+ def complete(key, icase = false, pat = nil, typ = nil)
460
+ # [lb] added: typ param
461
+ candidates = candidate(key, icase, pat, typ, &method(:each)).sort_by {|k, v, kn| kn.size}
462
+ if candidates.size == 1
463
+ canon, sw, * = candidates[0]
464
+ elsif candidates.size > 1
465
+ canon, sw, cn = candidates.shift
466
+ candidates.each do |k, v, kn|
467
+ next if sw == v
468
+ if String === cn and String === kn
469
+ if cn.rindex(kn, 0)
470
+ canon, sw, cn = k, v, kn
471
+ next
472
+ elsif kn.rindex(cn, 0)
473
+ next
474
+ end
475
+ end
476
+ throw :ambiguous, key
477
+ end
478
+ end
479
+ if canon
480
+ block_given? or return key, *sw
481
+ yield(key, *sw)
482
+ end
483
+ end
484
+
485
+ def convert(opt = nil, val = nil, *)
486
+ val
487
+ end
488
+ end
489
+
490
+
491
+ #
492
+ # Map from option/keyword string to object with completion.
493
+ #
494
+ class OptionMap < Hash
495
+ include Completion
496
+ end
497
+
498
+
499
+ #
500
+ # Individual switch class. Not important to the user.
501
+ #
502
+ # Defined within Switch are several Switch-derived classes: NoArgument,
503
+ # RequiredArgument, etc.
504
+ #
505
+ class Switch
506
+ attr_reader :pattern, :conv, :short, :long, :arg, :desc, :block
507
+
508
+ #
509
+ # Guesses argument style from +arg+. Returns corresponding
510
+ # OptionParser::Switch class (OptionalArgument, etc.).
511
+ #
512
+ def self.guess(arg)
513
+ case arg
514
+ when ""
515
+ t = self
516
+ when /\A=?\[/
517
+ t = Switch::OptionalArgument
518
+ when /\A\s+\[/
519
+ t = Switch::PlacedArgument
520
+ else
521
+ t = Switch::RequiredArgument
522
+ end
523
+ self >= t or incompatible_argument_styles(arg, t)
524
+ t
525
+ end
526
+
527
+ def self.incompatible_argument_styles(arg, t)
528
+ raise(ArgumentError, "#{arg}: incompatible argument styles\n #{self}, #{t}",
529
+ ParseError.filter_backtrace(caller(2)))
530
+ end
531
+
532
+ def self.pattern
533
+ NilClass
534
+ end
535
+
536
+ def initialize(pattern = nil, conv = nil,
537
+ short = nil, long = nil, arg = nil,
538
+ desc = ([] if short or long), block = Proc.new)
539
+ raise if Array === pattern
540
+ @pattern, @conv, @short, @long, @arg, @desc, @block =
541
+ pattern, conv, short, long, arg, desc, block
542
+ end
543
+
544
+ #
545
+ # Parses +arg+ and returns rest of +arg+ and matched portion to the
546
+ # argument pattern. Yields when the pattern doesn't match substring.
547
+ #
548
+ def parse_arg(arg)
549
+ pattern or return nil, [arg]
550
+ unless m = pattern.match(arg)
551
+ yield(InvalidArgument, arg)
552
+ return arg, []
553
+ end
554
+ if String === m
555
+ m = [s = m]
556
+ else
557
+ m = m.to_a
558
+ s = m[0]
559
+ return nil, m unless String === s
560
+ end
561
+ raise InvalidArgument, arg unless arg.rindex(s, 0)
562
+ return nil, m if s.length == arg.length
563
+ yield(InvalidArgument, arg) # didn't match whole arg
564
+ return arg[s.length..-1], m
565
+ end
566
+ private :parse_arg
567
+
568
+ #
569
+ # Parses argument, converts and returns +arg+, +block+ and result of
570
+ # conversion. Yields at semi-error condition instead of raising an
571
+ # exception.
572
+ #
573
+ def conv_arg(arg, val = [])
574
+ if conv
575
+ val = conv.call(*val)
576
+ else
577
+ val = proc {|v| v}.call(*val)
578
+ end
579
+ return arg, block, val
580
+ end
581
+ private :conv_arg
582
+
583
+ #
584
+ # Produces the summary text. Each line of the summary is yielded to the
585
+ # block (without newline).
586
+ #
587
+ # +sdone+:: Already summarized short style options keyed hash.
588
+ # +ldone+:: Already summarized long style options keyed hash.
589
+ # +width+:: Width of left side (option part). In other words, the right
590
+ # side (description part) starts after +width+ columns.
591
+ # +max+:: Maximum width of left side -> the options are filled within
592
+ # +max+ columns.
593
+ # +indent+:: Prefix string indents all summarized lines.
594
+ #
595
+ def summarize(sdone = [], ldone = [], width = 1, max = width - 1, indent = "")
596
+ sopts, lopts = [], [], nil
597
+ @short.each {|s| sdone.fetch(s) {sopts << s}; sdone[s] = true} if @short
598
+ @long.each {|s| ldone.fetch(s) {lopts << s}; ldone[s] = true} if @long
599
+ return if sopts.empty? and lopts.empty? # completely hidden
600
+
601
+ left = [sopts.join(', ')]
602
+ right = desc.dup
603
+
604
+ while s = lopts.shift
605
+ l = left[-1].length + s.length
606
+ l += arg.length if left.size == 1 && arg
607
+ l < max or sopts.empty? or left << ''
608
+ left[-1] << if left[-1].empty? then ' ' * 4 else ', ' end << s
609
+ end
610
+
611
+ if arg
612
+ left[0] << (left[1] ? arg.sub(/\A(\[?)=/, '\1') + ',' : arg)
613
+ end
614
+ mlen = left.collect {|ss| ss.length}.max.to_i
615
+ while mlen > width and l = left.shift
616
+ mlen = left.collect {|ss| ss.length}.max.to_i if l.length == mlen
617
+ if l.length < width and (r = right[0]) and !r.empty?
618
+ l = l.to_s.ljust(width) + ' ' + r
619
+ right.shift
620
+ end
621
+ yield(indent + l)
622
+ end
623
+
624
+ while begin l = left.shift; r = right.shift; l or r end
625
+ l = l.to_s.ljust(width) + ' ' + r if r and !r.empty?
626
+ yield(indent + l)
627
+ end
628
+
629
+ self
630
+ end
631
+
632
+ def add_banner(to) # :nodoc:
633
+ unless @short or @long
634
+ s = desc.join
635
+ to << " [" + s + "]..." unless s.empty?
636
+ end
637
+ to
638
+ end
639
+
640
+ def match_nonswitch?(str) # :nodoc:
641
+ @pattern =~ str unless @short or @long
642
+ end
643
+
644
+ #
645
+ # Main name of the switch.
646
+ #
647
+ def switch_name
648
+ (long.first || short.first).sub(/\A-+(?:\[no-\])?/, '')
649
+ end
650
+
651
+ def compsys(sdone, ldone) # :nodoc:
652
+ sopts, lopts = [], []
653
+ @short.each {|s| sdone.fetch(s) {sopts << s}; sdone[s] = true} if @short
654
+ @long.each {|s| ldone.fetch(s) {lopts << s}; ldone[s] = true} if @long
655
+ return if sopts.empty? and lopts.empty? # completely hidden
656
+
657
+ (sopts+lopts).each do |opt|
658
+ # "(-x -c -r)-l[left justify]" \
659
+ if /^--\[no-\](.+)$/ =~ opt
660
+ o = $1
661
+ yield("--#{o}", desc.join(""))
662
+ yield("--no-#{o}", desc.join(""))
663
+ else
664
+ yield("#{opt}", desc.join(""))
665
+ end
666
+ end
667
+ end
668
+
669
+ #
670
+ # Switch that takes no arguments.
671
+ #
672
+ class NoArgument < self
673
+
674
+ #
675
+ # Raises an exception if any arguments given.
676
+ #
677
+ def parse(arg, argv)
678
+ yield(NeedlessArgument, arg) if arg
679
+ conv_arg(arg)
680
+ end
681
+
682
+ def self.incompatible_argument_styles(*)
683
+ end
684
+
685
+ def self.pattern
686
+ Object
687
+ end
688
+ end
689
+
690
+ #
691
+ # Switch that takes an argument.
692
+ #
693
+ class RequiredArgument < self
694
+
695
+ #
696
+ # Raises an exception if argument is not present.
697
+ #
698
+ def parse(arg, argv)
699
+ unless arg
700
+ raise MissingArgument if argv.empty?
701
+ arg = argv.shift
702
+ end
703
+ conv_arg(*parse_arg(arg, &method(:raise)))
704
+ end
705
+ end
706
+
707
+ #
708
+ # Switch that can omit argument.
709
+ #
710
+ class OptionalArgument < self
711
+
712
+ #
713
+ # Parses argument if given, or uses default value.
714
+ #
715
+ def parse(arg, argv, &error)
716
+ if arg
717
+ conv_arg(*parse_arg(arg, &error))
718
+ else
719
+ conv_arg(arg)
720
+ end
721
+ end
722
+ end
723
+
724
+ #
725
+ # Switch that takes an argument, which does not begin with '-'.
726
+ #
727
+ class PlacedArgument < self
728
+
729
+ #
730
+ # Returns nil if argument is not present or begins with '-'.
731
+ #
732
+ def parse(arg, argv, &error)
733
+ if !(val = arg) and (argv.empty? or /\A-/ =~ (val = argv[0]))
734
+ return nil, block, nil
735
+ end
736
+ opt = (val = parse_arg(val, &error))[1]
737
+ val = conv_arg(*val)
738
+ if opt and !arg
739
+ argv.shift
740
+ else
741
+ val[0] = nil
742
+ end
743
+ val
744
+ end
745
+ end
746
+ end
747
+
748
+ #
749
+ # Simple option list providing mapping from short and/or long option
750
+ # string to OptionParser::Switch and mapping from acceptable argument to
751
+ # matching pattern and converter pair. Also provides summary feature.
752
+ #
753
+ class List
754
+ # Map from acceptable argument types to pattern and converter pairs.
755
+ attr_reader :atype
756
+
757
+ # Map from short style option switches to actual switch objects.
758
+ attr_reader :short
759
+
760
+ # Map from long style option switches to actual switch objects.
761
+ attr_reader :long
762
+
763
+ # List of all switches and summary string.
764
+ attr_reader :list
765
+
766
+ #
767
+ # Just initializes all instance variables.
768
+ #
769
+ def initialize
770
+ @atype = {}
771
+ @short = OptionMap.new
772
+ @long = OptionMap.new
773
+ @list = []
774
+ end
775
+
776
+ #
777
+ # See OptionParser.accept.
778
+ #
779
+ def accept(t, pat = /.*/m, &block)
780
+ if pat
781
+ pat.respond_to?(:match) or
782
+ raise TypeError, "has no `match'", ParseError.filter_backtrace(caller(2))
783
+ else
784
+ pat = t if t.respond_to?(:match)
785
+ end
786
+ unless block
787
+ block = pat.method(:convert).to_proc if pat.respond_to?(:convert)
788
+ end
789
+ @atype[t] = [pat, block]
790
+ end
791
+
792
+ #
793
+ # See OptionParser.reject.
794
+ #
795
+ def reject(t)
796
+ @atype.delete(t)
797
+ end
798
+
799
+ #
800
+ # Adds +sw+ according to +sopts+, +lopts+ and +nlopts+.
801
+ #
802
+ # +sw+:: OptionParser::Switch instance to be added.
803
+ # +sopts+:: Short style option list.
804
+ # +lopts+:: Long style option list.
805
+ # +nlopts+:: Negated long style options list.
806
+ #
807
+ def update(sw, sopts, lopts, nsw = nil, nlopts = nil)
808
+ sopts.each {|o| @short[o] = sw} if sopts
809
+ lopts.each {|o| @long[o] = sw} if lopts
810
+ nlopts.each {|o| @long[o] = nsw} if nsw and nlopts
811
+ used = @short.invert.update(@long.invert)
812
+ @list.delete_if {|o| Switch === o and !used[o]}
813
+ end
814
+ private :update
815
+
816
+ #
817
+ # Inserts +switch+ at the head of the list, and associates short, long
818
+ # and negated long options. Arguments are:
819
+ #
820
+ # +switch+:: OptionParser::Switch instance to be inserted.
821
+ # +short_opts+:: List of short style options.
822
+ # +long_opts+:: List of long style options.
823
+ # +nolong_opts+:: List of long style options with "no-" prefix.
824
+ #
825
+ # prepend(switch, short_opts, long_opts, nolong_opts)
826
+ #
827
+ def prepend(*args)
828
+ update(*args)
829
+ @list.unshift(args[0])
830
+ end
831
+
832
+ #
833
+ # Appends +switch+ at the tail of the list, and associates short, long
834
+ # and negated long options. Arguments are:
835
+ #
836
+ # +switch+:: OptionParser::Switch instance to be inserted.
837
+ # +short_opts+:: List of short style options.
838
+ # +long_opts+:: List of long style options.
839
+ # +nolong_opts+:: List of long style options with "no-" prefix.
840
+ #
841
+ # append(switch, short_opts, long_opts, nolong_opts)
842
+ #
843
+ def append(*args)
844
+ update(*args)
845
+ @list.push(args[0])
846
+ end
847
+
848
+ #
849
+ # Searches +key+ in +id+ list. The result is returned or yielded if a
850
+ # block is given. If it isn't found, nil is returned.
851
+ #
852
+ def search(id, key)
853
+ if list = __send__(id)
854
+ val = list.fetch(key) {return nil}
855
+ block_given? ? yield(val) : val
856
+ end
857
+ end
858
+
859
+ #
860
+ # Searches list +id+ for +opt+ and the optional patterns for completion
861
+ # +pat+. If +icase+ is true, the search is case insensitive. The result
862
+ # is returned or yielded if a block is given. If it isn't found, nil is
863
+ # returned.
864
+ #
865
+ def complete(id, opt, icase = false, *pat, &block)
866
+ # [lb] added: id param to complete params
867
+ __send__(id).complete(opt, icase, *pat, id, &block)
868
+ end
869
+
870
+ #
871
+ # Iterates over each option, passing the option to the +block+.
872
+ #
873
+ def each_option(&block)
874
+ list.each(&block)
875
+ end
876
+
877
+ #
878
+ # Creates the summary table, passing each line to the +block+ (without
879
+ # newline). The arguments +args+ are passed along to the summarize
880
+ # method which is called on every option.
881
+ #
882
+ def summarize(*args, &block)
883
+ sum = []
884
+ list.reverse_each do |opt|
885
+ if opt.respond_to?(:summarize) # perhaps OptionParser::Switch
886
+ s = []
887
+ opt.summarize(*args) {|l| s << l}
888
+ sum.concat(s.reverse)
889
+ elsif !opt or opt.empty?
890
+ sum << ""
891
+ elsif opt.respond_to?(:each_line)
892
+ sum.concat([*opt.each_line].reverse)
893
+ else
894
+ sum.concat([*opt.each].reverse)
895
+ end
896
+ end
897
+ sum.reverse_each(&block)
898
+ end
899
+
900
+ def add_banner(to) # :nodoc:
901
+ list.each do |opt|
902
+ if opt.respond_to?(:add_banner)
903
+ opt.add_banner(to)
904
+ end
905
+ end
906
+ to
907
+ end
908
+
909
+ def compsys(*args, &block) # :nodoc:
910
+ list.each do |opt|
911
+ if opt.respond_to?(:compsys)
912
+ opt.compsys(*args, &block)
913
+ end
914
+ end
915
+ end
916
+ end
917
+
918
+ #
919
+ # Hash with completion search feature. See OptionParser::Completion.
920
+ #
921
+ class CompletingHash < Hash
922
+ include Completion
923
+
924
+ #
925
+ # Completion for hash key.
926
+ #
927
+ def match(key)
928
+ *values = fetch(key) {
929
+ raise AmbiguousArgument, catch(:ambiguous) {return complete(key)}
930
+ }
931
+ return key, *values
932
+ end
933
+ end
934
+
935
+ # :stopdoc:
936
+
937
+ #
938
+ # Enumeration of acceptable argument styles. Possible values are:
939
+ #
940
+ # NO_ARGUMENT:: The switch takes no arguments. (:NONE)
941
+ # REQUIRED_ARGUMENT:: The switch requires an argument. (:REQUIRED)
942
+ # OPTIONAL_ARGUMENT:: The switch requires an optional argument. (:OPTIONAL)
943
+ #
944
+ # Use like --switch=argument (long style) or -Xargument (short style). For
945
+ # short style, only portion matched to argument pattern is treated as
946
+ # argument.
947
+ #
948
+ ArgumentStyle = {}
949
+ NoArgument.each {|el| ArgumentStyle[el] = Switch::NoArgument}
950
+ RequiredArgument.each {|el| ArgumentStyle[el] = Switch::RequiredArgument}
951
+ OptionalArgument.each {|el| ArgumentStyle[el] = Switch::OptionalArgument}
952
+ ArgumentStyle.freeze
953
+
954
+ #
955
+ # Switches common used such as '--', and also provides default
956
+ # argument classes
957
+ #
958
+ DefaultList = List.new
959
+ DefaultList.short['-'] = Switch::NoArgument.new {}
960
+ DefaultList.long[''] = Switch::NoArgument.new {throw :terminate}
961
+
962
+
963
+ COMPSYS_HEADER = <<'XXX' # :nodoc:
964
+
965
+ typeset -A opt_args
966
+ local context state line
967
+
968
+ _arguments -s -S \
969
+ XXX
970
+
971
+ def compsys(to, name = File.basename($0)) # :nodoc:
972
+ to << "#compdef #{name}\n"
973
+ to << COMPSYS_HEADER
974
+ visit(:compsys, {}, {}) {|o, d|
975
+ to << %Q[ "#{o}[#{d.gsub(/[\"\[\]]/, '\\\\\&')}]" \\\n]
976
+ }
977
+ to << " '*:file:_files' && return 0\n"
978
+ end
979
+
980
+ #
981
+ # Default options for ARGV, which never appear in option summary.
982
+ #
983
+ Officious = {}
984
+
985
+ #
986
+ # --help
987
+ # Shows option summary.
988
+ #
989
+ Officious['help'] = proc do |parser|
990
+ Switch::NoArgument.new do |arg|
991
+ puts parser.help
992
+ exit
993
+ end
994
+ end
995
+
996
+ #
997
+ # --*-completion-bash=WORD
998
+ # Shows candidates for command line completion.
999
+ #
1000
+ Officious['*-completion-bash'] = proc do |parser|
1001
+ Switch::RequiredArgument.new do |arg|
1002
+ puts parser.candidate(arg)
1003
+ exit
1004
+ end
1005
+ end
1006
+
1007
+ #
1008
+ # --*-completion-zsh[=NAME:FILE]
1009
+ # Creates zsh completion file.
1010
+ #
1011
+ Officious['*-completion-zsh'] = proc do |parser|
1012
+ Switch::OptionalArgument.new do |arg|
1013
+ parser.compsys(STDOUT, arg)
1014
+ exit
1015
+ end
1016
+ end
1017
+
1018
+ #
1019
+ # --version
1020
+ # Shows version string if Version is defined.
1021
+ #
1022
+ Officious['version'] = proc do |parser|
1023
+ Switch::OptionalArgument.new do |pkg|
1024
+ if pkg
1025
+ begin
1026
+ require 'optparse/version'
1027
+ rescue LoadError
1028
+ else
1029
+ show_version(*pkg.split(/,/)) or
1030
+ abort("#{parser.program_name}: no version found in package #{pkg}")
1031
+ exit
1032
+ end
1033
+ end
1034
+ v = parser.ver or abort("#{parser.program_name}: version unknown")
1035
+ puts v
1036
+ exit
1037
+ end
1038
+ end
1039
+
1040
+ # :startdoc:
1041
+
1042
+ #
1043
+ # Class methods
1044
+ #
1045
+
1046
+ #
1047
+ # Initializes a new instance and evaluates the optional block in context
1048
+ # of the instance. Arguments +args+ are passed to #new, see there for
1049
+ # description of parameters.
1050
+ #
1051
+ # This method is *deprecated*, its behavior corresponds to the older #new
1052
+ # method.
1053
+ #
1054
+ def self.with(*args, &block)
1055
+ opts = new(*args)
1056
+ opts.instance_eval(&block)
1057
+ opts
1058
+ end
1059
+
1060
+ #
1061
+ # Returns an incremented value of +default+ according to +arg+.
1062
+ #
1063
+ def self.inc(arg, default = nil)
1064
+ case arg
1065
+ when Integer
1066
+ arg.nonzero?
1067
+ when nil
1068
+ default.to_i + 1
1069
+ end
1070
+ end
1071
+ def inc(*args)
1072
+ self.class.inc(*args)
1073
+ end
1074
+
1075
+ #
1076
+ # Initializes the instance and yields itself if called with a block.
1077
+ #
1078
+ # +banner+:: Banner message.
1079
+ # +width+:: Summary width.
1080
+ # +indent+:: Summary indent.
1081
+ #
1082
+ def initialize(banner = nil, width = 32, indent = ' ' * 4)
1083
+ @stack = [DefaultList, List.new, List.new]
1084
+ @program_name = nil
1085
+ @banner = banner
1086
+ @summary_width = width
1087
+ @summary_indent = indent
1088
+ @default_argv = ARGV
1089
+ add_officious
1090
+ yield self if block_given?
1091
+ end
1092
+
1093
+ def add_officious # :nodoc:
1094
+ list = base()
1095
+ Officious.each do |opt, block|
1096
+ list.long[opt] ||= block.call(self)
1097
+ end
1098
+ end
1099
+
1100
+ #
1101
+ # Terminates option parsing. Optional parameter +arg+ is a string pushed
1102
+ # back to be the first non-option argument.
1103
+ #
1104
+ def terminate(arg = nil)
1105
+ self.class.terminate(arg)
1106
+ end
1107
+ def self.terminate(arg = nil)
1108
+ throw :terminate, arg
1109
+ end
1110
+
1111
+ @stack = [DefaultList]
1112
+ def self.top() DefaultList end
1113
+
1114
+ #
1115
+ # Directs to accept specified class +t+. The argument string is passed to
1116
+ # the block in which it should be converted to the desired class.
1117
+ #
1118
+ # +t+:: Argument class specifier, any object including Class.
1119
+ # +pat+:: Pattern for argument, defaults to +t+ if it responds to match.
1120
+ #
1121
+ # accept(t, pat, &block)
1122
+ #
1123
+ def accept(*args, &blk) top.accept(*args, &blk) end
1124
+ #
1125
+ # See #accept.
1126
+ #
1127
+ def self.accept(*args, &blk) top.accept(*args, &blk) end
1128
+
1129
+ #
1130
+ # Directs to reject specified class argument.
1131
+ #
1132
+ # +t+:: Argument class specifier, any object including Class.
1133
+ #
1134
+ # reject(t)
1135
+ #
1136
+ def reject(*args, &blk) top.reject(*args, &blk) end
1137
+ #
1138
+ # See #reject.
1139
+ #
1140
+ def self.reject(*args, &blk) top.reject(*args, &blk) end
1141
+
1142
+ #
1143
+ # Instance methods
1144
+ #
1145
+
1146
+ # Heading banner preceding summary.
1147
+ attr_writer :banner
1148
+
1149
+ # Program name to be emitted in error message and default banner,
1150
+ # defaults to $0.
1151
+ attr_writer :program_name
1152
+
1153
+ # Width for option list portion of summary. Must be Numeric.
1154
+ attr_accessor :summary_width
1155
+
1156
+ # Indentation for summary. Must be String (or have + String method).
1157
+ attr_accessor :summary_indent
1158
+
1159
+ # Strings to be parsed in default.
1160
+ attr_accessor :default_argv
1161
+
1162
+ #
1163
+ # Heading banner preceding summary.
1164
+ #
1165
+ def banner
1166
+ unless @banner
1167
+ @banner = "Usage: #{program_name} [options]"
1168
+ visit(:add_banner, @banner)
1169
+ end
1170
+ @banner
1171
+ end
1172
+
1173
+ #
1174
+ # Program name to be emitted in error message and default banner, defaults
1175
+ # to $0.
1176
+ #
1177
+ def program_name
1178
+ @program_name || File.basename($0, '.*')
1179
+ end
1180
+
1181
+ # for experimental cascading :-)
1182
+ alias set_banner banner=
1183
+ alias set_program_name program_name=
1184
+ alias set_summary_width summary_width=
1185
+ alias set_summary_indent summary_indent=
1186
+
1187
+ # Version
1188
+ attr_writer :version
1189
+ # Release code
1190
+ attr_writer :release
1191
+
1192
+ #
1193
+ # Version
1194
+ #
1195
+ def version
1196
+ @version || (defined?(::Version) && ::Version)
1197
+ end
1198
+
1199
+ #
1200
+ # Release code
1201
+ #
1202
+ def release
1203
+ @release || (defined?(::Release) && ::Release) || (defined?(::RELEASE) && ::RELEASE)
1204
+ end
1205
+
1206
+ #
1207
+ # Returns version string from program_name, version and release.
1208
+ #
1209
+ def ver
1210
+ if v = version
1211
+ str = "#{program_name} #{[v].join('.')}"
1212
+ str << " (#{v})" if v = release
1213
+ str
1214
+ end
1215
+ end
1216
+
1217
+ def warn(mesg = $!)
1218
+ super("#{program_name}: #{mesg}")
1219
+ end
1220
+
1221
+ def abort(mesg = $!)
1222
+ super("#{program_name}: #{mesg}")
1223
+ end
1224
+
1225
+ #
1226
+ # Subject of #on / #on_head, #accept / #reject
1227
+ #
1228
+ def top
1229
+ @stack[-1]
1230
+ end
1231
+
1232
+ #
1233
+ # Subject of #on_tail.
1234
+ #
1235
+ def base
1236
+ @stack[1]
1237
+ end
1238
+
1239
+ #
1240
+ # Pushes a new List.
1241
+ #
1242
+ def new
1243
+ @stack.push(List.new)
1244
+ if block_given?
1245
+ yield self
1246
+ else
1247
+ self
1248
+ end
1249
+ end
1250
+
1251
+ #
1252
+ # Removes the last List.
1253
+ #
1254
+ def remove
1255
+ @stack.pop
1256
+ end
1257
+
1258
+ #
1259
+ # Puts option summary into +to+ and returns +to+. Yields each line if
1260
+ # a block is given.
1261
+ #
1262
+ # +to+:: Output destination, which must have method <<. Defaults to [].
1263
+ # +width+:: Width of left side, defaults to @summary_width.
1264
+ # +max+:: Maximum length allowed for left side, defaults to +width+ - 1.
1265
+ # +indent+:: Indentation, defaults to @summary_indent.
1266
+ #
1267
+ def summarize(to = [], width = @summary_width, max = width - 1, indent = @summary_indent, &blk)
1268
+ blk ||= proc {|l| to << (l.index($/, -1) ? l : l + $/)}
1269
+ visit(:summarize, {}, {}, width, max, indent, &blk)
1270
+ to
1271
+ end
1272
+
1273
+ #
1274
+ # Returns option summary string.
1275
+ #
1276
+ def help; summarize("#{banner}".sub(/\n?\z/, "\n")) end
1277
+ alias to_s help
1278
+
1279
+ #
1280
+ # Returns option summary list.
1281
+ #
1282
+ def to_a; summarize("#{banner}".split(/^/)) end
1283
+
1284
+ #
1285
+ # Checks if an argument is given twice, in which case an ArgumentError is
1286
+ # raised. Called from OptionParser#switch only.
1287
+ #
1288
+ # +obj+:: New argument.
1289
+ # +prv+:: Previously specified argument.
1290
+ # +msg+:: Exception message.
1291
+ #
1292
+ def notwice(obj, prv, msg)
1293
+ unless !prv or prv == obj
1294
+ raise(ArgumentError, "argument #{msg} given twice: #{obj}",
1295
+ ParseError.filter_backtrace(caller(2)))
1296
+ end
1297
+ obj
1298
+ end
1299
+ private :notwice
1300
+
1301
+ SPLAT_PROC = proc {|*a| a.length <= 1 ? a.first : a} # :nodoc:
1302
+ #
1303
+ # Creates an OptionParser::Switch from the parameters. The parsed argument
1304
+ # value is passed to the given block, where it can be processed.
1305
+ #
1306
+ # See at the beginning of OptionParser for some full examples.
1307
+ #
1308
+ # +opts+ can include the following elements:
1309
+ #
1310
+ # [Argument style:]
1311
+ # One of the following:
1312
+ # :NONE, :REQUIRED, :OPTIONAL
1313
+ #
1314
+ # [Argument pattern:]
1315
+ # Acceptable option argument format, must be pre-defined with
1316
+ # OptionParser.accept or OptionParser#accept, or Regexp. This can appear
1317
+ # once or assigned as String if not present, otherwise causes an
1318
+ # ArgumentError. Examples:
1319
+ # Float, Time, Array
1320
+ #
1321
+ # [Possible argument values:]
1322
+ # Hash or Array.
1323
+ # [:text, :binary, :auto]
1324
+ # %w[iso-2022-jp shift_jis euc-jp utf8 binary]
1325
+ # { "jis" => "iso-2022-jp", "sjis" => "shift_jis" }
1326
+ #
1327
+ # [Long style switch:]
1328
+ # Specifies a long style switch which takes a mandatory, optional or no
1329
+ # argument. It's a string of the following form:
1330
+ # "--switch=MANDATORY" or "--switch MANDATORY"
1331
+ # "--switch[=OPTIONAL]"
1332
+ # "--switch"
1333
+ #
1334
+ # [Short style switch:]
1335
+ # Specifies short style switch which takes a mandatory, optional or no
1336
+ # argument. It's a string of the following form:
1337
+ # "-xMANDATORY"
1338
+ # "-x[OPTIONAL]"
1339
+ # "-x"
1340
+ # There is also a special form which matches character range (not full
1341
+ # set of regular expression):
1342
+ # "-[a-z]MANDATORY"
1343
+ # "-[a-z][OPTIONAL]"
1344
+ # "-[a-z]"
1345
+ #
1346
+ # [Argument style and description:]
1347
+ # Instead of specifying mandatory or optional arguments directly in the
1348
+ # switch parameter, this separate parameter can be used.
1349
+ # "=MANDATORY"
1350
+ # "=[OPTIONAL]"
1351
+ #
1352
+ # [Description:]
1353
+ # Description string for the option.
1354
+ # "Run verbosely"
1355
+ #
1356
+ # [Handler:]
1357
+ # Handler for the parsed argument value. Either give a block or pass a
1358
+ # Proc or Method as an argument.
1359
+ #
1360
+ def make_switch(opts, block = nil)
1361
+ short, long, nolong, style, pattern, conv, not_pattern, not_conv, not_style = [], [], []
1362
+ ldesc, sdesc, desc, arg = [], [], []
1363
+ default_style = Switch::NoArgument
1364
+ default_pattern = nil
1365
+ klass = nil
1366
+ q, a = nil
1367
+ has_arg = false
1368
+
1369
+ opts.each do |o|
1370
+ # argument class
1371
+ next if search(:atype, o) do |pat, c|
1372
+ klass = notwice(o, klass, 'type')
1373
+ if not_style and not_style != Switch::NoArgument
1374
+ not_pattern, not_conv = pat, c
1375
+ else
1376
+ default_pattern, conv = pat, c
1377
+ end
1378
+ end
1379
+
1380
+ # directly specified pattern(any object possible to match)
1381
+ if (!(String === o || Symbol === o)) and o.respond_to?(:match)
1382
+ pattern = notwice(o, pattern, 'pattern')
1383
+ if pattern.respond_to?(:convert)
1384
+ conv = pattern.method(:convert).to_proc
1385
+ else
1386
+ conv = SPLAT_PROC
1387
+ end
1388
+ next
1389
+ end
1390
+
1391
+ # anything others
1392
+ case o
1393
+ when Proc, Method
1394
+ block = notwice(o, block, 'block')
1395
+ when Array, Hash
1396
+ case pattern
1397
+ when CompletingHash
1398
+ when nil
1399
+ pattern = CompletingHash.new
1400
+ conv = pattern.method(:convert).to_proc if pattern.respond_to?(:convert)
1401
+ else
1402
+ raise ArgumentError, "argument pattern given twice"
1403
+ end
1404
+ o.each {|pat, *v| pattern[pat] = v.fetch(0) {pat}}
1405
+ when Module
1406
+ raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4))
1407
+ when *ArgumentStyle.keys
1408
+ style = notwice(ArgumentStyle[o], style, 'style')
1409
+ when /^--no-([^\[\]=\s]*)(.+)?/
1410
+ q, a = $1, $2
1411
+ o = notwice(a ? Object : TrueClass, klass, 'type')
1412
+ not_pattern, not_conv = search(:atype, o) unless not_style
1413
+ not_style = (not_style || default_style).guess(arg = a) if a
1414
+ default_style = Switch::NoArgument
1415
+ default_pattern, conv = search(:atype, FalseClass) unless default_pattern
1416
+ ldesc << "--no-#{q}"
1417
+ long << 'no-' + (q = q.downcase)
1418
+ nolong << q
1419
+ when /^--\[no-\]([^\[\]=\s]*)(.+)?/
1420
+ q, a = $1, $2
1421
+ o = notwice(a ? Object : TrueClass, klass, 'type')
1422
+ if a
1423
+ default_style = default_style.guess(arg = a)
1424
+ default_pattern, conv = search(:atype, o) unless default_pattern
1425
+ end
1426
+ ldesc << "--[no-]#{q}"
1427
+ long << (o = q.downcase)
1428
+ not_pattern, not_conv = search(:atype, FalseClass) unless not_style
1429
+ not_style = Switch::NoArgument
1430
+ nolong << 'no-' + o
1431
+ when /^--([^\[\]=\s]*)(.+)?/
1432
+ q, a = $1, $2
1433
+ if a
1434
+ o = notwice(NilClass, klass, 'type')
1435
+ default_style = default_style.guess(arg = a)
1436
+ default_pattern, conv = search(:atype, o) unless default_pattern
1437
+ end
1438
+ ldesc << "--#{q}"
1439
+ long << (o = q.downcase)
1440
+ when /^-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/
1441
+ q, a = $1, $2
1442
+ o = notwice(Object, klass, 'type')
1443
+ if a
1444
+ default_style = default_style.guess(arg = a)
1445
+ default_pattern, conv = search(:atype, o) unless default_pattern
1446
+ else
1447
+ has_arg = true
1448
+ end
1449
+ sdesc << "-#{q}"
1450
+ short << Regexp.new(q)
1451
+ when /^-(.)(.+)?/
1452
+ q, a = $1, $2
1453
+ if a
1454
+ o = notwice(NilClass, klass, 'type')
1455
+ default_style = default_style.guess(arg = a)
1456
+ default_pattern, conv = search(:atype, o) unless default_pattern
1457
+ end
1458
+ sdesc << "-#{q}"
1459
+ short << q
1460
+ when /^=/
1461
+ style = notwice(default_style.guess(arg = o), style, 'style')
1462
+ default_pattern, conv = search(:atype, Object) unless default_pattern
1463
+ else
1464
+ desc.push(o)
1465
+ end
1466
+ end
1467
+
1468
+ default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern
1469
+ if !(short.empty? and long.empty?)
1470
+ if has_arg and default_style == Switch::NoArgument
1471
+ default_style = Switch::RequiredArgument
1472
+ end
1473
+ s = (style || default_style).new(pattern || default_pattern,
1474
+ conv, sdesc, ldesc, arg, desc, block)
1475
+ elsif !block
1476
+ if style or pattern
1477
+ raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller)
1478
+ end
1479
+ s = desc
1480
+ else
1481
+ short << pattern
1482
+ s = (style || default_style).new(pattern,
1483
+ conv, nil, nil, arg, desc, block)
1484
+ end
1485
+ return s, short, long,
1486
+ (not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style),
1487
+ nolong
1488
+ end
1489
+
1490
+ def define(*opts, &block)
1491
+ top.append(*(sw = make_switch(opts, block)))
1492
+ sw[0]
1493
+ end
1494
+
1495
+ #
1496
+ # Add option switch and handler. See #make_switch for an explanation of
1497
+ # parameters.
1498
+ #
1499
+ def on(*opts, &block)
1500
+ define(*opts, &block)
1501
+ self
1502
+ end
1503
+ alias def_option define
1504
+
1505
+ def define_head(*opts, &block)
1506
+ top.prepend(*(sw = make_switch(opts, block)))
1507
+ sw[0]
1508
+ end
1509
+
1510
+ #
1511
+ # Add option switch like with #on, but at head of summary.
1512
+ #
1513
+ def on_head(*opts, &block)
1514
+ define_head(*opts, &block)
1515
+ self
1516
+ end
1517
+ alias def_head_option define_head
1518
+
1519
+ def define_tail(*opts, &block)
1520
+ base.append(*(sw = make_switch(opts, block)))
1521
+ sw[0]
1522
+ end
1523
+
1524
+ #
1525
+ # Add option switch like with #on, but at tail of summary.
1526
+ #
1527
+ def on_tail(*opts, &block)
1528
+ define_tail(*opts, &block)
1529
+ self
1530
+ end
1531
+ alias def_tail_option define_tail
1532
+
1533
+ #
1534
+ # Add separator in summary.
1535
+ #
1536
+ def separator(string)
1537
+ top.append(string, nil, nil)
1538
+ end
1539
+
1540
+ #
1541
+ # Parses command line arguments +argv+ in order. When a block is given,
1542
+ # each non-option argument is yielded.
1543
+ #
1544
+ # Returns the rest of +argv+ left unparsed.
1545
+ #
1546
+ def order(*argv, &block)
1547
+ argv = argv[0].dup if argv.size == 1 and Array === argv[0]
1548
+ order!(argv, &block)
1549
+ end
1550
+
1551
+ #
1552
+ # Same as #order, but removes switches destructively.
1553
+ # Non-option arguments remain in +argv+.
1554
+ #
1555
+ def order!(argv = default_argv, &nonopt)
1556
+ parse_in_order(argv, &nonopt)
1557
+ end
1558
+
1559
+ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc:
1560
+ opt, arg, val, rest = nil
1561
+ nonopt ||= proc {|a| throw :terminate, a}
1562
+ argv.unshift(arg) if arg = catch(:terminate) {
1563
+ while arg = argv.shift
1564
+ case arg
1565
+ # long option
1566
+ when /\A--([^=]*)(?:=(.*))?/m
1567
+ opt, rest = $1, $2
1568
+ begin
1569
+ sw, = complete(:long, opt, true)
1570
+ rescue ParseError
1571
+ raise $!.set_option(arg, true)
1572
+ end
1573
+ begin
1574
+ opt, cb, val = sw.parse(rest, argv) {|*exc| raise(*exc)}
1575
+ val = cb.call(val) if cb
1576
+ setter.call(sw.switch_name, val) if setter
1577
+ rescue ParseError
1578
+ raise $!.set_option(arg, rest)
1579
+ end
1580
+
1581
+ # short option
1582
+ when /\A-(.)((=).*|.+)?/m
1583
+ opt, has_arg, eq, val, rest = $1, $3, $3, $2, $2
1584
+ begin
1585
+ sw, = search(:short, opt)
1586
+ unless sw
1587
+ begin
1588
+ sw, = complete(:short, opt)
1589
+ # short option matched.
1590
+ val = arg.sub(/\A-/, '')
1591
+ has_arg = true
1592
+ rescue InvalidOption
1593
+ # if no short options match, try completion with long
1594
+ # options.
1595
+ sw, = complete(:long, opt)
1596
+ eq ||= !rest
1597
+ end
1598
+ end
1599
+ rescue ParseError
1600
+ raise $!.set_option(arg, true)
1601
+ end
1602
+ begin
1603
+ opt, cb, val = sw.parse(val, argv) {|*exc| raise(*exc) if eq}
1604
+ raise InvalidOption, arg if has_arg and !eq and arg == "-#{opt}"
1605
+ argv.unshift(opt) if opt and (!rest or (opt = opt.sub(/\A-*/, '-')) != '-')
1606
+ val = cb.call(val) if cb
1607
+ setter.call(sw.switch_name, val) if setter
1608
+ rescue ParseError
1609
+ raise $!.set_option(arg, arg.length > 2)
1610
+ end
1611
+
1612
+ # non-option argument
1613
+ else
1614
+ catch(:prune) do
1615
+ visit(:each_option) do |sw0|
1616
+ sw = sw0
1617
+ sw.block.call(arg) if Switch === sw and sw.match_nonswitch?(arg)
1618
+ end
1619
+ nonopt.call(arg)
1620
+ end
1621
+ end
1622
+ end
1623
+
1624
+ nil
1625
+ }
1626
+
1627
+ visit(:search, :short, nil) {|sw| sw.block.call(*argv) if !sw.pattern}
1628
+
1629
+ argv
1630
+ end
1631
+ private :parse_in_order
1632
+
1633
+ #
1634
+ # Parses command line arguments +argv+ in permutation mode and returns
1635
+ # list of non-option arguments.
1636
+ #
1637
+ def permute(*argv)
1638
+ argv = argv[0].dup if argv.size == 1 and Array === argv[0]
1639
+ permute!(argv)
1640
+ end
1641
+
1642
+ #
1643
+ # Same as #permute, but removes switches destructively.
1644
+ # Non-option arguments remain in +argv+.
1645
+ #
1646
+ def permute!(argv = default_argv)
1647
+ nonopts = []
1648
+ order!(argv, &nonopts.method(:<<))
1649
+ argv[0, 0] = nonopts
1650
+ argv
1651
+ end
1652
+
1653
+ #
1654
+ # Parses command line arguments +argv+ in order when environment variable
1655
+ # POSIXLY_CORRECT is set, and in permutation mode otherwise.
1656
+ #
1657
+ def parse(*argv)
1658
+ argv = argv[0].dup if argv.size == 1 and Array === argv[0]
1659
+ parse!(argv)
1660
+ end
1661
+
1662
+ #
1663
+ # Same as #parse, but removes switches destructively.
1664
+ # Non-option arguments remain in +argv+.
1665
+ #
1666
+ def parse!(argv = default_argv)
1667
+ if ENV.include?('POSIXLY_CORRECT')
1668
+ order!(argv)
1669
+ else
1670
+ permute!(argv)
1671
+ end
1672
+ end
1673
+
1674
+ #
1675
+ # Wrapper method for getopts.rb.
1676
+ #
1677
+ # params = ARGV.getopts("ab:", "foo", "bar:", "zot:Z;zot option)
1678
+ # # params[:a] = true # -a
1679
+ # # params[:b] = "1" # -b1
1680
+ # # params[:foo] = "1" # --foo
1681
+ # # params[:bar] = "x" # --bar x
1682
+ # # params[:zot] = "z" # --zot Z
1683
+ #
1684
+ def getopts(*args)
1685
+ argv = Array === args.first ? args.shift : default_argv
1686
+ single_options, *long_options = *args
1687
+
1688
+ result = {}
1689
+
1690
+ single_options.scan(/(.)(:)?/) do |opt, val|
1691
+ if val
1692
+ result[opt] = nil
1693
+ define("-#{opt} VAL")
1694
+ else
1695
+ result[opt] = false
1696
+ define("-#{opt}")
1697
+ end
1698
+ end if single_options
1699
+
1700
+ long_options.each do |arg|
1701
+ arg, desc = arg.split(';', 2)
1702
+ opt, val = arg.split(':', 2)
1703
+ if val
1704
+ result[opt] = val.empty? ? nil : val
1705
+ define("--#{opt}=#{result[opt] || "VAL"}", *[desc].compact)
1706
+ else
1707
+ result[opt] = false
1708
+ define("--#{opt}", *[desc].compact)
1709
+ end
1710
+ end
1711
+
1712
+ parse_in_order(argv, result.method(:[]=))
1713
+ result
1714
+ end
1715
+
1716
+ #
1717
+ # See #getopts.
1718
+ #
1719
+ def self.getopts(*args)
1720
+ new.getopts(*args)
1721
+ end
1722
+
1723
+ #
1724
+ # Traverses @stack, sending each element method +id+ with +args+ and
1725
+ # +block+.
1726
+ #
1727
+ def visit(id, *args, &block)
1728
+ @stack.reverse_each do |el|
1729
+ el.send(id, *args, &block)
1730
+ end
1731
+ nil
1732
+ end
1733
+ private :visit
1734
+
1735
+ #
1736
+ # Searches +key+ in @stack for +id+ hash and returns or yields the result.
1737
+ #
1738
+ def search(id, key)
1739
+ block_given = block_given?
1740
+ visit(:search, id, key) do |k|
1741
+ return block_given ? yield(k) : k
1742
+ end
1743
+ end
1744
+ private :search
1745
+
1746
+ #
1747
+ # Completes shortened long style option switch and returns pair of
1748
+ # canonical switch and switch descriptor OptionParser::Switch.
1749
+ #
1750
+ # +typ+:: Searching table.
1751
+ # +opt+:: Searching key.
1752
+ # +icase+:: Search case insensitive if true.
1753
+ # +pat+:: Optional pattern for completion.
1754
+ #
1755
+ def complete(typ, opt, icase = false, *pat)
1756
+ if pat.empty?
1757
+ search(typ, opt) {|sw| return [sw, opt]} # exact match or...
1758
+ end
1759
+ raise AmbiguousOption, catch(:ambiguous) {
1760
+ visit(:complete, typ, opt, icase, *pat) {|o, *sw| return sw}
1761
+ raise InvalidOption, opt
1762
+ }
1763
+ end
1764
+ private :complete
1765
+
1766
+ def candidate(word)
1767
+ list = []
1768
+ case word
1769
+ when /\A--/
1770
+ word, arg = word.split(/=/, 2)
1771
+ argpat = Completion.regexp(arg, false) if arg and !arg.empty?
1772
+ long = true
1773
+ when /\A-(!-)/
1774
+ short = true
1775
+ when /\A-/
1776
+ long = short = true
1777
+ end
1778
+ pat = Completion.regexp(word, true)
1779
+ visit(:each_option) do |opt|
1780
+ next unless Switch === opt
1781
+ opts = (long ? opt.long : []) + (short ? opt.short : [])
1782
+ opts = Completion.candidate(word, true, pat, &opts.method(:each)).map(&:first) if pat
1783
+ # opts = Completion.candidate(TYP?, word, true, pat, &opts.method(:each)).map(&:first) if pat
1784
+ if /\A=/ =~ opt.arg
1785
+ opts.map! {|sw| sw + "="}
1786
+ if arg and CompletingHash === opt.pattern
1787
+ if opts = opt.pattern.candidate(arg, false, argpat)
1788
+ # if opts = opt.pattern.candidate(TYP?, arg, false, argpat)
1789
+ opts.map!(&:last)
1790
+ end
1791
+ end
1792
+ end
1793
+ list.concat(opts)
1794
+ end
1795
+ list
1796
+ end
1797
+
1798
+ #
1799
+ # Loads options from file names as +filename+. Does nothing when the file
1800
+ # is not present. Returns whether successfully loaded.
1801
+ #
1802
+ # +filename+ defaults to basename of the program without suffix in a
1803
+ # directory ~/.options.
1804
+ #
1805
+ def load(filename = nil)
1806
+ begin
1807
+ filename ||= File.expand_path(File.basename($0, '.*'), '~/.options')
1808
+ rescue
1809
+ return false
1810
+ end
1811
+ begin
1812
+ parse(*IO.readlines(filename).each {|s| s.chomp!})
1813
+ true
1814
+ rescue Errno::ENOENT, Errno::ENOTDIR
1815
+ false
1816
+ end
1817
+ end
1818
+
1819
+ #
1820
+ # Parses environment variable +env+ or its uppercase with splitting like a
1821
+ # shell.
1822
+ #
1823
+ # +env+ defaults to the basename of the program.
1824
+ #
1825
+ def environment(env = File.basename($0, '.*'))
1826
+ env = ENV[env] || ENV[env.upcase] or return
1827
+ require 'shellwords'
1828
+ parse(*Shellwords.shellwords(env))
1829
+ end
1830
+
1831
+ #
1832
+ # Acceptable argument classes
1833
+ #
1834
+
1835
+ #
1836
+ # Any string and no conversion. This is fall-back.
1837
+ #
1838
+ accept(Object) {|s,|s or s.nil?}
1839
+
1840
+ accept(NilClass) {|s,|s}
1841
+
1842
+ #
1843
+ # Any non-empty string, and no conversion.
1844
+ #
1845
+ accept(String, /.+/m) {|s,*|s}
1846
+
1847
+ #
1848
+ # Ruby/C-like integer, octal for 0-7 sequence, binary for 0b, hexadecimal
1849
+ # for 0x, and decimal for others; with optional sign prefix. Converts to
1850
+ # Integer.
1851
+ #
1852
+ decimal = '\d+(?:_\d+)*'
1853
+ binary = 'b[01]+(?:_[01]+)*'
1854
+ hex = 'x[\da-f]+(?:_[\da-f]+)*'
1855
+ octal = "0(?:[0-7]+(?:_[0-7]+)*|#{binary}|#{hex})?"
1856
+ integer = "#{octal}|#{decimal}"
1857
+
1858
+ accept(Integer, %r"\A[-+]?(?:#{integer})\z"io) {|s,|
1859
+ begin
1860
+ Integer(s)
1861
+ rescue ArgumentError
1862
+ raise OptionParser::InvalidArgument, s
1863
+ end if s
1864
+ }
1865
+
1866
+ #
1867
+ # Float number format, and converts to Float.
1868
+ #
1869
+ float = "(?:#{decimal}(?:\\.(?:#{decimal})?)?|\\.#{decimal})(?:E[-+]?#{decimal})?"
1870
+ floatpat = %r"\A[-+]?#{float}\z"io
1871
+ accept(Float, floatpat) {|s,| s.to_f if s}
1872
+
1873
+ #
1874
+ # Generic numeric format, converts to Integer for integer format, Float
1875
+ # for float format, and Rational for rational format.
1876
+ #
1877
+ real = "[-+]?(?:#{octal}|#{float})"
1878
+ accept(Numeric, /\A(#{real})(?:\/(#{real}))?\z/io) {|s, d, n|
1879
+ if n
1880
+ Rational(d, n)
1881
+ elsif s
1882
+ eval(s)
1883
+ end
1884
+ }
1885
+
1886
+ #
1887
+ # Decimal integer format, to be converted to Integer.
1888
+ #
1889
+ DecimalInteger = /\A[-+]?#{decimal}\z/io
1890
+ accept(DecimalInteger, DecimalInteger) {|s,|
1891
+ begin
1892
+ Integer(s)
1893
+ rescue ArgumentError
1894
+ raise OptionParser::InvalidArgument, s
1895
+ end if s
1896
+ }
1897
+
1898
+ #
1899
+ # Ruby/C like octal/hexadecimal/binary integer format, to be converted to
1900
+ # Integer.
1901
+ #
1902
+ OctalInteger = /\A[-+]?(?:[0-7]+(?:_[0-7]+)*|0(?:#{binary}|#{hex}))\z/io
1903
+ accept(OctalInteger, OctalInteger) {|s,|
1904
+ begin
1905
+ Integer(s, 8)
1906
+ rescue ArgumentError
1907
+ raise OptionParser::InvalidArgument, s
1908
+ end if s
1909
+ }
1910
+
1911
+ #
1912
+ # Decimal integer/float number format, to be converted to Integer for
1913
+ # integer format, Float for float format.
1914
+ #
1915
+ DecimalNumeric = floatpat # decimal integer is allowed as float also.
1916
+ accept(DecimalNumeric, floatpat) {|s,|
1917
+ begin
1918
+ eval(s)
1919
+ rescue SyntaxError
1920
+ raise OptionParser::InvalidArgument, s
1921
+ end if s
1922
+ }
1923
+
1924
+ #
1925
+ # Boolean switch, which means whether it is present or not, whether it is
1926
+ # absent or not with prefix no-, or it takes an argument
1927
+ # yes/no/true/false/+/-.
1928
+ #
1929
+ yesno = CompletingHash.new
1930
+ %w[- no false].each {|el| yesno[el] = false}
1931
+ %w[+ yes true].each {|el| yesno[el] = true}
1932
+ yesno['nil'] = false # should be nil?
1933
+ accept(TrueClass, yesno) {|arg, val| val == nil or val}
1934
+ #
1935
+ # Similar to TrueClass, but defaults to false.
1936
+ #
1937
+ accept(FalseClass, yesno) {|arg, val| val != nil and val}
1938
+
1939
+ #
1940
+ # List of strings separated by ",".
1941
+ #
1942
+ accept(Array) do |s,|
1943
+ if s
1944
+ s = s.split(',').collect {|ss| ss unless ss.empty?}
1945
+ end
1946
+ s
1947
+ end
1948
+
1949
+ #
1950
+ # Regular expression with options.
1951
+ #
1952
+ accept(Regexp, %r"\A/((?:\\.|[^\\])*)/([[:alpha:]]+)?\z|.*") do |all, s, o|
1953
+ f = 0
1954
+ if o
1955
+ f |= Regexp::IGNORECASE if /i/ =~ o
1956
+ f |= Regexp::MULTILINE if /m/ =~ o
1957
+ f |= Regexp::EXTENDED if /x/ =~ o
1958
+ k = o.delete("imx")
1959
+ k = nil if k.empty?
1960
+ end
1961
+ Regexp.new(s || all, f, k)
1962
+ end
1963
+
1964
+ #
1965
+ # Exceptions
1966
+ #
1967
+
1968
+ #
1969
+ # Base class of exceptions from OptionParser.
1970
+ #
1971
+ class ParseError < RuntimeError
1972
+ # Reason which caused the error.
1973
+ Reason = 'parse error'.freeze
1974
+
1975
+ def initialize(*args)
1976
+ @args = args
1977
+ @reason = nil
1978
+ end
1979
+
1980
+ attr_reader :args
1981
+ attr_writer :reason
1982
+
1983
+ #
1984
+ # Pushes back erred argument(s) to +argv+.
1985
+ #
1986
+ def recover(argv)
1987
+ argv[0, 0] = @args
1988
+ argv
1989
+ end
1990
+
1991
+ def self.filter_backtrace(array)
1992
+ unless $DEBUG
1993
+ array.delete_if(&%r"\A#{Regexp.quote(__FILE__)}:"o.method(:=~))
1994
+ end
1995
+ array
1996
+ end
1997
+
1998
+ def set_backtrace(array)
1999
+ super(self.class.filter_backtrace(array))
2000
+ end
2001
+
2002
+ def set_option(opt, eq)
2003
+ if eq
2004
+ @args[0] = opt
2005
+ else
2006
+ @args.unshift(opt)
2007
+ end
2008
+ self
2009
+ end
2010
+
2011
+ #
2012
+ # Returns error reason. Override this for I18N.
2013
+ #
2014
+ def reason
2015
+ @reason || self.class::Reason
2016
+ end
2017
+
2018
+ def inspect
2019
+ "#<#{self.class}: #{args.join(' ')}>"
2020
+ end
2021
+
2022
+ #
2023
+ # Default stringizing method to emit standard error message.
2024
+ #
2025
+ def message
2026
+ reason + ': ' + args.join(' ')
2027
+ end
2028
+
2029
+ alias to_s message
2030
+ end
2031
+
2032
+ #
2033
+ # Raises when ambiguously completable string is encountered.
2034
+ #
2035
+ class AmbiguousOption < ParseError
2036
+ const_set(:Reason, 'ambiguous option'.freeze)
2037
+ end
2038
+
2039
+ #
2040
+ # Raises when there is an argument for a switch which takes no argument.
2041
+ #
2042
+ class NeedlessArgument < ParseError
2043
+ const_set(:Reason, 'needless argument'.freeze)
2044
+ end
2045
+
2046
+ #
2047
+ # Raises when a switch with mandatory argument has no argument.
2048
+ #
2049
+ class MissingArgument < ParseError
2050
+ const_set(:Reason, 'missing argument'.freeze)
2051
+ end
2052
+
2053
+ #
2054
+ # Raises when switch is undefined.
2055
+ #
2056
+ class InvalidOption < ParseError
2057
+ const_set(:Reason, 'invalid option'.freeze)
2058
+ end
2059
+
2060
+ #
2061
+ # Raises when the given argument does not match required format.
2062
+ #
2063
+ class InvalidArgument < ParseError
2064
+ const_set(:Reason, 'invalid argument'.freeze)
2065
+ end
2066
+
2067
+ #
2068
+ # Raises when the given argument word can't be completed uniquely.
2069
+ #
2070
+ class AmbiguousArgument < InvalidArgument
2071
+ const_set(:Reason, 'ambiguous argument'.freeze)
2072
+ end
2073
+
2074
+ #
2075
+ # Miscellaneous
2076
+ #
2077
+
2078
+ #
2079
+ # Extends command line arguments array (ARGV) to parse itself.
2080
+ #
2081
+ module Arguable
2082
+
2083
+ #
2084
+ # Sets OptionParser object, when +opt+ is +false+ or +nil+, methods
2085
+ # OptionParser::Arguable#options and OptionParser::Arguable#options= are
2086
+ # undefined. Thus, there is no ways to access the OptionParser object
2087
+ # via the receiver object.
2088
+ #
2089
+ def options=(opt)
2090
+ unless @optparse = opt
2091
+ class << self
2092
+ undef_method(:options)
2093
+ undef_method(:options=)
2094
+ end
2095
+ end
2096
+ end
2097
+
2098
+ #
2099
+ # Actual OptionParser object, automatically created if nonexistent.
2100
+ #
2101
+ # If called with a block, yields the OptionParser object and returns the
2102
+ # result of the block. If an OptionParser::ParseError exception occurs
2103
+ # in the block, it is rescued, a error message printed to STDERR and
2104
+ # +nil+ returned.
2105
+ #
2106
+ def options
2107
+ @optparse ||= OptionParser.new
2108
+ @optparse.default_argv = self
2109
+ block_given? or return @optparse
2110
+ begin
2111
+ yield @optparse
2112
+ rescue ParseError
2113
+ @optparse.warn $!
2114
+ nil
2115
+ end
2116
+ end
2117
+
2118
+ #
2119
+ # Parses +self+ destructively in order and returns +self+ containing the
2120
+ # rest arguments left unparsed.
2121
+ #
2122
+ def order!(&blk) options.order!(self, &blk) end
2123
+
2124
+ #
2125
+ # Parses +self+ destructively in permutation mode and returns +self+
2126
+ # containing the rest arguments left unparsed.
2127
+ #
2128
+ def permute!() options.permute!(self) end
2129
+
2130
+ #
2131
+ # Parses +self+ destructively and returns +self+ containing the
2132
+ # rest arguments left unparsed.
2133
+ #
2134
+ def parse!() options.parse!(self) end
2135
+
2136
+ #
2137
+ # Substitution of getopts is possible as follows. Also see
2138
+ # OptionParser#getopts.
2139
+ #
2140
+ # def getopts(*args)
2141
+ # ($OPT = ARGV.getopts(*args)).each do |opt, val|
2142
+ # eval "$OPT_#{opt.gsub(/[^A-Za-z0-9_]/, '_')} = val"
2143
+ # end
2144
+ # rescue OptionParser::ParseError
2145
+ # end
2146
+ #
2147
+ def getopts(*args)
2148
+ options.getopts(self, *args)
2149
+ end
2150
+
2151
+ #
2152
+ # Initializes instance variable.
2153
+ #
2154
+ def self.extend_object(obj)
2155
+ super
2156
+ obj.instance_eval {@optparse = nil}
2157
+ end
2158
+ def initialize(*args)
2159
+ super
2160
+ @optparse = nil
2161
+ end
2162
+ end
2163
+
2164
+ #
2165
+ # Acceptable argument classes. Now contains DecimalInteger, OctalInteger
2166
+ # and DecimalNumeric. See Acceptable argument classes (in source code).
2167
+ #
2168
+ module Acceptables
2169
+ const_set(:DecimalInteger, OptionParser::DecimalInteger)
2170
+ const_set(:OctalInteger, OptionParser::OctalInteger)
2171
+ const_set(:DecimalNumeric, OptionParser::DecimalNumeric)
2172
+ end
2173
+ end
2174
+
2175
+ # ARGV is arguable by OptionParser
2176
+ ARGV.extend(OptionParser::Arguable)
2177
+
2178
+ OptParse = OptionParser