swivel 0.0.103 → 0.0.146
Sign up to get free protection for your applications and to get access to all the features.
- data/README +126 -256
- data/Rakefile +5 -5
- data/bin/swivel +108 -96
- data/lib/swivel.rb +204 -69
- data/lib/swivel2.rb +11 -0
- metadata +6 -4
data/README
CHANGED
@@ -1,336 +1,206 @@
|
|
1
|
-
=
|
1
|
+
= swivel2.rb
|
2
2
|
|
3
|
-
|
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
|
-
|
9
|
+
== The Swivel API
|
14
10
|
|
15
|
-
|
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
|
-
|
15
|
+
----------- ----------- ------------
|
16
|
+
HTTP Method URI Description
|
17
|
+
----------- ----------- ------------
|
18
18
|
|
19
|
-
|
20
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
42
|
+
An example ~/.swivelrc:
|
59
43
|
|
60
|
-
$
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
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
|
-
==
|
53
|
+
== Examples
|
78
54
|
|
79
|
-
|
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
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
Other nice ones:
|
61
|
+
$ irb
|
62
|
+
>> require 'swivel2'
|
63
|
+
=> true
|
96
64
|
|
97
|
-
|
98
|
-
|
65
|
+
A Config instance encapsulates important settings stored in your ~/.swivelrc
|
66
|
+
file.
|
99
67
|
|
100
|
-
|
68
|
+
>> config = Swivel2::Config.load '/home/huned/.swivelrc'
|
69
|
+
=> #<struct Swivel2::Config ...>
|
101
70
|
|
102
|
-
|
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
|
-
|
108
|
-
|
73
|
+
>> connection = Swivel2::Connection.new config
|
74
|
+
=> #<Swivel2::Connection:0xb7b34a54 ...>
|
109
75
|
|
110
|
-
|
111
|
-
|
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
|
-
|
80
|
+
>> data_sets = connection.get '/data_sets'
|
114
81
|
|
115
|
-
|
116
|
-
>> require 'swivel'
|
117
|
-
=> true
|
82
|
+
=== Data sets
|
118
83
|
|
119
|
-
|
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
|
-
|
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
|
-
|
128
|
-
|
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
|
-
|
143
|
-
|
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
|
-
|
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
|
-
|
154
|
-
|
96
|
+
When getting the CSV, you can optionally specify +limit+ and +offset+
|
97
|
+
parameters.
|
155
98
|
|
156
|
-
|
157
|
-
surlily.
|
99
|
+
==== List data sets
|
158
100
|
|
159
|
-
>>
|
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
|
-
|
167
|
-
|
103
|
+
You can also list data sets for a specific user. For example, to list huned's
|
104
|
+
data sets:
|
168
105
|
|
169
|
-
|
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
|
-
|
177
|
-
the
|
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
|
-
>>
|
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
|
-
|
114
|
+
==== Uploading new data
|
197
115
|
|
198
|
-
|
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
|
-
|
201
|
-
|
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
|
-
|
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
|
-
|
209
|
-
url = "http://swivel.com/#{resource}s/show/#{id}"
|
126
|
+
==== Updating an existing data set's attributes
|
210
127
|
|
211
|
-
|
128
|
+
>> params = {'data_set[name]' => 'new name'}
|
129
|
+
>> data_set = connection.put '/data_sets/1234567', params
|
130
|
+
=> #<Swivel2::Response::DataSet:0xb7e0051c ...>
|
212
131
|
|
213
|
-
|
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
|
-
|
134
|
+
==== Appending more data to an existing data set
|
218
135
|
|
219
|
-
|
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
|
-
|
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
|
-
|
142
|
+
==== Replacing data for an existing data set
|
224
143
|
|
225
|
-
|
226
|
-
|
227
|
-
|
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
|
-
|
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
|
-
|
150
|
+
==== Deleting a data set
|
238
151
|
|
239
|
-
|
152
|
+
>> data_set = connection.delete '/data_sets/1234567'
|
153
|
+
=> #<Swivel2::Response::DataSet:0xb7e0051c ...>
|
240
154
|
|
241
|
-
|
155
|
+
== Quickest way to upload? The Swivel command line tool
|
242
156
|
|
243
|
-
|
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
|
-
|
246
|
-
|
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
|
-
|
252
|
-
|
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
|
-
|
172
|
+
=== Uploading new data
|
283
173
|
|
284
|
-
$ swivel upload "my
|
285
|
-
uploaded
|
174
|
+
$ cat data.csv | /usr/bin/swivel upload "my data"
|
175
|
+
uploaded 1234567
|
286
176
|
|
287
|
-
|
177
|
+
=== Appending more data to an existing data set
|
288
178
|
|
289
|
-
|
179
|
+
$ cat more_data.csv | /usr/bin/swivel append 1234567
|
180
|
+
appended 1234567
|
290
181
|
|
291
|
-
|
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
|
-
|
296
|
-
uploaded data_set 1000234
|
184
|
+
=== Replacing data for an existing data set
|
297
185
|
|
298
|
-
|
186
|
+
$ cat more_data.csv | /usr/bin/swivel replace 1234567
|
187
|
+
replaced 1234567
|
299
188
|
|
300
|
-
|
301
|
-
appended data_set 1000234
|
189
|
+
Caveat: Your new data must have the same column structure as the original data.
|
302
190
|
|
303
|
-
|
304
|
-
fancier data:
|
191
|
+
== More options
|
305
192
|
|
306
|
-
$
|
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
|
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
|
-
|
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.
|
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', '
|
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
|
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 =
|
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 '
|
4
|
+
require 'open-uri'
|
5
|
+
require 'active_support'
|
5
6
|
require 'optparse'
|
6
|
-
require '
|
7
|
-
|
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 =
|
11
|
-
config.load
|
12
|
-
config.save
|
56
|
+
config = Swivel2::Config.load
|
13
57
|
|
14
58
|
ARGV.options do |opts|
|
15
|
-
opts.banner = 'Usage: swivel <
|
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 '-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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.
|
35
|
-
"Column names." do |v|
|
36
|
-
options[:column_names] = v
|
37
|
-
end
|
73
|
+
opts.separator ''
|
38
74
|
|
39
|
-
opts.on '-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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.
|
63
|
-
|
64
|
-
|
65
|
-
|
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.
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
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
|
90
|
-
@swivel =
|
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
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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 = {
|
114
|
-
|
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 = {
|
120
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
-
@
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
-
|
179
|
-
|
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
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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
|
203
|
+
value_for @hash_doc[method_name]
|
190
204
|
end
|
191
|
-
|
192
|
-
|
193
|
-
elsif
|
194
|
-
|
195
|
-
|
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
|
-
|
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
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
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
|
-
|
310
|
-
|
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
|
-
|
330
|
-
|
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
|
396
|
-
|
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
|
408
|
-
|
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
|
483
|
-
super
|
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
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
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
|
-
|
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.
|
7
|
-
date:
|
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
|
-
-
|
45
|
+
- swivel2.rb
|
44
46
|
- --main
|
45
47
|
- README
|
46
48
|
- --inline-source
|