swivel 0.0.103 → 0.0.146

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 (6) hide show
  1. data/README +126 -256
  2. data/Rakefile +5 -5
  3. data/bin/swivel +108 -96
  4. data/lib/swivel.rb +204 -69
  5. data/lib/swivel2.rb +11 -0
  6. metadata +6 -4
data/README CHANGED
@@ -1,336 +1,206 @@
1
- = swivel.rb
1
+ = swivel2.rb
2
2
 
3
- Without fancy screencast software, I'm content with this:
3
+ swivel2.rb is a small library that lets you interface with Swivel's REST API.
4
+ We've only run the Swivel gem on Linux and OSX platforms, though it may work on
5
+ others.
4
6
 
5
- ##### # # ### # # ####### # ###### ######
6
- # # # # # # # # # # # # # #
7
- # # # # # # # # # # # # #
8
- ##### # # # # # # ##### # ###### ######
9
- # # # # # # # # # ### # # # #
10
- # # # # # # # # # # ### # # # #
11
- ##### ## ## ### # ####### ####### ### # # ######
7
+ You can always find the latest documentation for the Swivel API online at https://inviteonly.swivel.com/developer
12
8
 
13
- (bam!)
9
+ == The Swivel API
14
10
 
15
- swivel.rb is a smallish bit that lets you interface with Swivel's REST API.
11
+ The Swivel API is RESTful. At this time, you can perform CRUD operations on
12
+ data_set resources, which allows you to get your data into Swivel and keep
13
+ it updated. You can also retrieve your data as CSV.
16
14
 
17
- == The REST API
15
+ ----------- ----------- ------------
16
+ HTTP Method URI Description
17
+ ----------- ----------- ------------
18
18
 
19
- The Swivel API is mostly RESTful. You can read and write many kinds of
20
- resources. Here's the list.
19
+ POST /data_sets Create a new data set (upload data)
20
+ DELETE /data_sets/#{id} Delete data set #{id}
21
+ GET /data_sets List data sets
22
+ GET /users/huned/data_sets List huned's data sets
23
+ GET /data_sets/#{id} Show data_set #{id}
24
+ PUT /data_sets/#{id} Update data_set #{id}
21
25
 
