t2-server 0.6.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. data/.rvmrc +1 -0
  2. data/CHANGES.rdoc +48 -0
  3. data/LICENCE.rdoc +2 -2
  4. data/README.rdoc +245 -10
  5. data/Rakefile +108 -0
  6. data/bin/t2-delete-runs +21 -34
  7. data/bin/t2-get-output +134 -0
  8. data/bin/t2-run-workflow +121 -109
  9. data/bin/t2-server-admin +128 -0
  10. data/bin/t2-server-info +25 -38
  11. data/lib/t2-server-cli.rb +116 -0
  12. data/lib/t2-server.rb +16 -27
  13. data/lib/t2-server/admin.rb +147 -0
  14. data/lib/t2-server/connection-parameters.rb +144 -0
  15. data/lib/t2-server/connection.rb +352 -0
  16. data/lib/t2-server/credentials.rb +84 -0
  17. data/lib/t2-server/exceptions.rb +42 -21
  18. data/lib/t2-server/port.rb +472 -0
  19. data/lib/t2-server/run.rb +822 -227
  20. data/lib/t2-server/server.rb +313 -317
  21. data/lib/t2-server/util.rb +71 -0
  22. data/lib/t2-server/xml/libxml.rb +87 -0
  23. data/lib/t2-server/xml/nokogiri.rb +85 -0
  24. data/lib/t2-server/xml/rexml.rb +85 -0
  25. data/lib/t2-server/xml/xml.rb +111 -0
  26. data/lib/t2server.rb +4 -1
  27. data/t2-server.gemspec +112 -0
  28. data/test/tc_admin.rb +63 -0
  29. data/test/{tc_paths.rb → tc_params.rb} +11 -25
  30. data/test/tc_perms.rb +132 -0
  31. data/test/tc_run.rb +200 -67
  32. data/test/tc_secure.rb +191 -0
  33. data/test/tc_server.rb +25 -23
  34. data/test/tc_util.rb +74 -0
  35. data/test/ts_t2server.rb +57 -12
  36. data/test/workflows/always_fail.t2flow +69 -0
  37. data/test/workflows/list_and_value.t2flow +12 -0
  38. data/test/workflows/list_with_errors.t2flow +107 -0
  39. data/test/workflows/secure/basic-http.t2flow +74 -0
  40. data/test/workflows/secure/basic-https.t2flow +74 -0
  41. data/test/workflows/secure/client-https.t2flow +162 -0
  42. data/test/workflows/secure/digest-http.t2flow +129 -0
  43. data/test/workflows/secure/digest-https.t2flow +107 -0
  44. data/test/workflows/secure/heater-pk.pem +20 -0
  45. data/test/workflows/secure/user-cert.p12 +0 -0
  46. data/test/workflows/secure/ws-http.t2flow +180 -0
  47. data/test/workflows/secure/ws-https.t2flow +180 -0
  48. data/test/workflows/strings.txt +10 -0
  49. data/test/workflows/xml_xpath.t2flow +136 -136
  50. data/version.yml +4 -0
  51. metadata +132 -34
  52. data/lib/t2-server/xml.rb +0 -86
