rpw 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7da44e397174a310bc8ba3ed4c2ecb28f2811cf30275a2cb7cd3ec85029ab99a
4
- data.tar.gz: c096b4539f5570d6994eed51ec758007e08f75632e0e26f7d13e15b531633995
3
+ metadata.gz: '05387043187fc7a9e95503872a907814efe4bf14cd20d42420a0754b6792b8be'
4
+ data.tar.gz: df48bbf5d6cdb4c9c23d26bdaa647692271abe5a51aa262c930288195b1ebd88
5
5
  SHA512:
6
- metadata.gz: 3557e0688d5141ae96bcfed87ced6539059bbbd0517a3f2bf85910c5b13b99e153d940fbf56d9bd86bf2cf23525f32a9c848d7fa3ee2d58ae664db806854e93b
7
- data.tar.gz: 0e804e064e64cd66c703238e00d80f75a35871bf724c08203594d4eec1c94db5815d5ac2bacc11b743e008eb1aeec2c39f2b240fefac87aebe2c005bb194ae19
6
+ metadata.gz: 004e0811e1b491e835baa74fb238df98af7580ffc730614332a9cd432d99d8567e83bcf8c332e56f68f9b99a3b3af1dedb5c8b02e5dc5a530c949727ffe1d7e6
7
+ data.tar.gz: 25f7bd4c5de94368fe9c306252b6dc4c2c5b2743284450550812cfece422dc1b2feff9a4a76f1836a03d592e87899060285d9f4e8f1865323c0316b8f472961b
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rpw (0.0.2)
4
+ rpw (0.0.3)
5
5
  thor
6
+ thor-hollaback
6
7
  typhoeus
7
8
 
8
9
  GEM
@@ -12,6 +13,7 @@ GEM
12
13
  ethon (0.12.0)
13
14
  ffi (>= 1.3.0)
14
15
  ffi (1.13.1)
16
+ hollaback (0.1.0)
15
17
  minitest (5.14.2)
16
18
  parallel (1.19.2)
17
19
  parser (2.7.1.5)
@@ -39,6 +41,9 @@ GEM
39
41
  rubocop (= 0.92)
40
42
  rubocop-performance (= 1.8.1)
41
43
  thor (1.0.1)
44
+ thor-hollaback (0.2.0)
45
+ hollaback (~> 0.1.0)
46
+ thor (>= 0.19.1)
42
47
  typhoeus (1.4.0)
43
48
  ethon (>= 0.9.0)
44
49
  unicode-display_width (1.7.0)
data/HISTORY.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.0.3
2
+
3
+ * beta
4
+
1
5
  ## 0.0.2
2
6
 
3
7
  * first command-complete version
data/README.md CHANGED
@@ -1,2 +1,4 @@
1
+ # The Rails Performance Workshop Client
2
+
1
3
  This is `rpw`, the CLI client for the Rails Performance Workshop.
2
4
  Copyright (C) 2020 The Speedshop Ltd. Co.
data/exe/rpw CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require "thor"
4
+ require "thor/hollaback"
4
5
  require_relative "../lib/rpw"
5
6
 
6
7
  module RPW
@@ -12,14 +13,30 @@ module RPW
12
13
  def self.subcommand_prefix
13
14
  name.gsub(%r{.*::}, "").gsub(%r{^[A-Z]}) { |match| match[0].downcase }.gsub(%r{[A-Z]}) { |match| "-#{match[0].downcase}" }
14
15
  end
16
+
17
+ no_commands do
18
+ def client
19
+ @client ||= RPW::Client.new
20
+ end
21
+ end
22
+ end
23
+
24
+ class Key < SubCommandBase
25
+ desc "register [EMAIL_ADDRESS]", "Change email registered with Speedshop. One-time only."
26
+ def register(email)
27
+ client.register_email(email)
28
+ say "License registered. You should receive a Slack invite soon."
29
+ end
15
30
  end
16
31
 
17
32
  class Lesson < SubCommandBase
