fisheye-crucible 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,39 @@
1
+ module CommonHelpers
2
+ def in_tmp_folder(&block)
3
+ FileUtils.chdir(@tmp_root, &block)
4
+ end
5
+
6
+ def in_project_folder(&block)
7
+ project_folder = @active_project_folder || @tmp_root
8
+ FileUtils.chdir(project_folder, &block)
9
+ end
10
+
11
+ def in_home_folder(&block)
12
+ FileUtils.chdir(@home_path, &block)
13
+ end
14
+
15
+ def force_local_lib_override(project_name = @project_name)
16
+ rakefile = File.read(File.join(project_name, 'Rakefile'))
17
+ File.open(File.join(project_name, 'Rakefile'), "w+") do |f|
18
+ f << "$:.unshift('#{@lib_path}')\n"
19
+ f << rakefile
20
+ end
21
+ end
22
+
23
+ def setup_active_project_folder project_name
24
+ @active_project_folder = File.join(@tmp_root, project_name)
25
+ @project_name = project_name
26
+ end
27
+ end
28
+
29
+ World(CommonHelpers)
30
+
31
+ # Common steps
32
+ Given /^I have logged in$/ do
33
+ @fc.login 'gemtest', 'gemtest'
34
+ @fc.instance_eval("@token").should_not be_nil
35
+ end
36
+
37
+ Given /^I have not logged in$/ do
38
+ @fc.instance_eval("@token").should be_nil
39
+ end
@@ -0,0 +1,3 @@
1
+ ---
2
+ username:
3
+ password:
@@ -0,0 +1,16 @@
1
+ require File.dirname(__FILE__) + "/../../lib/fisheye-crucible"
2
+ require File.dirname(__FILE__) + "/../../lib/fisheye-crucible/client/legacy"
3
+
4
+ gem 'cucumber'
5
+ require 'cucumber'
6
+ gem 'rspec'
7
+ require 'spec'
8
+
9
+ Before do
10
+ @tmp_root = File.dirname(__FILE__) + "/../../tmp"
11
+ @home_path = File.expand_path(File.join(@tmp_root, "home"))
12
+ @lib_path = File.expand_path(File.dirname(__FILE__) + "/../../lib")
13
+ FileUtils.rm_rf @tmp_root
14
+ FileUtils.mkdir_p @home_path
15
+ ENV['HOME'] = @home_path
16
+ end
@@ -0,0 +1,9 @@
1
+ Before do
2
+ @fc = FisheyeCrucible::Client::Legacy.new 'http://sandbox.fisheye.atlassian.com'
3
+ end
4
+
5
+ =begin
6
+ at_exit do
7
+ @fc.logout if @fc
8
+ end
9
+ =end
@@ -0,0 +1,11 @@
1
+ module Matchers
2
+ def contain(expected)
3
+ simple_matcher("contain #{expected.inspect}") do |given, matcher|
4
+ matcher.failure_message = "expected #{given.inspect} to contain #{expected.inspect}"
5
+ matcher.negative_failure_message = "expected #{given.inspect} not to contain #{expected.inspect}"
6
+ given.index expected
7
+ end
8
+ end
9
+ end
10
+
11
+ World(Matchers)
@@ -0,0 +1,10 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) ||
3
+ $:.include?(File.expand_path(File.dirname(__FILE__)))
4
+
5
+ require 'fisheye_crucible_exception'
6
+
7
+ module FisheyeCrucible
8
+ VERSION = '0.0.1'
9
+ WWW = 'http://github.com/turboladen/fisheye-crucible'
10
+ end
@@ -0,0 +1,33 @@
1
+ require 'rubygems'
2
+ require 'fisheye-crucible'
3
+ require 'rest-client'
4
+
5
+ # This is the parent class for accessing Fisheye/Crucible. This will change
6
+ # quite a bit once work on the current Fisheye/Crucible gets started. For
7
+ # now, look at the docs for FisheyeCrucible::Client::Legacy to get started.
8
+ class FisheyeCrucible::Client
9
+
10
+ # @return [Boolean] Turn debug on or off
11
+ attr_accessor :do_debug
12
+
13
+ ##
14
+ # Sets up a Rest object to interact with the server(s).
15
+ #
16
+ # @param [String] server The base URL of the server to connect to.
17
+ def initialize(server)
18
+ @server = server
19
+ @fisheye_rest = RestClient::Resource.new(@server)
20
+ @token = nil
21
+ @do_debug = false
22
+ end
23
+
24
+ ##
25
+ # Print out string if debug is turned on.
26
+ #
27
+ # @param [String] string The debug string to print out.
28
+ def debug(string)
29
+ if @do_debug
30
+ puts string
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,315 @@
1
+ require 'rubygems'
2
+ require 'fisheye-crucible/client'
3
+ require 'fisheye-crucible/type_converter'
4
+ require 'rexml/document'
5
+
6
+ ##
7
+ # This class provides access to the Fisheye/Crucible REST API that was
8
+ # used before version 2.0. More info here:
9
+ # http://confluence.atlassian.com/display/FECRUDEV/FishEye+Legacy+Remote+API
10
+ #
11
+ # All methods are named in Ruby style, however aliases are provided that are
12
+ # named after the Fisheye/Crucible function name (if different).
13
+ class FisheyeCrucible::Client::Legacy < FisheyeCrucible::Client
14
+
15
+ def initialize(server)
16
+ super(server)
17
+ end
18
+
19
+ ##
20
+ # Logs in with provided credentials and returns a token that can be used for
21
+ # all other calls.
22
+ #
23
+ # @param [String] username The user to login with.
24
+ # @param [String] password The password of the user to login with.
25
+ # @return [String] The token to use for other calls.
26
+ def login(username=nil, password=nil)
27
+ @token = build_rest_call('api/rest/login',
28
+ :post,
29
+ {
30
+ :username => username,
31
+ :password => password
32
+ }
33
+ )
34
+ end
35
+
36
+ ##
37
+ # Logs out of Fisheye/Crucible.
38
+ #
39
+ # @return [Boolean] Returns true if logout was successful.
40
+ def logout
41
+ unless @token
42
+ error_message = "Can't log out--it seems you're not logged in."
43
+ raise FisheyeCrucibleError, error_message
44
+ end
45
+
46
+ result = build_rest_call('api/rest/logout', :post, { :auth => @token })
47
+
48
+ @token = '' if result == true
49
+
50
+ result
51
+ end
52
+
53
+ ##
54
+ # Gets the version of Fisheye.
55
+ #
56
+ # @return [String] The version of Fisheye.
57
+ # @alias fisheyeVersion
58
+ def fisheye_version
59
+ build_rest_call('api/rest/fisheyeVersion', :get)
60
+ end
61
+ alias_method :fisheyeVersion, :fisheye_version
62
+
63
+ ##
64
+ # Gets the version of Crucible.
65
+ #
66
+ # @return [String] The version of Crucible.
67
+ def crucible_version
68
+ build_rest_call('api/rest/fisheyeVersion', :get)
69
+ end
70
+ alias_method :crucibleVersion, :crucible_version
71
+
72
+ ##
73
+ # Gets the list of repositories to an array.
74
+ #
75
+ # @return [Array] The list of repositories.
76
+ def repositories
77
+ build_rest_call('api/rest/repositories',
78
+ :post,
79
+ { :auth => @token }
80
+ )
81
+ end
82
+ alias_method :listRepositories, :repositories
83
+
84
+ ##
85
+ # Gets the file/dir listing from a repository.
86
+ #
87
+ # @param [String] repository The repository to get the listing for.
88
+ # @param [String] path The directory in the repository to get the listing
89
+ # for. If no path is given, listing is from /.
90
+ # @return [Hash<String><Hash>] The listing, where the key is the
91
+ # file/directory and the value is another Hash that contains properties of
92
+ # the file/directory.
93
+ def list_paths_from(repository, path='')
94
+ build_rest_call('api/rest/listPaths',
95
+ :post,
96
+ {
97
+ :auth => @token,
98
+ :rep => repository,
99
+ :path => path
100
+ }
101
+ )
102
+ end
103
+ alias_method :listPaths, :list_paths_from
104
+
105
+ ##
106
+ # Gets details about a specific file/directory revision from the given
107
+ # repository.
108
+ #
109
+ # @param [String] repository The repository in which the file resides.
110
+ # @param [String] path The path, relative to the repository, in which the
111
+ # file resides.
112
+ # @param [Fixnum] revision The revision of the file/directory to get the info
113
+ # about.
114
+ # @return [Hash] The list of details about the file revision.
115
+ def revision(repository, path, revision)
116
+ build_rest_call('api/rest/revision',
117
+ :post,
118
+ {
119
+ :auth => @token,
120
+ :rep => repository,
121
+ :path => path,
122
+ :rev => revision.to_s
123
+ }
124
+ )
125
+ end
126
+ alias_method :getRevision, :revision
127
+
128
+ ##
129
+ # Gets tags associated with a file/directory revision.
130
+ #
131
+ # @param [String] repository The repository in which the file resides.
132
+ # @param [String] path The path, relative to the repository, in which the
133
+ # file resides.
134
+ # @param [Fixnum] revision The revision of the file/directory to get the tags
135
+ # for.
136
+ # @return [Hash] The list of tags for the file revision.
137
+ def tags(repository, path, revision)
138
+ puts "WARNING: This method is untested!"
139
+
140
+ tags = build_rest_call('api/rest/tags',
141
+ :post,
142
+ {
143
+ :auth => @token,
144
+ :rep => repository,
145
+ :path => path,
146
+ :rev => revision.to_s
147
+ }
148
+ )
149
+ =begin
150
+ tags_xml = @fisheye_rest['api/rest/tags'].post :auth => @token,
151
+ :rep => repository,
152
+ :path => path,
153
+ :rev => revision.to_s
154
+
155
+ #debug tags_xml
156
+ return tags_xml.to_ruby
157
+ doc = REXML::Document.new(tags_xml)
158
+
159
+ if doc.root.name.eql? 'error'
160
+ raise doc.root.text
161
+ elsif doc.root.name.eql? 'response' and doc.root.has_elements?
162
+ # TODO: Not sure if this works since I can't find any files
163
+ # with tags.
164
+ return doc.root.elements['//tags'].text
165
+ elsif doc.root.name.eql? 'response' and !doc.root.has_elements?
166
+ return ""
167
+ end
168
+ =end
169
+ end
170
+ alias_method :listTagsForRevision, :tags
171
+
172
+ ##
173
+ # Gets the history for a file/directory, which is a list of revisions and
174
+ # their associated info.
175
+ #
176
+ # @param [String] repository The repository for which to get the history
177
+ # info about.
178
+ # @param [String] path The path, relative to root, for which to get info
179
+ # about.
180
+ # @return [Array<Hash>] The list of revisions.
181
+ def path_history(repository, path='')
182
+ build_rest_call('api/rest/pathHistory',
183
+ :post,
184
+ {
185
+ :auth => @token,
186
+ :rep => repository,
187
+ :path => path
188
+ }
189
+ )
190
+ end
191
+ alias :pathHistory :path_history
192
+
193
+ ##
194
+ # Gets information about a changeset.
195
+ #
196
+ # @param [String] repository The repository for which to get the changeset
197
+ # info about.
198
+ # @param [Fixnum] csid The changeset ID to get the info about.
199
+ # @return [Hash] All of the changeset info as defined by the API.
200
+ def changeset(repository, csid)
201
+ build_rest_call('api/rest/changeset',
202
+ :post,
203
+ {
204
+ :auth => @token,
205
+ :rep => repository,
206
+ :csid => csid.to_s
207
+ }
208
+ )
209
+ end
210
+ alias_method :getChangeset, :changeset
211
+
212
+ ##
213
+ # Gets all changeset IDs (csids) that match the parameters given.
214
+ #
215
+ # @param [String] repository The repository for which to get the changesets
216
+ # for.
217
+ # @param [String] path Path to the item(s) to get changesets for. Can only
218
+ # be a directory.
219
+ # @param [Fixnum] max_return The maximum number of values to return. If not
220
+ # set, this is limited by the internal Fisheye server. If set, the most
221
+ # recent results are returned.
222
+ # @param [DateTime] start_date The DateTime object representing the start
223
+ # date for which to filter the query.
224
+ # @param [DateTime] end_date The DateTime object representing the end date
225
+ # for which to filter the query.
226
+ # @return [Hash<Array,String>] :csids => the Array of changeset IDs;
227
+ # :max_return => Max values returned.
228
+ def changesets(repository, path='/', max_return=nil, start_date=nil,
229
+ end_date=nil)
230
+ build_rest_call('api/rest/changesets',
231
+ :post,
232
+ {
233
+ :auth => @token,
234
+ :rep => repository,
235
+ :path => path,
236
+ :start => start_date,
237
+ :end => end_date,
238
+ :maxReturn => max_return
239
+ }
240
+ )
241
+ end
242
+ alias_method :listChangesets, :changesets
243
+
244
+ ##
245
+ # Sends an EyeQL query to the server for a given repository. Return types
246
+ # can differ depending on the 'return-clause' used in the query. For more
247
+ # info, see http://confluence.atlassian.com/display/FISHEYE/EyeQL+Reference+Guide.
248
+ #
249
+ # @param [String] repository The repository to run the EyeQL query on.
250
+ # @param [String] query The EyeQL query to run.
251
+ # @return [Object]
252
+ def query(repository, query)
253
+ build_rest_call('api/rest/query',
254
+ :post,
255
+ {
256
+ :auth => @token,
257
+ :rep => repository,
258
+ :query => query
259
+ }
260
+ )
261
+ end
262
+
263
+ ##
264
+ # Privates
265
+ private
266
+
267
+ ##
268
+ # Builds and makes the REST call from the arguments given.
269
+ #
270
+ # @param [String] url The API portion of the URL as defined by the API.
271
+ # @param [String] action 'post' or 'get'.
272
+ # @param [Hash] options The REST params to pass to the REST call.
273
+ # @return [Object] The Object that #to_ruby returns.
274
+ def build_rest_call(url, action, options=nil)
275
+ rest_call = "@fisheye_rest['#{url}'].#{action}"
276
+
277
+ unless options.nil?
278
+ actual_options = non_nil_options_in options
279
+
280
+ option_count = 1
281
+ actual_options.each_pair do |key,value|
282
+ unless value.nil?
283
+ rest_call << " :#{key} => '#{value}'"
284
+ rest_call << ',' unless option_count == actual_options.length
285
+ option_count += 1
286
+ end
287
+ end
288
+ end
289
+
290
+ response_xml = eval(rest_call)
291
+ response = response_xml.to_ruby
292
+
293
+ if response.class == FisheyeCrucibleError
294
+ raise response
295
+ end
296
+
297
+ response
298
+ end
299
+
300
+ ##
301
+ # Removes all key/value pairs from Hash that have a nil value and returns
302
+ # a Hash with keys that have values.
303
+ #
304
+ # @param [Hash] options The Hash to remove nil key/value pairs from.
305
+ def non_nil_options_in options
306
+ non_nil_options = {}
307
+ options.each_pair do |k,v|
308
+ non_nil_options[k] = v unless v.nil?
309
+ end
310
+
311
+ non_nil_options
312
+ end
313
+ rescue FisheyeCrucibleError => e
314
+ puts e.message
315
+ end
@@ -0,0 +1,285 @@
1
+ require 'rexml/document'
2
+ require 'fisheye-crucible'
3
+
4
+ # By adding this method to String, the #to_ruby method can be called directly
5
+ # on the return data from RestClient. This, effectively, accomplishes turning
6
+ # a String of XML into the Ruby data types that make sense for the return types
7
+ # that Atlassian defined.
8
+ class String
9
+
10
+ # Takes a String of XML then converts in to a correlating Ruby data type.
11
+ # Aside from the documentation for each private method below, here is the
12
+ # conversion table:
13
+ # | Fisheye/Crucible Type | Ruby Type |
14
+ # | string | String |
15
+ # | boolean | TrueClass, FalseClass |
16
+ # | pathinfo | Hash with child Hashes |
17
+ # | revision | Hash |
18
+ # | history | Array of revisions |
19
+ # | changeset | Hash |
20
+ # | changesets | Hash of changesets |
21
+ # | revisionkey | Array with child Hashes |
22
+ # | row | Array |
23
+ def to_ruby
24
+ doc = REXML::Document.new self
25
+
26
+ type = doc.root.name
27
+ doc_text = doc.root.text
28
+
29
+ responses = []
30
+ response_type = ''
31
+
32
+ if type == 'error'
33
+ return FisheyeCrucibleError.new(doc_text)
34
+ elsif type == 'response'
35
+ doc.root.each_element do |element|
36
+ # The data type
37
+ response_type = element.name
38
+
39
+ # Text for the next element
40
+ responses << element.text
41
+ end
42
+ else
43
+ message = "Not sure what to do with this response:\n#{doc_text}"
44
+ return FisheyeCrucibleError.new(message)
45
+ end
46
+
47
+ # If we have 0 or 1 actual strings, return the string or ""
48
+ if response_type.eql? 'string' and responses.length <= 1
49
+ return string_to_string(doc)
50
+ # If we have mulitple strings, return the Array of Strings
51
+ elsif response_type.eql? 'string'
52
+ return string_to_array(doc)
53
+ elsif response_type.eql? 'boolean'
54
+ return boolean_to_true_false(doc)
55
+ elsif response_type.eql? 'pathinfo'
56
+ return pathinfo_to_hash(doc)
57
+ elsif response_type.eql? 'revision'
58
+ return revision_to_hash(doc)
59
+ elsif response_type.eql? 'history'
60
+ return history_to_array(doc)
61
+ elsif response_type.eql? 'changeset'
62
+ return changeset_to_hash(doc)
63
+ elsif response_type.eql? 'changesets'
64
+ return changesets_to_hash(doc)
65
+ elsif response_type.eql? 'revisionkey'
66
+ return revisionkeys_to_array(doc)
67
+ elsif response_type.eql? 'row'
68
+ return custom_to_array(doc)
69
+ end
70
+
71
+ message = "Response type unknown: '#{response_type}'"
72
+ return FisheyeCrucibleError.new(message)
73
+ end
74
+
75
+ ##
76
+ # PRIVATES!
77
+ private
78
+
79
+ ##
80
+ # Converts a REXML::Document with 1 element of type <string> into a String.
81
+ #
82
+ # @param [REXML::Document] xml_doc The XML document to convert.
83
+ # @return [String] The string from the XML document.
84
+ def string_to_string(xml_doc)
85
+ xml_doc.root.elements[1].text
86
+ end
87
+
88
+ ##
89
+ # Converts a String to its related Boolean type. If the string doesn't
90
+ # contain such a type, nil is returned.
91
+ #
92
+ # @param [String] string The String to convert.
93
+ # @return [Boolean,String] true, false, or the original string.
94
+ def string_to_true_false(string)
95
+ return true if string.eql? 'true'
96
+ return false if string.eql? 'false'
97
+ return string
98
+ end
99
+
100
+ ##
101
+ # Converts a REXML::Document with element <boolean> into a true or false
102
+ # value.
103
+ #
104
+ # @param [REXML::Document] xml_doc The XML document to convert.
105
+ # @return [Boolean] true, false, or nil.
106
+ def boolean_to_true_false(xml_doc)
107
+ string_to_true_false(xml_doc.root.elements[1].text)
108
+ end
109
+
110
+ ##
111
+ # Converts a REXML::Document with multiple elements of type <string> into an
112
+ # Array.
113
+ #
114
+ # @param [REXML::Document] xml_doc The XML document to convert.
115
+ # @return [Array] The list of strings.
116
+ def string_to_array(xml_doc)
117
+ responses = []
118
+ xml_doc.root.each_element do |element|
119
+ response_type = element.name
120
+ responses << element.text
121
+ end
122
+
123
+ responses
124
+ end
125
+
126
+ ##
127
+ # Takes Fisheye/Crucible's <pathinfo> return type and turns it in to
128
+ # a Hash of Hashes.
129
+ #
130
+ # @param [REXML::Document] xml_doc The XML document to convert.
131
+ # @return [Hash<Hash>] The path info as a Hash. The Hash contains keys
132
+ # which are the file/directory names in the path; values for those keys
133
+ # are Hashes which contain the properties of that file/directory.
134
+ def pathinfo_to_hash(xml_doc)
135
+ path_name = ''
136
+ path_names = {}
137
+
138
+ xml_doc.root.each_element do |element|
139
+ path_name = element.attributes["name"]
140
+ path_names[path_name] = {}
141
+
142
+ element.attributes.each_attribute do |attribute|
143
+ next attribute if attribute.name.eql? 'name'
144
+ boolean_value = string_to_true_false(attribute.value)
145
+ path_names[path_name][attribute.name.to_sym] = boolean_value
146
+ end
147
+ end
148
+
149
+ path_names
150
+ end
151
+
152
+ ##
153
+ # Takes Fisheye/Crucible's <revision> return type and turns it in to a single
154
+ # Hash.
155
+ #
156
+ # @param [REXML::Document] xml_doc The XML document to convert.
157
+ # @return [Hash] The info about the revision.
158
+ def revision_to_hash(xml_doc)
159
+ details = {}
160
+
161
+ xml_doc.root.elements['//revision'].attributes.each do |attribute|
162
+ # Convert the value to an Int if the string is just a number
163
+ if attribute[1] =~ /^\d+$/
164
+ details[attribute.first.to_sym] = attribute[1].to_i
165
+ else
166
+ details[attribute.first.to_sym] = attribute[1]
167
+ end
168
+ end
169
+ details[:log] = xml_doc.root.elements['//log'].text
170
+
171
+ details
172
+ end
173
+
174
+ ##
175
+ # Takes Fisheye/Crucible's <history> return type and turns it in to an
176
+ # Array of revisions, which are Hashes.
177
+ #
178
+ # @param [REXML::Document] xml_doc The XML document to convert.
179
+ # @return [Array<Hash>] The Array of revision Hashes.
180
+ def history_to_array(xml_doc)
181
+ revisions = []
182
+
183
+ revisions_xml = REXML::XPath.match(xml_doc, "//revisions/revision")
184
+ revisions_xml.each do |revision_xml|
185
+ revision = REXML::Document.new(revision_xml.to_s)
186
+ revisions << revision_to_hash(revision)
187
+ end
188
+
189
+ revisions
190
+ end
191
+
192
+ ##
193
+ # Takes Fisheye/Crucible's <changeset> return type and turns it in to a Hash.
194
+ #
195
+ # @param [REXML::Document] xml_doc The XML document to convert.
196
+ # @return [Hash] Hash containting the changeset history information as
197
+ # defined by the API.
198
+ def changeset_to_hash(xml_doc)
199
+ details = {}
200
+
201
+ xml_doc.root.elements['//changeset'].attributes.each do |attribute|
202
+ # Convert the value to an Int if the string is just a number
203
+ int_attribute = attribute[1]
204
+ if int_attribute =~ /^\d+$/
205
+ details[attribute.first.to_sym] = int_attribute.to_i
206
+ else
207
+ details[attribute.first.to_sym] = int_attribute
208
+ end
209
+ end
210
+ details[:log] = xml_doc.root.elements['//log'].text
211
+
212
+ # Revisions is an Array of Hashes, where each Hash is a key/value pair that
213
+ # contains the path and revsion of one of the files/directories that's
214
+ # part of the changeset.
215
+ details[:revisions] = []
216
+ details[:revisions] << revisionkeys_to_array(xml_doc)
217
+
218
+ details
219
+ end
220
+
221
+ ##
222
+ # Takes Fisheye/Crucible's <revisionkey> return type and turns it in to an
223
+ # Array of Hashes.
224
+ #
225
+ # @param [REXML::Document] xml_doc The XML document to convert.
226
+ # @return [Array<Hash>] The Array of path & rev data.
227
+ def revisionkeys_to_array(xml_doc)
228
+ revisionkeys = []
229
+
230
+ xml_doc.root.elements.each('//revisionkey') do |element|
231
+ revisionkey = { :path => element.attributes['path'],
232
+ :rev => element.attributes['rev'].to_i
233
+ }
234
+
235
+ revisionkeys << revisionkey
236
+ end
237
+
238
+ revisionkeys
239
+ end
240
+
241
+ ##
242
+ # Takes Fisheye/Crucible's <changesets> return type and turns it in to a
243
+ # Hash.
244
+ #
245
+ # @param [REXML::Document] xml_doc The XML document to convert.
246
+ # @return [Hash] Contains the changeset IDs as defined by the query.
247
+ def changesets_to_hash(xml_doc)
248
+ changesets = {}
249
+ changesets[:csids] = []
250
+
251
+ changesets[:max_return] = xml_doc.root.elements['//changesets'].
252
+ attributes['maxReturn']
253
+
254
+ xml_doc.root.elements['//csids'].each_element do |element|
255
+ changesets[:csids] << element.text.to_i
256
+ end
257
+
258
+ changesets
259
+ end
260
+
261
+ ##
262
+ # Takes Fisheye/Crucible's custom <row> return type (from a query) and turns
263
+ # it in to an Array of Hashes containing the results from the query.
264
+ #
265
+ # @param [REXML::Document] xml_doc The XML document to convert.
266
+ # @return [Array] The result from the query.
267
+ def custom_to_array(xml_doc)
268
+ responses = []
269
+
270
+ xml_doc.elements.each('//row') do |element|
271
+ response = {}
272
+ element.each do |subs|
273
+ if subs.is_a? REXML::Text
274
+ else
275
+ # TODO: If subs.text is a Boolean string, it doesn't get converted
276
+ # to the actual Boolean. Same if it's an int or empty string.
277
+ response[subs.name.to_sym] = subs.text
278
+ end
279
+ end
280
+ responses << response
281
+ end
282
+
283
+ responses
284
+ end
285
+ end