@@ -0,0 +1,84 @@
1
+ # Copyright (c) 2010, 2011 The University of Manchester, UK.
2
+ #
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # * Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ #
15
+ # * Neither the names of The University of Manchester nor the names of its
16
+ # contributors may be used to endorse or promote products derived from this
17
+ # software without specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
+ # POSSIBILITY OF SUCH DAMAGE.
30
+ #
31
+ # Author: Robert Haines
32
+
33
+ module T2Server
34
+
35
+ # This class serves as a base class for concrete HTTP credential systems.
36
+ class HttpCredentials
37
+ # The username held by these credentials.
38
+ attr_reader :username
39
+
40
+ # Create a set of credentials with the supplied username and password.
41
+ def initialize(username, password)
42
+ @username = username
43
+ @password = password
44
+ end
45
+
46
+ # :call-seq:
47
+ # to_s
48
+ #
49
+ # Return the username held by these credentials.
50
+ def to_s
51
+ @username
52
+ end
53
+
54
+ # Used within #inspect, below to help override the built in version.
55
+ @@to_s = Kernel.instance_method(:to_s)
56
+
57
+ # :call-seq:
58
+ # inspect
59
+ #
60
+ # Override the Kernel#inspect method so that the password is not exposed
61
+ # when it is called.
62
+ def inspect
63
+ @@to_s.bind(self).call.sub!(/>\z/) {" Username:#{self}>"}
64
+ end
65
+ end
66
+
67
+ # A class representing HTTP Basic credentials.
68
+ class HttpBasic < HttpCredentials
69
+
70
+ # Create a set of credentials with the supplied username and password.
71
+ def initialize(username, password)
72
+ super(username, password)
73
+ end
74
+
75
+ # :call-seq:
76
+ # authenticate(request)
77
+ #
78
+ # Authenticate the supplied HTTP request with the credentials held within
79
+ # this class.
80
+ def authenticate(request)
81
+ request.basic_auth @username, @password
82
+ end
83
+ end
84
+ end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2010, 2011 The University of Manchester, UK.
1
+ # Copyright (c) 2010-2012 The University of Manchester, UK.
2
2
  #
3
3
  # All rights reserved.
4
4
  #
@@ -14,7 +14,7 @@
14
14
  #
15
15
  # * Neither the names of The University of Manchester nor the names of its
16
16
  # contributors may be used to endorse or promote products derived from this
17
- # software without specific prior written permission.
17
+ # software without specific prior written permission.
18
18
  #
19
19
  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
20
  # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
@@ -85,8 +85,8 @@ module T2Server
85
85
  # response. The response to be passed in is that which was returned by a
86
86
  # call to Net::HTTP#request.
87
87
  def initialize(response)
88
- body = "\n#{response.body}" if response.body
89
- super "Unexpected server response: #{response.code}\n#{response.error!}#{body}"
88
+ body = response.body ? "\n#{response.body}" : ""
89
+ super "Unexpected server response: #{response.code}\n#{body}"
90
90
  end
91
91
  end
92
92
 
@@ -94,12 +94,12 @@ module T2Server
94
94
  # expectation is that the run exists then it could have been destroyed by
95
95
  # a timeout or another user.
96
96
  class RunNotFoundError < T2ServerError
97
- attr_reader :uuid
97
+ attr_reader :identifier
98
98
 
99
- # Create a new RunNotFoundError with the specified UUID.
100
- def initialize(uuid)
101
- @uuid = uuid
102
- super "Could not find run #{@uuid}"
99
+ # Create a new RunNotFoundError with the specified identifier.
100
+ def initialize(id)
101
+ @identifier = id
102
+ super "Could not find run #{@identifier}"
103
103
  end
104
104
  end
105
105
 
@@ -118,13 +118,10 @@ module T2Server
118
118
 
119
119
  # The server is at capacity and cannot accept anymore runs at this time.
120
120
  class ServerAtCapacityError < T2ServerError
121
- attr_reader :limit
122
-
123
- # Create a new ServerAtCapacityError with the specified limit for
124
- # information.
125
- def initialize(limit)
126
- @limit = limit
127
- super "The server is already running its configured limit of concurrent workflows (#{@limit})"
121
+ # Create a new ServerAtCapacityError.
122
+ def initialize
123
+ super "The server is already running its configured limit of " +
124
+ "concurrent workflows."
128
125
  end
129
126
  end
130
127
 
@@ -137,7 +134,9 @@ module T2Server
137
134
  # attribute.
138
135
  def initialize(path)
139
136
  @path = path
140
- super "Access to #{@path} is forbidden. Either you do not have the required credentials or the server does not allow the requested operation"
137
+ super "Access to #{@path} is forbidden. Either you do not have the " +
138
+ "required credentials or the server does not allow the requested " +
139
+ "operation"
141
140
  end