18
- desc "next", "Proceed to the next lesson of the workshop"
33
+ class_before :exit_with_no_key
19
34
 
35
+ desc "next", "Proceed to the next lesson of the workshop"
36
+ option :open
20
37
  def next
21
38
  say "Proceeding to next lesson..."
22
- client.next
39
+ client.next(options[:open])
23
40
  end
24
41
 
25
42
  desc "complete", "Mark the current lesson as complete"
@@ -44,20 +61,22 @@ module RPW
44
61
  client.download(content)
45
62
  end
46
63
 
47
- desc "show [CONTENT]", "Show any workshop lesson"
48
-
49
- def show(content)
50
- client.show(content)
64
+ desc "show [CONTENT]", "Show any workshop lesson, shows current lesson w/no arguments"
65
+ option :open
66
+ def show(content = :current)
67
+ client.show(content, options[:open])
51
68
  end
52
69
 
53
70
  private
54
71
 
55
- def client
56
- @client ||= RPW::Client.new
72
+ def exit_with_no_key
73
+ exit(1) unless client.setup?
57
74
  end
58
75
  end
59
76
 
60
77
  class Progress < SubCommandBase
78
+ class_before :exit_with_no_key
79
+
61
80
  desc "set [LESSON]", "Set current lesson to a particular lesson"
62
81
 
63
82
  def set(lesson)
@@ -85,14 +104,19 @@ module RPW
85
104
 
86
105
  private
87
106
 
88
- def client
89
- @client ||= RPW::Client.new
107
+ def exit_with_no_key
108
+ exit(1) unless client.setup?
90
109
  end
91
110
 
92
111
  default_task :show
93
112
  end
94
113
 
95
114
  class CLI < Thor
115
+ class_before :check_version
116
+ class_before :check_setup
117
+
118
+ desc "key register [EMAIL_ADDRESS]", "Change email registered w/Speedshop"
119
+ subcommand "key", Key
96
120
  desc "lesson [SUBCOMMAND]", "View and download lessons"
97
121
  subcommand "lesson", Lesson
98
122
  desc "progress [SUBCOMMAND]", "View and set progress"
@@ -102,27 +126,66 @@ module RPW
102
126
  true
103
127
  end
104
128
 
105
- desc "setup", "Set up purchase key and directories"
106
-
107
- def setup
108
- return unless yes? "We're going to (idempotently!) create a few files and directories in the current working directory. Is that OK? (Y/N)"
129
+ desc "start", "Tutorial and onboarding"
130
+
131
+ def start
132
+ print_banner
133
+ say "Welcome to the Rails Performance Workshop."
134
+ say ""
135
+ say "This is rpw, the command line client for this workshop."
136
+ say ""
137
+ say "This client will download files from the internet into the current"
138
+ say "working directory, so it's best to run this client from a new directory"
139
+ say "that you'll use as your 'scratch space' for working on the Workshop."
140
+ say ""
141
+ return unless yes? "Are you in a directory that we can safely add new folders and files to? (Y/N)"
142
+ puts ""
143
+ say "We'll create a .rpw_info file at #{File.expand_path("~/.rpw")} to save your purchase key."
144
+ home_dir_ok = yes?("Is this OK? (Y/N) (N will create it in the current directory)")
109
145
 
110
- say "We'll create a .rpw_key file in the current directory to save your purchase key and course data."
111
146
  key = ask("Your Purchase Key: ")
112
147
 
113
148
  client.setup(key)
114
149
 
150
+ puts ""
151
+
115
152
  say "Successfully authenticated with the RPW server and saved your key."
116
- say "We'll also create a few directories for your course data and progress."
153
+
154
+ puts ""
155
+
156
+ say "rpw works by downloading files into the current working directory."
157
+ say "We're going to (idempotently!) create a few directories in the current working directory."
158
+ puts ""
159
+ return unless yes? "Is that OK? (Y/N)"
117
160
 
118
161
  client.directory_setup
119
162
 
120
163
  say "Setup complete!"
