rpw 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/lib/rpw.rb CHANGED
@@ -1,395 +1,8 @@
1
- require "typhoeus"
2
- require "json"
3
- require_relative "rpw/version"
1
+ require "rpw/version"
2
+ require "rpw/client"
3
+ require "rpw/client_data"
4
+ require "rpw/gateway"
4
5
 
5
6
  module RPW
6
7
  class Error < StandardError; end
7
-
8
- class Gateway
9
- attr_accessor :domain
10
-
11
- def initialize(domain, key)
12
- @domain = domain
13
- @key = key
14
- end
15
-
16
- class Error < StandardError; end
17
-
18
- def authenticate_key(key)
19
- Typhoeus.get(domain + "/license", userpwd: key + ":").success?
20
- end
21
-
22
- def get_content_by_position(position)
23
- response = Typhoeus.get(domain + "/contents/positional?position=#{position}", userpwd: @key + ":")
24
- if response.success?
25
- JSON.parse(response.body)
26
- else
27
- puts response.inspect
28
- raise Error, "There was a problem fetching this content."
29
- end
30
- end
31
-
32
- def list_content
33
- response = Typhoeus.get(domain + "/contents", userpwd: @key + ":")
34
- if response.success?
35
- JSON.parse(response.body)
36
- else
37
- puts response.inspect
38
- raise Error, "There was a problem fetching this content."
39
- end
40
- end
41
-
42
- def download_content(content, folder:)
43
- puts "Downloading #{content["title"]}..."
44
- downloaded_file = File.open("#{folder}/#{content["s3_key"]}", "w")
45
- request = Typhoeus::Request.new(content["url"])
46
- request.on_body do |chunk|
47
- downloaded_file.write(chunk)
48
- printf(".") if rand(500) == 0 # lol
49
- end
50
- request.on_complete { |response| downloaded_file.close }
51
- request
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
63
- end
64
-
65
- class Client
66
- RPW_SERVER_DOMAIN = ENV["RPW_SERVER_DOMAIN"] || "https://rpw-licensor.speedshop.co"
67
-
68
- def setup(key)
69
- gateway.authenticate_key(key)
70
- client_data["key"] = key
71
- end
72
-
73
- def register_email(email)
74
- gateway.register_email(email)
75
- end
76
-
77
- def directory_setup(home_dir_ok = true)
78
- ["video", "quiz", "lab", "text", "cgrp"].each do |path|
79
- FileUtils.mkdir_p(path) unless File.directory?(path)
80
- end
81
-
82
- if home_dir_ok
83
- ClientData.create_in_home!
84
- else
85
- ClientData.create_in_pwd!
86
- end
87
-
88
- unless File.exist?(".gitignore") && File.read(".gitignore").match(/rpw_key/)
89
- File.open(".gitignore", "a") do |f|
90
- f.puts "\n"
91
- f.puts ".rpw_key\n"
92
- f.puts ".rpw_info\n"
93
- f.puts "video\n"
94
- f.puts "quiz\n"
95
- f.puts "lab\n"
96
- f.puts "text\n"
97
- f.puts "cgrp\n"
98
- end
99
- end
100
-
101
- File.open("README.md", "w+") do |f|
102
- f.puts File.read(File.join(File.dirname(__FILE__), "README.md"))
103
- end
104
- end
105
-
106
- def next(open_after = false)
107
- complete
108
- content = next_content
109
- if content.nil?
110
- finished_workshop
111
- return
112
- end
113
-
114
- unless File.exist?(content["style"] + "/" + content["s3_key"])
115
- gateway.download_content(content, folder: content["style"]).run
116
- extract_content(content) if content["s3_key"].end_with?(".tar.gz")
117
- end
118
- client_data["current_lesson"] = content["position"]
119
- display_content(content, open_after)
120
- end
121
-
122
- def complete
123
- reset_progress unless client_data["current_lesson"] && client_data["completed"]
124
- client_data["completed"] ||= []
125
- client_data["completed"] += [client_data["current_lesson"] || 0]
126
- end
127
-
128
- def list
129
- gateway.list_content
130
- end
131
-
132
- def show(content_pos, open_after = false)
133
- content_pos = client_data["current_lesson"] if content_pos == :current
134
- content = gateway.get_content_by_position(content_pos)
135
- unless File.exist?(content["style"] + "/" + content["s3_key"])
136
- gateway.download_content(content, folder: content["style"]).run
137
- extract_content(content) if content["s3_key"].end_with?(".tar.gz")
138
- end
139
- client_data["current_lesson"] = content["position"]
140
- display_content(content, open_after)
141
- end
142
-
143
- def download(content_pos)
144
- if content_pos.downcase == "all"
145
- to_download = gateway.list_content
146
- hydra = Typhoeus::Hydra.new(max_concurrency: 5)
147
- to_download.each do |content|
148
- unless File.exist?(content["style"] + "/" + content["s3_key"])
149
- hydra.queue gateway.download_content(content, folder: content["style"])
150
- end
151
- end
152
- hydra.run
153
- to_download.each { |content| extract_content(content) if content["s3_key"].end_with?(".tar.gz") }
154
- else
155
- content = gateway.get_content_by_position(content_pos)
156
- unless File.exist?(content["style"] + "/" + content["s3_key"])
157
- gateway.download_content(content, folder: content["style"]).run
158
- extract_content(content) if content["s3_key"].end_with?(".tar.gz")
159
- end
160
- end
161
- end
162
-
163
- def progress
164
- contents = gateway.list_content
165
- {
166
- completed: client_data["completed"].size,
167
- total: contents.size,
168
- current_lesson: contents.find { |c| c["position"] == client_data["current_lesson"] },
169
- sections: chart_section_progress(contents)
170
- }
171
- end
172
-
173
- def set_progress(lesson)
174
- client_data["current_lesson"] = lesson.to_i
175
- end
176
-
177
- def reset_progress
178
- client_data["current_lesson"] = 0
179
- client_data["completed"] = []
180
- end
181
-
182
- def latest_version?
183
- return true unless ClientData.exists?
184
-
185
- if client_data["last_version_check"]
186
- return true if client_data["last_version_check"] >= Time.now - (60 * 60 * 24)
187
- return false if client_data["last_version_check"] == false
188
- end
189
-
190
- begin
191
- latest = gateway.latest_version?
192
- rescue
193
- return true
194
- end
195
-
196
- client_data["last_version_check"] = if latest
197
- Time.now
198
- else
199
- false
200
- end
201
- end
202
-
203
- def setup?
204
- return false unless ClientData.exists?
205
- client_data["key"]
206
- end
207
-
208
- def directories_ready?
209
- ["video", "quiz", "lab", "text", "cgrp"].all? do |path|
210
- File.directory?(path)
211
- end
212
- end
213
-
214
- private
215
-
216
- def finished_workshop
217
- RPW::CLI.new.print_banner
218
- puts "Congratulations!"
219
- puts "You have completed the Rails Performance Workshop."
220
- end
221
-
222
- def chart_section_progress(contents)
223
- contents.group_by { |c| c["position"] / 100 }
224
- .each_with_object([]) do |(_, c), memo|
225
- completed_str = c.map { |l|
226
- if l["position"] == client_data["current_lesson"]
227
- "O"
228
- elsif client_data["completed"].include?(l["position"])
229
- "X"
230
- else
231
- "."
232
- end
233
- }.join
234
- memo << {
235
- title: c[0]["title"],
236
- progress: completed_str
237
- }
238
- end
239
- end
240
-
241
- def next_content
242
- contents = gateway.list_content
243
- return contents.first unless client_data["completed"]
244
- contents.delete_if { |c| client_data["completed"].include? c["position"] }
245
- contents.min_by { |c| c["position"] }
246
- end
247
-
248
- def client_data
249
- @client_data ||= ClientData.new
250
- end
251
-
252
- def gateway
253
- @gateway ||= Gateway.new(RPW_SERVER_DOMAIN, client_data["key"])
254
- end
255
-
256
- def extract_content(content)
257
- folder = content["style"]
258
- `tar -C #{folder} -xvzf #{folder}/#{content["s3_key"]}`
259
- end
260
-
261
- def display_content(content, open_after)
262
- puts "\nCurrent Lesson: #{content["title"]}"
263
- openable = false
264
- case content["style"]
265
- when "video"
266
- location = "video/#{content["s3_key"]}"
267
- openable = true
268
- when "quiz"
269
- Quiz.start(["give_quiz", "quiz/" + content["s3_key"]])
270
- when "lab"
271
- location = "lab/#{content["s3_key"][0..-8]}"
272
- when "text"
273
- location = "lab/#{content["s3_key"]}"
274
- openable = true
275
- when "cgrp"
276
- puts "The Complete Guide to Rails Performance has been downloaded and extracted to the ./cgrp directory."
277
- puts "All source code for the CGRP is in the src directory, PDF and other compiled formats are in the release directory."
278
- end
279
- if location
280
- puts "This file can be opened automatically if you add the --open flag." if openable && !open_after
281
- puts "Downloaded to:"
282
- puts location.to_s
283
- if open_after && openable
284
- exec "#{open_command} #{location}"
285
- end
286
- end
287
- end
288
-
289
- require "rbconfig"
290
- def open_command
291
- host_os = RbConfig::CONFIG["host_os"]
292
- case host_os
293
- when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
294
- "start"
295
- when /darwin|mac os/
296
- "open"
297
- else
298
- "xdg-open"
299
- end
300
- end
301
- end
302
-
303
- require "fileutils"
304
- require "yaml"
305
-
306
- class ClientData
307
- DOTFILE_NAME = ".rpw_info"
308
-
309
- def initialize
310
- data # access file to load
311
- end
312
-
313
- def [](key)
314
- data[key]
315
- end
316
-
317
- def []=(key, value)
318
- data
319
- data[key] = value
320
-
321
- begin
322
- File.open(filestore_location, "w") { |f| f.write(YAML.dump(data)) }
323
- rescue
324
- raise Error, "The RPW data at #{filestore_location} is not writable. \
325
- Check your file permissions."
326
- end
327
- end
328
-
329
- def self.create_in_pwd!
330
- FileUtils.touch(File.expand_path("./" + DOTFILE_NAME))
331
- end
332
-
333
- def self.create_in_home!
334
- FileUtils.mkdir_p("~/.rpw/") unless File.directory?("~/.rpw/")
335
- FileUtils.touch(File.expand_path("~/.rpw/" + DOTFILE_NAME))
336
- end
337
-
338
- def self.delete_filestore
339
- return unless File.exist?(filestore_location)
340
- FileUtils.remove(filestore_location)
341
- end
342
-
343
- def self.exists?
344
- File.exist? filestore_location
345
- end
346
-
347
- def self.filestore_location
348
- if File.exist?(File.expand_path("./" + DOTFILE_NAME))
349
- File.expand_path("./" + DOTFILE_NAME)
350
- else
351
- File.expand_path("~/.rpw/" + DOTFILE_NAME)
352
- end
353
- end
354
-
355
- private
356
-
357
- def filestore_location
358
- self.class.filestore_location
359
- end
360
-
361
- def data
362
- @data ||= begin
363
- yaml = YAML.safe_load(File.read(filestore_location), permitted_classes: [Time])
364
- yaml || {}
365
- end
366
- end
367
- end
368
-
369
- require "digest"
370
- require "thor"
371
-
372
- class Quiz < Thor
373
- desc "give_quiz FILENAME", ""
374
- def give_quiz(filename)
375
- @quiz_data = YAML.safe_load(File.read(filename))
376
- @quiz_data["questions"].each { |q| question(q) }
377
- end
378
-
379
- private
380
-
381
- def question(data)
382
- puts data["prompt"]
383
- data["answer_choices"].each { |ac| puts ac }
384
- provided_answer = ask("Your answer?")
385
- answer_digest = Digest::MD5.hexdigest(data["prompt"] + provided_answer.upcase)
386
- if answer_digest == data["answer_digest"]
387
- say "Correct!"
388
- else
389
- say "Incorrect."
390
- say "I encourage you to try reviewing the material to see what the correct answer is."
391
- end
392
- say ""
393
- end
394
- end
395
8
  end
