td-client 0.8.0

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.
@@ -0,0 +1,5 @@
1
+
2
+ == 2011-08-21 version 0.8.0
3
+
4
+ * First release
5
+
@@ -0,0 +1,12 @@
1
+ = Treasure Data API library for Ruby
2
+
3
+ = Getting Started
4
+
5
+ > gem install td-client
6
+
7
+
8
+ == Copyright
9
+
10
+ Copyright:: Copyright (c) 2011 Treasure Data Inc.
11
+ License:: Apache License, Version 2.0
12
+
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), 'td', 'client')
@@ -0,0 +1,152 @@
1
+
2
+ module TreasureData
3
+
4
+ require 'td/client/api'
5
+ require 'td/client/model'
6
+
7
+
8
+ class Client
9
+ def self.authenticate(user, password)
10
+ api = API.new(nil)
11
+ apikey = api.authenticate(user, password)
12
+ new(apikey)
13
+ end
14
+
15
+ def self.server_status
16
+ api = API.new(nil)
17
+ api.server_status
18
+ end
19
+
20
+ def initialize(apikey)
21
+ @api = API.new(apikey)
22
+ end
23
+
24
+ attr_reader :api
25
+
26
+ def apikey
27
+ @api.apikey
28
+ end
29
+
30
+ def server_status
31
+ @api.server_status
32
+ end
33
+
34
+ # => true
35
+ def create_database(db_name)
36
+ @api.create_database(db_name)
37
+ end
38
+
39
+ # => true
40
+ def delete_database(db_name)
41
+ @api.delete_database(db_name)
42
+ end
43
+
44
+ # => [Database]
45
+ def databases
46
+ names = @api.list_databases
47
+ names.map {|db_name|
48
+ Database.new(self, db_name)
49
+ }
50
+ end
51
+
52
+ # => Database
53
+ def database(db_name)
54
+ names = @api.list_databases
55
+ names.each {|n|
56
+ if n == db_name
57
+ return Database.new(self, db_name)
58
+ end
59
+ }
60
+ raise NotFoundError, "Database '#{db_name}' does not exist"
61
+ end
62
+
63
+ # => true
64
+ def create_log_table(db_name, table_name)
65
+ @api.create_log_table(db_name, table_name)
66
+ end
67
+
68
+ # => true
69
+ def create_item_table(db_name, table_name)
70
+ @api.create_item_table(db_name, table_name)
71
+ end
72
+
73
+ # => true
74
+ def update_schema(db_name, table_name, schema)
75
+ @api.update_schema(db_name, table_name, schema.to_json)
76
+ end
77
+
78
+ # => type:Symbol
79
+ def delete_table(db_name, table_name)
80
+ @api.delete_table(db_name, table_name)
81
+ end
82
+
83
+ # => [Table]
84
+ def tables(db_name)
85
+ m = @api.list_tables(db_name)
86
+ m.map {|table_name,(type,schema,count)|
87
+ schema = Schema.new.from_json(schema)
88
+ Table.new(self, db_name, table_name, type, schema, count)
89
+ }
90
+ end
91
+
92
+ # => Table
93
+ def table(db_name, table_name)
94
+ tables(db_name).each {|t|
95
+ if t.name == table_name
96
+ return t
97
+ end
98
+ }
99
+ raise NotFoundError, "Table '#{db_name}.#{table_name}' does not exist"
100
+ end
101
+
102
+ # => Job
103
+ def query(db_name, q)
104
+ job_id = @api.hive_query(q, db_name)
105
+ Job.new(self, job_id, :hive, q) # TODO url
106
+ end
107
+
108
+ # => [Job=]
109
+ def jobs(from=nil, to=nil)
110
+ js = @api.list_jobs(from, to)
111
+ js.map {|job_id,type,status,query,start_at,end_at|
112
+ Job.new(self, job_id, type, query, status, nil, nil, start_at, end_at)
113
+ }
114
+ end
115
+
116
+ # => Job
117
+ def job(job_id)
118
+ job_id = job_id.to_s
119
+ type, query, status, url, debug, start_at, end_at = @api.show_job(job_id)
120
+ Job.new(self, job_id, type, query, status, url, debug, start_at, end_at)
121
+ end
122
+
123
+ # => type:Symbol, url:String
124
+ def job_status(job_id)
125
+ type, query, status, url, debug, start_at, end_at = @api.show_job(job_id)
126
+ return query, status, url, debug, start_at, end_at
127
+ end
128
+
129
+ # => result:[{column:String=>value:Object]
130
+ def job_result(job_id)
131
+ @api.job_result(job_id)
132
+ end
133
+
134
+ # => result:String
135
+ def job_result_format(job_id, format)
136
+ @api.job_result_format(job_id, format)
137
+ end
138
+
139
+ # => nil
140
+ def job_result_each(job_id, &block)
141
+ @api.job_result_each(job_id, &block)
142
+ end
143
+
144
+ # => time:Flaot
145
+ def import(db_name, table_name, format, stream, size)
146
+ @api.import(db_name, table_name, format, stream, size)
147
+ end
148
+ end
149
+
150
+
151
+ end
152
+
@@ -0,0 +1,460 @@
1
+
2
+ module TreasureData
3
+
4
+
5
+ class APIError < StandardError
6
+ end
7
+
8
+ class AuthError < APIError
9
+ end
10
+
11
+ class AlreadyExistsError < APIError
12
+ end
13
+
14
+ class NotFoundError < APIError
15
+ end
16
+
17
+
18
+ class API
19
+ def initialize(apikey)
20
+ require 'json'
21
+ require 'time'
22
+ @apikey = apikey
23
+ end
24
+
25
+ # TODO error check & raise appropriate errors
26
+
27
+ attr_reader :apikey
28
+
29
+ def self.validate_database_name(name)
30
+ name = name.to_s
31
+ if name.empty?
32
+ raise "Empty name is not allowed"
33
+ end
34
+ if name.length < 3 || 32 < name.length
35
+ raise "Name must be 3 to 32 characters, got #{name.length} characters."
36
+ end
37
+ unless name =~ /^([a-z0-9_]+)$/
38
+ raise "Name must consist only of alphabets, numbers, '_'."
39
+ end
40
+ name
41
+ end
42
+
43
+ def self.validate_table_name(name)
44
+ validate_database_name(name)
45
+ end
46
+
47
+ def self.validate_column_name(name)
48
+ name = name.to_s
49
+ if name.empty?
50
+ raise "Empty column name is not allowed"
51
+ end
52
+ if 32 < name.length
53
+ raise "Column name must be to 32 characters, got #{name.length} characters."
54
+ end
55
+ unless name =~ /^([a-z0-9_]+)$/
56
+ raise "Column name must consist only of alphabets, numbers, '_'."
57
+ end
58
+ end
59
+
60
+ def self.normalize_type_name(name)
61
+ case name
62
+ when /int/i, /integer/i
63
+ "int"
64
+ when /long/i, /bigint/i
65
+ "long"
66
+ when /string/i
67
+ "string"
68
+ when /float/i
69
+ "float"
70
+ when /double/i
71
+ "double"
72
+ else
73
+ raise "Type name must eather of int, long, string float or double"
74
+ end
75
+ end
76
+
77
+ ####
78
+ ## Database API
79
+ ##
80
+
81
+ # => [name:String]
82
+ def list_databases
83
+ code, body, res = get("/v3/database/list")
84
+ if code != "200"
85
+ raise_error("List databases failed", res)
86
+ end
87
+ # TODO format check
88
+ js = JSON.load(body)
89
+ names = js["databases"].map {|dbinfo| dbinfo['name'] }
90
+ return names
91
+ end
92
+
93
+ # => true
94
+ def delete_database(db)
95
+ code, body, res = post("/v3/database/delete/#{e db}")
96
+ if code != "200"
97
+ raise_error("Delete database failed", res)
98
+ end
99
+ return true
100
+ end
101
+
102
+ # => true
103
+ def create_database(db)
104
+ code, body, res = post("/v3/database/create/#{e db}")
105
+ if code != "200"
106
+ raise_error("Create database failed", res)
107
+ end
108
+ return true
109
+ end
110
+
111
+
112
+ ####
113
+ ## Table API
114
+ ##
115
+
116
+ # => {name:String => [type:Symbol, count:Integer]}
117
+ def list_tables(db)
118
+ code, body, res = get("/v3/table/list/#{e db}")
119
+ if code != "200"
120
+ raise_error("List tables failed", res)
121
+ end
122
+ # TODO format check
123
+ js = JSON.load(body)
124
+ result = {}
125
+ js["tables"].map {|m|
126
+ name = m['name']
127
+ type = (m['type'] || '?').to_sym
128
+ count = (m['count'] || 0).to_i # TODO?
129
+ schema = JSON.parse(m['schema'] || '[]')
130
+ result[name] = [type, schema, count]
131
+ }
132
+ return result
133
+ end
134
+
135
+ def create_log_or_item_table(db, table, type)
136
+ code, body, res = post("/v3/table/create/#{e db}/#{e table}/#{type}")
137
+ if code != "200"
138
+ raise_error("Create #{type} table failed", res)
139
+ end
140
+ return true
141
+ end
142
+ private :create_log_or_item_table
143
+
144
+ # => true
145
+ def create_log_table(db, table)
146
+ create_table(db, table, :log)
147
+ end
148
+
149
+ # => true
150
+ def create_item_table(db, table)
151
+ create_table(db, table, :item)
152
+ end
153
+
154
+ def create_table(db, table, type)
155
+ schema = schema.to_s
156
+ code, body, res = post("/v3/table/create/#{e db}/#{e table}/#{type}")
157
+ if code != "200"
158
+ raise_error("Create #{type} table failed", res)
159
+ end
160
+ return true
161
+ end
162
+ private :create_table
163
+
164
+ # => true
165
+ def update_schema(db, table, schema_json)
166
+ code, body, res = post("/v3/table/update-schema/#{e db}/#{e table}", {'schema'=>schema_json})
167
+ if code != "200"
168
+ raise_error("Create schema table failed", res)
169
+ end
170
+ return true
171
+ end
172
+
173
+ # => type:Symbol
174
+ def delete_table(db, table)
175
+ code, body, res = post("/v3/table/delete/#{e db}/#{e table}")
176
+ if code != "200"
177
+ raise_error("Drop table failed", res)
178
+ end
179
+ # TODO format check
180
+ js = JSON.load(body)
181
+ type = (js['type'] || '?').to_sym
182
+ return type
183
+ end
184
+
185
+
186
+ ####
187
+ ## Job API
188
+ ##
189
+
190
+ # => [(jobId:String, type:Symbol, status:String, start_at:String, end_at:String)]
191
+ def list_jobs(from=0, to=nil)
192
+ params = {}
193
+ params['from'] = from.to_s if from
194
+ params['to'] = to.to_s if to
195
+ code, body, res = get("/v3/job/list", params)
196
+ if code != "200"
197
+ raise_error("List jobs failed", res)
198
+ end
199
+ # TODO format check
200
+ js = JSON.load(body)
201
+ result = []
202
+ js['jobs'].each {|m|
203
+ job_id = m['job_id']
204
+ type = (m['type'] || '?').to_sym
205
+ status = m['status']
206
+ query = m['query']
207
+ start_at = m['start_at']
208
+ end_at = m['end_at']
209
+ result << [job_id, type, status, query, start_at, end_at]
210
+ }
211
+ return result
212
+ end
213
+
214
+ # => (type:Symbol, status:String, result:String, url:String)
215
+ def show_job(job_id)
216
+ code, body, res = get("/v3/job/show/#{e job_id}")
217
+ if code != "200"
218
+ raise_error("Show job failed", res)
219
+ end
220
+ # TODO format check
221
+ js = JSON.load(body)
222
+ # TODO debug
223
+ type = (js['type'] || '?').to_sym # TODO
224
+ query = js['query']
225
+ status = js['status']
226
+ debug = js['debug']
227
+ url = js['url']
228
+ start_at = js['start_at']
229
+ end_at = js['end_at']
230
+ return [type, query, status, url, debug, start_at, end_at]
231
+ end
232
+
233
+ def job_result(job_id)
234
+ require 'msgpack'
235
+ code, body, res = get("/v3/job/result/#{e job_id}", {'format'=>'msgpack'})
236
+ if code != "200"
237
+ raise_error("Get job result failed", res)
238
+ end
239
+ result = []
240
+ MessagePack::Unpacker.new.feed_each(body) {|row|
241
+ result << row
242
+ }
243
+ return result
244
+ end
245
+
246
+ def job_result_format(job_id, format)
247
+ # TODO chunked encoding
248
+ code, body, res = get("/v3/job/result/#{e job_id}", {'format'=>format})
249
+ if code != "200"
250
+ raise_error("Get job result failed", res)
251
+ end
252
+ return body
253
+ end
254
+
255
+ def job_result_each(job_id, &block)
256
+ # TODO chunked encoding
257
+ require 'msgpack'
258
+ code, body, res = get("/v3/job/result/#{e job_id}", {'format'=>'msgpack'})
259
+ if code != "200"
260
+ raise_error("Get job result failed", res)
261
+ end
262
+ result = []
263
+ MessagePack::Unpacker.new.feed_each(body) {|row|
264
+ yield row
265
+ }
266
+ nil
267
+ end
268
+
269
+ def job_result_raw(job_id, format)
270
+ code, body, res = get("/v3/job/result/#{e job_id}", {'format'=>format})
271
+ if code != "200"
272
+ raise_error("Get job result failed", res)
273
+ end
274
+ return body
275
+ end
276
+
277
+ # => jobId:String
278
+ def hive_query(q, db=nil)
279
+ code, body, res = post("/v3/job/issue/hive/#{e db}", {'query'=>q})
280
+ if code != "200"
281
+ raise_error("Query failed", res)
282
+ end
283
+ # TODO format check
284
+ js = JSON.load(body)
285
+ return js['job_id'].to_s
286
+ end
287
+
288
+
289
+ ####
290
+ ## Import API
291
+ ##
292
+
293
+ # => time:Float
294
+ def import(db, table, format, stream, size)
295
+ code, body, res = put("/v3/table/import/#{e db}/#{e table}/#{format}", stream, size)
296
+ if code[0] != ?2
297
+ raise_error("Import failed", res)
298
+ end
299
+ # TODO format check
300
+ js = JSON.load(body)
301
+ time = js['time'].to_f
302
+ return time
303
+ end
304
+
305
+
306
+ ####
307
+ ## User API
308
+ ##
309
+
310
+ # apikey:String
311
+ def authenticate(user, password)
312
+ code, body, res = post("/v3/user/authenticate", {'user'=>user, 'password'=>password})
313
+ if code != "200"
314
+ raise_error("Authentication failed", res)
315
+ end
316
+ # TODO format check
317
+ js = JSON.load(body)
318
+ apikey = js['apikey']
319
+ return apikey
320
+ end
321
+
322
+ ####
323
+ ## Server Status API
324
+ ##
325
+
326
+ # => status:String
327
+ def server_status
328
+ code, body, res = get('/v3/system/server_status')
329
+ if code != "200"
330
+ return "Server is down (#{code})"
331
+ end
332
+ # TODO format check
333
+ js = JSON.load(body)
334
+ status = js['status']
335
+ return status
336
+ end
337
+
338
+ private
339
+ host = 'api.treasure-data.com'
340
+ port = 80
341
+ if e = ENV['TD_API_SERVER']
342
+ host, port_ = e.split(':',2)
343
+ port_ = port_.to_i
344
+ port = port_ if port_ != 0
345
+ end
346
+
347
+ HOST = host
348
+ PORT = port
349
+ USE_SSL = false
350
+ BASE_URL = ''
351
+
352
+ def get(url, params=nil)
353
+ http, header = new_http
354
+
355
+ path = BASE_URL + url
356
+ if params && !params.empty?
357
+ path << "?"+params.map {|k,v|
358
+ "#{k}=#{e v}"
359
+ }.join('&')
360
+ end
361
+
362
+ request = Net::HTTP::Get.new(path, header)
363
+
364
+ response = http.request(request)
365
+ return [response.code, response.body, response]
366
+ end
367
+
368
+ def post(url, params=nil)
369
+ http, header = new_http
370
+
371
+ path = BASE_URL + url
372
+
373
+ request = Net::HTTP::Post.new(path, header)
374
+ request.set_form_data(params) if params
375
+
376
+ response = http.request(request)
377
+ return [response.code, response.body, response]
378
+ end
379
+
380
+ def put(url, stream, size)
381
+ http, header = new_http
382
+
383
+ path = BASE_URL + url
384
+
385
+ header['Content-Type'] = 'application/octet-stream'
386
+ header['Content-Length'] = size.to_s
387
+
388
+ request = Net::HTTP::Put.new(url, header)
389
+ if stream.class.name == 'StringIO'
390
+ request.body = stream.string
391
+ else
392
+ if request.respond_to?(:body_stream=)
393
+ request.body_stream = stream
394
+ else # Ruby 1.8
395
+ request.body = stream.read
396
+ end
397
+ end
398
+
399
+ response = http.request(request)
400
+ return [response.code, response.body, response]
401
+ end
402
+
403
+ def new_http
404
+ require 'net/http'
405
+ require 'time'
406
+
407
+ http = Net::HTTP.new(HOST, PORT)
408
+ if USE_SSL
409
+ http.use_ssl = true
410
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
411
+ store = OpenSSL::X509::Store.new
412
+ http.cert_store = store
413
+ end
414
+
415
+ #http.read_timeout = options[:read_timeout]
416
+
417
+ header = {}
418
+ if @apikey
419
+ header['Authorization'] = "TD1 #{apikey}"
420
+ end
421
+ header['Date'] = Time.now.rfc2822
422
+
423
+ return http, header
424
+ end
425
+
426
+ def raise_error(msg, res)
427
+ begin
428
+ js = JSON.load(res.body)
429
+ msg = js['message']
430
+ error_code = js['error_code']
431
+
432
+ if res.code == "404"
433
+ raise NotFoundError, "#{error_code}: #{msg}"
434
+ elsif res.code == "409"
435
+ raise AlreadyExistsError, "#{error_code}: #{msg}"
436
+ else
437
+ raise APIError, "#{error_code}: #{msg}"
438
+ end
439
+
440
+ rescue
441
+ if res.code == "404"
442
+ raise NotFoundError, "#{msg}: #{res.body}"
443
+ elsif res.code == "409"
444
+ raise AlreadyExistsError, "#{msg}: #{res.body}"
445
+ else
446
+ raise APIError, "#{msg}: #{res.body}"
447
+ end
448
+ end
449
+ # TODO error
450
+ end
451
+
452
+ def e(s)
453
+ require 'cgi'
454
+ CGI.escape(s.to_s)
455
+ end
456
+ end
457
+
458
+
459
+ end
460
+
@@ -0,0 +1,237 @@
1
+
2
+ module TreasureData
3
+
4
+
5
+ class Model
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+ end
10
+
11
+ class Database < Model
12
+ def initialize(client, db_name, tables=nil)
13
+ super(client)
14
+ @db_name = db_name
15
+ @tables = tables
16
+ end
17
+
18
+ def name
19
+ @db_name
20
+ end
21
+
22
+ def tables
23
+ update_tables! unless @tables
24
+ @tables
25
+ end
26
+
27
+ def create_log_table(name)
28
+ @client.create_log_table(@db_name, name)
29
+ end
30
+
31
+ def create_item_table(name)
32
+ @client.create_item_table(@db_name, name)
33
+ end
34
+
35
+ def table(table_name)
36
+ @client.table(@db_name, table_name)
37
+ end
38
+
39
+ def delete
40
+ @client.delete_database(@db_name)
41
+ end
42
+
43
+ def update_tables!
44
+ @tables = @client.tables(@db_name)
45
+ end
46
+ end
47
+
48
+ class Table < Model
49
+ def initialize(client, db_name, table_name, type, schema, count)
50
+ super(client)
51
+ @db_name = db_name
52
+ @table_name = table_name
53
+ @type = type
54
+ @schema = schema
55
+ @count = count
56
+ end
57
+
58
+ attr_reader :type, :db_name, :table_name, :schema, :count
59
+
60
+ alias database_name db_name
61
+ alias name table_name
62
+
63
+ def database
64
+ @client.database(@db_name)
65
+ end
66
+
67
+ def identifier
68
+ "#{@db_name}.#{@table_name}"
69
+ end
70
+
71
+ def delete
72
+ @client.delete_table(@db_name, @table_name)
73
+ end
74
+ end
75
+
76
+ class Schema
77
+ class Field
78
+ def initialize(name, type)
79
+ @name = name
80
+ @type = type
81
+ end
82
+ attr_reader :name
83
+ attr_reader :type
84
+ end
85
+
86
+ def self.parse(cols)
87
+ fields = cols.split(',').map {|col|
88
+ name, type, *_ = col.split(':')
89
+ Field.new(name, type)
90
+ }
91
+ Schema.new(fields)
92
+ end
93
+
94
+ def initialize(fields=[])
95
+ @fields = fields
96
+ end
97
+
98
+ attr_reader :fields
99
+
100
+ def add_field(name, type)
101
+ @fields << Field.new(name, type)
102
+ end
103
+
104
+ def merge(schema)
105
+ nf = @fields.dup
106
+ schema.fields.each {|f|
107
+ if i = nf.find_index {|sf| sf.name == f.name }
108
+ nf[i] = f
109
+ else
110
+ nf << f
111
+ end
112
+ }
113
+ Schema.new(nf)
114
+ end
115
+
116
+ def to_json(*args)
117
+ @fields.map {|f| [f.name, f.type] }.to_json(*args)
118
+ end
119
+
120
+ def from_json(obj)
121
+ @fields = obj.map {|f|
122
+ Field.new(f[0], f[1])
123
+ }
124
+ self
125
+ end
126
+ end
127
+
128
+ class Job < Model
129
+ def initialize(client, job_id, type, query, status=nil, url=nil, debug=nil, start_at=nil, end_at=nil, result=nil)
130
+ super(client)
131
+ @job_id = job_id
132
+ @type = type
133
+ @url = url
134
+ @query = query
135
+ @status = status
136
+ @debug = debug
137
+ @start_at = start_at
138
+ @end_at = end_at
139
+ @result = result
140
+ end
141
+
142
+ attr_reader :job_id, :type
143
+
144
+ def wait(timeout=nil)
145
+ # TODO
146
+ end
147
+
148
+ def query
149
+ update_status! unless @query
150
+ @query
151
+ end
152
+
153
+ def status
154
+ update_status! unless @status
155
+ @status
156
+ end
157
+
158
+ def url
159
+ update_status! unless @url
160
+ @url
161
+ end
162
+
163
+ def debug
164
+ update_status! unless @debug
165
+ @debug
166
+ end
167
+
168
+ def start_at
169
+ update_status! unless @start_at
170
+ @start_at && !@start_at.empty? ? Time.parse(@start_at) : nil
171
+ end
172
+
173
+ def end_at
174
+ update_status! unless @end_at
175
+ @end_at && !@end_at.empty? ? Time.parse(@end_at) : nil
176
+ end
177
+
178
+ def result
179
+ unless @result
180
+ return nil unless finished?
181
+ @result = @client.job_result(@job_id)
182
+ end
183
+ @result
184
+ end
185
+
186
+ def result_format(format)
187
+ return nil unless finished?
188
+ @client.job_result_format(@job_id, format)
189
+ end
190
+
191
+ def result_each(&block)
192
+ if @result
193
+ @result.each(&block)
194
+ else
195
+ @client.job_result_each(@job_id, &block)
196
+ end
197
+ nil
198
+ end
199
+
200
+ def finished?
201
+ update_status! unless @status
202
+ if @status == "success" || @status == "error"
203
+ return true
204
+ else
205
+ return false
206
+ end
207
+ end
208
+
209
+ def running?
210
+ !finished?
211
+ end
212
+
213
+ def success?
214
+ update_status! unless @status
215
+ @status == "success"
216
+ end
217
+
218
+ def error?
219
+ update_status! unless @status
220
+ @status == "error"
221
+ end
222
+
223
+ def update_status!
224
+ query, status, url, debug, start_at, end_at = @client.job_status(@job_id)
225
+ @query = query
226
+ @status = status
227
+ @url = url
228
+ @debug = debug
229
+ @start_at = start_at
230
+ @end_at = end_at
231
+ self
232
+ end
233
+ end
234
+
235
+
236
+ end
237
+
@@ -0,0 +1,5 @@
1
+ module TreasureData
2
+
3
+ VERSION = '0.8.0'
4
+
5
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: td-client
3
+ version: !ruby/object:Gem::Version
4
+ hash: 63
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 8
9
+ - 0
10
+ version: 0.8.0
11
+ platform: ruby
12
+ authors:
13
+ - Sadayuki Furuhashi
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-08-21 00:00:00 +09:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: msgpack
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 7
30
+ segments:
31
+ - 0
32
+ - 4
33
+ - 4
34
+ version: 0.4.4
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: json
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 1
46
+ segments:
47
+ - 1
48
+ - 4
49
+ - 3
50
+ version: 1.4.3
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ description:
54
+ email:
55
+ executables: []
56
+
57
+ extensions: []
58
+
59
+ extra_rdoc_files:
60
+ - ChangeLog
61
+ - README.rdoc
62
+ files:
63
+ - lib/td-client.rb
64
+ - lib/td/client.rb
65
+ - lib/td/client/api.rb
66
+ - lib/td/client/model.rb
67
+ - lib/td/client/version.rb
68
+ - ChangeLog
69
+ - README.rdoc
70
+ has_rdoc: true
71
+ homepage:
72
+ licenses: []
73
+
74
+ post_install_message:
75
+ rdoc_options:
76
+ - --charset=UTF-8
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ hash: 3
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ hash: 3
94
+ segments:
95
+ - 0
96
+ version: "0"
97
+ requirements: []
98
+
99
+ rubyforge_project:
100
+ rubygems_version: 1.3.7
101
+ signing_key:
102
+ specification_version: 3
103
+ summary: Treasure Data API library for Ruby
104
+ test_files: []
105
+