142
141
  end
143
142
 
@@ -146,9 +145,14 @@ module T2Server
146
145
  attr_reader :username
147
146
 
148
147
  # Create a new AuthorizationError with the rejected username
149
- def initialize(username)
150
- @username = username
151
- super "The username '#{@username}' is not authorized to connect to this server"
148
+ def initialize(credentials)
149
+ if credentials != nil
150
+ @username = credentials.username
151
+ else
152
+ @username = ""
153
+ end
154
+ super "The username '#{@username}' is not authorized to connect to " +
155
+ "this server"
152
156
  end
153
157
  end
154
158
 
@@ -160,7 +164,24 @@ module T2Server
160
164
  # Create a new RunStateError specifying both the current state and that
161
165
  # which is needed to run the operation.
162
166
  def initialize(current, need)
163
- super "The run is in the wrong state (#{current}); it should be '#{need}' to perform that action"
167
+ super "The run is in the wrong state (#{current}); it should be " +
168
+ "'#{need}' to perform that action"
169
+ end
170
+ end
171
+
172
+ # Raised if the server wishes to redirect the connection. This typically
173
+ # happens if a client tries to connect to a https server vis a http uri.
174
+ class ConnectionRedirectError < T2ServerError
175
+
176
+ # The redirected connection
177
+ attr_reader :redirect
178
+
179
+ # Create a new ConnectionRedirectError with the new, redirected,
180
+ # connection supplied.
181
+ def initialize(connection)
182
+ @redirect = connection
183
+
184
+ super "The server returned an unhandled redirect to '#{@redirect}'."
164
185
  end
165
186
  end
166
187
  end
