turbot 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +36 -0
  3. data/bin/turbot +17 -0
  4. data/data/cacert.pem +3988 -0
  5. data/lib/turbot/auth.rb +315 -0
  6. data/lib/turbot/cli.rb +38 -0
  7. data/lib/turbot/client/cisaurus.rb +25 -0
  8. data/lib/turbot/client/pgbackups.rb +113 -0
  9. data/lib/turbot/client/rendezvous.rb +111 -0
  10. data/lib/turbot/client/ssl_endpoint.rb +25 -0
  11. data/lib/turbot/client/turbot_postgresql.rb +148 -0
  12. data/lib/turbot/client.rb +757 -0
  13. data/lib/turbot/command/auth.rb +85 -0
  14. data/lib/turbot/command/base.rb +192 -0
  15. data/lib/turbot/command/bots.rb +326 -0
  16. data/lib/turbot/command/config.rb +123 -0
  17. data/lib/turbot/command/help.rb +179 -0
  18. data/lib/turbot/command/keys.rb +115 -0
  19. data/lib/turbot/command/logs.rb +34 -0
  20. data/lib/turbot/command/ssl.rb +43 -0
  21. data/lib/turbot/command/status.rb +51 -0
  22. data/lib/turbot/command/update.rb +47 -0
  23. data/lib/turbot/command/version.rb +23 -0
  24. data/lib/turbot/command.rb +304 -0
  25. data/lib/turbot/deprecated/help.rb +38 -0
  26. data/lib/turbot/deprecated.rb +5 -0
  27. data/lib/turbot/distribution.rb +9 -0
  28. data/lib/turbot/errors.rb +28 -0
  29. data/lib/turbot/excon.rb +11 -0
  30. data/lib/turbot/helpers/log_displayer.rb +70 -0
  31. data/lib/turbot/helpers/pg_dump_restore.rb +115 -0
  32. data/lib/turbot/helpers/turbot_postgresql.rb +213 -0
  33. data/lib/turbot/helpers.rb +521 -0
  34. data/lib/turbot/plugin.rb +165 -0
  35. data/lib/turbot/updater.rb +171 -0
  36. data/lib/turbot/version.rb +3 -0
  37. data/lib/turbot.rb +19 -0
  38. data/lib/vendor/turbot/okjson.rb +598 -0
  39. data/spec/helper/legacy_help.rb +16 -0
  40. data/spec/helper/pg_dump_restore_spec.rb +67 -0
  41. data/spec/schemas/dummy_schema.json +12 -0
  42. data/spec/spec.opts +1 -0
  43. data/spec/spec_helper.rb +220 -0
  44. data/spec/support/display_message_matcher.rb +49 -0
  45. data/spec/support/dummy_api.rb +120 -0
  46. data/spec/support/openssl_mock_helper.rb +8 -0
  47. data/spec/support/organizations_mock_helper.rb +11 -0
  48. data/spec/turbot/auth_spec.rb +214 -0
  49. data/spec/turbot/client/pgbackups_spec.rb +43 -0
  50. data/spec/turbot/client/rendezvous_spec.rb +62 -0
  51. data/spec/turbot/client/ssl_endpoint_spec.rb +48 -0
  52. data/spec/turbot/client/turbot_postgresql_spec.rb +71 -0
  53. data/spec/turbot/client_spec.rb +548 -0
  54. data/spec/turbot/command/auth_spec.rb +38 -0
  55. data/spec/turbot/command/base_spec.rb +66 -0
  56. data/spec/turbot/command/bots_spec.rb +54 -0
  57. data/spec/turbot/command/config_spec.rb +143 -0
  58. data/spec/turbot/command/help_spec.rb +90 -0
  59. data/spec/turbot/command/keys_spec.rb +117 -0
  60. data/spec/turbot/command/logs_spec.rb +60 -0
  61. data/spec/turbot/command/status_spec.rb +48 -0
  62. data/spec/turbot/command/version_spec.rb +16 -0
  63. data/spec/turbot/command_spec.rb +131 -0
  64. data/spec/turbot/helpers/turbot_postgresql_spec.rb +181 -0
  65. data/spec/turbot/helpers_spec.rb +48 -0
  66. data/spec/turbot/plugin_spec.rb +172 -0
  67. data/spec/turbot/updater_spec.rb +44 -0
  68. data/templates/manifest.json +7 -0
  69. data/templates/scraper.py +5 -0
  70. data/templates/scraper.rb +6 -0
  71. metadata +199 -0
