td-client 0.8.0

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