datalanche 0.9.2
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.
- data/lib/datalanche.rb +3 -0
- data/lib/datalanche/client.rb +118 -0
- data/lib/datalanche/exception.rb +27 -0
- data/lib/datalanche/query.rb +464 -0
- metadata +68 -0
data/lib/datalanche.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "json"
|
5
|
+
require "net/http"
|
6
|
+
require "net/https"
|
7
|
+
require "zlib"
|
8
|
+
require "uri"
|
9
|
+
require_relative "./query"
|
10
|
+
require_relative "./exception"
|
11
|
+
|
12
|
+
class DLClient
|
13
|
+
def initialize(key, secret, host = nil, port = nil, verify_ssl = true)
|
14
|
+
|
15
|
+
@auth_key = key
|
16
|
+
@auth_secret = secret
|
17
|
+
@url = 'https://api.datalanche.com'
|
18
|
+
@verify_ssl = verify_ssl
|
19
|
+
@verify_mode = OpenSSL::SSL::VERIFY_PEER
|
20
|
+
|
21
|
+
if host != nil
|
22
|
+
@url = 'https://' + host
|
23
|
+
end
|
24
|
+
|
25
|
+
if port != nil
|
26
|
+
@url = @url + ':' + port.to_s()
|
27
|
+
end
|
28
|
+
|
29
|
+
if verify_ssl != true
|
30
|
+
@verify_mode = OpenSSL::SSL::VERIFY_NONE
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
def key(key)
|
36
|
+
@auth_key = key
|
37
|
+
end
|
38
|
+
|
39
|
+
def secret(secret)
|
40
|
+
@auth_secret = secret
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_debug_info(res,req)
|
44
|
+
info = Hash.new
|
45
|
+
|
46
|
+
info['request'] = Hash.new
|
47
|
+
info['request']['method'] = req['method']
|
48
|
+
info['request']['url'] = req['url']
|
49
|
+
info['request']['headers'] = req['headers']
|
50
|
+
info['request']['body'] = req['body']
|
51
|
+
|
52
|
+
info['response'] = Hash.new
|
53
|
+
info['response']['http_status'] = res.code # r.status_code
|
54
|
+
info['response']['message'] = res.message
|
55
|
+
info['response']['http_version'] = res.http_version
|
56
|
+
|
57
|
+
return info
|
58
|
+
end
|
59
|
+
|
60
|
+
def query(q = nil)
|
61
|
+
if q == nil
|
62
|
+
raise Exception('query == nil')
|
63
|
+
end
|
64
|
+
|
65
|
+
url = @url
|
66
|
+
|
67
|
+
if q.params.has_key?('database')
|
68
|
+
url = url + '/' + q.params['database']
|
69
|
+
q.params.delete('database')
|
70
|
+
end
|
71
|
+
|
72
|
+
url = URI.parse(url + '/query')
|
73
|
+
|
74
|
+
header = {
|
75
|
+
# 'Accept-Encoding'=>'gzip', # will be resumed after method of decompression of gzip found
|
76
|
+
'Content-Type'=>'application/json',
|
77
|
+
'User-Agent'=>'Datalanche Ruby Client'
|
78
|
+
}
|
79
|
+
|
80
|
+
req = Net::HTTP::Post.new(url.path, header)
|
81
|
+
req.basic_auth @auth_key, @auth_secret
|
82
|
+
|
83
|
+
https = Net::HTTP.new(url.host,url.port)
|
84
|
+
https.use_ssl = true
|
85
|
+
https.verify_mode = @verify_mode
|
86
|
+
https.ssl_version = "SSLv3"
|
87
|
+
|
88
|
+
req.body = "#{q.params.to_json}"
|
89
|
+
res = https.request(req)
|
90
|
+
|
91
|
+
result = Hash.new
|
92
|
+
req_info = Hash.new
|
93
|
+
|
94
|
+
req_info['headers'] = header
|
95
|
+
req_info['url'] = url
|
96
|
+
req_info['method'] = req.method
|
97
|
+
req_info['body'] = JSON.parse(req.body)
|
98
|
+
|
99
|
+
debug_info = self.get_debug_info(res,req_info)
|
100
|
+
|
101
|
+
begin
|
102
|
+
result['data'] = JSON.parse(res.body)
|
103
|
+
rescue # in case the server does not return a body
|
104
|
+
result['data'] = nil
|
105
|
+
end
|
106
|
+
|
107
|
+
result['response'] = debug_info['response']
|
108
|
+
result['request'] = debug_info['request']
|
109
|
+
|
110
|
+
|
111
|
+
status_code = res.code.to_i
|
112
|
+
if not (200 <= status_code and status_code < 300)
|
113
|
+
raise DLException.new(res.code, result['data'], debug_info),"Http request error: "
|
114
|
+
end
|
115
|
+
return result
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class DLException < Exception
|
4
|
+
|
5
|
+
attr_accessor :detail, :status_code
|
6
|
+
|
7
|
+
def initialize(status_code, body, debug_info)
|
8
|
+
|
9
|
+
@request = debug_info['request']
|
10
|
+
@response = debug_info['response']
|
11
|
+
@response['body'] = body
|
12
|
+
@error_message = debug_info['response']['message']
|
13
|
+
@error_type = body
|
14
|
+
@status_code = status_code
|
15
|
+
|
16
|
+
@detail = {
|
17
|
+
'status_code' => @status_code,
|
18
|
+
'error_message' => @error_message,
|
19
|
+
'error_type' => @error_type,
|
20
|
+
'request' => @request,
|
21
|
+
'response' => @response
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
|
@@ -0,0 +1,464 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
class DLQuery
|
4
|
+
|
5
|
+
# return params
|
6
|
+
attr_reader :params
|
7
|
+
|
8
|
+
# initialize parameters
|
9
|
+
def initialize(database = nil)
|
10
|
+
@params = Hash.new
|
11
|
+
if database != nil
|
12
|
+
@params['database'] = database
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# COMMON
|
18
|
+
#
|
19
|
+
|
20
|
+
def cascade(boolean)
|
21
|
+
@params['cascade'] = boolean
|
22
|
+
return self # method chaining
|
23
|
+
end
|
24
|
+
|
25
|
+
def columns(columns)
|
26
|
+
@params['columns'] = columns
|
27
|
+
return self # method chaining
|
28
|
+
end
|
29
|
+
|
30
|
+
def description(text)
|
31
|
+
@params['description'] = text
|
32
|
+
return self # method chaining
|
33
|
+
end
|
34
|
+
|
35
|
+
def rename_to(table_name)
|
36
|
+
@params['rename_to'] = table_name
|
37
|
+
return self # method chaining
|
38
|
+
end
|
39
|
+
|
40
|
+
def where(expression)
|
41
|
+
@params['where'] = expression
|
42
|
+
return self # method chaining
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# EXPRESSIONS
|
47
|
+
#
|
48
|
+
|
49
|
+
#
|
50
|
+
# usage examples
|
51
|
+
#
|
52
|
+
# q.expr(2, "+", 2)
|
53
|
+
# q.expr("~", 2)
|
54
|
+
# q.expr(2, "!")
|
55
|
+
# q.expr(q.column("c1"), "$like", "%abc%")
|
56
|
+
# q.expr(q.column("c1"), "$not", "$in", [1, 2, 3, 4])
|
57
|
+
# q.expr(q.column("c1"), "=", 1, "$and", q.column("c2"), "=", 2)
|
58
|
+
#
|
59
|
+
def expr(*args)
|
60
|
+
# *args is a built-in Ruby variable which is a tuple of function args
|
61
|
+
return { '$expr'=> args }
|
62
|
+
end
|
63
|
+
|
64
|
+
def alias(alias_name)
|
65
|
+
return { '$alias'=>alias_name }
|
66
|
+
end
|
67
|
+
|
68
|
+
def column(column_name)
|
69
|
+
return { '$column'=>column_name }
|
70
|
+
end
|
71
|
+
|
72
|
+
def literal(value)
|
73
|
+
return { '$literal'=>value }
|
74
|
+
end
|
75
|
+
|
76
|
+
def table(table_name)
|
77
|
+
return { '$table'=>table_name }
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# FUNCTIONS
|
82
|
+
#
|
83
|
+
|
84
|
+
# NOTE: *args is a built-in Ruby variable which is a tuple of function args
|
85
|
+
|
86
|
+
#
|
87
|
+
# usage examples
|
88
|
+
#
|
89
|
+
# q.count("*")
|
90
|
+
# q.sum(q.column("c1"))
|
91
|
+
#
|
92
|
+
def avg(*args)
|
93
|
+
return { '$avg'=>args }
|
94
|
+
end
|
95
|
+
|
96
|
+
def count(*args)
|
97
|
+
return { '$count'=>args }
|
98
|
+
end
|
99
|
+
|
100
|
+
def max(*args)
|
101
|
+
return { '$max'=>args }
|
102
|
+
end
|
103
|
+
|
104
|
+
def min(*args)
|
105
|
+
return { '$min'=>args }
|
106
|
+
end
|
107
|
+
|
108
|
+
def sum(*args)
|
109
|
+
return { '$sum'=>args }
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
# ALTER DATABASE
|
114
|
+
#
|
115
|
+
|
116
|
+
def alter_database(database_name)
|
117
|
+
@params['alter_database'] = database_name
|
118
|
+
return self # method chaining
|
119
|
+
end
|
120
|
+
|
121
|
+
def add_collaborator(username, permission)
|
122
|
+
if @params.has_key?('add_collaborators')
|
123
|
+
@params['add_collaborators'] = Hase.new
|
124
|
+
end
|
125
|
+
|
126
|
+
@params['add_collaborators'][username] = permission
|
127
|
+
return self # method chaining
|
128
|
+
end
|
129
|
+
|
130
|
+
def alter_collaborator(username, permission)
|
131
|
+
if @params.has_key?('alter_collaborators')
|
132
|
+
@params['alter_collaborators'] = Hash.new
|
133
|
+
end
|
134
|
+
|
135
|
+
@params['alter_collaborators'][username] = permission
|
136
|
+
return self # method chaining
|
137
|
+
end
|
138
|
+
|
139
|
+
def drop_collaborator(username)
|
140
|
+
if @params.has_key?('drop_collaborators')
|
141
|
+
@params['drop_collaborators'] = Array.new
|
142
|
+
end
|
143
|
+
|
144
|
+
@params['drop_collaborators'].push(username)
|
145
|
+
return self # method chaining
|
146
|
+
end
|
147
|
+
|
148
|
+
def is_private(boolean)
|
149
|
+
@params['is_private'] = boolean
|
150
|
+
return self # method chaining
|
151
|
+
end
|
152
|
+
|
153
|
+
def max_size_gb(integer)
|
154
|
+
@params['max_size_gb'] = integer
|
155
|
+
return self # method chaining
|
156
|
+
end
|
157
|
+
|
158
|
+
#
|
159
|
+
# ALTER INDEX
|
160
|
+
#
|
161
|
+
|
162
|
+
def alter_index(index_name)
|
163
|
+
@params['alter_index'] = index_name
|
164
|
+
return self # method chaining
|
165
|
+
end
|
166
|
+
|
167
|
+
#
|
168
|
+
# ALTER SCHEMA
|
169
|
+
#
|
170
|
+
|
171
|
+
def alter_schema(schema_name)
|
172
|
+
@params['alter_schema'] = schema_name
|
173
|
+
return self # method chaining
|
174
|
+
end
|
175
|
+
|
176
|
+
#
|
177
|
+
# ALTER TABLE
|
178
|
+
#
|
179
|
+
|
180
|
+
def alter_table(table_name)
|
181
|
+
@params['alter_table'] = table_name
|
182
|
+
return self # method chaining
|
183
|
+
end
|
184
|
+
|
185
|
+
def add_column(column_name, attributes)
|
186
|
+
if !(@params.has_key?('add_columns'))
|
187
|
+
@params['add_columns'] = Hash.new
|
188
|
+
end
|
189
|
+
|
190
|
+
@params['add_columns'][column_name] = attributes
|
191
|
+
return self # method chaining
|
192
|
+
end
|
193
|
+
|
194
|
+
# TODO: add_constraint
|
195
|
+
|
196
|
+
def alter_column(column_name, attributes)
|
197
|
+
if !(@params.has_key?('alter_columns'))
|
198
|
+
@params['alter_columns'] = Hash.new
|
199
|
+
end
|
200
|
+
|
201
|
+
@params['alter_columns'][column_name] = attributes
|
202
|
+
return self # method chaining
|
203
|
+
end
|
204
|
+
|
205
|
+
def drop_column(column_name, cascade = false)
|
206
|
+
if !(@params.has_key?('drop_columns'))
|
207
|
+
@params['drop_columns'] = Array.new
|
208
|
+
end
|
209
|
+
|
210
|
+
column_obj = Hash.new
|
211
|
+
column_obj['name'] = column_name
|
212
|
+
column_obj['cascade'] = cascade
|
213
|
+
self.params['drop_columns'].push(column_obj)
|
214
|
+
return self # method chaining
|
215
|
+
end
|
216
|
+
|
217
|
+
# TODO: drop_constraint
|
218
|
+
|
219
|
+
def rename_column(column_name, new_name)
|
220
|
+
if !(@params.has_key?('rename_columns'))
|
221
|
+
@params['rename_columns'] = Hash.new
|
222
|
+
end
|
223
|
+
|
224
|
+
@params['rename_columns'][column_name] = new_name
|
225
|
+
return self # method chaining
|
226
|
+
end
|
227
|
+
|
228
|
+
# TODO: rename_constraint
|
229
|
+
|
230
|
+
def set_schema(schema_name)
|
231
|
+
@params['set_schema'] = schema_name
|
232
|
+
return self # method chaining
|
233
|
+
end
|
234
|
+
|
235
|
+
#
|
236
|
+
# CREATE INDEX
|
237
|
+
#
|
238
|
+
|
239
|
+
def create_index(index_name)
|
240
|
+
@params['create_index'] = index_name
|
241
|
+
return self # method chaining
|
242
|
+
end
|
243
|
+
|
244
|
+
def on_table(tableName)
|
245
|
+
@params['on_table'] = tableName
|
246
|
+
return self # method chaining
|
247
|
+
end
|
248
|
+
|
249
|
+
def unique(boolean)
|
250
|
+
@params['unique'] = boolean
|
251
|
+
return self # method chaining
|
252
|
+
end
|
253
|
+
|
254
|
+
def using_method(text)
|
255
|
+
@params['using_method'] = text
|
256
|
+
return self # method chaining
|
257
|
+
end
|
258
|
+
|
259
|
+
#
|
260
|
+
# CREATE SCHEMA
|
261
|
+
#
|
262
|
+
|
263
|
+
def create_schema(schema_name)
|
264
|
+
@params['create_schema'] = schema_name
|
265
|
+
return self # method chaining
|
266
|
+
end
|
267
|
+
|
268
|
+
#
|
269
|
+
# CREATE TABLE
|
270
|
+
#
|
271
|
+
|
272
|
+
def create_table(table_name)
|
273
|
+
@params['create_table'] = table_name
|
274
|
+
return self # method chaining
|
275
|
+
end
|
276
|
+
|
277
|
+
|
278
|
+
# TODO: constraints
|
279
|
+
|
280
|
+
#
|
281
|
+
# DELETE
|
282
|
+
#
|
283
|
+
|
284
|
+
def delete_from(table_name)
|
285
|
+
@params['delete_from'] = table_name
|
286
|
+
return self # method chaining
|
287
|
+
end
|
288
|
+
|
289
|
+
#
|
290
|
+
# DESCRIBE DATABASE
|
291
|
+
#
|
292
|
+
|
293
|
+
def describe_database(database_name)
|
294
|
+
@params['describe_database'] = database_name
|
295
|
+
return self # method chaining
|
296
|
+
end
|
297
|
+
|
298
|
+
#
|
299
|
+
# DESCRIBE SCHEMA
|
300
|
+
#
|
301
|
+
|
302
|
+
def describe_schema(schema_name)
|
303
|
+
@params['describe_schema'] = schema_name
|
304
|
+
return self # method chaining
|
305
|
+
end
|
306
|
+
|
307
|
+
#
|
308
|
+
# DESCRIBE TABLE
|
309
|
+
#
|
310
|
+
|
311
|
+
def describe_table(table_name)
|
312
|
+
@params['describe_table'] = table_name
|
313
|
+
return self # method chaining
|
314
|
+
end
|
315
|
+
|
316
|
+
#
|
317
|
+
# DROP INDEX
|
318
|
+
#
|
319
|
+
|
320
|
+
def drop_index(index_name)
|
321
|
+
@params['drop_index'] = index_name
|
322
|
+
return self # method chaining
|
323
|
+
end
|
324
|
+
|
325
|
+
#
|
326
|
+
# DROP SCHEMA
|
327
|
+
#
|
328
|
+
|
329
|
+
def drop_schema(schema_name)
|
330
|
+
@params['drop_schema'] = schema_name
|
331
|
+
return self # method chaining
|
332
|
+
end
|
333
|
+
|
334
|
+
#
|
335
|
+
# DROP TABLE
|
336
|
+
#
|
337
|
+
|
338
|
+
def drop_table(table_name)
|
339
|
+
@params['drop_table'] = table_name
|
340
|
+
return self # method chaining
|
341
|
+
end
|
342
|
+
|
343
|
+
#
|
344
|
+
# INSERT
|
345
|
+
#
|
346
|
+
|
347
|
+
def insert_into(table_name)
|
348
|
+
@params['insert_into'] = table_name
|
349
|
+
return self # method chaining
|
350
|
+
end
|
351
|
+
|
352
|
+
def values(rows)
|
353
|
+
@params['values'] = rows
|
354
|
+
return self # method chaining
|
355
|
+
end
|
356
|
+
|
357
|
+
#
|
358
|
+
# SEARCH TABLE
|
359
|
+
#
|
360
|
+
|
361
|
+
def search_table(query_text)
|
362
|
+
@params['search_table'] = query_text
|
363
|
+
return self # method chaining
|
364
|
+
end
|
365
|
+
|
366
|
+
def with_query(query_text)
|
367
|
+
@params['with_query'] = query_text
|
368
|
+
return self # method chaining
|
369
|
+
end
|
370
|
+
|
371
|
+
#
|
372
|
+
# SELECT
|
373
|
+
#
|
374
|
+
|
375
|
+
def select(columns)
|
376
|
+
if columns == '*'
|
377
|
+
raise Exception('please use select_all() instead of select("*")')
|
378
|
+
end
|
379
|
+
|
380
|
+
@params['select'] = columns
|
381
|
+
return self # method chaining
|
382
|
+
end
|
383
|
+
|
384
|
+
def select_all()
|
385
|
+
@params['select'] = true
|
386
|
+
return self # method chaining
|
387
|
+
end
|
388
|
+
|
389
|
+
def distinct(boolean)
|
390
|
+
@params['distinct'] = boolean
|
391
|
+
return self # method chaining
|
392
|
+
end
|
393
|
+
|
394
|
+
def from(tables)
|
395
|
+
@params['from'] = tables
|
396
|
+
return self # method chaining
|
397
|
+
end
|
398
|
+
|
399
|
+
def group_by(columns)
|
400
|
+
@params['group_by'] = columns
|
401
|
+
return self # method chaining
|
402
|
+
end
|
403
|
+
|
404
|
+
def having(expression)
|
405
|
+
@params['having'] = expression
|
406
|
+
return self # method chaining
|
407
|
+
end
|
408
|
+
|
409
|
+
def limit(integer)
|
410
|
+
@params['limit'] = integer
|
411
|
+
return self # method chaining
|
412
|
+
end
|
413
|
+
|
414
|
+
def offset(integer)
|
415
|
+
@params['offset'] = integer
|
416
|
+
return self # method chaining
|
417
|
+
end
|
418
|
+
|
419
|
+
def order_by(expr_array)
|
420
|
+
@params['order_by'] = expr_array
|
421
|
+
return self # method chaining
|
422
|
+
end
|
423
|
+
|
424
|
+
#
|
425
|
+
# SHOW DATABASES
|
426
|
+
#
|
427
|
+
|
428
|
+
def show_databases()
|
429
|
+
@params['show_databases'] = true
|
430
|
+
return self # method chaining
|
431
|
+
end
|
432
|
+
|
433
|
+
#
|
434
|
+
# SHOW SCHEMAS
|
435
|
+
#
|
436
|
+
|
437
|
+
def show_schemas()
|
438
|
+
@params['show_schemas'] = true
|
439
|
+
return self # method chaining
|
440
|
+
end
|
441
|
+
|
442
|
+
#
|
443
|
+
# SHOW TABLES
|
444
|
+
#
|
445
|
+
|
446
|
+
def show_tables()
|
447
|
+
@params['show_tables'] = true
|
448
|
+
return self # method chaining
|
449
|
+
end
|
450
|
+
|
451
|
+
#
|
452
|
+
# UPDATE
|
453
|
+
#
|
454
|
+
|
455
|
+
def update(table_name)
|
456
|
+
@params['update'] = table_name
|
457
|
+
return self # method chaining
|
458
|
+
end
|
459
|
+
|
460
|
+
def set(kv_pairs)
|
461
|
+
@params['set'] = kv_pairs
|
462
|
+
return self # method chaining
|
463
|
+
end
|
464
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: datalanche
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 63
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 9
|
9
|
+
- 2
|
10
|
+
version: 0.9.2
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Datalanche Inc.
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2014-04-23 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: Official Ruby client for Datalanche.
|
22
|
+
email: contact@datalanche.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- lib/datalanche.rb
|
31
|
+
- lib/datalanche/client.rb
|
32
|
+
- lib/datalanche/query.rb
|
33
|
+
- lib/datalanche/exception.rb
|
34
|
+
homepage: https://api.datalanche.com
|
35
|
+
licenses:
|
36
|
+
- MIT
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
hash: 3
|
48
|
+
segments:
|
49
|
+
- 0
|
50
|
+
version: "0"
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
requirements: []
|
61
|
+
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 1.8.15
|
64
|
+
signing_key:
|
65
|
+
specification_version: 3
|
66
|
+
summary: datalanche
|
67
|
+
test_files: []
|
68
|
+
|