164
+ puts ""
165
+ say "Here are some important commands for you to know:"
166
+ say "$ rpw lesson next | Proceed to the next part of the workshop."
167
+ say "$ rpw lesson complete | Mark current lesson as complete."
168
+ say "$ rpw lesson list | List all workshop lessons. Note each lesson is preceded with an ID."
169
+ say "$ rpw lesson download | Download any or all lessons."
170
+ say "$ rpw lesson show | Show any particular workshop lesson."
171
+ say "$ rpw progress | Show where you're currently at in the workshop."
172
+ say "$ rpw help | Help! You can also ask in Slack."
173
+ puts ""
174
+ say "Generally, you'll just be doing a lot of $ rpw lesson next"
175
+ say "Remember to ask on Slack for help if you get stuck or encounter bugs."
176
+ say "Let's get going! $ rpw lesson next"
121
177
  end
122
178
 
123
- desc "support", "Create a new support ticket, report a bug or issue"
124
-
125
- def support
179
+ no_commands do
180
+ def print_banner
181
+ puts r
182
+ if `tput cols 80`.to_i < 80
183
+ puts small_banner
184
+ else
185
+ puts banner
186
+ end
187
+ puts reset
188
+ end
126
189
  end
127
190
 
128
191
  private
@@ -130,6 +193,63 @@ module RPW
130
193
  def client
131
194
  @client ||= RPW::Client.new
132
195
  end
