td 0.7.1 → 0.7.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/ChangeLog CHANGED
@@ -1,10 +1,17 @@
1
1
 
2
+ == 2011-08-15 version 0.7.2
3
+
4
+ * Supports TD_API_KEY and TD_CONFIG_PATH environment variable
5
+ * query, show-job: supports --output and --format options
6
+ * show-jobs: supports --page, --skip, --from and --around options
7
+
8
+
2
9
  == 2011-08-15 version 0.7.1
3
10
 
4
11
  * Validate name of a database/table on create-database/create-log-table
5
12
  subcommands
6
- * Uses /v3/job/result?format=msgpack API to get result of a job
7
13
  * -d, --database DB_NAME option is now required on query subcommand
14
+ * Uses /v3/job/result?format=msgpack API to get result of a job
8
15
  * API server can be changed using TD_API_SERVER=HOST:PORT environment variable
9
16
 
10
17
 
data/lib/td/api.rb CHANGED
@@ -130,15 +130,20 @@ class API
130
130
  @iface.job_result(job_id)
131
131
  end
132
132
 
133
+ # => result:String
134
+ def job_result_format(job_id, format)
135
+ @iface.job_result_format(job_id, format)
136
+ end
137
+
133
138
  # => nil
134
139
  def job_result_each(job_id, &block)
135
140
  @iface.job_result_each(job_id, &block)
136
141
  end
137
142
 
138
143
  # => time:Flaot
139
- def import(db_name, table_name, format, stream, stream_size=stream.lstat.size)
144
+ def import(db_name, table_name, format, stream, stream_size=stream.lstat.size)
140
145
  @iface.import(db_name, table_name, format, stream, stream_size)
141
- end
146
+ end
142
147
 
143
148
  def self.validate_database_name(name)
144
149
  name = name.to_s
@@ -300,11 +305,16 @@ class Job < APIObject
300
305
  @result
301
306
  end
302
307
 
308
+ def result_format(format)
309
+ return nil unless finished?
310
+ @api.job_result_format(@job_id, format)
311
+ end
312
+
303
313
  def result_each(&block)
304
314
  if @result
305
315
  @result.each(&block)
306
316
  else
307
- @api.job_result_each(&block)
317
+ @api.job_result_each(@job_id, &block)
308
318
  end
309
319
  nil
310
320
  end
@@ -323,10 +333,12 @@ class Job < APIObject
323
333
  end
324
334
 
325
335
  def success?
336
+ update_status! unless @status
326
337
  @status == "success"
327
338
  end
328
339
 
329
340
  def error?
341
+ update_status! unless @status
330
342
  @status == "error"
331
343
  end
332
344
 
data/lib/td/api_iface.rb CHANGED
@@ -161,6 +161,15 @@ class APIInterface
161
161
  return result
162
162
  end
163
163
 
164
+ def job_result_format(job_id, format)
165
+ # TODO chunked encoding
166
+ code, body, res = get("/v3/job/result/#{e job_id}", {'format'=>format})
167
+ if code != "200"
168
+ raise_error("Get job result failed", res)
169
+ end
170
+ return body
171
+ end
172
+
164
173
  def job_result_each(job_id, &block)
165
174
  # TODO chunked encoding
166
175
  require 'msgpack'
@@ -17,7 +17,7 @@ module Command
17
17
  require 'td/config'
18
18
  conf = nil
19
19
  begin
20
- conf = Config.read($TRD_CONFIG_PATH)
20
+ conf = Config.read
21
21
  rescue ConfigError
22
22
  end
23
23
  if conf && conf['account.user']
@@ -74,7 +74,7 @@ module Command
74
74
  conf ||= Config.new
75
75
  conf["account.user"] = user_name
76
76
  conf["account.apikey"] = api.apikey
77
- conf.save($TRD_CONFIG_PATH)
77
+ conf.save
78
78
 
79
79
  $stderr.puts "Use '#{$prog} create-database <db_name>' to create a database."
80
80
  end
@@ -56,13 +56,8 @@ EOF
56
56
  op
57
57
  end
58
58
 
59
- def cmd_config
60
- require 'td/config'
61
- Config.read($TRD_CONFIG_PATH)
62
- end
63
-
64
- def cmd_api(conf)
65
- apikey = conf['account.apikey']
59
+ def cmd_api
60
+ apikey = Config.apikey
66
61
  unless apikey
67
62
  raise ConfigError, "Account is not configured."
68
63
  end
@@ -6,8 +6,7 @@ module Command
6
6
  op = cmd_opt 'create-database', :db_name
7
7
  db_name = op.cmd_parse
8
8
 