@@ -0,0 +1,85 @@
1
+ require "turbot/command/base"
2
+
3
+ # authentication (login, logout)
4
+ #
5
+ class Turbot::Command::Auth < Turbot::Command::Base
6
+
7
+ # auth
8
+ #
9
+ # Authenticate, display token and current user
10
+ def index
11
+ validate_arguments!
12
+
13
+ Turbot::Command::Help.new.send(:help_for_command, current_command)
14
+ end
15
+
16
+ # auth:login
17
+ #
18
+ # log in with your turbot credentials
19
+ #
20
+ #Example:
21
+ #
22
+ # $ turbot auth:login
23
+ # Enter your Turbot credentials:
24
+ # Email: email@example.com
25
+ # Password (typing will be hidden):
26
+ # Authentication successful.
27
+ #
28
+ def login
29
+ validate_arguments!
30
+
31
+ Turbot::Auth.login
32
+ display "Authentication successful."
33
+ end
34
+
35
+ alias_command "login", "auth:login"
36
+
37
+ # auth:logout
38
+ #
39
+ # clear local authentication credentials
40
+ #
41
+ #Example:
42
+ #
43
+ # $ turbot auth:logout
44
+ # Local credentials cleared.
45
+ #
46
+ def logout
47
+ validate_arguments!
48
+
49
+ Turbot::Auth.logout
50
+ display "Local credentials cleared."
51
+ end
52
+
53
+ alias_command "logout", "auth:logout"
54
+
55
+ # auth:token
56
+ #
57
+ # display your api token
58
+ #
59
+ #Example:
60
+ #
61
+ # $ turbot auth:token
62
+ # ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCD
63
+ #
64
+ def token
65
+ validate_arguments!
66
+
67
+ display Turbot::Auth.api_key
68
+ end
69
+
70
+ # auth:whoami
71
+ #
72
+ # display your turbot email address
73
+ #
74
+ #Example:
75
+ #
76
+ # $ turbot auth:whoami
77
+ # email@example.com
78
+ #
79
+ def whoami
80
+ validate_arguments!
81
+
82
+ display Turbot::Auth.user
83
+ end
84
+
85
+ end
@@ -0,0 +1,192 @@
1
+ require "fileutils"
2
+ require "turbot/auth"
3
+ require "turbot/client/rendezvous"
4
+ require "turbot/command"
5
+
6
+ class Turbot::Command::Base
7
+ include Turbot::Helpers
8
+
9
+ def self.namespace
10
+ self.to_s.split("::").last.downcase
11
+ end
12
+
13
+ attr_reader :args
14
+ attr_reader :options
15
+
16
+ def initialize(args=[], options={})
17
+ @args = args
18
+ @options = options
19
+ end
20
+
21
+ def bot
22
+ @bot ||= if options[:bot].is_a?(String)
23
+ options[:bot]
24
+ elsif ENV.has_key?('TURBOT_BOT')
25
+ ENV['TURBOT_BOT']
26
+ elsif bot_from_manifest = extract_bot_from_manifest(Dir.pwd)
27
+ bot_from_manifest
28
+ else
29
+ # raise instead of using error command to enable rescuing when bot is optional
30
+ raise Turbot::Command::CommandFailed.new("No bot specified.\nRun this command from a bot folder containing a `manifest.json`, or specify which bot to use with --bot BOT_ID.") unless options[:ignore_no_bot]
31
+ end
32
+ end
33
+
34
+ def api
35
+ Turbot::Auth.api
36
+ end
37
+
38
+ def turbot
39
+ Turbot::Auth.client
40
+ end
41
+
42
+ protected
43
+
44
+ def self.inherited(klass)
45
+ unless klass == Turbot::Command::Base
46
+ help = extract_help_from_caller(caller.first)
47
+
48
+ Turbot::Command.register_namespace(
49
+ :name => klass.namespace,
50
+ :description => help.first
51
+ )
52
+ end
53
+ end
54
+
55
+ def self.method_added(method)
56
+ return if self == Turbot::Command::Base
57
+ return if private_method_defined?(method)
58
+ return if protected_method_defined?(method)
59
+
60
+ help = extract_help_from_caller(caller.first)
61
+ resolved_method = (method.to_s == "index") ? nil : method.to_s
62
+ command = [ self.namespace, resolved_method ].compact.join(":")
63
+ banner = extract_banner(help) || command
64
+
65
+ Turbot::Command.register_command(
66
+ :klass => self,
67
+ :method => method,
68
+ :namespace => self.namespace,
69
+ :command => command,
70
+ :banner => banner.strip,
71
+ :help => help.join("\n"),
72
+ :summary => extract_summary(help),
73
+ :description => extract_description(help),
74
+ :options => extract_options(help)
75
+ )
76
+
77
+ alias_command command.gsub(/_/, '-'), command if command =~ /_/
78
+ end
79
+
80
+ def self.alias_command(new, old)
81
+ raise "no such command: #{old}" unless Turbot::Command.commands[old]
82
+ Turbot::Command.command_aliases[new] = old
83
+ end
84
+
85
+ def extract_bot
86
+ output_with_bang "Command::Base#extract_bot has been deprecated. Please use Command::Base#bot instead. #{caller.first}"
87
+ bot
88
+ end
89
+
90
+ #
91
+ # Parse the caller format and identify the file and line number as identified
92
+ # in : http://www.ruby-doc.org/core/classes/Kernel.html#M001397. This will
93
+ # look for a colon followed by a digit as the delimiter. The biggest
94
+ # complication is windows paths, which have a colon after the drive letter.
95
+ # This regex will match paths as anything from the beginning to a colon
96
+ # directly followed by a number (the line number).
97
+ #
98
+ # Examples of the caller format :
99
+ # * c:/Ruby192/lib/.../lib/turbot/command/addons.rb:8:in `<module:Command>'
100
+ # * c:/Ruby192/lib/.../turbot-2.0.1/lib/turbot/command/pg.rb:96:in `<class:Pg>'
101
+ # * /Users/ph7/...../xray-1.1/lib/xray/thread_dump_signal_handler.rb:9
102
+ #
103
+ def self.extract_help_from_caller(line)
104
+ # pull out of the caller the information for the file path and line number
105
+ if line =~ /^(.+?):(\d+)/
106
+ extract_help($1, $2)
107
+ else
108
+ raise("unable to extract help from caller: #{line}")
109
+ end
110
+ end
111
+
112
+ def self.extract_help(file, line_number)
113
+ buffer = []
114
+ lines = Turbot::Command.files[file]
115
+
116
+ (line_number.to_i-2).downto(0) do |i|
117
+ line = lines[i]
118
+ case line[0..0]
119
+ when ""
120
+ when "#"
121
+ buffer.unshift(line[1..-1])
122
+ else
123
+ break
124
+ end
125
+ end
126
+
127
+ buffer
128
+ end
129
+
130
+ def self.extract_banner(help)
131
+ help.first
132
+ end
133
+
134
+ def self.extract_summary(help)
135
+ extract_description(help).split("\n")[2].to_s.split("\n").first
136
+ end
137
+
138
+ def self.extract_description(help)
139
+ help.reject do |line|
140
+ line =~ /^\s+-(.+)#(.+)/
141
+ end.join("\n")
142
+ end
143
+
144
+ def self.extract_options(help)
145
+ help.select do |line|
146
+ line =~ /^\s+-(.+)#(.+)/
147
+ end.inject([]) do |options, line|
148
+ args = line.split('#', 2).first
149
+ args = args.split(/,\s*/).map {|arg| arg.strip}.sort.reverse
150
+ name = args.last.split(' ', 2).first[2..-1]
151
+ options << { :name => name, :args => args }
152
+ end
153
+ end
154
+
155
+ def current_command
156
+ Turbot::Command.current_command
157
+ end
158
+
159
+ def extract_option(key)
160
+ options[key.dup.gsub('-','_').to_sym]
161
+ end
162
+
163
+ def invalid_arguments
164
+ Turbot::Command.invalid_arguments
165
+ end
166
+
167
+ def shift_argument
168
+ Turbot::Command.shift_argument
169
+ end
170
+
171
+ def validate_arguments!
172
+ Turbot::Command.validate_arguments!
173
+ end
174
+
175
+ def extract_bot_from_manifest(dir)
176
+ begin
177
+ config = JSON.load(open("#{dir}/manifest.json").read)
178
+ config && config["bot_id"]
179
+ rescue Errno::ENOENT
180
+ end
181
+ end
182
+
183
+ def escape(value)
184
+ turbot.escape(value)
185
+ end
186
+ end
187
+
188
+ module Turbot::Command
189
+ unless const_defined?(:BaseWithApp)
190
+ BaseWithApp = Base
191
+ end
192
+ end
@@ -0,0 +1,326 @@
1
+ require "turbot/command/base"
2
+ require 'zip'
3
+ require 'json-schema'
4
+ require 'open3'
5
+ require 'base64'
6
+ require 'shellwords'
7
+
8
+ # manage bots (create, destroy)
9
+ #
10
+ class Turbot::Command::Bots < Turbot::Command::Base
11
+
12
+ # bots
13
+ #
14
+ # list your bots
15
+ #
16
+ #Example:
17
+ #
18
+ # $ turbot bots
19
+ # === My Bots
20
+ # example
21
+ # example2
22
+ #
23
+ def index
24
+ validate_arguments!
25
+ bots = api.get_bots
26
+ unless bots.empty?
27
+ styled_header("Bots")
28
+ styled_array(bots.map{|k,data| data['name']})
29
+ else
30
+ display("You have no bots.")
31
+ end
32
+ end
33
+
34
+ alias_command "list", "bots"
35
+
36
+ # bots:info
37
+ #
38
+ # show detailed bot information
39
+ #
40
+ # -s, --shell # output more shell friendly key/value pairs
41
+ #
42
+ #Examples:
43
+ #
44
+ # $ turbot bots:info
45
+ # === example
46
+ # Last run status: OK
47
+ # Last run ended: 2001/01/01
48
+ # ...
49
+ #
50
+ # $ turbot bots:info --shell
51
+ # last_run_status: OK
52
+ # last_run_ended: 2001/01/01
53
+ # ...
54
+ #
55
+ def info
56
+ validate_arguments!
57
+ bot_data = api.get_bot(bot)
58
+ unless options[:shell]
59
+ styled_header(bot_data["name"])
60
+ end
61
+
62
+ if options[:shell]
63
+ bot_data.keys.sort_by { |a| a.to_s }.each do |key|
64
+ hputs("#{key}=#{bot_data[key]}")
65
+ end
66
+ else
67
+ data = {}
68
+ if bot_data["last_run_status"]
69
+ data["Last run status"] = bot_data["last_run_status"]
70
+ end
71
+ if bot_data["last_run_ended"]
72
+ data["Last run ended"] = format_date(bot_data["last_run_ended"]) if bot_data["last_run_ended"]
73
+ end
74
+ data["Git URL"] = bot_data["git_url"]
75
+ data["Repo Size"] = format_bytes(bot_data["repo_size"]) if bot_data["repo_size"]
76
+ styled_hash(data)
77
+ end
78
+ end
79
+
80
+ alias_command "info", "bots:info"
81
+
82
+
83
+ # bots:generate --bot name_of_bot
84
+ #
85
+ # Generate stub code for a bot in specified language
86
+ #
87
+ # -l, --language LANGUAGE # language to generate (currently `ruby` (default) or `python`)
88
+
89
+ # $ turbot bots:generate --language=ruby --bot my_amazing_bot
90
+ # Created new bot template at my_amazing_bot!
91
+
92
+ def generate
93
+ validate_arguments!
94
+ language = options[:language] || "ruby"
95
+ puts "Generating #{language} codes..."
96
+ FileUtils.mkdir(bot)
97
+ case language
98
+ when "ruby"
99
+ scraper = "scraper.rb"
100
+ when "python"
101
+ scraper = "scraper.py"
102
+ end
103
+ manifest_template = File.expand_path("../../../../templates/manifest.json", __FILE__)
104
+ scraper_template = File.expand_path("../../../../templates/#{scraper}", __FILE__)
105
+ manifest = open(manifest_template).read.sub(/{{bot_id}}/, bot)
106
+ FileUtils.cp(scraper_template, "#{bot}/#{scraper}")
107
+ open("#{bot}/manifest.json", "w") do |f|
108
+ f.write(manifest)
109
+ end
110
+ puts "Created new bot template at #{bot}!"
111
+ end
112
+
113
+
114
+ # bots:push
115
+ #
116
+ # Push bot code to the turbot server. Must be run from a local bot checkout.
117
+ #
118
+ # $ turbot bots:push
119
+ # Creating example... done
120
+
121
+ def push
122
+ validate_arguments!
123
+
124
+ working_dir = Dir.pwd
125
+ manifest = parsed_manifest(working_dir)
126
+ #archive_file = File.join(working_dir, 'tmp', "#{manifest['bot_id']}.zip")
127
+ archive = Tempfile.new(bot)
128
+ archive_path = "#{archive.path}.zip"
129
+
130
+ Zip.continue_on_exists_proc = true
131
+ Zip::File.open(archive_path, Zip::File::CREATE) do |zipfile|
132
+ zipfile.add("manifest.json", manifest_path)
133
+ manifest['files'].each { |f| zipfile.add(f, File.join(working_dir,f)) }
134
+ end
135
+
136
+ File.open(archive_path) do |file|
137
+ params = {
138
+ "bot[archive]" => file,
139
+ "bot[manifest]" => manifest
140
+ }
141
+ api.post_bot(params)
142
+ end
143
+ end
144
+
145
+ alias_command "push", "bots:push"
146
+
147
+ # bots:validate
148
+ #
149
+ # Validate bot output against its schema
150
+ #
151
+ # $ heroku bots:validate
152
+ # Validating example... done
153
+
154
+ def validate
155
+ scraper_path = shift_argument || scraper_file(Dir.pwd)
156
+ validate_arguments!
157
+ config = parsed_manifest(Dir.pwd)
158
+ type = config["data_type"]
159
+
160
+ schema = get_schema(type)
161
+
162
+ if !schema || !File.exists?(schema)
163
+ error("No schema found for data_type: #{type}")
164
+ end
165
+
166
+ count = 0
167
+
168
+ run_scraper_each_line("#{scraper_path} #{bot}") do |line|
169
+ errors = ""
170
+ errors = JSON::Validator.fully_validate(
171
+ schema,
172
+ line,
173
+ {:errors_as_objects => true})
174
+
175
+ if !errors.empty?
176
+ error("LINE WITH ERROR: #{line}\n\nERRORS: #{errors}")
177
+ end
178
+ count += 1
179
+ end
180
+ puts "Validated #{count} records successfully!"
181
+ end
182
+
183
+ # bots:dump
184
+ #
185
+ # Execute bot locally (writes to STDOUT)
186
+ #
187
+ # $ heroku bots:dump
188
+ # {'foo': 'bar'}
189
+ # {'foo2': 'bar2'}
190
+
191
+ def dump
192
+ # This will need to be language-aware, eventually
193
+ scraper_path = shift_argument || scraper_file(Dir.pwd)
194
+ validate_arguments!
195
+ count = 0
196
+ run_scraper_each_line("#{scraper_path} #{bot}") do |line|
197
+ puts line
198
+ count += 1
199
+ end
200
+ end
201
+
202
+ # bots:single
203
+ #
204
+ # Execute bot in same way as OpenCorporates single-record update
205
+ #
206
+ # $ heroku bots:single
207
+ # Enter argument (as JSON object):
208
+ # {"id": "frob123"}
209
+ # {"id": "frob123", "stuff": "updated-data-for-this-record"}
210
+
211
+ def single
212
+ # This will need to be language-aware, eventually
213
+ scraper_path = shift_argument || scraper_file(Dir.pwd)
214
+ validate_arguments!
215
+ print 'Arguments (as JSON object, e.g. {"id":"ABC123"}: '
216
+ arg = ask
217
+ count = 0
218
+ run_scraper_each_line("#{scraper_path} #{bot} #{Shellwords.shellescape(arg)}") do |line|
219
+ raise "Your scraper returned more than one value!" if count > 1
220
+ puts line
221
+ count += 1
222
+ end
223
+ end
224
+
225
+
226
+ # bots:preview
227
+ #
228
+ # Send bot data to Angler for remote previewing / sharing
229
+ #
230
+ # Sending example to Angler... done
231
+ def preview
232
+ scraper_path = shift_argument || scraper_file(Dir.pwd)
233
+ validate_arguments!
234
+
235
+ bots = api.get_bots
236
+ raise "You have not pushed your bot" unless bots.include?(bot)
237
+
238
+ batch = []
239
+ count = 0
240
+ puts "Sending to angler... "
241
+ result = ""
242
+ run_scraper_each_line("#{scraper_path} #{bot}") do |line|
243
+ batch << JSON.parse(line)
244
+ spinner(count)
245
+ if count % 20 == 0
246
+ result = api.send_drafts_to_angler(bot, batch.to_json)
247
+ batch = []
248
+ end
249
+ count += 1
250
+ end
251
+ if !batch.empty?
252
+ result = api.send_drafts_to_angler(bot, batch.to_json)
253
+ end
254
+ puts "Sent #{count} records."
255
+ puts "View your records at #{JSON.parse(result)['url']}"
256
+ end
257
+
258
+ private
259
+
260
+ def spinner(p)
261
+ parts = "\|/-" * 2
262
+ print parts[p % parts.length] + "\r"
263
+ end
264
+
265
+ def run_scraper_each_line(scraper_path, options={})
266
+ case scraper_path
267
+ when /scraper.rb /
268
+ interpreter = "ruby"
269
+ when /scraper.py /
270
+ interpreter = "python"
271
+ else
272
+ raise "Unsupported file extension at #{scraper_path}"
273
+ end
274
+
275
+ command = "#{interpreter} #{scraper_path}"
276
+ Open3::popen3(command, options) do |_, stdout, stderr, wait_thread|
277
+ loop do
278
+ check_output_with_timeout(stdout)
279
+
280
+ begin
281
+ result = stdout.readline.strip
282
+ yield result unless result.empty?
283
+ # add run id and bot name
284
+ rescue EOFError
285
+ break
286
+ end
287
+ end
288
+ status = wait_thread.value.exitstatus
289
+ if status > 0
290
+ message = "Bot <#{command}> exited with status #{status}: #{stderr.read}"
291
+ raise RuntimeError.new(message)
292
+ end
293
+ end
294
+ end
295
+
296
+ def check_output_with_timeout(stdout, initial_interval = 10, timeout = 21600)
297
+ interval = initial_interval
298
+ loop do
299
+ reads, _, _ = IO.select([stdout], [], [], interval)
300
+ break if !reads.nil?
301
+ raise "Timeout! - could not read from external bot after #{timeout} seconds" if reads.nil? && interval > timeout
302
+ interval *= 2
303
+ end
304
+ end
305
+
306
+ def parsed_manifest(dir)
307
+ begin
308
+ JSON.parse(open(manifest_path).read)
309
+ rescue Errno::ENOENT
310
+ raise "This command must be run from a directory including `manifest.json`"
311
+ end
312
+ end
313
+
314
+ def scraper_file(dir)
315
+ Dir.glob("scraper*").first
316
+ end
317
+
318
+ def manifest_path
319
+ File.join(Dir.pwd, 'manifest.json')
320
+ end
321
+
322
+ def get_schema(type)
323
+ hyphenated_name = type.to_s.gsub("_", "-").gsub(" ", "-")
324
+ schema = File.expand_path("../../../../schema/schemas/#{hyphenated_name}-schema.json", __FILE__)
325
+ end
326
+ end
@@ -0,0 +1,123 @@
1
+ require "turbot/command/base"
2
+
3
+ # manage bot config vars
4
+ #
5
+ class Turbot::Command::Config < Turbot::Command::Base
6
+
7
+ # config
8
+ #
9
+ # display the config vars for an bot
10
+ #
11
+ # -s, --shell # output config vars in shell format
12
+ #
13
+ #Examples:
14
+ #
15
+ # $ turbot config
16
+ # A: one
17
+ # B: two
18
+ #
19
+ # $ turbot config --shell
20
+ # A=one
21
+ # B=two
22
+ #
23
+ def index
24
+ validate_arguments!
25
+
26
+ vars = api.get_config_vars(bot)
27
+ if vars.empty?
28
+ display("#{bot} has no config vars.")
29
+ else
30
+ vars.each {|key, value| vars[key] = value.to_s.strip}
31
+ if options[:shell]
32
+ vars.keys.sort.each do |key|
33
+ display(%{#{key}=#{vars[key]}})
34
+ end
35
+ else
36
+ styled_header("#{bot} Config Vars")
37
+ styled_hash(vars)
38
+ end
39
+ end
40
+ end
41
+
42
+ # config:set KEY1=VALUE1 [KEY2=VALUE2 ...]
43
+ #
44
+ # set one or more config vars
45
+ #
46
+ #Example:
47
+ #
48
+ # $ turbot config:set A=one
49
+ # Setting config vars and restarting example... done, v123
50
+ # A: one
51
+ #
52
+ # $ turbot config:set A=one B=two
53
+ # Setting config vars and restarting example... done, v123
54
+ # A: one
55
+ # B: two
56
+ #
57
+ def set
58
+ unless args.size > 0 and args.all? { |a| a.include?('=') }
59
+ error("Usage: turbot config:set KEY1=VALUE1 [KEY2=VALUE2 ...]\nMust specify KEY and VALUE to set.")
60
+ end
61
+
62
+ vars = args.inject({}) do |vars, arg|
63
+ key, value = arg.split('=', 2)
64
+ vars[key] = value
65
+ vars
66
+ end
67
+
68
+ action("Setting config vars and restarting #{bot}") do
69
+ api.put_config_vars(bot, vars)
70
+ end
71
+
72
+ vars.each {|key, value| vars[key] = value.to_s}
73
+ styled_hash(vars)
74
+ end
75
+
76
+ alias_command "config:add", "config:set"
77
+
78
+ # config:get KEY
79
+ #
80
+ # display a config value for an bot
81
+ #
82
+ #Examples:
83
+ #
84
+ # $ turbot config:get A
85
+ # one
86
+ #
87
+ def get
88
+ unless key = shift_argument
89
+ error("Usage: turbot config:get KEY\nMust specify KEY.")
90
+ end
91
+ validate_arguments!
92
+
93
+ vars = api.get_config_vars(bot)
94
+ key, value = vars.detect {|k,v| k == key}
95
+ display(value.to_s)
96
+ end
97
+
98
+ # config:unset KEY1 [KEY2 ...]
99
+ #
100
+ # unset one or more config vars
101
+ #
102
+ # $ turbot config:unset A
103
+ # Unsetting A and restarting example... done, v123
104
+ #
105
+ # $ turbot config:unset A B
106
+ # Unsetting A and restarting example... done, v123
107
+ # Unsetting B and restarting example... done, v124
108
+ #
109
+ def unset
110
+ if args.empty?
111
+ error("Usage: turbot config:unset KEY1 [KEY2 ...]\nMust specify KEY to unset.")
112
+ end
113
+
114
+ args.each do |key|
115
+ action("Unsetting #{key} and restarting #{bot}") do
116
+ api.delete_config_var(bot, key)
117
+ end
118
+ end
119
+ end
120
+
121
+ alias_command "config:remove", "config:unset"
122
+
123
+ end