rpw 0.0.6 → 1.3.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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +1 -1
- data/Gemfile.lock +3 -1
- data/HISTORY.md +21 -0
- data/lib/rpw/README.md +83 -11
- data/lib/rpw/cli.rb +203 -31
- data/lib/rpw/cli/key.rb +5 -3
- data/lib/rpw/cli/quiz.rb +2 -2
- data/lib/rpw/cli/sub_command_base.rb +0 -12
- data/lib/rpw/client.rb +48 -58
- data/lib/rpw/client_data.rb +2 -2
- data/lib/rpw/gateway.rb +14 -20
- data/lib/rpw/version.rb +1 -1
- data/rpw.gemspec +1 -0
- metadata +16 -4
- data/lib/rpw/cli/lesson.rb +0 -99
- data/lib/rpw/cli/progress.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4e52497c6e3760c9aa301c8eeae51459c46458625a809847f83a0d660d104754
|
4
|
+
data.tar.gz: 01aac30eece6c29a294baa6e6859ff3de5afe52cd33c5113527351430169d4b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c86fdd2626b6d1e49a9c67c51e5476453799ae1a42013056f20064b59cf151e2779a28f5c6ebc7d0ee700f8cf3a1085f64370fa2282290e9eb80aa359433aa29
|
7
|
+
data.tar.gz: 74368f4f9e457c67ea43358c0e5fcb253bd38fa8487f5cd3fdd6b683a396b765ca0125d29366318536076fdd553020cc2d9349ca0548b15eeb772974de459bf5
|
data/.github/workflows/test.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rpw (
|
4
|
+
rpw (1.3.0)
|
5
|
+
cli-ui
|
5
6
|
excon
|
6
7
|
thor
|
7
8
|
thor-hollaback
|
@@ -10,6 +11,7 @@ GEM
|
|
10
11
|
remote: https://rubygems.org/
|
11
12
|
specs:
|
12
13
|
ast (2.4.1)
|
14
|
+
cli-ui (1.4.0)
|
13
15
|
excon (0.78.0)
|
14
16
|
hollaback (0.1.0)
|
15
17
|
minitest (5.14.2)
|
data/HISTORY.md
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
## 1.3.0
|
2
|
+
|
3
|
+
* Added suggested schedules to README
|
4
|
+
|
5
|
+
## 1.2.0
|
6
|
+
|
7
|
+
* Actual release version!
|
8
|
+
* Greatly streamlined commands
|
9
|
+
|
10
|
+
## 1.1.0
|
11
|
+
|
12
|
+
* sick colors
|
13
|
+
|
14
|
+
## 1.0.1
|
15
|
+
|
16
|
+
* Pre-launch bugfix
|
17
|
+
|
18
|
+
## 1.0.0
|
19
|
+
|
20
|
+
* First release!
|
21
|
+
|
1
22
|
## 0.0.6
|
2
23
|
|
3
24
|
* more bugfixes from beta testers
|
data/lib/rpw/README.md
CHANGED
@@ -1,22 +1,94 @@
|
|
1
1
|
## Installation Requirements
|
2
2
|
|
3
|
-
This client assumes you
|
3
|
+
This client assumes you're using Ruby 2.6 or later.
|
4
|
+
|
5
|
+
This client assumes you have `tar` installed and available on your PATH.
|
6
|
+
|
7
|
+
The way that the client opens files (using `open` or `xdg-open` depending on platform) assumes you have your default program for the following filetypes set correctly:
|
8
|
+
|
9
|
+
* .md for Markdown (XCode by default on Mac: you probably want to change that!)
|
10
|
+
* .mp4 for videos
|
11
|
+
|
12
|
+
## Slack Invite
|
13
|
+
|
14
|
+
The Slack channel is your best resource for questions about Rails Performance
|
15
|
+
or other material in the workshop. Nate is almost always monitoring that channel.
|
16
|
+
|
17
|
+
If you encounter a **bug or other software problem**, please email support@speedshop.co.
|
18
|
+
|
19
|
+
If you purchased the Workshop yourself, you will receive a Slack channel invitation
|
20
|
+
shortly. If you are attending the Workshop as part of a group and your license key
|
21
|
+
was provided to you, you need to register your key to get an invite:
|
22
|
+
|
23
|
+
```
|
24
|
+
$ rpw key register [YOUR_EMAIL_ADDRESS]
|
25
|
+
```
|
26
|
+
|
27
|
+
Please note you can only register your key once.
|
4
28
|
|
5
29
|
## Important Commands
|
6
30
|
|
7
31
|
Here are some important commands for you to know:
|
8
32
|
|
9
|
-
|
10
|
-
$ rpw
|
11
|
-
$ rpw
|
12
|
-
$ rpw
|
13
|
-
$ rpw
|
14
|
-
$ rpw
|
15
|
-
$ rpw
|
33
|
+
```
|
34
|
+
$ rpw next | Proceed to the next part of the workshop.
|
35
|
+
$ rpw complete | Mark current lesson as complete.
|
36
|
+
$ rpw list | List all workshop lessons. Shows progress.
|
37
|
+
$ rpw download | Download all lessons. Useful for offline access.
|
38
|
+
$ rpw show | Show any particular workshop lesson.
|
39
|
+
$ rpw current | Opens the current lesson.
|
40
|
+
```
|
41
|
+
|
42
|
+
Generally, you'll just be doing a lot of `$ rpw next`! It's basically the same thing as `$ rpw complete && rpw show`.
|
43
|
+
|
44
|
+
#### --no-open
|
16
45
|
|
17
|
-
|
46
|
+
By default, `$ rpw next` (and `$ rpw show` and `$ rpw current`) will try to open the content it downloads. If you
|
47
|
+
either don't like this, or for some reason it doesn't work, use `$ rpw next --no-open`.
|
18
48
|
|
19
|
-
##
|
49
|
+
## Working Offline
|
50
|
+
|
51
|
+
By default, the course will download each piece of content as you progress through
|
52
|
+
the course. However, you can use `rpw download` to download all content
|
53
|
+
at once, and complete the workshop entirely offline.
|
20
54
|
|
21
55
|
Videos in this workshop are generally about 100MB each, which means the entire
|
22
|
-
course is about a 3 to 4GB download.
|
56
|
+
course is about a 3 to 4GB download.
|
57
|
+
|
58
|
+
## Bugs and Support
|
59
|
+
|
60
|
+
If you encounter any problems, please email support@speedshop.co for the fastest possible response.
|
61
|
+
|
62
|
+
## Suggested Schedules
|
63
|
+
|
64
|
+
I've found that self-directed learners are most effective when they set a schedule for themselves. These schedules will work if you are learning alone or in a group.
|
65
|
+
|
66
|
+
### 4-Week Schedule
|
67
|
+
|
68
|
+
This is my recommended schedule. It is designed so that you can do the workshop as "20% time", or as part of a larger performance "sprint".
|
69
|
+
|
70
|
+
If you follow this schedule, you should be able to complete the workshop in about 4 weeks. I suggest leaving an entire day aside to complete your material for the week, e.g. Mondays. Tuesday through Friday should be used to complete exercises and PR Safaris.
|
71
|
+
|
72
|
+
* **Week 1**: Modules 1 and 2
|
73
|
+
* **Week 2**: Module 3 and 4
|
74
|
+
* **Week 3**: Module 5
|
75
|
+
* **Week 4**: Module 6
|
76
|
+
|
77
|
+
### 1-Week Intensive Schedule
|
78
|
+
|
79
|
+
For those of you looking to immerse yourselves and only work on this workshop for 8 hours a day for 5 days, this schedule is suggested:
|
80
|
+
|
81
|
+
* **Day 1**: Modules 1 and 2
|
82
|
+
* **Day 2**: Module 3
|
83
|
+
* **Day 3**: Module 4
|
84
|
+
* **Day 4**: Module 5
|
85
|
+
* **Day 5**: Module 6
|
86
|
+
|
87
|
+
### 80/20 Schedule
|
88
|
+
|
89
|
+
Short on time? Here's the 20% of the course content that's the most important:
|
90
|
+
|
91
|
+
* Module 1
|
92
|
+
* Module 5
|
93
|
+
|
94
|
+
If I had the time to go through a limited amount of the workshop content, I would concentrate exclusively on these two modules.
|
data/lib/rpw/cli.rb
CHANGED
@@ -4,20 +4,16 @@ require "rpw"
|
|
4
4
|
require "rpw/cli/bannerlord"
|
5
5
|
require "rpw/cli/sub_command_base"
|
6
6
|
require "rpw/cli/key"
|
7
|
-
require "
|
8
|
-
|
7
|
+
require "cli/ui"
|
8
|
+
|
9
|
+
CLI::UI::StdoutRouter.enable
|
9
10
|
|
10
11
|
module RPW
|
11
12
|
class CLI < Thor
|
12
13
|
class_before :check_version
|
13
|
-
class_before :check_setup
|
14
14
|
|
15
15
|
desc "key register [EMAIL_ADDRESS]", "Change email registered w/Speedshop"
|
16
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
17
|
|
22
18
|
def self.exit_on_failure?
|
23
19
|
true
|
@@ -28,7 +24,7 @@ module RPW
|
|
28
24
|
warn_if_already_started
|
29
25
|
|
30
26
|
print_banner
|
31
|
-
say "Welcome to the Rails Performance Workshop."
|
27
|
+
say "\u{1F48E} Welcome to the Rails Performance Workshop. \u{1F48E}"
|
32
28
|
say ""
|
33
29
|
say "This is rpw, the command line client for this workshop."
|
34
30
|
say ""
|
@@ -36,28 +32,159 @@ module RPW
|
|
36
32
|
say "working directory, so it's best to run this client from a new directory"
|
37
33
|
say "that you'll use as your 'scratch space' for working on the Workshop."
|
38
34
|
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
35
|
|
46
|
-
|
36
|
+
ans = ::CLI::UI.confirm "Create files and folders in this directory? (no will quit)"
|
37
|
+
|
38
|
+
exit(1) unless ans
|
39
|
+
|
40
|
+
say ""
|
41
|
+
|
42
|
+
ans = ::CLI::UI::Prompt.ask("Where should we save your course progress?",
|
43
|
+
options: [
|
44
|
+
"here",
|
45
|
+
"my home directory (~/.rpw)"
|
46
|
+
])
|
47
|
+
|
48
|
+
client.directory_setup((ans == "my home directory (~/.rpw)"))
|
49
|
+
|
50
|
+
key = ::CLI::UI::Prompt.ask("Your Purchase Key: ")
|
47
51
|
|
48
52
|
unless client.setup(key)
|
49
53
|
say "That is not a valid key. Please try again."
|
50
54
|
exit(0)
|
51
55
|
end
|
52
56
|
|
53
|
-
|
57
|
+
say ""
|
54
58
|
say "Successfully authenticated with the RPW server and saved your key."
|
55
|
-
|
59
|
+
say ""
|
56
60
|
say "Setup complete!"
|
57
|
-
|
58
|
-
say "To learn how to use this command-line client, consult README.md
|
59
|
-
say "
|
60
|
-
say "
|
61
|
+
say ""
|
62
|
+
say "To learn how to use this command-line client, consult ./README.md,"
|
63
|
+
say "which we just created."
|
64
|
+
say ""
|
65
|
+
say "Once you've read that and you're ready to get going: $ rpw next"
|
66
|
+
end
|
67
|
+
|
68
|
+
desc "next", "Proceed to the next lesson of the workshop"
|
69
|
+
option :"no-open", type: :boolean
|
70
|
+
def next
|
71
|
+
exit_with_no_key
|
72
|
+
content = client.next
|
73
|
+
|
74
|
+
if content.nil?
|
75
|
+
RPW::CLI.new.print_banner
|
76
|
+
say "Congratulations!"
|
77
|
+
say "You have completed the Rails Performance Workshop."
|
78
|
+
exit(0)
|
79
|
+
end
|
80
|
+
|
81
|
+
say "Proceeding to next lesson: #{content["title"]}"
|
82
|
+
client.download_and_extract(content)
|
83
|
+
client.complete(content["position"])
|
84
|
+
display_content(content, !options[:"no-open"])
|
85
|
+
end
|
86
|
+
|
87
|
+
desc "current", "Open the current lesson"
|
88
|
+
option :"no-open", type: :boolean
|
89
|
+
def current
|
90
|
+
exit_with_no_key
|
91
|
+
content = client.current
|
92
|
+
say "Opening: #{content["title"]}"
|
93
|
+
client.download_and_extract(content)
|
94
|
+
display_content(content, !options[:"no-open"])
|
95
|
+
end
|
96
|
+
|
97
|
+
desc "complete", "Mark the current lesson as complete"
|
98
|
+
def complete
|
99
|
+
say "Marked current lesson as complete"
|
100
|
+
client.complete(nil)
|
101
|
+
end
|
102
|
+
|
103
|
+
desc "list", "Show all available workshop lessons"
|
104
|
+
def list
|
105
|
+
::CLI::UI::Frame.open("{{*}} {{bold:All Lessons}}", color: :green)
|
106
|
+
|
107
|
+
frame_open = false
|
108
|
+
client.list.each do |lesson|
|
109
|
+
if lesson["title"].start_with?("Section")
|
110
|
+
::CLI::UI::Frame.close(nil) if frame_open
|
111
|
+
::CLI::UI::Frame.open(lesson["title"])
|
112
|
+
frame_open = true
|
113
|
+
next
|
114
|
+
end
|
115
|
+
|
116
|
+
no_data = client.send(:client_data)["completed"].nil?
|
117
|
+
completed = client.send(:client_data)["completed"]&.include?(lesson["position"])
|
118
|
+
|
119
|
+
str = if no_data
|
120
|
+
""
|
121
|
+
elsif completed
|
122
|
+
"\u{2705} "
|
123
|
+
else
|
124
|
+
"\u{274C} "
|
125
|
+
end
|
126
|
+
|
127
|
+
case lesson["style"]
|
128
|
+
when "video"
|
129
|
+
puts str + ::CLI::UI.fmt("{{red:#{lesson["title"]}}}")
|
130
|
+
when "quiz"
|
131
|
+
# puts ::CLI::UI.fmt "{{green:#{" " + lesson["title"]}}}"
|
132
|
+
when "lab"
|
133
|
+
puts str + ::CLI::UI.fmt("{{yellow:#{" " + lesson["title"]}}}")
|
134
|
+
when "text"
|
135
|
+
puts str + ::CLI::UI.fmt("{{magenta:#{" " + lesson["title"]}}}")
|
136
|
+
else
|
137
|
+
puts str + ::CLI::UI.fmt("{{magenta:#{" " + lesson["title"]}}}")
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
::CLI::UI::Frame.close(nil)
|
142
|
+
::CLI::UI::Frame.close(nil, color: :green)
|
143
|
+
end
|
144
|
+
|
145
|
+
desc "download", "Download all workshop contents"
|
146
|
+
def download
|
147
|
+
exit_with_no_key
|
148
|
+
total = client.list.size
|
149
|
+
client.list.each do |content|
|
150
|
+
current = client.list.index(content) + 1
|
151
|
+
puts "Downloading #{content["title"]} (#{current}/#{total})"
|
152
|
+
client.download_and_extract(content)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
desc "show", "Show any individal workshop lesson"
|
157
|
+
option :"no-open", type: :boolean
|
158
|
+
def show
|
159
|
+
exit_with_no_key
|
160
|
+
title = ::CLI::UI::Prompt.ask(
|
161
|
+
"Which lesson would you like to view?",
|
162
|
+
options: client.list.reject { |l| l["title"] == "Quiz" }.map { |l| " " * l["indent"] + l["title"] }
|
163
|
+
)
|
164
|
+
title.strip!
|
165
|
+
content_order = client.list.find { |l| l["title"] == title }["position"]
|
166
|
+
content = client.show(content_order)
|
167
|
+
client.download_and_extract(content)
|
168
|
+
display_content(content, !options[:"no-open"])
|
169
|
+
end
|
170
|
+
|
171
|
+
desc "set_progress", "Set current lesson to a particular lesson"
|
172
|
+
def set_progress
|
173
|
+
title = ::CLI::UI::Prompt.ask(
|
174
|
+
"Which lesson would you like to set your progress to? All prior lessons will be marked complete",
|
175
|
+
options: client.list.reject { |l| l["title"] == "Quiz" }.map { |l| " " * l["indent"] + l["title"] }
|
176
|
+
)
|
177
|
+
title.strip!
|
178
|
+
content_order = client.list.find { |l| l["title"] == title }["position"]
|
179
|
+
content = client.set_progress(content_order, all_prior: true)
|
180
|
+
say "Setting current progress to #{content.last["title"]}"
|
181
|
+
end
|
182
|
+
|
183
|
+
desc "reset", "Erase all progress and start over"
|
184
|
+
def reset
|
185
|
+
return unless ::CLI::UI.confirm("Are you sure you want to erase all of your progress?", default: false)
|
186
|
+
say "Resetting progress."
|
187
|
+
client.set_progress(nil)
|
61
188
|
end
|
62
189
|
|
63
190
|
no_commands do
|
@@ -68,28 +195,73 @@ module RPW
|
|
68
195
|
|
69
196
|
private
|
70
197
|
|
198
|
+
def exit_with_no_key
|
199
|
+
unless client.setup?
|
200
|
+
say "You have not yet set up the client. Run $ rpw start"
|
201
|
+
exit(1)
|
202
|
+
end
|
203
|
+
unless client.directories_ready?
|
204
|
+
say "You are not in your workshop scratch directory, or you have not yet"
|
205
|
+
say "set up the client. Change directory or run $ rpw start"
|
206
|
+
exit(1)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
71
210
|
def client
|
72
211
|
@client ||= RPW::Client.new
|
73
212
|
end
|
74
213
|
|
214
|
+
def display_content(content, open_after)
|
215
|
+
openable = false
|
216
|
+
case content["style"]
|
217
|
+
when "video"
|
218
|
+
location = "video/#{content["s3_key"]}"
|
219
|
+
openable = true
|
220
|
+
when "quiz"
|
221
|
+
Quiz.start(["give_quiz", "quiz/" + content["s3_key"]])
|
222
|
+
when "lab"
|
223
|
+
location = "lab/#{content["s3_key"][0..-8]}"
|
224
|
+
openable = true
|
225
|
+
when "text"
|
226
|
+
location = "text/#{content["s3_key"]}"
|
227
|
+
openable = true
|
228
|
+
when "cgrp"
|
229
|
+
say "The Complete Guide to Rails Performance has been downloaded and extracted to the ./cgrp directory."
|
230
|
+
say "All source code for the CGRP is in the src directory, PDF and other compiled formats are in the release directory."
|
231
|
+
say "You can check it out now, or to continue: $ rpw next "
|
232
|
+
end
|
233
|
+
if location
|
234
|
+
if openable && !open_after
|
235
|
+
say "Download complete. Open with: $ #{open_command} #{location}"
|
236
|
+
elsif open_after && openable
|
237
|
+
exec "#{open_command} #{location}"
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
require "rbconfig"
|
243
|
+
def open_command
|
244
|
+
host_os = RbConfig::CONFIG["host_os"]
|
245
|
+
case host_os
|
246
|
+
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
247
|
+
"start"
|
248
|
+
when /darwin|mac os/
|
249
|
+
"open"
|
250
|
+
else
|
251
|
+
"xdg-open"
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
75
255
|
def warn_if_already_started
|
76
256
|
return unless client.setup?
|
77
|
-
exit(0) unless
|
78
|
-
"this command will wipe all of your current progress. Continue?
|
257
|
+
exit(0) unless ::CLI::UI.confirm "You have already started the workshop. Continuing "\
|
258
|
+
"this command will wipe all of your current progress. Continue?", default: false
|
79
259
|
end
|
80
260
|
|
81
261
|
def check_version
|
82
262
|
unless client.latest_version?
|
83
263
|
say "WARNING: You are running an old version of rpw."
|
84
264
|
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
265
|
end
|
94
266
|
end
|
95
267
|
end
|
data/lib/rpw/cli/key.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
module RPW
|
2
2
|
class Key < SubCommandBase
|
3
|
-
class_before :exit_with_no_key
|
4
|
-
|
5
3
|
desc "register [EMAIL_ADDRESS]", "Change email registered with Speedshop. One-time only."
|
6
4
|
def register(email)
|
5
|
+
unless client.setup?
|
6
|
+
say "You have not yet set up the client. Run $ rpw start"
|
7
|
+
exit(1)
|
8
|
+
end
|
7
9
|
if client.register_email(email)
|
8
10
|
say "Key registered with #{email}. You should receive a Slack invite soon."
|
9
11
|
else
|
10
12
|
say "Key has already been registered. If you believe this is in error,"\
|
11
|
-
" please email
|
13
|
+
" please email support@speedshop.co"
|
12
14
|
end
|
13
15
|
end
|
14
16
|
end
|
data/lib/rpw/cli/quiz.rb
CHANGED
@@ -14,8 +14,8 @@ module RPW
|
|
14
14
|
def question(data)
|
15
15
|
puts data["prompt"]
|
16
16
|
data["answer_choices"].each { |ac| puts ac }
|
17
|
-
provided_answer = ask("Your answer?")
|
18
|
-
answer_digest = Digest::MD5.hexdigest(data["prompt"] + provided_answer
|
17
|
+
provided_answer = ::CLI::UI::Prompt.ask("Your answer?", options: %w[A B C D])
|
18
|
+
answer_digest = Digest::MD5.hexdigest(data["prompt"] + provided_answer)
|
19
19
|
if answer_digest == data["answer_digest"]
|
20
20
|
say "Correct!"
|
21
21
|
else
|
@@ -13,18 +13,6 @@ module RPW
|
|
13
13
|
def client
|
14
14
|
@client ||= RPW::Client.new
|
15
15
|
end
|
16
|
-
|
17
|
-
def exit_with_no_key
|
18
|
-
unless client.setup?
|
19
|
-
say "You have not yet set up the client. Run $ rpw start"
|
20
|
-
exit(1)
|
21
|
-
end
|
22
|
-
unless client.directories_ready?
|
23
|
-
say "You are not in your workshop scratch directory, or you have not yet"
|
24
|
-
say "set up the client. Change directory or run $ rpw start"
|
25
|
-
exit(1)
|
26
|
-
end
|
27
|
-
end
|
28
16
|
end
|
29
17
|
end
|
30
18
|
end
|
data/lib/rpw/client.rb
CHANGED
@@ -21,19 +21,35 @@ module RPW
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def next
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
return list.first unless client_data["completed"]
|
25
|
+
list.sort_by { |c| c["position"] }.find { |c| c["position"] > current_position }
|
26
|
+
end
|
27
|
+
|
28
|
+
def current
|
29
|
+
return list.first unless client_data["completed"]
|
30
|
+
list.sort_by { |c| c["position"] }.find { |c| c["position"] == current_position }
|
28
31
|
end
|
29
32
|
|
30
33
|
def list
|
31
|
-
|
34
|
+
@list ||= begin
|
35
|
+
if client_data["content_cache_generated"] &&
|
36
|
+
client_data["content_cache_generated"] >= Time.now - 60 * 60
|
37
|
+
|
38
|
+
client_data["content_cache"]
|
39
|
+
else
|
40
|
+
begin
|
41
|
+
client_data["content_cache"] = gateway.list_content
|
42
|
+
client_data["content_cache_generated"] = Time.now
|
43
|
+
client_data["content_cache"]
|
44
|
+
rescue
|
45
|
+
client_data["content_cache"] || (raise Error.new("No internet connection"))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
32
49
|
end
|
33
50
|
|
34
51
|
def show(content_pos)
|
35
|
-
|
36
|
-
gateway.get_content_by_position(content_pos)
|
52
|
+
list.find { |l| l["position"] == content_pos }
|
37
53
|
end
|
38
54
|
|
39
55
|
def directory_setup(home_dir_ok = true)
|
@@ -64,38 +80,23 @@ module RPW
|
|
64
80
|
end
|
65
81
|
end
|
66
82
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
current_lesson: contents.find { |c| c["position"] == client_data["current_lesson"] },
|
79
|
-
sections: chart_section_progress(contents, completed_lessons)
|
80
|
-
}
|
81
|
-
end
|
82
|
-
|
83
|
-
def set_progress(lesson)
|
84
|
-
client_data["current_lesson"] = lesson.to_i
|
85
|
-
end
|
86
|
-
|
87
|
-
def reset_progress
|
88
|
-
client_data["current_lesson"] = 0
|
89
|
-
client_data["completed"] = []
|
83
|
+
def set_progress(pos, all_prior: false)
|
84
|
+
client_data["completed"] = [] && return if pos.nil?
|
85
|
+
if all_prior
|
86
|
+
lessons = list.select { |l| l["position"] <= pos }
|
87
|
+
client_data["completed"] = lessons.map { |l| l["position"] }
|
88
|
+
lessons
|
89
|
+
else
|
90
|
+
lesson = list.find { |l| l["position"] == pos }
|
91
|
+
client_data["completed"] += [pos]
|
92
|
+
lesson
|
93
|
+
end
|
90
94
|
end
|
91
95
|
|
92
96
|
def latest_version?
|
93
97
|
return true unless ClientData.exists?
|
94
|
-
|
95
|
-
|
96
|
-
return true if client_data["last_version_check"] >= Time.now - (60 * 60 * 24)
|
97
|
-
return false if client_data["last_version_check"] == false
|
98
|
-
end
|
98
|
+
return true if client_data["last_version_check"] &&
|
99
|
+
client_data["last_version_check"] >= Time.now - (60 * 60)
|
99
100
|
|
100
101
|
begin
|
101
102
|
latest = gateway.latest_version?
|
@@ -129,31 +130,20 @@ module RPW
|
|
129
130
|
end
|
130
131
|
end
|
131
132
|
|
133
|
+
def complete(position)
|
134
|
+
if client_data["completed"]
|
135
|
+
# we actually have to put the _next_ lesson on the completed stack
|
136
|
+
set_progress(self.next["position"])
|
137
|
+
else
|
138
|
+
client_data["completed"] = []
|
139
|
+
set_progress(position)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
132
143
|
private
|
133
144
|
|
134
|
-
def
|
135
|
-
|
136
|
-
client_data["completed"] ||= []
|
137
|
-
client_data["completed"] += [client_data["current_lesson"] || 0]
|
138
|
-
end
|
139
|
-
|
140
|
-
def chart_section_progress(contents, completed)
|
141
|
-
contents.group_by { |c| c["position"] / 100 }
|
142
|
-
.each_with_object([]) do |(_, c), memo|
|
143
|
-
completed_str = c.map { |l|
|
144
|
-
if l["position"] == client_data["current_lesson"]
|
145
|
-
"O"
|
146
|
-
elsif completed.include?(l["position"])
|
147
|
-
"X"
|
148
|
-
else
|
149
|
-
"."
|
150
|
-
end
|
151
|
-
}.join
|
152
|
-
memo << {
|
153
|
-
title: c[0]["title"],
|
154
|
-
progress: completed_str
|
155
|
-
}
|
156
|
-
end
|
145
|
+
def current_position
|
146
|
+
@current_position ||= client_data["completed"]&.last || 0
|
157
147
|
end
|
158
148
|
|
159
149
|
def client_data
|
data/lib/rpw/client_data.rb
CHANGED
@@ -20,8 +20,8 @@ module RPW
|
|
20
20
|
begin
|
21
21
|
File.open(filestore_location, "w") { |f| f.write(YAML.dump(data)) }
|
22
22
|
rescue
|
23
|
-
raise Error, "The RPW data at #{filestore_location} is not writable. \
|
24
|
-
|
23
|
+
# raise Error, "The RPW data at #{filestore_location} is not writable. \
|
24
|
+
# Check your file permissions."
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
data/lib/rpw/gateway.rb
CHANGED
@@ -16,16 +16,6 @@ module RPW
|
|
16
16
|
Excon.get(domain + "/license", user: key).status == 200
|
17
17
|
end
|
18
18
|
|
19
|
-
def get_content_by_position(position)
|
20
|
-
response = Excon.get(domain + "/contents/positional?position=#{position}", user: @key)
|
21
|
-
if response.status == 200
|
22
|
-
JSON.parse(response.body)
|
23
|
-
else
|
24
|
-
puts response.inspect
|
25
|
-
raise Error, "There was a problem fetching this content."
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
19
|
def list_content
|
30
20
|
response = Excon.get(domain + "/contents", user: @key)
|
31
21
|
if response.status == 200
|
@@ -37,17 +27,21 @@ module RPW
|
|
37
27
|
end
|
38
28
|
|
39
29
|
def download_content(content, folder:)
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
30
|
+
::CLI::UI::Progress.progress do |bar|
|
31
|
+
downloaded_file = File.open("#{folder}/#{content["s3_key"]}.partial", "w")
|
32
|
+
streamer = lambda do |chunk, remaining_bytes, total_bytes|
|
33
|
+
downloaded_file.write(chunk)
|
34
|
+
bar.tick(set_percent: 1 - (remaining_bytes.to_f / total_bytes).round(2))
|
35
|
+
end
|
36
|
+
response = Excon.get(content["url"], response_block: streamer)
|
37
|
+
unless response.status == 200
|
38
|
+
puts response.inspect
|
39
|
+
raise Error.new("Server problem: #{response.status}")
|
40
|
+
end
|
41
|
+
downloaded_file.close
|
42
|
+
File.rename(downloaded_file, "#{folder}/#{content["s3_key"]}")
|
43
|
+
bar.tick(set_percent: 1)
|
46
44
|
end
|
47
|
-
Excon.get(content["url"], response_block: streamer)
|
48
|
-
downloaded_file.close
|
49
|
-
print "\n"
|
50
|
-
File.rename(downloaded_file, "#{folder}/#{content["s3_key"]}")
|
51
45
|
end
|
52
46
|
|
53
47
|
def latest_version?
|
data/lib/rpw/version.rb
CHANGED
data/rpw.gemspec
CHANGED
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:
|
4
|
+
version: 1.3.0
|
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-11-
|
11
|
+
date: 2020-11-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: cli-ui
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
description:
|
56
70
|
email:
|
57
71
|
- nate@speedshop.co
|
@@ -76,8 +90,6 @@ files:
|
|
76
90
|
- lib/rpw/cli.rb
|
77
91
|
- lib/rpw/cli/bannerlord.rb
|
78
92
|
- lib/rpw/cli/key.rb
|
79
|
-
- lib/rpw/cli/lesson.rb
|
80
|
-
- lib/rpw/cli/progress.rb
|
81
93
|
- lib/rpw/cli/quiz.rb
|
82
94
|
- lib/rpw/cli/sub_command_base.rb
|
83
95
|
- lib/rpw/client.rb
|
data/lib/rpw/cli/lesson.rb
DELETED
@@ -1,99 +0,0 @@
|
|
1
|
-
module RPW
|
2
|
-
class Lesson < SubCommandBase
|
3
|
-
class_before :exit_with_no_key
|
4
|
-
|
5
|
-
desc "next", "Proceed to the next lesson of the workshop"
|
6
|
-
option :open
|
7
|
-
def next
|
8
|
-
say "Proceeding to next lesson..."
|
9
|
-
content = client.next
|
10
|
-
|
11
|
-
if content.nil?
|
12
|
-
RPW::CLI.new.print_banner
|
13
|
-
say "Congratulations!"
|
14
|
-
say "You have completed the Rails Performance Workshop."
|
15
|
-
exit(0)
|
16
|
-
end
|
17
|
-
|
18
|
-
client.download_and_extract(content)
|
19
|
-
client.increment_current_lesson!(content["position"])
|
20
|
-
display_content(content, options[:open])
|
21
|
-
end
|
22
|
-
|
23
|
-
desc "complete", "Mark the current lesson as complete"
|
24
|
-
def complete
|
25
|
-
say "Marked current lesson as complete"
|
26
|
-
client.complete
|
27
|
-
end
|
28
|
-
|
29
|
-
desc "list", "Show all available workshop lessons"
|
30
|
-
def list
|
31
|
-
say "All available workshop lessons:"
|
32
|
-
client.list.each do |lesson|
|
33
|
-
puts "#{" " * lesson["indent"]}[#{lesson["position"]}]: #{lesson["title"]}"
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
desc "download [CONTENT | all]", "Download one or all workshop contents"
|
38
|
-
def download(content_pos)
|
39
|
-
to_download = if content_pos.downcase == "all"
|
40
|
-
client.list
|
41
|
-
else
|
42
|
-
[client.show(content_pos)]
|
43
|
-
end
|
44
|
-
to_download.each { |content| client.download_and_extract(content) }
|
45
|
-
end
|
46
|
-
|
47
|
-
desc "show [CONTENT]", "Show any workshop lesson, shows current lesson w/no arguments"
|
48
|
-
option :open
|
49
|
-
def show(content_order = :current)
|
50
|
-
content = client.show(content_order)
|
51
|
-
client.download_and_extract(content)
|
52
|
-
display_content(content, options[:open])
|
53
|
-
end
|
54
|
-
|
55
|
-
private
|
56
|
-
|
57
|
-
def display_content(content, open_after)
|
58
|
-
say "Current Lesson: #{content["title"]}"
|
59
|
-
openable = false
|
60
|
-
case content["style"]
|
61
|
-
when "video"
|
62
|
-
location = "video/#{content["s3_key"]}"
|
63
|
-
openable = true
|
64
|
-
when "quiz"
|
65
|
-
Quiz.start(["give_quiz", "quiz/" + content["s3_key"]])
|
66
|
-
when "lab"
|
67
|
-
location = "lab/#{content["s3_key"][0..-8]}"
|
68
|
-
when "text"
|
69
|
-
location = "lab/#{content["s3_key"]}"
|
70
|
-
openable = true
|
71
|
-
when "cgrp"
|
72
|
-
say "The Complete Guide to Rails Performance has been downloaded and extracted to the ./cgrp directory."
|
73
|
-
say "All source code for the CGRP is in the src directory, PDF and other compiled formats are in the release directory."
|
74
|
-
end
|
75
|
-
if location
|
76
|
-
if openable && !open_after
|
77
|
-
say "This file can be opened automatically if you use the --open flag next time."
|
78
|
-
say "e.g. $ rpw lesson next --open"
|
79
|
-
say "Download complete. Open with: $ #{open_command} #{location}"
|
80
|
-
elsif open_after && openable
|
81
|
-
exec "#{open_command} #{location}"
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
require "rbconfig"
|
87
|
-
def open_command
|
88
|
-
host_os = RbConfig::CONFIG["host_os"]
|
89
|
-
case host_os
|
90
|
-
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
91
|
-
"start"
|
92
|
-
when /darwin|mac os/
|
93
|
-
"open"
|
94
|
-
else
|
95
|
-
"xdg-open"
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
data/lib/rpw/cli/progress.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
module RPW
|
2
|
-
class Progress < SubCommandBase
|
3
|
-
class_before :exit_with_no_key
|
4
|
-
|
5
|
-
desc "set [LESSON]", "Set current lesson to a particular lesson"
|
6
|
-
def set(lesson)
|
7
|
-
client.set_progress(lesson)
|
8
|
-
end
|
9
|
-
|
10
|
-
desc "reset", "Erase all progress and start over"
|
11
|
-
def reset
|
12
|
-
yes? "Are you sure you want to reset your progress? (Y/N)"
|
13
|
-
client.reset_progress
|
14
|
-
end
|
15
|
-
|
16
|
-
desc "show", "Show current workshop progress"
|
17
|
-
def show
|
18
|
-
data = client.progress
|
19
|
-
say "The Rails Performance Workshop"
|
20
|
-
say "You have completed #{data[:completed]} out of #{data[:total]} total sections."
|
21
|
-
say "Current lesson: #{data[:current_lesson]["title"]}" if data[:current_lesson]
|
22
|
-
say "Progress by Section (X == completed, O == current):"
|
23
|
-
data[:sections].each do |section|
|
24
|
-
say "#{section[:title]}: #{section[:progress]}"
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
default_task :show
|
31
|
-
end
|
32
|
-
end
|