9
- conf = cmd_config
10
- api = cmd_api(conf)
9
+ api = cmd_api
11
10
 
12
11
  API.validate_database_name(db_name)
13
12
 
@@ -34,8 +33,7 @@ module Command
34
33
 
35
34
  db_name = op.cmd_parse
36
35
 
37
- conf = cmd_config
38
- api = cmd_api(conf)
36
+ api = cmd_api
39
37
 
40
38
  begin
41
39
  db = api.database(db_name)
@@ -58,8 +56,7 @@ module Command
58
56
  op = cmd_opt 'show-databases'
59
57
  op.cmd_parse
60
58
 
61
- conf = cmd_config
62
- api = cmd_api(conf)
59
+ api = cmd_api
63
60
 
64
61
  dbs = api.databases
65
62
 
@@ -56,8 +56,7 @@ module Command
56
56
 
57
57
  db_name, table_name, *paths = op.cmd_parse
58
58
 
59
- conf = cmd_config
60
- api = cmd_api(conf)
59
+ api = cmd_api
61
60
 
62
61
  case format
63
62
  when 'json', 'msgpack'
@@ -8,19 +8,29 @@ module Command
8
8
  op.banner << "\noptions:\n"
9
9
 
10
10
  db_name = nil
11
+ wait = false
12
+ output = nil
13
+ format = 'tsv'
14
+
11
15
  op.on('-d', '--database DB_NAME', 'use the database (required)') {|s|
12
16
  db_name = s
13
17
  }
14
-
15
- wait = false
16
18
  op.on('-w', '--wait', 'wait for finishing the job', TrueClass) {|b|
17
19
  wait = b
18
20
  }
21
+ op.on('-o', '--output PATH', 'write result to the file') {|s|
22
+ output = s
23
+ }
24
+ op.on('-f', '--format FORMAT', 'format of the result to write to the file (tsv, csv, json or msgpack)') {|s|
25
+ unless ['tsv', 'csv', 'json', 'msgpack'].include?(s)
26
+ raise "Unknown format #{s.dump}. Supported format: tsv, csv, json, msgpack"
27
+ end
28
+ format = s
29
+ }
19
30
 
20
31
  sql = op.cmd_parse
21
32
 
22
- conf = cmd_config
23
- api = cmd_api(conf)
33
+ api = cmd_api
24
34
 
25
35
  unless db_name
26
36
  $stderr.puts "-d, --database DB_NAME option is required."
@@ -38,22 +48,56 @@ module Command
38
48
  if wait && !job.finished?
39
49
  wait_job(job)
40
50
  puts "Status : #{job.status}"
41
- puts "Result :"
42
- puts render_result(job.result)
51
+ if job.success?
52
+ puts "Result :"
53
+ show_result(job, output, format)
54
+ end
43
55
  end
44
56
  end
45
57
 
46
58
  def show_jobs
47
- op = cmd_opt 'show-jobs', :max?, :from?
48
- max, from = op.cmd_parse
59
+ op = cmd_opt 'show-jobs', :max?
60
+
61
+ op.banner << "\noptions:\n"
62
+
63
+ page = 0
64
+ skip = 0
65
+ from = nil
66
+ around = nil
67
+
68
+ op.on('-p', '--page PAGE', 'skip N pages', Integer) {|i|
69
+ page = i
70
+ }
71
+ op.on('-s', '--skip N', 'skip N jobs', Integer) {|i|
72
+ skip = i
73
+ }
74
+ op.on('-f', '--from JOB_ID', 'show jobs from the id', Integer) {|i|
75
+ from = i
76
+ }
77
+ op.on('-a', '--around JOB_ID', 'show jobs around the id', Integer) {|i|
78
+ around = i
79
+ }
80
+
81
+ max = op.cmd_parse
49
82
 
50
83
  max = (max || 20).to_i
51
- from = (from || 0).to_i
52
84
 
53
- conf = cmd_config
54
- api = cmd_api(conf)
85
+ api = cmd_api
55
86
 
56
- jobs = api.jobs(from, from+max-1)
87
+ if from || around
88
+ jobs = api.jobs(0, 1)
89
+ if last = jobs[0]
90
+ if from
91
+ skip += last.job_id.to_i - from - (max-1)
92
+ else
93
+ skip += last.job_id.to_i - around - (max-1) + (max-1)/2
94
+ end
95
+ end
96
+ end
97
+ if page
98
+ skip += max * page
99
+ end
100
+ jobs = api.jobs(skip, skip+max-1)
57
101
 
58
102
  rows = []
