td 0.8.0 → 0.9.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.
@@ -3,79 +3,128 @@ module TreasureData
3
3
  module Command
4
4
  module List
5
5
 
6
+ class CommandOption < OptionParser
7
+ def initialize(name, args, description)
8
+ super()
9
+
10
+ @name = name
11
+ @description = description.to_s
12
+
13
+ if args.last.to_s =~ /_$/
14
+ @varlen = true
15
+ args.push args.pop.to_s[0..-2]+'...'
16
+ elsif args.last.to_s =~ /_\?$/
17
+ @varlen = true
18
+ args.push args.pop.to_s[0..-3]+'...?'
19
+ end
20
+
21
+ @req_args, opt_args = args.partition {|a| a.to_s !~ /\?$/ }
22
+ @opt_args = opt_args.map {|a| a.to_s[0..-2].to_sym }
23
+ @args = @req_args + @opt_args
24
+
25
+ @usage_args = "#{@name}"
26
+ @req_args.each {|a| @usage_args << " <#{a}>" }
27
+ @opt_args.each {|a| @usage_args << " [#{a}]" }
28
+
29
+ @has_options = false
30
+
31
+ self.summary_indent = " "
32
+
33
+ banner = "usage:\n"
34
+ banner << " $ #{File.basename($0)} #{@usage_args}\n"
35
+ banner << "\n"
36
+ banner << "description:\n"
37
+ @description.split("\n").each {|l|
38
+ banner << " #{l}\n"
39
+ }
40
+ self.banner = banner
41
+
42
+ @message = nil
43
+ end
44
+
45
+ attr_accessor :message
46
+
47
+ def message
48
+ @message || banner
49
+ end
50
+
51
+ def banner
52
+ s = super.dup
53
+ if @has_options
54
+ s << "\n"
55
+ s << "options:\n"
56
+ end
57
+ s << "\n"
58
+ s
59
+ end
60
+
61
+ def usage
62
+ "%-40s # %s" % [@usage_args, @description]
63
+ end
64
+
65
+ def cmd_parse(argv=ARGV)
66
+ parse!(argv)
67
+ if argv.length < @req_args.length || (!@varlen && argv.length > @args.length)
68
+ cmd_usage nil
69
+ end
70
+ if argv.length <= 1
71
+ return argv[0]
72
+ else
73
+ return argv
74
+ end
75
+ rescue
76
+ cmd_usage $!
77
+ end
78
+
79
+ def cmd_usage(msg=nil)
80
+ puts self.message
81
+ if msg
82
+ puts ""
83
+ puts "error: #{msg}"
84
+ end
85
+ exit 1
86
+ end
87
+
88
+ def group
89
+ name.split(':', 2).first
90
+ end
91
+
92
+ def on(*args)
93
+ @has_options = true
94
+ super
95
+ end
96
+
97
+ attr_reader :name
98
+ attr_reader :description
99
+ end
100
+
6
101
  LIST = []
7
- ALIASES = {}
102
+ COMMAND = {}
8
103
  GUESS = {}
104
+ HELP_EXCLUDE = ['help', 'account']
9
105
 
10
- def self.add_list(file, cmd, description)
11
- LIST << [cmd, file, description]
106
+ def self.add_list(name, args, description)
107
+ LIST << COMMAND[name] = CommandOption.new(name, args, description)
12
108
  end
13
109
 
14
110
  def self.add_alias(new_cmd, old_cmd)
15
- ALIASES[new_cmd] = old_cmd
16
- end
17
-
18
- def self.get_description(command)
19
- LIST.each {|cmd,file,description|
20
- if cmd == command
21
- return description
22
- end
23
- }
24
- nil
111
+ COMMAND[new_cmd] = COMMAND[old_cmd]
25
112
  end
26
113
 
27
114
  def self.add_guess(wrong, correct)
28
115
  GUESS[wrong] = correct
29
116
  end
30
117
 
