td 0.7.1 → 0.7.2

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