@@ -1,3 +1,9 @@
1
+ ## Installation Requirements
2
+
3
+ This client assumes you have `tar` installed.
4
+
5
+ ## Important Commands
6
+
1
7
  Here are some important commands for you to know:
2
8
 
3
9
  $ rpw lesson next | Proceed to the next part of the workshop.
@@ -8,4 +14,9 @@ $ rpw lesson show | Show any particular workshop lesson.
8
14
  $ rpw progress | Show where you're currently at in the workshop.
9
15
  $ rpw help | Help! You can also ask in Slack.
10
16
 
11
- Generally, you'll just be doing a lot of $ rpw lesson next
17
+ Generally, you'll just be doing a lot of $ rpw lesson next
18
+
19
+ ## Data Size Notice
20
+
21
+ Videos in this workshop are generally about 100MB each, which means the entire
22
+ course is about a 3 to 4GB download.
@@ -0,0 +1,96 @@
1
+ require "thor"
2
+ require "thor/hollaback"
3
+ require "rpw"
4
+ require "rpw/cli/bannerlord"
5
+ require "rpw/cli/sub_command_base"
6
+ require "rpw/cli/key"
7
+ require "rpw/cli/lesson"
8
+ require "rpw/cli/progress"
9
+
10
+ module RPW
11
+ class CLI < Thor
12
+ class_before :check_version
13
+ class_before :check_setup
14
+
15
+ desc "key register [EMAIL_ADDRESS]", "Change email registered w/Speedshop"
16
+ subcommand "key", Key
17
+ desc "lesson [SUBCOMMAND]", "View and download lessons"
18
+ subcommand "lesson", Lesson
19
+ desc "progress [SUBCOMMAND]", "View and set progress"
20
+ subcommand "progress", Progress
21
+
22
+ def self.exit_on_failure?
23
+ true
24
+ end
25
+
26
+ desc "start", "Tutorial and onboarding"
27
+ def start
28
+ warn_if_already_started
29
+
30
+ print_banner
31
+ say "Welcome to the Rails Performance Workshop."
32
+ say ""
33
+ say "This is rpw, the command line client for this workshop."
34
+ say ""
35
+ say "This client will download files from the internet into the current"
36
+ say "working directory, so it's best to run this client from a new directory"
37
+ say "that you'll use as your 'scratch space' for working on the Workshop."
38
+ say ""
39
+ say "We will create a handful of new files and folders in the current directory."
40
+ return unless yes? "Is this OK? (y/N) (N will quit)"
41
+ puts ""
42
+ say "We'll also create a .rpw_info file at #{File.expand_path("~/.rpw")} to save your purchase key."
43
+ home_dir_ok = yes?("Is this OK? (y/N) (N will create it in the current directory)")
44
+ client.directory_setup(home_dir_ok)
45
+
46
+ key = ask("Your Purchase Key: ")
47
+
48
+ unless client.setup(key)
49
+ say "That is not a valid key. Please try again."
50
+ exit(0)
51
+ end
52
+
53
+ puts ""
54
+ say "Successfully authenticated with the RPW server and saved your key."
55
+ puts ""
56
+ say "Setup complete!"
57
+ puts ""
58
+ say "To learn how to use this command-line client, consult README.md."
59
+ say "Remember to ask on Slack for help if you get stuck or encounter bugs."
60
+ say "Once you're ready to get going: $ rpw lesson next"
61
+ end
62
+
63
+ no_commands do
64
+ def print_banner
65
+ RPW::Bannerlord.print_banner
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def client
72
+ @client ||= RPW::Client.new
73
+ end
74
+
75
+ def warn_if_already_started
76
+ return unless client.setup?
77
+ exit(0) unless yes? "You have already started the workshop. Continuing "\
78
+ "this command will wipe all of your current progress. Continue? (y/N)"
79
+ end
80
+
81
+ def check_version
82
+ unless client.latest_version?
83
+ say "WARNING: You are running an old version of rpw."
84
+ say "WARNING: Please run `$ gem install rpw`"
85
+ exit(0)
86
+ end
87
+ end
88
+
89
+ def check_setup
90
+ unless client.setup? || current_command_chain == [:start]
91
+ say "WARNING: You do not have a purchase key set. Run `$ rpw start`"
92
+ exit(0)
93
+ end
94
+ end
95
+ end
96
+ end