turbot 0.0.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.
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