196
+
197
+ def check_version
198
+ unless client.latest_version?
199
+ say "#{r}WARNING: You are running an old version of rpw."
200
+ say "WARNING: Please run `$ gem install rpw`#{reset}"
201
+ end
202
+ end
203
+
204
+ def check_setup
205
+ unless client.setup? || current_command_chain == [:start]
206
+ say "#{r}WARNING: You do not have a purchase key set. Run `$ rpw start`#{reset}"
207
+ end
208
+ end
209
+
210
+ def r
211
+ "\e[31m"
212
+ end
213
+
214
+ def reset
215
+ "\e[0m"
216
+ end
217
+
218
+ def small_banner
219
+ %(
220
+ _____ _ _____ _ _
221
+ |_ _| |_ ___ | __ |___|_| |___
222
+ | | | | -_| | -| .'| | |_ -|
223
+ |_| |_|_|___| |__|__|__,|_|_|___|
224
+ _____ ___
225
+ | _ |___ ___| _|___ ___ _____ ___ ___ ___ ___
226
+ | __| -_| _| _| . | _| | .'| | _| -_|
227
+ |__| |___|_| |_| |___|_| |_|_|_|__,|_|_|___|___|
228
+ _ _ _ _ _
229
+ | | | |___ ___| |_ ___| |_ ___ ___
230
+ | | | | . | _| '_|_ -| | . | . |
231
+ |_____|___|_| |_,_|___|_|_|___| _|
232
+ |_|
233
+ #{reset})
234
+ end
235
+
236
+ def banner
237
+ %(
238
+ _____ _ _____ _ _
239
+ +hmNMMMMMm/` -ymMMNh/ |_ _| |_ ___ | __ |___|_| |___
240
+ sMMMMMMMMMy +MMMMMMMMy | | | | -_| | -| .'| | |_ -|
241
+ yMMMMMMMMMMy` yMMMMMMMMN |_| |_|_|___| |__|__|__,|_|_|___|
242
+ `dMMMMMMMMMMm:-dMMMMMMm: _____ ___
243
+ `sNMMMMMMMMMMs.:+sso:` | _ |___ ___| _|___ ___ _____ ___ ___ ___ ___
244
+ :dMMMMMMMMMMm/ | __| -_| _| _| . | _| | .'| | _| -_|
245
+ `:oss+:.sNMMMMMMMMMMy` |__| |___|_| |_| |___|_| |_|_|_|__,|_|_|___|___|
246
+ /mMMMMMMd-:mMMMMMMMMMMd. _ _ _ _ _
247
+ NMMMMMMMMy `hMMMMMMMMMMh | | | |___ ___| |_ ___| |_ ___ ___
248
+ yMMMMMMMM+ `dMMMMMMMMMy | | | | . | _| '_|_ -| | . | . |
249
+ /hNMMmy- `/mMMMMMNmy/ |_____|___|_| |_,_|___|_|_|___| _|
250
+ |_|
251
+ #{reset})
252
+ end
133
253
  end
134
254
  end
135
255
 
data/lib/rpw.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "typhoeus"
2
2
  require "json"
3
+ require_relative "rpw/version"
3
4
 
4
5
  module RPW
5
6
  class Error < StandardError; end
@@ -44,11 +45,21 @@ module RPW
44
45
  request = Typhoeus::Request.new(content["url"])
45
46
  request.on_body do |chunk|
46
47
  downloaded_file.write(chunk)
47
- printf(".") if rand(10) == 0 # lol
48
+ printf(".") if rand(500) == 0 # lol
48
49
  end
49
50
  request.on_complete { |response| downloaded_file.close }
50
51
  request
51
52
  end
53
+
54
+ def latest_version?
55
+ resp = Typhoeus.get("https://rubygems.org/api/v1/gems/rpw.json")
56
+ data = JSON.parse resp.body
57
+ Gem::Version.new(RPW::VERSION) >= Gem::Version.new(data["version"])
58
+ end
59
+
60
+ def register_email(email)
61
+ Typhoeus.put(domain + "/license", params: {email: email, key: @key})
62
+ end
52
63
  end
53
64
 
54
65
  class Client
@@ -56,7 +67,11 @@ module RPW
56
67
 
57
68
  def setup(key)
58
69
  gateway.authenticate_key(key)
59
- keyfile["key"] = key
70
+ client_data["key"] = key
71
+ end
72
+
73
+ def register_email(email)
74
+ gateway.register_email(email)
60
75
  end
61
76
 
62
77
  def directory_setup
@@ -80,33 +95,41 @@ module RPW
80
95
  end
81
96
  end
82
97
 
83
- def next
98
+ def next(open_after = false)
99
+ complete
84
100
  content = next_content
101
+ if content.nil?
102
+ finished_workshop
103
+ return
104
+ end
105
+
85
106
  unless File.exist?(content["style"] + "/" + content["s3_key"])
86
107
  gateway.download_content(content, folder: content["style"]).run
87
108
  extract_content(content) if content["s3_key"].end_with?(".tar.gz")
88
109
  end
89
110
  client_data["current_lesson"] = content["position"]
90
- display_content(content)
111
+ display_content(content, open_after)
91
112
  end
92
113
 
93
114
  def complete
115
+ reset_progress unless client_data["current_lesson"] && client_data["completed"]
94
116
  client_data["completed"] ||= []
95
- client_data["completed"] += [client_data["current_lesson"]]
117
+ client_data["completed"] += [client_data["current_lesson"] || 0]
96
118
  end
97
119
 
98
120
  def list
99
121
  gateway.list_content
100
122
  end
101
123
 
102
- def show(content_pos)
124
+ def show(content_pos, open_after = false)
125
+ content_pos = client_data["current_lesson"] if content_pos == :current
103
126
  content = gateway.get_content_by_position(content_pos)
104
127
  unless File.exist?(content["style"] + "/" + content["s3_key"])
105
128
  gateway.download_content(content, folder: content["style"]).run
106
129
  extract_content(content) if content["s3_key"].end_with?(".tar.gz")
107
130
  end
108
131
  client_data["current_lesson"] = content["position"]
109
- display_content(content)
132
+ display_content(content, open_after)
110
133
  end
111
134
 
112
135
  def download(content_pos)
@@ -135,7 +158,7 @@ module RPW
135
158
  completed: client_data["completed"].size,
136
159
  total: contents.size,
137
160
  current_lesson: contents.find { |c| c["position"] == client_data["current_lesson"] },
138
- sections: chart_section_progress(contents)
161
+ sections: chart_section_progress(contentsi)
139
162
  }
140
163
  end
141
164
 
@@ -148,8 +171,37 @@ module RPW
148
171
  client_data["completed"] = []
149
172
  end
150
173
 
174
+ def latest_version?
175
+ if client_data["last_version_check"]
176
+ return true if client_data["last_version_check"] >= Time.now - (60 * 60 * 24)
177
+ return false if client_data["last_version_check"] == false
178
+ end
179
+
180
+ begin
181
+ latest = gateway.latest_version?
182
+ rescue
183
+ return true
184
+ end
185
+
186
+ client_data["last_version_check"] = if latest
187
+ Time.now
188
+ else
189
+ false
190
+ end
191
+ end
192
+
193
+ def setup?
194
+ client_data["key"]
195
+ end
196
+
151
197
  private
152
198
 
199
+ def finished_workshop
200
+ RPW::CLI.new.print_banner
201
+ puts "Congratulations!"
202
+ puts "You have completed the Rails Performance Workshop."
203
+ end
204
+
153
205
  def chart_section_progress(contents)
154
206
  contents.group_by { |c| c["position"] / 100 }
155
207
  .each_with_object([]) do |(_, c), memo|
@@ -180,12 +232,8 @@ module RPW
180
232
  @client_data ||= ClientData.new
181
233
  end
182
234
 
183
- def keyfile
184
- @keyfile ||= Keyfile.new
185
- end
186
-
187
235
  def gateway
188
- @gateway ||= Gateway.new(RPW_SERVER_DOMAIN, keyfile["key"])
236
+ @gateway ||= Gateway.new(RPW_SERVER_DOMAIN, client_data["key"])
189
237
  end
190
238
 
191
239
  def extract_content(content)
@@ -193,23 +241,41 @@ module RPW
193
241
  `tar -C #{folder} -xvzf #{folder}/#{content["s3_key"]}`
194
242
  end
195
243
 
196
- def display_content(content)
244
+ def display_content(content, open_after)
245
+ puts "\nCurrent Lesson: #{content["title"]}"
197
246
  case content["style"]
198
247
  when "video"
199
- puts "Opening video: #{content["title"]}"
200
- exec("open video/#{content["s3_key"]}")
248
+ location = "video/#{content["s3_key"]}"
201
249
  when "quiz"
202
250
  Quiz.start(["give_quiz", "quiz/" + content["s3_key"]])
203
251
  when "lab"
204
- # extract and rm archive
205
- puts "Lab downloaded to lab/#{content["s3_key"]}, navigate there and look at the README to continue"
252
+ location = "lab/#{content["s3_key"]}"
206
253
  when "text"
207
- puts "Opening in your editor: #{content["title"]}"
208
- exec("$EDITOR text/#{content["s3_key"]}")
254
+ location = "lab/#{content["s3_key"]}"
209
255
  when "cgrp"
210
- puts "The Complete Guide to Rails Performance has been downloaded and extracted to the cgrp directory."
256
+ puts "The Complete Guide to Rails Performance has been downloaded and extracted to the ./cgrp directory."
211
257
  puts "All source code for the CGRP is in the src directory, PDF and other compiled formats are in the release directory."
212
258
  end
259
+ if location
260
+ puts "Downloaded to:"
261
+ puts location.to_s
262
+ if open_after
263
+ exec "#{open_command} #{location}"
264
+ end
265
+ end
266
+ end
267
+
268
+ require "rbconfig"
269
+ def open_command
270
+ host_os = RbConfig::CONFIG["host_os"]
271
+ case host_os
272
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
273
+ "start"
274
+ when /darwin|mac os/
275
+ "open"
276
+ else
277
+ "xdg-open"
278
+ end
213
279
  end
214
280
  end
215
281
 
@@ -219,50 +285,72 @@ module RPW
219
285
  class ClientData
220
286
  DOTFILE_NAME = ".rpw_info"
221
287
 
222
- def [](key)
288
+ def initialize
223
289
  make_sure_dotfile_exists
290
+ data # access file to load
291
+ end
292
+
293
+ def [](key)
224
294
  data[key]
225
295
  end
226
296
 
227
297
  def []=(key, value)
298
+ data
228
299
  data[key] = value
300
+
229
301
  begin
230
- File.open(self.class::DOTFILE_NAME, "w") { |f| f.write(YAML.dump(data)) }
302
+ File.open(filestore_location, "w") { |f| f.write(YAML.dump(data)) }
231
303
  rescue
232
- raise Error, "The RPW data file in this directory is not writable. \
304
+ raise Error, "The RPW data at #{filestore_location} is not writable. \
233
305
  Check your file permissions."
234
306
  end
235
307
  end
236
308
 
309
+ def self.delete_filestore
310
+ return unless File.exist?(filestore_location)
311
+ FileUtils.remove(filestore_location)
312
+ end
313
+
314
+ def self.filestore_location
315
+ if File.exist?(File.expand_path("./" + self::DOTFILE_NAME))
316
+ File.expand_path("./" + + self::DOTFILE_NAME)
317
+ else
318
+ File.expand_path("~/.rpw/" + self::DOTFILE_NAME)
319
+ end
320
+ end
321
+
237
322
  private
238
323
 
324
+ def filestore_location
325
+ self.class.filestore_location
326
+ end
327
+
328
+ def create_client_data_directory(path)
329
+ dirname = File.dirname(path)
330
+ FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
331
+ end
332
+
239
333
  def data
240
334
  @data ||= begin
241
- yaml = begin
242
- YAML.safe_load(File.read(self.class::DOTFILE_NAME))
243
- rescue
244
- nil
245
- end
335
+ yaml = YAML.safe_load(File.read(filestore_location), permitted_classes: [Time])
246
336
  yaml || {}
247
337
  end
248
338
  end
249
339
 
250
340
  def make_sure_dotfile_exists
251
- return true if File.exist?(self.class::DOTFILE_NAME)
341
+ return true if File.exist?(filestore_location)
342
+ create_client_data_directory(filestore_location)
252
343
  begin
253
- FileUtils.touch(self.class::DOTFILE_NAME)
344
+ FileUtils.touch(filestore_location)
254
345
  rescue
255
- raise Error, "Could not create the RPW data file in this directory \
346
+ raise Error, "Could not create the RPW data file at ~/.rpw/ \
256
347
  Check your file permissions."
257
348
  end
258
349
  end
259
350
  end
260
351
 
261
- class Keyfile < ClientData
262
- DOTFILE_NAME = ".rpw_key"
263
- end
264
-
265
352
  require "digest"
353
+ require "thor"
266
354
 
267
355
  class Quiz < Thor
268
356
  desc "give_quiz FILENAME", ""
@@ -277,13 +365,14 @@ module RPW
277
365
  puts data["prompt"]
278
366
  data["answer_choices"].each { |ac| puts ac }
279
367
  provided_answer = ask("Your answer?")
280
- answer_digest = Digest::MD5.hexdigest(data["prompt"] + provided_answer)
368
+ answer_digest = Digest::MD5.hexdigest(data["prompt"] + provided_answer.upcase)
281
369
  if answer_digest == data["answer_digest"]
282
370
  say "Correct!"
283
371
  else
284
372
  say "Incorrect."
285
373
  say "I encourage you to try reviewing the material to see what the correct answer is."
286
374
  end
375
+ say ""
287
376
  end
288
377
  end
289
378
  end
@@ -1,3 +1,3 @@
1
1
  module RPW
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -25,5 +25,6 @@ Gem::Specification.new do |spec|
25
25
  spec.require_paths = ["lib"]
26
26
 
27
27
  spec.add_dependency "thor"
28
+ spec.add_dependency "thor-hollaback"
28
29
  spec.add_dependency "typhoeus"
29
30
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rpw
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nate Berkopec
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-09 00:00:00.000000000 Z
11
+ date: 2020-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor-hollaback
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: typhoeus
29
43
  requirement: !ruby/object:Gem::Requirement