22
- ------ ----------- ------------
23
- method uri what it does
24
- ------ ----------- ------------
25
-
26
- data_columns
27
- get /rest/data_columns list data_columns
28
- get /rest/data_columns/#{id} show data_column #{id}
29
- put /rest/data_columns/#{id} update data_column #{id}
26
+ Most method calls take parameters. Check out the
27
+ {Swivel API Documentation}[https://inviteonly.swivel.com/developer] for the most up to date documentation. More REST resources will become available as the Swivel API matures.
30
28
 
31
- data_sets
32
- post /rest/data_sets create a new data_set
33
- delete /rest/data_sets/#{id} delete data_set #{id}
34
- get /rest/data_sets list data_sets
35
- get /rest/data_sets/#{id} show data_set #{id}
36
- put /rest/data_sets/#{id} update data_set #{id}
29
+ == Installing
37
30
 
38
- graphs
39
- post /rest/graphs create a new graph
40
- delete /rest/graphs/#{id} delete graph #{id}
41
- get /rest/graphs list graphs
42
- get /rest/graphs/#{id} show graph #{id}
43
- put /rest/graphs/#{id} update graph #{id}
31
+ $ sudo gem install -y swivel
32
+ Successfully installed swivel, version 0.0.103
33
+ Installing ri documentation for swivel-0.0.103...
34
+ Installing RDoc documentation for swivel-0.0.103...
44
35
 
45
- users
46
- get /rest/users list users
47
- get /rest/users/#{id} show user #{id}
48
- put /rest/users/#{id} update user #{id}
36
+ == You need an API key
49
37
 
50
- (non-RESTful methods)
51
- get /rest/test/echo/#{text} echo some text
52
- get /rest/search search for stuff
38
+ You need an API key to perform any POST, PUT, or DELETE requests. You must also use an API key to perform a GET request to access your private or shared data. Get your API key from https://inviteonly.swivel.com/developer. Please treat your API key as you would a password.
53
39
 
54
- Most method calls take parameters. Check out the
55
- {Swivel API Explorer}[http://swivel.com/api/list] to try calls interactively.
56
- Also, check out some {sample code}[http://swivel.com/api/code].
40
+ Your API key is stored in ~/.swivelrc along with some other information. Copy the swivelrc.default provided with this gem to ~/.swivelrc and modify site parameter to include your API key. Please make sure you set appropriate permissions on your ~/.swivelrc file to prevent others from seeing your API key.
57
41
 
58
- == Make it go go racer
42
+ An example ~/.swivelrc:
59
43
 
60
- $ sudo gem install swivel
61
- Successfully installed swivel, version 0.0.1
62
- Installing ri documentation for swivel-0.0.1...
63
- Installing RDoc documentation for swivel-0.0.1...
44
+ $ cat ~/.swivelrc
45
+ --- !ruby/struct:Swivel2::Config
46
+ site: http://x:<YOUR API KEY>@api.swivel.com
47
+ timeout_read: 500
48
+ timeout_write: 1_000
49
+ extra_params: { noviews: true }
64
50
 
65
- $ irb
66
- >> require 'swivel'
67
- => true
68
- >> swivel = Swivel::Connection.new
69
- => #<Swivel::Connection:0xb7a29fb0 ...
70
- >> puts swivel.call('/rest/test/echo/howdy')
71
- <?xml version="1.0" encoding="UTF-8"?>
72
- <response at="Sat, 09 Jun 2007 23:59:29 -0700" success="true">
73
- <echo version="0" text="howdy"/>
74
- </response>
75
- => nil
51
+ TODO: describe what each parameter means.
76
52
 
77
- == Class hierarchy
53
+ == Examples
78
54
 
79
- * Swivel::Connection
80
- * Swivel::Connection::Config
81
- * Swivel::Response
82
- * Swivel::DataColumn
83
- * Swivel::DataSet
84
- * Swivel::Graph
85
- * Swivel::User
86
- * Swivel::List
87
- * Swivel::ApiError
55
+ === Basic set up
88
56
 
89
- == RTF!M
57
+ Before you can use this library, you need to know a bit about the Config and
58
+ Connection classes. After you set up your ~/.swivelrc, you can run these
59
+ examples through irb.
90
60
 
91
- Never fear, it's installed when you install the gem.
92
-
93
- $ ri Swivel
94
-
95
- Other nice ones:
61
+ $ irb
62
+ >> require 'swivel2'
63
+ => true
96
64
 
97
- $ ri Swivel::Connection
98
- $ ri Swivel::Response
65
+ A Config instance encapsulates important settings stored in your ~/.swivelrc
66
+ file.
99
67
 
100
- == Examples. Worth a thousand words
68
+ >> config = Swivel2::Config.load '/home/huned/.swivelrc'
69
+ => #<struct Swivel2::Config ...>
101
70
 
102
- Ready, set, ... wait. Got an API key? {Get an API key}[http://swivel.com/api/key].
103
- Try to keep your API key close to your chest, safely tucked away from prying eyes
104
- and dangers of the "real world"... dangers like parking tickets and untied shoelaces.
105
- It's like a password, so don't share it.
71
+ A Connection instance is your main interface to Swivel's API. Construct a Connection instance by passing a Config instance into the constructor.
106
72
 
107
- # writes your api key into ~/.swivelrc
108
- swivel -k <your api key>
73
+ >> connection = Swivel2::Connection.new config
74
+ => #<Swivel2::Connection:0xb7b34a54 ...>
109
75
 
110
- (Did you notice you got a `swivel` at your command line? Sneaky! It's part of the
111
- gem. And it can query or pipe data into Swivel from your terminal!)
76
+ The Connection class defines post, get, put, and delete methods that you can
77
+ call with paths to REST resources. These methods automatically send your API
78
+ key with each request. For example:
112
79
 
113
- Now, back to it. Ready, set, go!
80
+ >> data_sets = connection.get '/data_sets'
114
81
 
115
- $ irb
116
- >> require 'swivel'
117
- => true
82
+ === Data sets
118
83
 
119
- A Swivel::Connection instance is your main interface to Swivel's API. The
120
- connection loads up (and possibly creates) your ~/.swivelrc and sets up
121
- several parameters, such as your API key, that are used throughout calls to
122
- Swivel.
84
+ ==== Show a data set
123
85
 
124
- >> swivel = Swivel::Connection.new
125
- => #<Swivel::Connection:0xb7a2bb58 @config={:timeout_down=>10, :api_key=>"xxx", :host=>"api.swivel.com", :port=>80, :timeout_up=>200}, headers{"Accept"=>"application/xml"}
86
+ You can show a data set's XML by making a get call to its resource path:
126
87
 
127
- Swivel::Connection#call is the method you'll probably use most frequently.
128
- Send in any REST url (including query strings containing any options) and
129
- it will try faithfully to get back something useful for you.
130
-
131
- In many cases, Swivel::Connection#call will return an object whose class is
132
- inherited from Swivel::Response.
133
-
134
- # look! we got a Swivel::DataSet object! frabjous day!
135
- >> data_set = swivel.call '/rest/data_sets/1000000'
136
- => #<Swivel::DataSet:0xb79dd688 @response=<response success='true' at='Mon, 04 Jun 2007 04:41:04 -0700'> .... , xml_tag"data-set", docUNDEFINED ....
137
-
138
- Question: what if it can't find an appropriate class to instantiate? Then it
139
- just gives you back the XML as a String, trusting that you'll love and care for
140
- it.
88
+ >> data_set = connection.get '/data_sets/1234567'
89
+ => #<Swivel2::Response::DataSet:0xb79fd244 ...>
141
90
 
142
- # Swivel::Connection#call can't find a class to instantiate this time,
143
- # so it just sends us XML.
144
- >> puts swivel.call('/rest/test/echo/howdy')
145
- <?xml version="1.0" encoding="UTF-8"?>
146
- <response success="true" at="Mon, 04 Jun 2007 03:34:49 -0700">
147
- <echo text="howdy" version="0"/>
148
- </response>
91
+ You don't need to go through the Swivel gem to get a data set's csv. It's
92
+ simply a GET:
149
93
 
150
- But, back to objects. Swivel::Connection#call often returns objects that
151
- encapsulate the XML response that the Swivel API sent.
94
+ GET http://api.swivel.com/data_sets/1234567.csv?api_key=<YOUR API KEY>
152
95
 
153
- >> data_set = swivel.call '/rest/data_sets/1000000'
154
- => #<Swivel::DataSet:0xb79dd688 @response=<response success='true' at='Mon, 04 Jun 2007 04:41:04 -0700'> .... , xml_tag"data-set", docUNDEFINED ....
96
+ When getting the CSV, you can optionally specify +limit+ and +offset+
97
+ parameters.
155
98
 
156
- These objects are rich and meaty. You can poke them and they shall respond,
157
- surlily.
99
+ ==== List data sets
158
100
 
159
- >> data_set.id
160
- => 1000000
161
- >> data_set.user.name
162
- => "huned"
163
- >> data_set.data_columns[3].name
164
- => "by-nc-nd-2.0"
101
+ >> data_sets = connection.get '/data_sets'
165
102
 
166
- However, these objects are magickal in the ruby way, and they wish to often
167
- tightly conceal their secrets. Some standard snooping shall leave you unsatisfied:
103
+ You can also list data sets for a specific user. For example, to list huned's
104
+ data sets:
168
105
 
169
- # let's call Swivel::DataSet#id
170
- >> data_set.swivel_id
171
- => 1000000 # huzzah!
172
- # yet... it's not there in the list of methods
173
- >> data_set.methods.grep /swivel_id/
174
- => [] # what the..?
106
+ >> data_sets = connection.get '/users/huned/data_sets'
175
107
 
176
- So how do you know what to call? At this time, the best way is to inspect
177
- the inner power-juice, the XML.
108
+ When you list data sets, you can optionally provide +limit+ and +offset+
109
+ parameters to page through the results. For example, to list data sets 11
110
+ through 20:
178
111
 
179
- >> puts data_set.to_xml
180
- <data-set swivel-id='1000000' version='0'>
181
- <name>name?</name>
182
- <user swivel-id='1000010'>
183
- <name>huned</name>
184
- </user>
185
- <created-at>Sat, 02 Jun 2007 20:35:45 -0700</created-at>
186
- <updated-at>Sat, 02 Jun 2007 20:35:49 -0700</updated-at>
187
- <source>
188
- <citation>name?</citation>
189
- <citation-url/>
190
- </source>
191
- <rows>367</rows>
192
- <columns>7</columns>
193
- ...
194
- </data-set>
112
+ >> data_sets = connection.get '/data_sets', :limit => 10, :offset => 10
195
113
 
196
- == Constructing URLs
114
+ ==== Uploading new data
197
115
 
198
- Constructing URLs are free and easy! (Freeasy... mmm!)
116
+ You just need a CSV and a name for your data set before you can upload it.
117
+ Without specifying any other options, Swivel will try to figure out the column
118
+ structure and types automatically.
199
119
 
200
- Any object in Swivel has a corresponding page that you can view with your
201
- browser.
120
+ >> params = {'file[data]' => `cat ~/data.csv`, 'data_set[name]' => 'My Data'}
121
+ >> data_set = connection.post '/data_sets', params
122
+ => #<Swivel2::Response::DataSet:0xb7e0051c ...>
202
123
 
203
- # get the data_set's id
204
- id = data_set.id
205
- # get the data_set's resource... turns Swivel::DataSet into 'data_set'
206
- resource = data_set.class.name.split('::').last.underscore # => "data_set"
124
+ See the {full list of options}[https://inviteonly.swivel.com/api/data_sets] that you can use for POST and PUT to /data_sets.
207
125
 
208
- # copy/paste this url into your browser
209
- url = "http://swivel.com/#{resource}s/show/#{id}"
126
+ ==== Updating an existing data set's attributes
210
127
 
211
- Graphs have pages, but if you want to grab the actual graph image, here's how:
128
+ >> params = {'data_set[name]' => 'new name'}
129
+ >> data_set = connection.put '/data_sets/1234567', params
130
+ => #<Swivel2::Response::DataSet:0xb7e0051c ...>
212
131
 
213
- # as before, we get the id and resource type
214
- id = data_set.id
215
- resource = data_set.class.name.split('::').last.underscore # => "data_set"
132
+ See the {full list of options}[https://inviteonly.swivel.com/api/data_sets] that you can use for POST and PUT to /data_sets.
216
133
 
217
- url = "http://swivel.com/#{resource}s/image/share/#{width}/#{height}"
134
+ ==== Appending more data to an existing data set
218
135
 
219
- If you have a specific graph visualization in mind, you can use a complex-er URL that allows you to decorate the image with various options like time scales, aggregation functions, and graph types.
136
+ >> params = {'file[mode]' => 'append', 'file[data]' => `cat ~/more_data.csv`}
137
+ >> data_set = connection.put '/data_sets/1234567', params
138
+ => #<Swivel2::Response::DataSet:0xb7e0051c ...>
220
139
 
221
- url = "http://swivel.com/#{resource}s/image/share/#{width}/#{height}/#{limit}/#{scale}/#{graph_type}/#{order_by_direction}/#{time_range}/#{time_scale}/#{aggregation_function}"
140
+ See the {full list of options}[https://inviteonly.swivel.com/api/data_sets] that you can use for POST and PUT to /data_sets. Caveat: Your new data must have the same column structure as the original data.
222
141
 
223
- TODO: explain it in this here table
142
+ ==== Replacing data for an existing data set
224
143
 
225
- * width
226
- * height
227
- * limit
228
- * scale
229
- * graph_type
230
- * order_by_direction
231
- * time_range
232
- * time_scale
233
- * aggregation_function
144
+ >> params = {'file[mode]' => 'replace', 'file[data]' => `cat ~/new_data.csv`}
145
+ >> data_set = connection.put '/data_sets/1234567', params
146
+ => #<Swivel2::Response::DataSet:0xb7e0051c ...>
234
147
 
235
- == Uploading Data
148
+ See the {full list of options}[https://inviteonly.swivel.com/api/data_sets] that you can use for POST and PUT to /data_sets. Caveat: Your new data must have the same column structure as the original data.
236
149
 
237
- See Swivel::Connection#upload
150
+ ==== Deleting a data set
238
151
 
239
- == Create Graphs
152
+ >> data_set = connection.delete '/data_sets/1234567'
153
+ => #<Swivel2::Response::DataSet:0xb7e0051c ...>
240
154
 
241
- TODO
155
+ == Quickest way to upload? The Swivel command line tool
242
156
 
243
- == Something completely different: A tryst at the command line
157
+ The Swivel command line tool the quickest way to get your data into Swivel and
158
+ keep it updated if you have basic Unix and shell scripting knowledge.
159
+ Installing the Swivel gem also installs a command line tool, /usr/bin/swivel,
160
+ that you can use to upload and update data sets without writing a single line
161
+ of ruby code.
244
162
 
245
- The command line program lets you query Swivel or upload data into Swivel.
246
- You get this program when you install the Swivel gem.
163
+ Before using this tool, please set up your ~/.swivelrc as described at the
164
+ beginning of this documentation.
247
165
 
248
166
  $ which swivel
249
167
  /usr/bin/swivel
250
168
 
251
- It uses a ~/.swivelrc file to remember settings. If you don't have a
252
- ~/.swivelrc, running the program will create a default one for you.
253
-
254
- $ ls -lh ~/.swivelrc
255
- ls: /home/huned/.swivelrc: No such file or directory
256
- $ swivel
257
- Usage: swivel [options]
258
- -h, --host=name Swivel hostname or IP address.
259
- Default:
260
- -p, --port=number Swivel host's port number.
261
- Default:
262
- -f, --file=file File to upload, append, or replace.
263
- -r, --raw=path Perform a raw call and print the XML response.
264
- -?, --help Show this help message.
265
- -k, --key=api-key Set your API key.
266
- $ ls -lh ~/.swivelrc
267
- -rw-rw-r-- 1 huned huned 105 Jun 4 05:04 /home/huned/.swivelrc
268
-
269
- Note, however, that the api_key setting is blank. Once you finagle an api key,
270
- run `swivel -k <your api key>` to update your ~/.swivelrc. (Remember: {finagle an api
271
- key}[http://swivel.com/api/key].)
272
-
273
- $ cat ~/.swivelrc
274
- ---
275
- protocol: http://
276
- timeout_up: 200
277
- api_key: ""
278
- timeout_down: 100
279
- host: api.swivel.com
280
- port: 80
169
+ Aside from being useful on its own, the command line tool is also a nice
170
+ example of how to write ruby code that uses this gem.
281
171
 
282
- So how about uploading data?
172
+ === Uploading new data
283
173
 
284
- $ swivel upload "my awesome data" -f data.csv
285
- uploaded data_set 1000234
174
+ $ cat data.csv | /usr/bin/swivel upload "my data"
175
+ uploaded 1234567
286
176
 
287
- Then your dataset magickally appears online at:
177
+ === Appending more data to an existing data set
288
178
 
289
- http://swivel.com/data_sets/show/1000234
179
+ $ cat more_data.csv | /usr/bin/swivel append 1234567
180
+ appended 1234567
290
181
 
291
- Here's the same thing, but via STDIN. Consuming data from STDIN is a powerful
292
- little mechanism that lets you rig arbitrary swivel uploads through unix
293
- process piping (|).
182
+ Caveat: Your new data must have the same column structure as the original data.
294
183
 
295
- $ cat data.csv | swivel upload "my awesome data"
296
- uploaded data_set 1000234
184
+ === Replacing data for an existing data set
297
185
 
298
- If you want to append to a data_set you previously uploaded:
186
+ $ cat more_data.csv | /usr/bin/swivel replace 1234567
187
+ replaced 1234567
299
188
 
300
- $ cat more_data.csv | swivel append 1000234
301
- appended data_set 1000234
189
+ Caveat: Your new data must have the same column structure as the original data.
302
190
 
303
- And finally, if you want to replace the entire data set with some other,
304
- fancier data:
191
+ == More options
305
192
 
306
- $ cat fancier_data.csv | swivel replace 1000234
307
- replaced data_set 1000234
193
+ $ /usr/bin/swivel --help
308
194
 
309
- One caveat when appending or replacing: Your new data must have the same
310
- column structure as the original data. Or in other words, your new data must
311
- have the same column structure as the original data.
312
-
313
- In addition to being a fine way to use swivel, the command line program
314
- serves as a nice example of how you might use swivel.rb. swivel.rb is the
315
- piece of code that allows ruby and the swivel api to be superhero and sidekick.
316
- (Stop here for a moment and visualize that.)
317
-
318
- Edit by Visnu: Huned wrote this at 4am. He tired out right here.
319
195
 
320
196
  == Feedback
321
197
 
322
198
  Feedback, comments, and (especially) patches welcome at mailto:developer@swivel.com.
323
199
 
324
- You can rcov swivel.rb by running this from the top level directory:
325
-
326
- $ rcov -Ilib -t test/test_swivel.rb
327
-
328
- == Respek
200
+ You can rcov this code by running rcov from the top level directory:
329
201
 
330
- Respeks to _why, errtheblog, 37signals, our moms, and of course the community.
331
- Wanna write code with us? {We're hiring!}[http://swivel.com/about/jobs]
202
+ $ rcov -Ilib -t test/*_test.rb
332
203
 
333
204
  == License
334
205
 
335
- This software is licensed under the exact same license as Ruby itself. Peace
336
- out.
206
+ This software is licensed under the exact same license as Ruby itself.
data/Rakefile CHANGED
@@ -14,7 +14,7 @@ NAME = "swivel"
14
14
  REV = `svn info`[/Revision: (\d+)/, 1] rescue nil
15
15
  VERS = ENV['VERSION'] || "0.0" + (REV ? ".#{REV}" : "")
16
16
  CLEAN.include ['doc', 'pkg']
17
- RDOC_OPTS = ['--line-numbers', '--title', 'swivel.rb', '--main', 'README', '--inline-source']
17
+ RDOC_OPTS = ['--line-numbers', '--title', 'swivel2.rb', '--main', 'README', '--inline-source']
18
18
 
19
19
  desc "Does a full compile, test run"
20
20
  task :default => [:package, :test, :rdoc]
@@ -28,7 +28,7 @@ task :release => [:package]
28
28
  desc "Run all the tests"
29
29
  Rake::TestTask.new do |t|
30
30
  t.libs << "test"
31
- t.test_files = FileList['test/test_*.rb']
31
+ t.test_files = FileList['test/*_test.rb']
32
32
  t.verbose = true
33
33
  end
34
34
 
@@ -36,7 +36,7 @@ Rake::RDocTask.new do |rdoc|
36
36
  rdoc.rdoc_dir = 'doc/rdoc'
37
37
  rdoc.options += RDOC_OPTS
38
38
  rdoc.main = "README"
39
- rdoc.rdoc_files.add ['README', 'CHANGELOG', 'COPYING', 'lib/*.rb']
39
+ rdoc.rdoc_files.add ['README', 'CHANGELOG', 'COPYING', 'lib/swivel2.rb', 'lib/swivel2/*.rb']
40
40
  end
41
41
 
42
42
  spec =
@@ -54,13 +54,13 @@ spec =
54
54
 
55
55
  s.author = 'huned'
56
56
  s.email = 'huned@swivel.com'
57
- s.homepage = 'http://swivel.com/developer'
57
+ s.homepage = 'http://www.swivel.com/developer'
58
58
 
59
59
  s.files = %w/COPYING README Rakefile/ + Dir['{lib,bin}/*']
60
60
  s.require_path = "lib"
61
61
 
62
62
  s.bindir = "bin"
63
- s.executables = ['swivel']
63
+ s.executables = %w/swivel/
64
64
 
65
65
  s.add_dependency 'activesupport'
66
66
  s.add_dependency 'cobravsmongoose'
data/bin/swivel CHANGED
@@ -1,81 +1,110 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'rubygems'
4
- require 'active_support' # TODO: make it work w/o this
4
+ require 'open-uri'
5
+ require 'active_support'
5
6
  require 'optparse'
6
- require 'yaml'
7
- require File.dirname(__FILE__) + '/../lib/swivel'
7
+ require File.dirname(__FILE__) + '/../lib/swivel2'
8
+
9
+ COLUMN_TYPE_HELP = <<-EOS
10
+ Valid types:
11
+ currency: Currency values, including the
12
+ currency symbol.
13
+ date: Date values that don't include
14
+ timestamps.
15
+ datetime: Date and time values.
16
+ float: Floating point values. Swivel
17
+ groks scientific notation!
18
+ integer: Integer values.
19
+ ignore: Columns of this type will not
20
+ be uploaded.
21
+ percent: Percent values without the "%"
22
+ symbol. A value of 0.03 will
23
+ appear as 3% in Swivel.
24
+ string: String values.
25
+ EOS
26
+
27
+ SWIVELRC = <<-EOS
28
+
29
+ Your API key is read from ~/.swivelrc:
30
+
31
+ You need a ~/.swivelrc file before you can use this program! The contents
32
+ of your swivelrc file should look like this:
33
+
34
+ --- !ruby/struct:Swivel2::Config
35
+ site: https://x:<YOUR_API_KEY>@api.swivel.com
36
+ timeout_read: 500
37
+ timeout_write: 1_000
38
+ extra_params: { noviews: true }
39
+ EOS
40
+
41
+ EXAMPLES = <<-EOS
42
+
43
+ Examples:
44
+
45
+ Upload a data set
46
+ $ cat data.csv | #{$0} upload 1234567
47
+
48
+ Append data to data set with id 1234567
49
+ $ cat more_data.csv | #{$0} append 1234567
50
+
51
+ Replace underlying data for data set with id 1234567
52
+ $ cat new_data.csv | #{$0} replace 1234567
53
+ EOS
8
54
 
9
55
  options = Hash.new
10
- config = Swivel::Connection::Config.new
11
- config.load
12
- config.save
56
+ config = Swivel2::Config.load
13
57
 
14
58
  ARGV.options do |opts|
15
- opts.banner = 'Usage: swivel < list, show, upload, append, replace > [options]'
59
+ opts.banner = 'Usage: swivel <upload, append, replace> [options]'
16
60
 
17
61
  opts.separator ''
18
62
 
19
63
  opts.separator 'Options:'
20
64
 
21
- opts.on '-h', '--host=name', String,
22
- "Swivel hostname or IP address.",
23
- "Default: #{options[:host]}" do |v| options[:host] = v end
24
-
25
- opts.on '-p', '--port=number', String,
26
- "Swivel host's port number.",
27
- "Default: #{options[:port]}" do |v| options[:port] = v end
28
-
29
- opts.on '-f', '--file=file', String,
30
- "File to upload, append, or replace." do |v|
65
+ opts.on '-f', '--file=<file>', String,
66
+ 'File to upload, append, or replace.',
67
+ 'Your actual data is read from this file.',
68
+ 'If omitted, data is read from STDIN.',
69
+ 'OPTIONAL' do |v|
31
70
  options[:filename] = v
32
71
  end
33
72
 
34
- opts.on '-n', '--column-names=column_names', String,
35
- "Column names." do |v|
36
- options[:column_names] = v
37
- end
73
+ opts.separator ''
38
74
 
39
- opts.on '-t', '--column-types=column_types', String,
40
- "Column types specification. Choose from {number, currency, date, percent, text}." do |v|
41
- options[:column_types] =
42
- v.split(',').collect do |type|
43
- case type
44
- when /^n/i
45
- 'NumberDataColumnType'
46
- when /^w/i
47
- 'WholeNumberDataColumnType'
48
- when /^c/i
49
- 'CurrencyDataColumnType'
50
- when /^t/i
51
- 'DateTimeDataColumnType'
52
- when /^d/i
53
- 'DateDataColumnType'
54
- when /^p/i
55
- 'PercentageDataColumnType'
56
- else
57
- 'TextDataColumnType'
58
- end
59
- end.join ','
60
- end
75
+ opts.on '-c', '--headings=<headings>', String,
76
+ 'Column headings as a comma separated list.',
77
+ 'Example: "Year,Revenue"',
78
+ 'OPTIONAL' do |v|
79
+ options[:headings] = v
80
+ end
61
81
 
62
- opts.on_tail '-r', '--raw=path', String,
63
- "Perform a raw call and print the XML response." do |v|
64
- puts Swivel::Connection.new(options).call(v, options)
65
- exit
82
+ opts.separator ''
83
+
84
+ opts.on *['-t', '--types=<types>', String,
85
+ 'Column types as a comma separated list.',
86
+ 'Example: "date,currency"',
87
+ COLUMN_TYPE_HELP.split("\n"),
88
+ 'OPTIONAL'].flatten do |v|
89
+ options[:types] = v
66
90
  end
67
91
 
68
- opts.on_tail '-k', '--key=api-key',
69
- "Set your API key." do |v|
70
- config = Swivel::Connection::Config.new
71
- config.config[:old_api_key] = config.config[:api_key]
72
- config.config[:api_key] = v
73
- config.save
74
- exit
92
+ opts.separator ''
93
+
94
+ opts.on '-s', '--sep=<separator>', String,
95
+ 'Column separator.',
96
+ 'Example: "," or "\t"',
97
+ 'OPTIONAL' do |v|
98
+ options[:sep] = v
75
99
  end
76
100
 
101
+ opts.separator ''
102
+
77
103
  opts.on_tail '-?', '--help',
78
- "Show this help message." do puts opts; exit end
104
+ "Show this help message." do
105
+ puts opts
106
+ exit
107
+ end
79
108
 
80
109
  opts.parse!
81
110
 
@@ -86,77 +115,60 @@ ARGV.options do |opts|
86
115
  end
87
116
 
88
117
  class SwivelHelper
89
- def initialize options = Hash.new
90
- @swivel = Swivel::Connection.new options
91
- end
92
-
93
- def show resource, id
94
- resource = resource.pluralize
95
- response = @swivel.call "/#{resource}/#{id}"
96
- end
97
-
98
- def list resource, options = Hash.new
99
- resource = resource.pluralize
100
- response = @swivel.call "/#{resource}", options
118
+ def initialize config
119
+ @swivel = Swivel2::Connection.new config
101
120
  end
102
121
 
103
122
  def upload name, options = Hash.new
104
- filename = options[:filename]
105
- opts = {:original_asset_name => filename, :original_asset_path => filename,
106
- :name => name, :citation => $0, :display_tags => 'swivel'}
107
- opts.merge! column_types(options)
108
- data_set = @swivel.upload! opts.merge(:data => read(filename))
123
+ opts = { 'data_set[name]' => name,
124
+ 'data_set[citation]' => $0,
125
+ 'data_set[public]' => 0,
126
+ 'file[data]' => read(options[:filename]) }
127
+ opts.merge! 'file[types]' => options[:types] if options[:types]
128
+ opts.merge! 'file[headings]' => options[:headings] if options[:headings]
129
+ opts.merge! 'file[sep]' => options[:sep] if options[:sep]
130
+ data_set = @swivel.post '/data_sets', opts
109
131
  puts "uploaded #{data_set.id}"
110
132
  end
111
133
 
112
134
  def append id, options = Hash.new
113
- opts = {:id => id}.merge column_types(options)
114
- data_set = @swivel.append! opts.merge(:data => read(options[:filename]))
135
+ opts = { 'file[mode]' => 'append',
136
+ 'file[data]' => read(options[:filename]) }
137
+ data_set = update id, opts
115
138
  puts "appended #{data_set.id}"
116
139
  end
117
140
 
118
141
  def replace id, options = Hash.new
119
- opts = {:id => id}.merge column_types(options)
120
- data_set = @swivel.replace! opts.merge(:data => read(options[:filename]))
142
+ opts = { 'file[mode]' => 'replace',
143
+ 'file[data]' => read(options[:filename]) }
144
+ data_set = update id, opts
121
145
  puts "replaced #{data_set.id}"
122
146
  end
123
147
 
124
148
  private
149
+ def update id, options
150
+ @swivel.put "/data_sets/#{id}", options
151
+ end
152
+
125
153
  def read filename = nil
126
154
  if filename
127
- open filename, 'r' do |f|
128
- f.readlines.join
129
- end
155
+ open filename do |f| f.readlines.join end
130
156
  else
131
157
  readlines.join
132
158
  end
133
159
  end
134
-
135
- def column_types options
136
- if options[:column_types].blank?
137
- {:auto_estimate => true}
138
- else
139
- {:column_types => options[:column_types],
140
- :column_names => options[:column_names],
141
- :column_separator => options[:column_separator] || ','}
142
- end
143
- end
144
160
  end
145
161
 
146
- helper = SwivelHelper.new options
162
+ helper = SwivelHelper.new config
147
163
  action = ARGV.shift
148
164
  resource = ARGV.shift
149
165
  id = ARGV.shift
150
166
 
151
167
  case action.downcase
152
- when 'list'
153
- helper.list resource, options
154
- when 'show'
155
- helper.show resource, id
156
- when 'upload'
168
+ when /upload/i
157
169
  name = resource
158
170
  helper.upload name, options
159
- when 'append', 'replace'
171
+ when /append/i, /replace/i
160
172
  id = resource
161
173
  helper.send action.to_sym, id, options
162
174
  else
data/lib/swivel.rb CHANGED
@@ -11,7 +11,8 @@ require 'yaml'
11
11
 
12
12
  class Time
13
13
  silence_warnings do
14
- RFC822_DATETIME_REGEX = /\w{3}, \d{2} \w{3} \d{4} \d{2}:\d{2}:\d{2} [+-]\d{4}/
14
+ RFC822_DATETIME_REGEX = /\A\w{3}, \d{2} \w{3} \d{4} \d{2}:\d{2}:\d{2} [+-]\d{4}\Z/
15
+ XML_DATETIME_REGEX = /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+\-]\d{2}:\d{2}\Z/
15
16
  end
16
17
  end
17
18
 
@@ -134,22 +135,22 @@ module Swivel
134
135
 
135
136
  class Response
136
137
 
137
- attr_accessor :refreshed_at, :disable_auto_refresh
138
+ attr_accessor :refreshed_at, :disable_auto_refresh, :hash_doc
138
139
 
139
140
  def self.resource
140
141
  nil
141
142
  end
142
143
 
143
144
  # Instantiate from XML returned from Swivel.
144
- def initialize xml = nil, connection = nil
145
+ def initialize xml, connection, hashdoc
146
+ raise 'invalid arguments, either xml or hashdoc needs to be passed in' if hashdoc.nil? && xml.nil?
145
147
  @connection = connection
146
148
  @disable_auto_refresh = @connection && @connection.disable_auto_refresh
147
149
  @xml_tag = self.class.name.demodulize.to_xml_tag
148
- @doc = REXML::Document.new xml
149
- if @response = REXML::XPath.first(@doc, '/response')
150
- # if it's a full response, strip away the outer cruft
151
- @doc = REXML::Document.new @response.elements[1].to_s
152
- end
150
+ @hash_doc = hashdoc
151
+ @hash_doc ||= CobraVsMongoose.xml_to_hash xml
152
+ @hash_doc = @hash_doc['response'] if @hash_doc.is_a?(Hash) && @hash_doc['response']
153
+ @hash_doc = @hash_doc[self.class.resource] if @hash_doc.is_a?(Hash) && @hash_doc[self.class.resource]
153
154
  end
154
155
 
155
156
  # Most of the work in processing responses from Swivel happens here. It's
@@ -175,24 +176,55 @@ module Swivel
175
176
  # data_set.user.class # => Swivel::User
176
177
 
177
178
  def method_missing method_id
178
- select_element = "/#{self.class.resource ? self.class.resource : @xml_tag}/#{method_id.to_s.to_xml_tag}"
179
- select_attribute = "/#{self.class.resource ? self.class.resource : @xml_tag}/@#{method_id.to_s.to_xml_tag}"
180
- select_list = "/#{self.class.resource ? self.class.resource : @xml_tag}/list[@resource=\"#{method_id.to_s.singularize.to_xml_tag}\"]"
181
-
179
+ method_name = method_id.to_s.singularize.to_xml_tag
180
+ list_name = method_id.to_s.to_xml_tag
182
181
 
183
- if el = REXML::XPath.first(@doc, select_element)
184
- if el.attribute('swivel-id')
185
- Response.class_for(el.name).new(el.to_s, @connection)
186
- elsif el.has_elements?
187
- CobraVsMongoose.xml_to_hash el.to_s
182
+ # 1)look in attributes
183
+ # 2)look in elements
184
+ # 2a) if value return it
185
+ # 2b) if hash return an obj
186
+ # 2c) if method_name.pluralize, return a list
187
+ # 3)look in lists
188
+
189
+ if @hash_doc["@#{method_name}"]
190
+ value_for @hash_doc["@#{method_name}"]
191
+ elsif @hash_doc[method_name]
192
+ if @hash_doc[method_name].is_a? Hash
193
+ if @hash_doc[method_name].has_key?('$')
194
+ value_for @hash_doc[method_name]['$']
195
+ elsif @hash_doc[method_name].size == 1 and @hash_doc[method_name].has_key? '@type'
196
+ nil
197
+ elsif @hash_doc[method_name].blank?
198
+ nil
199
+ else
200
+ Response.class_for(method_name).new(nil, @connection, @hash_doc[method_name])
201
+ end
188
202
  else
189
- value_for el.text
203
+ value_for @hash_doc[method_name]
190
204
  end
191
- elsif el = REXML::XPath.first(@doc, select_attribute)
192
- value_for el.to_s
193
- elsif el = REXML::XPath.first(@doc, select_list)
194
- Swivel::List.new el.to_s, @connection
195
- else
205
+
206
+ # 2c) from above - enables rails ActiveRecord::Base#to_xml style lists
207
+ elsif @hash_doc[list_name].is_a? Hash and
208
+ @hash_doc[list_name]['@type'] == 'array'
209
+ member_name = (@hash_doc[list_name].keys.find{|k| !/^@/.match(k) } || list_name.singularize)
210
+ list = (@hash_doc[list_name][member_name] rescue [])
211
+ list = [list] unless list.is_a?(Array)
212
+ # hack to get the gem to parse all array types correctly
213
+ hack_list = list.map{|i| {member_name => i}}
214
+ Swivel::List.new nil, @connection, hack_list, member_name, list.size, list.size
215
+
216
+ elsif @hash_doc['list'] && @hash_doc['list'].is_a?(Hash) && @hash_doc['list']['@resource'] == method_name
217
+ Swivel::List.new nil, @connection, (@hash_doc['list'][method_name].nil? ? [] : @hash_doc['list'][method_name]), method_name, @hash_doc['list']['@count'], @hash_doc['list']['@total']
218
+ elsif @hash_doc['list'] && @hash_doc['list'].is_a?(Array) && @hash_doc['list'].any?{|h| h['@resource'] == method_name}
219
+ list_el = @hash_doc['list'].select{|h| h['@resource'] == method_name}.first
220
+ Swivel::List.new nil, @connection,(list_el[method_name].nil? ? [] : list_el[method_name]), method_name, list_el['@count'], list_el['@total']
221
+ elsif @hash_doc[method_name.pluralize]
222
+ if @hash_doc[method_name.pluralize].has_key?('$')
223
+ value_for @hash_doc[method_name.pluralize]['$']
224
+ else
225
+ @hash_doc[method_name.pluralize]
226
+ end
227
+ else
196
228
  raise NoMethodError, "#{method_id} isn't a method of #{self.class.name}"
197
229
  end
198
230
  rescue Exception => e
@@ -200,7 +232,7 @@ module Swivel
200
232
  nil
201
233
  else
202
234
  @retried = true
203
- refresh! true
235
+ # refresh! true
204
236
  retry
205
237
  end
206
238
  end
@@ -223,7 +255,7 @@ module Swivel
223
255
  # user.id == user.swivel_id # => true
224
256
 
225
257
  def id
226
- swivel_id
258
+ method_missing(:id) or swivel_id
227
259
  end
228
260
 
229
261
  # Same as id method, added to make Swivel objects play well with url_for
@@ -239,16 +271,16 @@ module Swivel
239
271
  # user = data_set.user
240
272
  # user.refresh! # populate the object fully from Swivel
241
273
 
242
- def refresh! force = false
243
- if @connection && (force || @refreshed_at.blank?)
244
- refreshed = @connection.call "/#{@xml_tag.undashify}s/#{id}"
245
- if refreshed.is_a? self.class
246
- @doc = REXML::Document.new refreshed.to_xml
247
- @refreshed_at = Time.now
248
- end
249
- end
250
- self
251
- end
274
+ # def refresh! force = false
275
+ # if @connection && (force || @refreshed_at.blank?)
276
+ # refreshed = @connection.call "/#{@xml_tag.undashify}s/#{id}"
277
+ # if refreshed.is_a? self.class
278
+ # @doc = REXML::Document.new refreshed.to_xml
279
+ # @refreshed_at = Time.now
280
+ # end
281
+ # end
282
+ # self
283
+ # end
252
284
 
253
285
  # Returns the underlying XML string for this object as a string.
254
286
  # user = swivel.call '/users/1000010'
@@ -283,8 +315,13 @@ module Swivel
283
315
  end
284
316
 
285
317
  def value_for s #:nordoc:
286
- if s.match Time::RFC822_DATETIME_REGEX
287
- Time.parse s
318
+ if s.match Time::RFC822_DATETIME_REGEX or
319
+ s.match Time::XML_DATETIME_REGEX
320
+ begin
321
+ Time.parse s
322
+ rescue ArgumentError => e
323
+ DateTime.parse s
324
+ end
288
325
  elsif s.numeric?
289
326
  s.to_i
290
327
  else
@@ -293,21 +330,56 @@ module Swivel
293
330
  end
294
331
  end
295
332
 
296
- class Annotation < Response; end
297
- class Comment < Response; end
298
- class Page < Response; end
299
- class PageAsset < Response; end
300
- class Prose < Response; end
333
+ class Annotation < Response
334
+ def self.resource
335
+ 'annotation'
336
+ end
337
+ end
338
+ class Comment < Response
339
+ def self.resource
340
+ 'comment'
341
+ end
342
+ end
343
+ class Invitation < Response
344
+ def self.resource
345
+ 'invitation'
346
+ end
347
+ end
348
+ class Page < Response
349
+ def self.resource
350
+ 'page'
351
+ end
352
+ end
353
+ class PageAsset < Response
354
+ def self.resource
355
+ 'page-asset'
356
+ end
357
+ end
358
+ class Prose < Response
359
+ def self.resource
360
+ 'prose'
361
+ end
362
+ end
301
363
 
302
364
  class Permission < Response
365
+ def self.resource
366
+ 'permission'
367
+ end
368
+
303
369
  %w/accessor accessable/.each do |thing|
304
370
  define_method thing.to_sym do
305
371
  unless instance_variable_get "@#{thing}".to_sym
306
372
  c =
307
373
  begin
308
374
  dashified = thing.dasherize
309
- o = REXML::XPath.first(@doc, "/permission/#{dashified}").elements[1]
310
- Response.class_for(o.name).new o.to_s
375
+ if @hash_doc[dashified]
376
+ raise 'unexpected document, expecting only one key to be name here' if @hash_doc[dashified].keys.size != 1
377
+ resource = @hash_doc[dashified].keys.first
378
+ h = @hash_doc[dashified][resource]
379
+ Response.class_for(resource).new nil, @connection, h
380
+ else
381
+ nil
382
+ end
311
383
  rescue
312
384
  warn e
313
385
  nil
@@ -320,14 +392,24 @@ module Swivel
320
392
  end
321
393
 
322
394
  class Activity < Response
395
+ def self.resource
396
+ 'activity'
397
+ end
398
+
323
399
  %w/source subject verb direct_object prepositional_object/.each do |part|
324
400
  define_method part.to_sym do
325
401
  unless instance_variable_get "@#{part}".to_sym
326
402
  c =
327
403
  begin
328
404
  dashified = part.dasherize
329
- o = REXML::XPath.first(@doc, "/activity/#{dashified}").elements[1]
330
- Response.class_for(o.name).new o.to_s
405
+ if @hash_doc[dashified]
406
+ raise 'unexpected document, expecting only one key to be name here' if @hash_doc[dashified].keys.size != 1
407
+ resource = @hash_doc[dashified].keys.first
408
+ h = @hash_doc[dashified][resource]
409
+ Response.class_for(resource).new nil, @connection, h
410
+ else
411
+ nil
412
+ end
331
413
  rescue
332
414
  warn e
333
415
  nil
@@ -346,6 +428,9 @@ module Swivel
346
428
  #
347
429
  # data_set = swivel.data_set id
348
430
  # data_set.append! :data => `/tmp/append.csv`
431
+ def self.resource
432
+ 'data-set'
433
+ end
349
434
 
350
435
  def append! options = Hash.new
351
436
  options.merge! :mode => 'append', :id => self.id
@@ -380,6 +465,9 @@ module Swivel
380
465
  end
381
466
 
382
467
  class Visual < Response
468
+ def self.resource
469
+ 'visual'
470
+ end
383
471
 
384
472
  ## Forms the url for an image.
385
473
  def image_url options={}
@@ -392,35 +480,43 @@ module Swivel
392
480
  end
393
481
 
394
482
  class User < Response
395
- def admin?
396
- admin.to_b
397
- rescue
398
- false
483
+ def self.resource
484
+ 'user'
399
485
  end
400
-
401
486
  def to_param
402
487
  name
403
488
  end
404
489
  end
405
490
 
406
491
  class Group < Response
407
- def members
408
- @members ||=
409
- begin
410
- o = REXML::XPath.first(@doc, "/group/members").elements[1]
411
- Response.class_for(o.name).new o.to_s
412
- rescue
413
- warn e
414
- nil
415
- end
492
+ def self.resource
493
+ 'group'
416
494
  end
417
-
418
495
  def to_param
419
496
  (!slug.blank? and slug) or super.to_param
420
497
  end
498
+ def permission_for(member)
499
+ raise 'group has no permissions' unless permissions
500
+ perm = permissions.find do |p|
501
+ p.accessor_id == member.id and
502
+ p.accessor_type == member.class.name.sub(/^Swivel::/,'')
503
+ end
504
+ perm
505
+ end
506
+ def has_admin?(member)
507
+ permission = permission_for(member)
508
+ permission and permission.rights == ::Permission::WRITE
509
+ end
510
+
511
+ def has_member?(member)
512
+ !!permission_for(member)
513
+ end
421
514
  end
422
515
 
423
516
  class Graph < Response
517
+ def self.resource
518
+ 'graph'
519
+ end
424
520
  def image_url options = Hash.new
425
521
  host, port = @connection.config[:host], @connection.config[:port]
426
522
  port = port == 80 ? '' : ":#{port}"
@@ -478,16 +574,19 @@ module Swivel
478
574
 
479
575
  class List < Response
480
576
 
577
+ attr_accessor :resource, :count, :total
578
+
481
579
  # Instantiate a new Swivel::List. Calls super, then does a bit more extra processing.
482
- def initialize *args
483
- super *args
580
+ def initialize xml, connection, hashdoc, resource, count, total
581
+ super(xml, connection, hashdoc)
484
582
  unless @processed
583
+ @count = count.nil? ? 0 : count.to_i
584
+ @total = total.nil? ? 0 : total.to_i
485
585
  @list = Array.new
486
- resource = @doc.elements[1].attributes['resource']
487
- resource ||= @doc.elements[1].elements[1].name
488
- selector = "/list/#{resource}"
489
- REXML::XPath.each @doc, selector do |e|
490
- @list << Response.class_for(resource).new(e.to_s, @connection)
586
+ @hash_doc ||= []
587
+ @hash_doc = [@hash_doc] if !@hash_doc.is_a?(Array)
588
+ @hash_doc.each do |h|
589
+ @list << Response.class_for(resource).new(nil, @connection, h)
491
590
  end
492
591
  @processed = true
493
592
  end
@@ -521,6 +620,9 @@ module Swivel
521
620
  class ApiError < StandardError; end
522
621
 
523
622
  class Error < Response
623
+ def self.resource
624
+ 'error'
625
+ end
524
626
  def raise
525
627
  super ApiError, message, backtrace.split("\n")
526
628
  end
@@ -595,6 +697,10 @@ module Swivel
595
697
  @headers.merge! 'Accept' => 'application/xml'
596
698
  end
597
699
 
700
+ def inspect
701
+ super.sub /:api_key=>"[^"]*"/, ':api_key=>[FILTERED]'
702
+ end
703
+
598
704
  # Keep track of how many calls have been made. Note that this means
599
705
  # multiple Swivel::Connection instances increment the same call count
600
706
  # if those Swivel::Connections run within the same thread.
@@ -660,11 +766,40 @@ module Swivel
660
766
  end
661
767
  @@calls << {:url => path, :time => elapsed_time}
662
768
  body = response.body
769
+ log "#{config[:host]}:#{config[:port]}/#{path}"
663
770
  log body
664
771
  case response.content_type
665
772
  when 'application/xml'
666
773
  doc = REXML::Document.new body
667
- Response.class_for(doc.root.elements[1].name).new body, self
774
+ if doc.root.elements[1].name.downcase == 'list'
775
+ h = CobraVsMongoose.xml_to_hash body
776
+ h = h["response"] if h["response"]
777
+ resource = h['list']['@resource']
778
+ count = h['list']['@count']
779
+ total = h['list']['@total']
780
+ h = h['list'][resource]
781
+ h ||= []
782
+ List.new nil, self, h, resource, count, total
783
+
784
+ # 2c) enables rails ActiveRecord::Base#to_xml style lists (at root)
785
+ elsif doc.root.elements.to_a.size == 1 and
786
+ doc.root.elements[1].attributes['type'] == 'array'
787
+ h = CobraVsMongoose.xml_to_hash body
788
+ h = h["response"] if h["response"]
789
+
790
+ list_name = doc.root.elements[1].name
791
+ member_name = (h[list_name].keys.find{|k| !/^@/.match(k) } || list_name.singularize)
792
+ list = h[list_name][member_name] || []
793
+
794
+ list = [list] unless list.is_a?(Array)
795
+ total = (doc.root.attributes['total'].to_i rescue list.size)
796
+ # hack to get the gem to parse all array types correctly
797
+ hack_list = list.map{|i| {member_name => i}}
798
+ Swivel::List.new nil, @connection, hack_list, member_name, list.size, total
799
+
800
+ else
801
+ Response.class_for(doc.root.elements[1].name).new body, self, nil
802
+ end
668
803
  when 'text/csv', 'text/plain'
669
804
  body
670
805
  else
data/lib/swivel2.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ require File.dirname(__FILE__) + '/../vendor/activesupport-2.0.2-/lib/active_support/core_ext/module/attr_accessor_with_default'
4
+ require File.dirname(__FILE__) + '/../vendor/activesupport-2.0.2-/lib/active_support/core_ext/object/misc'
5
+ require File.dirname(__FILE__) + '/../vendor/activeresource-2.0.2-/lib/active_resource'
6
+
7
+ require File.dirname(__FILE__) + '/swivel2/config'
8
+ require File.dirname(__FILE__) + '/swivel2/connection'
9
+ require File.dirname(__FILE__) + '/swivel2/formats'
10
+ require File.dirname(__FILE__) + '/swivel2/performance'
11
+ require File.dirname(__FILE__) + '/swivel2/response'
metadata CHANGED
@@ -3,13 +3,13 @@ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: swivel
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.0.103
7
- date: 2007-10-18 00:00:00 -07:00
6
+ version: 0.0.146
7
+ date: 2008-01-10 00:00:00 -08:00
8
8
  summary: Ruby interface to the Swivel API.
9
9
  require_paths:
10
10
  - lib
11
11
  email: huned@swivel.com
12
- homepage: http://swivel.com/developer
12
+ homepage: http://www.swivel.com/developer
13
13
  rubyforge_project:
14
14
  description: This gem installs client library for accessing Swivel through it's API.
15
15
  autorequire:
@@ -32,6 +32,8 @@ files:
32
32
  - COPYING
33
33
  - README
34
34
  - Rakefile
35
+ - lib/swivel2.rb
36
+ - lib/swivel2
35
37
  - lib/swivel.rb
36
38
  - bin/swivel
37
39
  - CHANGELOG
@@ -40,7 +42,7 @@ test_files: []
40
42
  rdoc_options:
41
43
  - --line-numbers
42
44
  - --title
43
- - swivel.rb
45
+ - swivel2.rb
44
46
  - --main
45
47
  - README
46
48
  - --inline-source