@@ -0,0 +1,472 @@
1
+ # Copyright (c) 2010-2012 The University of Manchester, UK.
2
+ #
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # * Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ #
15
+ # * Neither the names of The University of Manchester nor the names of its
16
+ # contributors may be used to endorse or promote products derived from this
17
+ # software without specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
+ # POSSIBILITY OF SUCH DAMAGE.
30
+ #
31
+ # Author: Robert Haines
32
+
33
+ module T2Server
34
+
35
+ # Base class of InputPort and OutputPort
36
+ class Port
37
+ include XML::Methods
38
+
39
+ # The port's name
40
+ attr_reader :name
41
+
42
+ # The "depth" of the port. 0 = a singleton value.
43
+ attr_reader :depth
44
+
45
+ # :stopdoc:
46
+ # Create a new port.
47
+ def initialize(run, xml)
48
+ @run = run
49
+
50
+ parse_xml(xml)
51
+ end
52
+ # :startdoc:
53
+
54
+ private
55
+ def parse_xml(xml)
56
+ @name = xml_node_attribute(xml, 'name')
57
+ @depth = xml_node_attribute(xml, 'depth').to_i
58
+ end
59
+ end
60
+
61
+ # Represents an input to a workflow.
62
+ class InputPort < Port
63
+
64
+ # If set, the file which has been used to supply this port's data.
65
+ attr_reader :file
66
+
67
+ # If set, the value held by this port. Could be a list (of lists (etc)).
68
+ attr_reader :value
69
+
70
+ # :stopdoc:
71
+ # Create a new InputPort.
72
+ def initialize(run, xml)
73
+ super(run, xml)
74
+
75
+ @value = nil
76
+ @file = nil
77
+ @remote_file = false
78
+ end
79
+ # :startdoc:
80
+
81
+ # :call-seq:
82
+ # value = value
83
+ #
84
+ # Set the value of this input port. This has no effect if the run is
85
+ # already running or finished.
86
+ def value=(value)
87
+ return unless @run.initialized?
88
+ @file = nil
89
+ @remote_file = false
90
+ @value = value
91
+ end
92
+
93
+ # :call-seq:
94
+ # file? -> bool
95
+ #
96
+ # Is this port's data being supplied by a file? The file could be local or
97
+ # remote (already on the server) for this to return true.
98
+ def file?
99
+ !@file.nil?
100
+ end
101
+
102
+ # :call-seq:
103
+ # remote_file? -> bool
104
+ #
105
+ # Is this port's data being supplied by a remote (one that is already on
106
+ # the server) file?
107
+ def remote_file?
108
+ file? && @remote_file
109
+ end
110
+
111
+ # :call-seq:
112
+ # remote_file = filename
113
+ #
114
+ # Set the remote file to use for this port's data. The file must already be
115
+ # on the server. This has no effect if the run is already running or
116
+ # finished.
117
+ def remote_file=(filename)
118
+ return unless @run.initialized?
119
+ @value = nil
120
+ @file = filename
121
+ @remote_file = true
122
+ end
123
+
124
+ # :call-seq:
125
+ # file = filename
126
+ #
127
+ # Set the file to use for this port's data. The file will be uploaded to
128
+ # the server before the run starts. This has no effect if the run is
129
+ # already running or finished.
130
+ def file=(filename)
131
+ return unless @run.initialized?
132
+ @value = nil
133
+ @file = filename
134
+ @remote_file = false
135
+ end
136
+
137
+ # :call-seq:
138
+ # baclava? -> bool
139
+ #
140
+ # Has this port been set via a baclava document?
141
+ def baclava?
142
+ @run.baclava_input?
143
+ end
144
+
145
+ # :call-seq:
146
+ # set? -> bool
147
+ #
148
+ # Has this port been set?
149
+ def set?
150
+ !value.nil? or file? or baclava?
151
+ end
152
+ end
153
+
154
+ # Represents an output port of a workflow.
155
+ class OutputPort < Port
156
+ include XML::Methods
157
+
158
+ # :stopdoc:
159
+ # Create a new OutputPort.
160
+ def initialize(run, xml)
161
+ super(run, xml)
162
+
163
+ @error = false
164
+ @structure = parse_data(xml_first_child(xml))
165
+
166
+ # cached outputs
167
+ @values = nil
168
+ @refs = nil
169
+ @types = nil
170
+ @sizes = nil
171
+ @total_size = nil
172
+ end
173
+ # :startdoc:
174
+
175
+ # :call-seq:
176
+ # error? -> bool
177
+ #
178
+ # Is there an error associated with this output port?
179
+ def error?
180
+ @error
181
+ end
182
+
183
+ # :call-seq:
184
+ # [int] -> obj
185
+ #
186
+ # This call provides access to the underlying structure of the OutputPort.
187
+ # It can only be used for ports of depth >= 1. For singleton ports, use
188
+ # OutputPort#value instead.
189
+ #
190
+ # Example usage - To get part of a value from an output port with depth 3:
191
+ # port[1][0][1].value(10...100)
192
+ def [](i)
193
+ return @structure if depth == 0
194
+ @structure[i]
195
+ end
196
+
197
+ # :call-seq:
198
+ # value -> obj
199
+ # value(range) -> obj
200
+ # value -> Array
201
+ #
202
+ # For singleton outputs get the value (or part of it). For list outputs
203
+ # get all the values in an Array structure that mirrors the structure of
204
+ # the output port. To get part of a value from a list use
205
+ # 'port[].value(range)'.
206
+ def value(range = nil)
207
+ if depth == 0
208
+ if range.nil?
209
+ @structure.value
210
+ else
211
+ @structure.value(range)
212
+ end
213
+ else
214
+ @values = strip(:value) if @values.nil?
215
+ @values
216
+ end
217
+ end
218
+
219
+ # :call-seq:
220
+ # reference -> String
221
+ # reference -> Array
222
+ #
223
+ # Get URI references to the data values of this output port as strings.
224
+ #
225
+ # For a singleton output a single uri is returned. For lists an array of
226
+ # uris is returned. For an individual reference from a list use
227
+ # 'port[].reference'.
228
+ def reference
229
+ @refs = strip(:reference) if @refs.nil?
230
+ @refs
231
+ end
232
+
233
+ # :call-seq:
234
+ # type -> String
235
+ # type -> Array
236
+ #
237
+ # Get the mime type of the data value in this output port.
238
+ #
239
+ # For a singleton output a single type is returned. For lists an array of
240
+ # types is returned. For an individual type from a list use 'port[].type'.
241
+ def type
242
+ @types = strip(:type) if @types.nil?
243
+ @types
244
+ end
245
+
246
+ # :call-seq:
247
+ # size -> int
248
+ # size -> Array
249
+ #
250
+ # Get the data size of the data value in this output port.
251
+ #
252
+ # For a singleton output a single size is returned. For lists an array of
253
+ # sizes is returned. For an individual size from a list use 'port[].size'.
254
+ def size
255
+ @sizes = strip(:size) if @sizes.nil?
256
+ @sizes
257
+ end
258
+
259
+ # :call-seq:
260
+ # error -> String
261
+ #
262
+ # Get the error message (if there is one) of this output port.
263
+ #
264
+ # This method is only for use on outputs of depth 0. For other depths use
265
+ # 'port[].error'
266
+ def error
267
+ return nil unless depth == 0
268
+ @structure.error
269
+ end
270
+
271
+ # :call-seq:
272
+ # total_size -> int
273
+ #
274
+ # Return the total data size of all the data in this output port.
275
+ def total_size
276
+ return @total_size if @total_size
277
+ if @structure.instance_of? Array
278
+ return 0 if @structure.empty?
279
+ @total_size = strip(:size).flatten.inject { |sum, i| sum + i }
280
+ else
281
+ @total_size = size
282
+ end
283
+ end
284
+
285
+ # :stopdoc:
286
+ def download(ref, range = nil)
287
+ @run.download_output_data("#{path(ref)}", range)
288
+ end
289
+ # :startdoc:
290
+
291
+ private
292
+
293
+ # Parse the XML port description into a raw data value structure.
294
+ def parse_data(node)
295
+ case xml_node_name(node)
296
+ when 'list'
297
+ data = []
298
+ xml_children(node) do |child|
299
+ data << parse_data(child)
300
+ end
301
+ return data
302
+ when 'value'
303
+ return PortValue.new(self, xml_node_attribute(node, 'href'), false,
304
+ xml_node_attribute(node, 'contentType'),
305
+ xml_node_attribute(node, 'contentByteLength').to_i)
306
+ when 'error'
307
+ @error = true
308
+ return PortValue.new(self, xml_node_attribute(node, 'href'), true)
309
+ end
310
+ end
311
+
312
+ # Generate the path to the actual data for a data value.
313
+ def path(ref)
314
+ parts = ref.split('/')
315
+ @depth == 0 ? parts[-1] : "/" + parts[-(@depth + 1)..-1].join('/')
316
+ end
317
+
318
+ # Strip the requested attribute from the raw values structure.
319
+ def strip(attribute, struct = @structure)
320
+ if struct.instance_of? Array
321
+ data = []
322
+ struct.each { |item| data << strip(attribute, item) }
323
+ return data
324
+ else
325
+ struct.method(attribute).call
326
+ end
327
+ end
328
+ end
329
+
330
+ # A class to represent an output port data value.
331
+ class PortValue
332
+
333
+ # The URI reference of this port value as a String.
334
+ attr_reader :reference
335
+
336
+ # The mime type of this port value as a String.
337
+ attr_reader :type
338
+
339
+ # The size (in bytes) of the port value.
340
+ attr_reader :size
341
+
342
+ # :stopdoc:
343
+ def initialize(port, ref, error, type = "", size = 0)
344
+ @port = port
345
+ @reference = ref
346
+ @type = type
347
+ @size = size
348
+ @value = nil
349
+ @vgot = nil
350
+ @error = nil
351
+
352
+ if error
353
+ @error = @port.download(@reference)
354
+ @type = "error"
355
+ end
356
+ end
357
+ # :startdoc:
358
+
359
+ # :call-seq:
360
+ # value -> obj
361
+ # value(range) -> obj
362
+ #
363
+ # Return the value of this port. It is downloaded from the server if it
364
+ # has not already been retrieved. If a range is specified then just that
365
+ # portion of the value is downloaded and returned. If no range is specified
366
+ # then the whole value is downloaded and returned.
367
+ #
368
+ # All downloaded data is cached and not downloaded a second time if the
369
+ # same or similar ranges are requested.
370
+ def value(range = 0...@size)
371
+ return nil if error?
372
+ return "" if @type == "application/x-empty"
373
+ return @value if range == :debug
374
+
375
+ # check that the range provided is sensible
376
+ range = 0..range.max if range.min < 0
377
+ range = range.min...@size if range.max >= @size
378
+
379
+ need = fill(@vgot, range)
380
+ case need.length
381
+ when 0
382
+ # we already have all the data we need, just return the right bit.
383
+ # @vgot cannot be nil here and must fully encompass range.
384
+ ret_range = (range.min - @vgot.min)..(range.max - @vgot.min)
385
+ @value[ret_range]
386
+ when 1
387
+ # we either have some data, at one end of range or either side of it,
388
+ # or none. @vgot can be nil here.
389
+ # In both cases we download what we need.
390
+ new_data = @port.download(@reference, need[0])
391
+ if @vgot.nil?
392
+ # this is the only data we have, return it all.
393
+ @vgot = range
394
+ @value = new_data
395
+ else
396
+ # add the new data to the correct end of the data we have, then
397
+ # return the range requested.
398
+ if range.max <= @vgot.max
399
+ @vgot = range.min..@vgot.max
400
+ @value = new_data + @value
401
+ @value[0..range.max]
402
+ else
403
+ @vgot = @vgot.min..range.max
404
+ @value = @value + new_data
405
+ @value[(range.min - @vgot.min)..@vgot.max]
406
+ end
407
+ end
408
+ when 2
409
+ # we definitely have some data and it is in the middle of the
410
+ # range requested. @vgot cannot be nil here.
411
+ @vgot = range
412
+ @value = @port.download(@reference, need[0]) + @value +
413
+ @port.download(@reference, need[1])
414
+ end
415
+ end
416
+
417
+ # :call-seq:
418
+ # error? -> bool
419
+ #
420
+ # Does this port represent an error?
421
+ def error?
422
+ !@error.nil?
423
+ end
424
+
425
+ # :call-seq:
426
+ # error -> string
427
+ #
428
+ # Return the error message for this value, or _nil_ if it is not an error.
429
+ def error
430
+ @error
431
+ end
432
+
433
+ # Used within #inspect, below to help override the built in version.
434
+ @@to_s = Kernel.instance_method(:to_s)
435
+
436
+ # :call-seq:
437
+ # inspect -> string
438
+ #
439
+ # Return a printable representation of this port value for debugging
440
+ # purposes.
441
+ def inspect
442
+ @@to_s.bind(self).call.sub!(/>\z/) { " @value=#{value.inspect}, " +
443
+ "@type=#{type.inspect}, @size=#{size.inspect}>"
444
+ }
445
+ end
446
+
447
+ private
448
+ def fill(got, want)
449
+ return [want] if got.nil?
450
+
451
+ if got.member? want.min
452
+ if got.member? want.max
453
+ return []
454
+ else
455
+ return [(got.max + 1)..want.max]
456
+ end
457
+ else
458
+ if got.member? want.max
459
+ return [want.min..(got.min - 1)]
460
+ else
461
+ if want.max < got.min
462
+ return [want.min..(got.min - 1)]
463
+ elsif want.min > got.max
464
+ return [(got.max + 1)..want.max]
465
+ else
466
+ return [want.min..(got.min - 1), (got.max + 1)..want.max]
467
+ end
468
+ end
469
+ end
470
+ end
471
+ end
472
+ end