swivel 0.0.103 → 0.0.146

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