31
- add_list 'list', 'help', 'Show usage of a command'
32
- add_list 'account', 'account', 'Setup a Treasure Data account'
33
- add_list 'server', 'server-status', 'Show status of the Treasure Data server'
34
- add_list 'database', 'show-databases', 'Show list of databases'
35
- add_list 'table', 'show-tables', 'Show list of tables'
36
- add_list 'database', 'create-database', 'Create a database'
37
- add_list 'table', 'create-log-table', 'Create a log table'
38
- #add_list 'table', 'create-item-table', 'Create a item table'
39
- add_list 'table', 'set-schema', 'Set a schema on a table'
40
- add_list 'table', 'describe-table', 'Describe information of a table'
41
- add_list 'database', 'drop-database', 'Delete a database'
42
- add_list 'table', 'drop-table', 'Delete a table'
43
- add_list 'query', 'query', 'Start a query'
44
- add_list 'query', 'job', 'Show status and result of a job'
45
- add_list 'query', 'show-jobs', 'Show list of jobs'
46
- add_list 'import', 'import', 'Import files to a table'
47
- add_list 'list', 'version', 'Show version'
48
-
49
- add_alias 'show-dbs', 'show-databases'
50
- add_alias 'databases', 'show-databases'
51
- add_alias 'dbs', 'show-databases'
52
- add_alias 'create-db', 'create-databases'
53
- add_alias 'drop-db', 'create-databases'
54
- add_alias 'tables', 'show-tables'
55
- add_alias 'table', 'describe-table'
56
- add_alias 'show-table', 'describe-table'
57
- add_alias 'delete-database', 'drop-database'
58
- add_alias 'delete-table', 'drop-table'
59
- add_alias 'jobs', 'show-jobs'
60
- add_alias 'update-schema', 'set-schema'
61
-
62
- add_guess 'create-table', 'create-log-table'
63
- add_guess 'drop-log-table', 'drop-table'
64
- #add_guess 'drop-item-table', 'drop-table'
65
- add_guess 'delete-log-table', 'drop-table'
66
- #add_guess 'delete-item-table', 'drop-table'
67
- add_guess 'show-job', 'job'
68
-
69
- def self.get_method(command)
70
- command = ALIASES[command] || command
71
- LIST.each {|cmd,file,description|
72
- if cmd == command
73
- require 'td/command/common'
74
- require "td/command/#{file}"
75
- name = command.gsub('-','_')
76
- return Object.new.extend(Command).method(name)
77
- end
78
- }
118
+ def self.get_method(name)
119
+ if op = COMMAND[name]
120
+ name = op.name
121
+ group, action = op.group
122
+ require 'td/command/common'
123
+ require "td/command/#{group}"
124
+ cmd = name.gsub(':', '_')
125
+ m = Object.new.extend(Command).method(cmd)
126
+ return Proc.new { m.call(op) }
127
+ end
79
128
  nil
80
129
  end
81
130
 
@@ -85,37 +134,117 @@ module List
85
134
  end
86
135
  end
87
136
 
88
- def self.help(indent)
89
- LIST.map {|cmd,file,description|
90
- if cmd != 'help'
91
- "#{indent}%-18s %s" % [cmd, description.split("\n").first]
92
- end
93
- }.join("\n")
137
+ def self.get_option(name)
138
+ COMMAND[name]
94
139
  end
95
- end
96
-
97
- def help
98
- op = cmd_opt 'help', :command
99
- cmd = op.cmd_parse
100
140
 
101
- ARGV.clear
102
- ARGV[0] = '--help'
141
+ def self.show_help(indent=' ')
142
+ before_group = nil
143
+ LIST.each {|op|
144
+ next if HELP_EXCLUDE.include?(op.name)
145
+ if before_group != op.group
146
+ before_group = op.group
147
+ puts ""
148
+ end
149
+ puts "#{indent}#{op.usage}"
150
+ }
151
+ end
103
152
 
104
- method = List.get_method(cmd)
105
- unless method
106
- $stderr.puts "'#{cmd}' is not a td command. Run '#{$prog}' to show the list."
107
- List.show_guess(cmd)
108
- exit 1
153
+ def self.get_group(group)
154
+ LIST.map {|op|
155
+ op.group == group
156
+ }
109
157
  end