59
103
  jobs.each {|job|
@@ -95,19 +139,29 @@ module Command
95
139
  op.banner << "\noptions:\n"
96
140
 
97
141
  verbose = nil
142
+ wait = false
143
+ output = nil
144
+ format = 'tsv'
145
+
98
146
  op.on('-v', '--verbose', 'show logs', TrueClass) {|b|
99
147
  verbose = b
100
148
  }
101
-
102
- wait = false
103
149
  op.on('-w', '--wait', 'wait for finishing the job', TrueClass) {|b|
104
150
  wait = b
105
151
  }
152
+ op.on('-o', '--output PATH', 'write result to the file') {|s|
153
+ output = s
154
+ }
155
+ op.on('-f', '--format FORMAT', 'format of the result to write to the file (tsv, csv, json or msgpack)') {|s|
156
+ unless ['tsv', 'csv', 'json', 'msgpack'].include?(s)
157
+ raise "Unknown format #{s.dump}. Supported format: tsv, csv, json, msgpack"
158
+ end
159
+ format = s
160
+ }
106
161
 
107
162
  job_id = op.cmd_parse
108
163
 
109
- conf = cmd_config
110
- api = cmd_api(conf)
164
+ api = cmd_api
111
165
 
112
166
  job = api.job(job_id)
113
167
 
@@ -118,13 +172,15 @@ module Command
118
172
 
119
173
  if wait && !job.finished?
120
174
  wait_job(job)
121
- puts "Result :"
122
- puts render_result(job.result)
175
+ if job.success?
176
+ puts "Result :"
177
+ show_result(job, output, format)
178
+ end
123
179
 
124
180
  else
125
- if job.finished?
181
+ if job.success?
126
182
  puts "Result :"
127
- puts render_result(job.result)
183
+ show_result(job, output, format)
128
184
  end
129
185
 
130
186
  if verbose
@@ -166,10 +222,75 @@ module Command
166
222
  end
167
223
  end
168
224
 
169
- def render_result(result)
225
+ def show_result(job, output, format)
226
+ if output
227
+ write_result(job, output, format)
228
+ puts "written to #{output} in #{format} format"
229
+ else
230
+ render_result(job)
231
+ end
232
+ end
233
+
234
+ def write_result(job, output, format)
235
+ case format
236
+ when 'json'
237
+ require 'json'
238
+ first = true
239
+ File.open(output, "w") {|f|
240
+ f.write "["
241
+ job.result_each {|row|
242
+ if first
243
+ first = false
244
+ else
245
+ f.write ","
246
+ end
247
+ f.write row.to_json
248
+ }
249
+ f.write "]"
250
+ }
251
+
252
+ when 'msgpack'
253
+ File.open(output, "w") {|f|
254
+ f.write job.result_format('msgpack')
255
+ }
256
+
257
+ when 'csv'
258
+ require 'json'
259
+ require 'csv'
260
+ CSV.open(output, "w") {|writer|
261
+ job.result_each {|row|
262
+ writer << row.map {|col| col.is_a?(String) ? col.to_s : col.to_json }
263
+ }
264
+ }
265
+
266
+ when 'tsv'
267
+ require 'json'
268
+ File.open(output, "w") {|f|
269
+ job.result_each {|row|
270
+ first = true
271
+ row.each {|col|
272
+ if first
273
+ first = false
274
+ else
275
+ f.write "\t"
276
+ end
277
+ f.write col.is_a?(String) ? col.to_s : col.to_json
278
+ }
279
+ f.write "\n"
280
+ }
281
+ }
282
+
283
+ else
284
+ raise "Unknown format #{format.inspect}"
285
+ end
286
+ end
287
+
288
+ def render_result(job)
170
289
  require 'json'
171
- rows = result.map {|row|
172
- row.map {|v|
290
+ rows = []
291
+ job.result_each {|row|
292
+ # TODO limit number of rows to show
293
+ rows << row.map {|v|
173
294
  if v.is_a?(String)
174
295
  v.to_s
175
296
  else
@@ -3,8 +3,7 @@ module TD
3
3
  module Command
4
4
 
5
5
  def create_table_type(type, db_name, table_name)
6
- conf = cmd_config
7
- api = cmd_api(conf)
6
+ api = cmd_api
8
7
 
9
8
  API.validate_table_name(table_name)
10
9
 
@@ -43,8 +42,7 @@ module Command
43
42
  op = cmd_opt 'drop-table', :db_name, :table_name
44
43
  db_name, table_name = op.cmd_parse
45
44
 
46
- conf = cmd_config
47
- api = cmd_api(conf)
45
+ api = cmd_api
48
46
 
49
47
  begin
50
48
  api.delete_table(db_name, table_name)
@@ -62,8 +60,7 @@ module Command
62
60
  op = cmd_opt 'show-tables', :db_name?
63
61
  db_name = op.cmd_parse
64
62
 
65
- conf = cmd_config
66
- api = cmd_api(conf)
63
+ api = cmd_api
67
64
 
68
65
  if db_name
69
66
  db = find_database(api, db_name)
data/lib/td/command/td.rb CHANGED
@@ -13,33 +13,38 @@ EOF
13
13
  op.summary_indent = " "
14
14
 
15
15
  (class<<self;self;end).module_eval do
16
- define_method(:usage) do |errmsg|
17
- require 'td/command/list'
18
- puts op.to_s
19
- puts ""
20
- puts "commands:"
21
- puts TD::Command::List.help(op.summary_indent)
22
- puts ""
23
- puts "Type 'td help COMMAND' for more information on a specific command."
24
- if errmsg
25
- puts "error: #{errmsg}"
26
- exit 1
27
- else
28
- exit 0
29
- end
30
- end
16
+ define_method(:usage) do |errmsg|
17
+ require 'td/command/list'
18
+ puts op.to_s
19
+ puts ""
20
+ puts "commands:"
21
+ puts TD::Command::List.help(op.summary_indent)
22
+ puts ""
23
+ puts "Type 'td help COMMAND' for more information on a specific command."
24
+ if errmsg
25
+ puts "error: #{errmsg}"
26
+ exit 1
27
+ else
28
+ exit 0
29
+ end
30
+ end
31
31
  end
32
32
 
33
- config_path = File.join(ENV['HOME'], '.td', 'td.conf')
33
+ config_path = nil
34
+ apikey = nil
34
35
  $verbose = false
35
36
  #$debug = false
36
37
 
37
38
  op.on('-c', '--config PATH', "path to config file (~/.td/td.conf)") {|s|
38
- config_path = s
39
+ config_path = s
40
+ }
41
+
42
+ op.on('-k', '--apikey KEY', "use this API key instead of reading the config file") {|s|
43
+ apikey = s
39
44
  }
40
45
 
41
46
  op.on('-v', '--verbose', "verbose mode", TrueClass) {|b|
42
- $verbose = b
47
+ $verbose = b
43
48
  }
44
49
 
45
50
  #op.on('-d', '--debug', "debug mode", TrueClass) {|b|
@@ -47,30 +52,37 @@ op.on('-v', '--verbose', "verbose mode", TrueClass) {|b|
47
52
  #}
48
53
 
49
54
  begin
50
- op.order!(ARGV)
51
- usage nil if ARGV.empty?
52
- cmd = ARGV.shift
53
- $TRD_CONFIG_PATH = config_path
55
+ op.order!(ARGV)
56
+ usage nil if ARGV.empty?
57
+ cmd = ARGV.shift
58
+
59
+ require 'td/config'
60
+ if config_path
61
+ TD::Config.path = config_path
62
+ end
63
+ if apikey
64
+ TD::Config.apikey = apikey
65
+ end
54
66
  rescue
55
- usage $!.to_s
67
+ usage $!.to_s
56
68
  end
57
69
 
58
70
  require 'td/command/list'
59
71
 
60
72
  method = TD::Command::List.get_method(cmd)
61
73
  unless method
62
- $stderr.puts "'#{cmd}' is not a td command. Run '#{$prog}' to show the list."
74
+ $stderr.puts "'#{cmd}' is not a td command. Run '#{$prog}' to show the list."
63
75
  TD::Command::List.show_guess(cmd)
64
- exit 1
76
+ exit 1
65
77
  end
66
78
 
67
79
  require 'td/error'
68
80
 
69
81
  begin
70
- method.call
82
+ method.call
71
83
  rescue TD::ConfigError
72
- $stderr.puts "TreasureData account is not configured yet."
73
- $stderr.puts "Run '#{$prog} account' first."
84
+ $stderr.puts "TreasureData account is not configured yet."
85
+ $stderr.puts "Run '#{$prog} account' first."
74
86
  rescue
75
87
  $stderr.puts "error #{$!.class}: backtrace:"
76
88
  $!.backtrace.each {|b|
data/lib/td/config.rb CHANGED
@@ -4,75 +4,96 @@ module TD
4
4
 
5
5
 
6
6
  class Config
7
- def initialize
8
- @path = nil
9
- @conf = {} # section.key = val
10
- end
11
-
12
- def self.read(path, create=false)
13
- new.read(path)
14
- end
15
-
16
- def [](cate_key)
17
- @conf[cate_key]
18
- end
19
-
20
- def []=(cate_key, val)
21
- @conf[cate_key] = val
22
- end
23
-
24
- def save(path=@path)
25
- @path = path
26
- write
27
- end
28
-
29
- def read(path=@path)
30
- @path = path
31
- begin
32
- data = File.read(@path)
33
- rescue
34
- e = ConfigNotFoundError.new($!.to_s)
7
+ def initialize
8
+ @path = nil
9
+ @conf = {} # section.key = val
10
+ end
11
+
12
+ def self.read(path=Config.path, create=false)
13
+ new.read(path)
14
+ end
15
+
16
+ def [](cate_key)
17
+ @conf[cate_key]
18
+ end
19
+
20
+ def []=(cate_key, val)
21
+ @conf[cate_key] = val
22
+ end
23
+
24
+ def read(path=@path)
25
+ @path = path
26
+ begin
27
+ data = File.read(@path)
28
+ rescue
29
+ e = ConfigNotFoundError.new($!.to_s)
35
30
  e.set_backtrace($!.backtrace)
36
31
  raise e
37
- end
38
-
39
- section = ""
40
-
41
- data.each_line {|line|
42
- line.strip!
43
- case line
44
- when /^#/
45
- next
46
- when /\[(.+)\]/
47
- section = $~[1]
48
- when /^(\w+)\s*=\s*(.+?)\s*$/
49
- key = $~[1]
50
- val = $~[2]
51
- @conf["#{section}.#{key}"] = val
52
- else
53
- raise ConfigParseError, "invalid config line '#{line}' at #{@path}"
54
- end
55
- }
56
-
57
- self
58
- end
59
-
60
- def write
61
- require 'fileutils'
62
- FileUtils.mkdir_p File.dirname(@path)
63
- File.open(@path, "w") {|f|
64
- @conf.keys.map {|cate_key|
65
- cate_key.split('.',2)
66
- }.zip(@conf.values).group_by {|(section,key),val|
67
- section
68
- }.each {|section,cate_key_vals|
69
- f.puts "[#{section}]"
70
- cate_key_vals.each {|(section,key),val|
71
- f.puts " #{key} = #{val}"
72
- }
73
- }
74
- }
75
- end
32
+ end
33
+
34
+ section = ""
35
+
36
+ data.each_line {|line|
37
+ line.strip!
38
+ case line
39
+ when /^#/
40
+ next
41
+ when /\[(.+)\]/
42
+ section = $~[1]
43
+ when /^(\w+)\s*=\s*(.+?)\s*$/
44
+ key = $~[1]
45
+ val = $~[2]
46
+ @conf["#{section}.#{key}"] = val
47
+ else
48
+ raise ConfigParseError, "invalid config line '#{line}' at #{@path}"
49
+ end
50
+ }
51
+
52
+ self
53
+ end
54
+
55
+ def save(path=@path||Config.path)
56
+ @path = path
57
+ write
58
+ end
59
+
60
+ private
61
+ def write
62
+ require 'fileutils'
63
+ FileUtils.mkdir_p File.dirname(@path)
64
+ File.open(@path, "w") {|f|
65
+ @conf.keys.map {|cate_key|
66
+ cate_key.split('.',2)
67
+ }.zip(@conf.values).group_by {|(section,key),val|
68
+ section
69
+ }.each {|section,cate_key_vals|
70
+ f.puts "[#{section}]"
71
+ cate_key_vals.each {|(section,key),val|
72
+ f.puts " #{key} = #{val}"
73
+ }
74
+ }
75
+ }
76
+ end
77
+
78
+ @@path = ENV['TD_CONFIG_PATH'] || File.join(ENV['HOME'], '.td', 'td.conf')
79
+ @@apikey = ENV['TD_API_KEY']
80
+ @@apikey = nil if @@apikey == ""
81
+
82
+ def self.path
83
+ @@path
84
+ end
85
+
86
+ def self.path=(path)
87
+ @@path = path
88
+ end
89
+
90
+ def self.apikey
91
+ @@apikey || Config.read['account.apikey']
92
+ end
93
+
94
+ def self.apikey=(apikey)
95
+ @@apikey = apikey
96
+ end
76
97
  end
77
98
 
78
99
 
data/lib/td/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module TD
2
2
 
3
- VERSION = '0.7.1'
3
+ VERSION = '0.7.2'
4
4
 
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: td
3
3
  version: !ruby/object:Gem::Version
4
- hash: 1
4
+ hash: 7
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 7
9
- - 1
10
- version: 0.7.1
9
+ - 2
10
+ version: 0.7.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Sadayuki Furuhashi