110
158
 
111
- method.call
112
- end
159
+ def self.finishup
160
+ groups = {}
161
+ LIST.each {|op|
162
+ (groups[op.group] ||= []) << op
163
+ }
164
+ groups.each_pair {|group,ops|
165
+ if ops.size > 1 && xop = COMMAND[group].dup
166
+ msg = %[Additional commands, type "#{File.basename($0)} help COMMAND" for more details:\n\n]
167
+ ops.each {|op|
168
+ msg << %[ #{op.usage}\n]
169
+ }
170
+ msg << %[\n]
171
+ xop.message = msg
172
+ COMMAND[group] = xop
173
+ end
174
+ }
175
+ end
113
176
 
114
- def version
115
- require 'td/version'
116
- puts "td-#{TreasureData::VERSION}"
177
+ add_list 'db:list', %w[], 'Show list of tables in a database'
178
+ add_list 'db:show', %w[db], 'Describe a information of a database'
179
+ add_list 'db:create', %w[db], 'Create a database'
180
+ add_list 'db:delete', %w[db], 'Delete a database'
181
+
182
+ add_list 'table:list', %w[db?], 'Show list of tables'
183
+ add_list 'table:show', %w[db table], 'Describe a information of a table'
184
+ add_list 'table:create', %w[db table], 'Create a table'
185
+ add_list 'table:delete', %w[db table], 'Delete a table'
186
+ add_list 'table:import', %w[db table files_], 'Parse and import files to a table'
187
+
188
+ add_list 'schema:show', %w[db table], 'Show schema of a table'
189
+ add_list 'schema:set', %w[db table columns_?], 'Set new schema on a table'
190
+ add_list 'schema:add', %w[db table columns_], 'Add new columns to a table'
191
+ add_list 'schema:remove', %w[db table columns_], 'Remove columns from a table'
192
+
193
+ add_list 'query', %w[sql], 'Issue a query'
194
+
195
+ add_list 'job:show', %w[job_id], 'Show status and result of a job'
196
+ add_list 'job:list', %w[max?], 'Show list of jobs'
197
+ #add_list 'job:kill', %w[job_id], 'Kill a job'
198
+
199
+ add_list 'account', %w[], 'Setup a Treasure Data account'
200
+
201
+ add_list 'server:status', %w[], 'Show status of the Treasure Data server'
202
+
203
+ add_list 'help', %w[command], 'Show usage of a command'
204
+
205
+ # aliases
206
+ add_alias 'db', 'db:show'
207
+ add_alias 'dbs', 'db:list'
208
+
209
+ add_alias 'database:show', 'db:show'
210
+ add_alias 'database:list', 'db:list'
211
+ add_alias 'database:create', 'db:create'
212
+ add_alias 'database:delete', 'db:delete'
213
+ add_alias 'database', 'db:show'
214
+ add_alias 'databases', 'db:list'
215
+
216
+ add_alias 'table', 'table:show'
217
+ add_alias 'tables', 'table:list'
218
+
219
+ add_alias 'schema', 'schema:show'
220
+
221
+ add_alias 'job', 'job:show'
222
+ add_alias 'jobs', 'job:list'
223
+
224
+ # backward compatibility
225
+ add_alias 'show-databases', 'db:list'
226
+ add_alias 'show-dbs', 'db:list'
227
+ add_alias 'create-database', 'db:create'
228
+ add_alias 'create-db', 'db:create'
229
+ add_alias 'drop-database', 'db:delete'
230
+ add_alias 'drop-db', 'db:delete'
231
+ add_alias 'delete-database', 'db:delete'
232
+ add_alias 'delete-db', 'db:delete'
233
+ add_alias 'show-tables', 'table:list'
234
+ add_alias 'show-table', 'table:show'
235
+ add_alias 'create-log-table', 'table:create'
236
+ add_alias 'create-table', 'table:create'
237
+ add_alias 'drop-log-table', 'table:delete'
238
+ add_alias 'drop-table', 'table:delete'
239
+ add_alias 'delete-log-table', 'table:delete'
240
+ add_alias 'delete-table', 'table:delete'
241
+ add_guess 'show-job', 'job:show'
242
+ add_guess 'show-jobs', 'job:list'
243
+ add_guess 'server-status', 'server:status'
244
+ add_alias 'import', 'table:import'
245
+
246
+ finishup
117
247
  end
118
-
119
248
  end
120
249
  end
121
250
 
@@ -2,11 +2,7 @@
2
2
  module TreasureData
3
3
  module Command
4
4
 
5
- def query
6
- op = cmd_opt 'query', :sql
7
-
8
- op.banner << "\noptions:\n"
9
-
5
+ def query(op)
10
6
  db_name = nil
11
7
  wait = false
12
8
  output = nil
@@ -37,12 +33,12 @@ module Command
37
33
  exit 1
38
34
  end
39
35
 
40
- find_database(client, db_name)
36
+ get_database(client, db_name)
41
37
 
42
38
  job = client.query(db_name, sql)
43
39
 
44
40
  $stderr.puts "Job #{job.job_id} is started."
45
- $stderr.puts "Use '#{$prog} job #{job.job_id}' to show the status."
41
+ $stderr.puts "Use '#{$prog} job:show #{job.job_id}' to show the status."
46
42
  $stderr.puts "See #{job.url} to see the progress."
47
43
 
48
44
  if wait && !job.finished?
@@ -55,233 +51,7 @@ module Command
55
51
  end
56
52
  end
57
53
 
58
- def show_jobs
59
- op = cmd_opt 'show-jobs', :max?
60
-
61
- op.banner << "\noptions:\n"
62
-
63
- page = 0
64
- skip = 0
65
-
66
- op.on('-p', '--page PAGE', 'skip N pages', Integer) {|i|
67
- page = i
68
- }
69
- op.on('-s', '--skip N', 'skip N jobs', Integer) {|i|
70
- skip = i
71
- }
72
-
73
- max = op.cmd_parse
74
-
75
- max = (max || 20).to_i
76
-
77
- client = get_client
78
-
79
- if page
80
- skip += max * page
81
- end
82
- jobs = client.jobs(skip, skip+max-1)
83
-
84
- rows = []
85
- jobs.each {|job|
86
- start = job.start_at
87
- finish = job.end_at
88
- if start
89
- if !finish
90
- finish = Time.now.utc
91
- end
92
- e = finish.to_i - start.to_i
93
- elapsed = ''
94
- if e >= 3600
95
- elapsed << "#{e/3600}h "
96
- e %= 3600
97
- elapsed << "% 2dm " % (e/60)
98
- e %= 60
99
- elapsed << "% 2dsec" % e
100
- elsif e >= 60
101
- elapsed << "% 2dm " % (e/60)
102
- e %= 60
103
- elapsed << "% 2dsec" % e
104
- else
105
- elapsed << "% 2dsec" % e
106
- end
107
- else
108
- elapsed = ''
109
- end
110
- elapsed = "% 10s" % elapsed # right aligned
111
-
112
- rows << {:JobID => job.job_id, :Status => job.status, :Query => job.query.to_s, :Start => start, :Elapsed => elapsed}
113
- }
114
-
115
- puts cmd_render_table(rows, :fields => [:JobID, :Status, :Start, :Elapsed, :Query])
116
- end
117
-
118
- def job
119
- op = cmd_opt 'job', :job_id
120
-
121
- op.banner << "\noptions:\n"
122
-
123
- verbose = nil
124
- wait = false
125
- output = nil
126
- format = 'tsv'
127
-
128
- op.on('-v', '--verbose', 'show logs', TrueClass) {|b|
129
- verbose = b
130
- }
131
- op.on('-w', '--wait', 'wait for finishing the job', TrueClass) {|b|
132
- wait = b
133
- }
134
- op.on('-o', '--output PATH', 'write result to the file') {|s|
135
- output = s
136
- }
137
- op.on('-f', '--format FORMAT', 'format of the result to write to the file (tsv, csv, json or msgpack)') {|s|
138
- unless ['tsv', 'csv', 'json', 'msgpack'].include?(s)
139
- raise "Unknown format #{s.dump}. Supported format: tsv, csv, json, msgpack"
140
- end
141
- format = s
142
- }
143
-
144
- job_id = op.cmd_parse
145
-
146
- client = get_client
147
-
148
- job = client.job(job_id)
149
-
150
- puts "JobID : #{job.job_id}"
151
- puts "URL : #{job.url}"
152
- puts "Status : #{job.status}"
153
- puts "Query : #{job.query}"
154
-
155
- if wait && !job.finished?
156
- wait_job(job)
157
- if job.success?
158
- puts "Result :"
159
- show_result(job, output, format)
160
- end
161
-
162
- else
163
- if job.success?
164
- puts "Result :"
165
- show_result(job, output, format)
166
- end
167
-
168
- if verbose
169
- puts ""
170
- puts "cmdout:"
171
- job.debug['cmdout'].to_s.split("\n").each {|line|
172
- puts " "+line
173
- }
174
- puts ""
175
- puts "stderr:"
176
- job.debug['stderr'].to_s.split("\n").each {|line|
177
- puts " "+line
178
- }
179
- end
180
- end
181
-
182
- $stderr.puts "Use '-v' option to show detailed messages." unless verbose
183
- end
184
-
185
- private
186
- def wait_job(job)
187
- $stderr.puts "running..."
188
-
189
- cmdout_lines = 0
190
- stderr_lines = 0
191
-
192
- until job.finished?
193
- sleep 2
194
-
195
- job.update_status!
196
-
197
- cmdout = job.debug['cmdout'].to_s.split("\n")[cmdout_lines..-1] || []
198
- stderr = job.debug['stderr'].to_s.split("\n")[stderr_lines..-1] || []
199
- (cmdout + stderr).each {|line|
200
- puts " "+line
201
- }
202
- cmdout_lines += cmdout.size
203
- stderr_lines += stderr.size
204
- end
205
- end
206
-
207
- def show_result(job, output, format)
208
- if output
209
- write_result(job, output, format)
210
- puts "written to #{output} in #{format} format"
211
- else
212
- render_result(job)
213
- end
214
- end
215
-
216
- def write_result(job, output, format)
217
- case format
218
- when 'json'
219
- require 'json'
220
- first = true
221
- File.open(output, "w") {|f|
222
- f.write "["
223
- job.result_each {|row|
224
- if first
225
- first = false
226
- else
227
- f.write ","
228
- end
229
- f.write row.to_json
230
- }
231
- f.write "]"
232
- }
233
-
234
- when 'msgpack'
235
- File.open(output, "w") {|f|
236
- f.write job.result_format('msgpack')
237
- }
238
-
239
- when 'csv'
240
- require 'json'
241
- require 'csv'
242
- CSV.open(output, "w") {|writer|
243
- job.result_each {|row|
244
- writer << row.map {|col| col.is_a?(String) ? col.to_s : col.to_json }
245
- }
246
- }
247
-
248
- when 'tsv'
249
- require 'json'
250
- File.open(output, "w") {|f|
251
- job.result_each {|row|
252
- first = true
253
- row.each {|col|
254
- if first
255
- first = false
256
- else
257
- f.write "\t"
258
- end
259
- f.write col.is_a?(String) ? col.to_s : col.to_json
260
- }
261
- f.write "\n"
262
- }
263
- }
264
-
265
- else
266
- raise "Unknown format #{format.inspect}"
267
- end
268
- end
269
-
270
- def render_result(job)
271
- require 'json'
272
- rows = []
273
- job.result_each {|row|
274
- # TODO limit number of rows to show
275
- rows << row.map {|v|
276
- if v.is_a?(String)
277
- v.to_s
278
- else
279
- v.to_json
280
- end
281
- }
282
- }
283
- puts cmd_render_table(rows, :max_width=>10000)
284
- end
54
+ require 'td/command/job' # wait_job
285
55
  end
286
56
  end
287
57