rpw 0.0.2 → 1.0.0

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: 45b744148f7441c89e4b754ec0b19105e0bfbdb8e9b97e0196d0d6c0ff51266c
4
+ data.tar.gz: 8eac7fc7584553d6af5e2c250520ff05844e9243882baceeb133f163cee2901e
5
5
  SHA512:
6
- metadata.gz: 3557e0688d5141ae96bcfed87ced6539059bbbd0517a3f2bf85910c5b13b99e153d940fbf56d9bd86bf2cf23525f32a9c848d7fa3ee2d58ae664db806854e93b
7
- data.tar.gz: 0e804e064e64cd66c703238e00d80f75a35871bf724c08203594d4eec1c94db5815d5ac2bacc11b743e008eb1aeec2c39f2b240fefac87aebe2c005bb194ae19
6
+ metadata.gz: e20a592d2777d2fbd325cae566fe4e49ed9d07eb496c6da31d1dd70010d08baf08827095c4ffec842e7a9cfeb886d755afd632bedf6e9b671fd3d32fd062382e
7
+ data.tar.gz: 0b8360255ef4fd5fba85bf982236e44b0f07050a9d45c1f557e047b82d5dd91a243f2c4be96c8202953a3e993c09ecfaec4de9f803c803f038456e952d18cc08
@@ -0,0 +1,109 @@
1
+ name: Test
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ name: >-
8
+ ${{ matrix.os }} ${{ matrix.ruby }}
9
+ env:
10
+ CI: true
11
+ TESTOPTS: -v
12
+
13
+ runs-on: ${{ matrix.os }}
14
+ if: |
15
+ !( contains(github.event.pull_request.title, '[ci skip]')
16
+ || contains(github.event.pull_request.title, '[skip ci]')
17
+ || contains(github.event.head_commit.message, '[ci skip]')
18
+ || contains(github.event.head_commit.message, '[skip ci]'))
19
+ strategy:
20
+ fail-fast: true
21
+ matrix:
22
+ os: [ ubuntu-20.04, macos-10.15, windows-2019 ]
23
+ ruby: [ 2.3, 2.6, 2.7, head ]
24
+
25
+ steps:
26
+ - name: repo checkout
27
+ uses: actions/checkout@v2
28
+
29
+ - name: load ruby
30
+ uses: MSP-Greg/setup-ruby-pkgs@v1
31
+ with:
32
+ ruby-version: ${{ matrix.ruby }}
33
+
34
+ - name: bundle install
35
+ run: |
36
+ bundle install --jobs 4 --retry 3
37
+
38
+ - name: standardrb
39
+ run: bundle exec standardrb
40
+
41
+ - name: test
42
+ run: bundle exec rake test
43
+
44
+ build-live:
45
+ services:
46
+ postgres:
47
+ image: postgres:alpine
48
+ env:
49
+ POSTGRES_PASSWORD: password
50
+ ports:
51
+ - 5432:5432
52
+ # needed because the postgres container does not provide a healthcheck
53
+ options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
54
+
55
+ name: Test against a live server
56
+ env:
57
+ CI: true
58
+ TESTOPTS: -v
59
+ LIVE_SERVER: true
60
+
61
+ runs-on: ubuntu-latest
62
+
63
+ steps:
64
+ - name: repo checkout
65
+ uses: actions/checkout@v2
66
+
67
+ - name: load ruby
68
+ uses: MSP-Greg/setup-ruby-pkgs@v1
69
+ with:
70
+ ruby-version: 2.7.2
71
+
72
+ - name: bundle install
73
+ run: |
74
+ bundle install --jobs 4 --retry 3
75
+
76
+ - uses: actions/checkout@master
77
+ with:
78
+ repository: speedshop/licensor
79
+ path: server
80
+
81
+ - name: nuke server ruby requirement
82
+ run: grep -v "^ruby" Gemfile > temp && mv -f temp Gemfile
83
+ working-directory: ./server
84
+
85
+ - name: bundle install the server
86
+ run: |
87
+ bundle install --jobs 4 --retry 3
88
+ bundle exec rails db:setup
89
+ working-directory: ./server
90
+ env:
91
+ POSTGRES_USER: postgres
92
+ POSTGRES_PASSWORD: password
93
+
94
+ - name: start the server
95
+ run: rails server &
96
+ working-directory: ./server
97
+ env:
98
+ POSTGRES_USER: postgres
99
+ POSTGRES_PASSWORD: password
100
+
101
+ - name: wait for server
102
+ run: |
103
+ until $(curl --output /dev/null --silent --head --fail http://localhost:3000); do
104
+ printf '.'
105
+ sleep 1
106
+ done
107
+
108
+ - name: test
109
+ run: bundle exec rake test
data/.gitignore CHANGED
@@ -3,3 +3,4 @@ bundle
3
3
  .rpw_key
4
4
  .rpw_info
5
5
  smoketest
6
+ scripts
@@ -1,46 +1,47 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rpw (0.0.2)
4
+ rpw (1.0.0)
5
+ excon
5
6
  thor
6
- typhoeus
7
+ thor-hollaback
7
8
 
8
9
  GEM
9
10
  remote: https://rubygems.org/
10
11
  specs:
11
12
  ast (2.4.1)
12
- ethon (0.12.0)
13
- ffi (>= 1.3.0)
14
- ffi (1.13.1)
13
+ excon (0.78.0)
14
+ hollaback (0.1.0)
15
15
  minitest (5.14.2)
16
- parallel (1.19.2)
17
- parser (2.7.1.5)
16
+ parallel (1.20.0)
17
+ parser (2.7.2.0)
18
18
  ast (~> 2.4.1)
19
19
  rainbow (3.0.0)
20
20
  rake (13.0.1)
21
- regexp_parser (1.8.1)
21
+ regexp_parser (1.8.2)
22
22
  rexml (3.2.4)
23
- rubocop (0.92.0)
23
+ rubocop (1.2.0)
24
24
  parallel (~> 1.10)
25
25
  parser (>= 2.7.1.5)
26
26
  rainbow (>= 2.2.2, < 4.0)
27
- regexp_parser (>= 1.7)
27
+ regexp_parser (>= 1.8)
28
28
  rexml
29
- rubocop-ast (>= 0.5.0)
29
+ rubocop-ast (>= 1.0.1)
30
30
  ruby-progressbar (~> 1.7)
31
31
  unicode-display_width (>= 1.4.0, < 2.0)
32
- rubocop-ast (0.7.1)
32
+ rubocop-ast (1.1.1)
33
33
  parser (>= 2.7.1.5)
34
34
  rubocop-performance (1.8.1)
35
35
  rubocop (>= 0.87.0)
36
36
  rubocop-ast (>= 0.4.0)
37
37
  ruby-progressbar (1.10.1)
38
- standard (0.7)
39
- rubocop (= 0.92)
38
+ standard (0.9.0)
39
+ rubocop (= 1.2.0)
40
40
  rubocop-performance (= 1.8.1)
41
41
  thor (1.0.1)
42
- typhoeus (1.4.0)
43
- ethon (>= 0.9.0)
42
+ thor-hollaback (0.2.0)
43
+ hollaback (~> 0.1.0)
44
+ thor (>= 0.19.1)
44
45
  unicode-display_width (1.7.0)
45
46
 
46
47
  PLATFORMS
data/HISTORY.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## 1.0.0
2
+
3
+ * First release!
4
+
5
+ ## 0.0.6
6
+
7
+ * more bugfixes from beta testers
8
+ * CI
9
+
10
+ ## 0.0.5
11
+
12
+ * more bugfixes from beta testers
13
+
14
+ ## 0.0.4
15
+
16
+ * bugfixes
17
+
18
+ ## 0.0.3
19
+
20
+ * beta
21
+
1
22
  ## 0.0.2
2
23
 
3
24
  * 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,136 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "thor"
4
- require_relative "../lib/rpw"
5
-
6
- module RPW
7
- class SubCommandBase < Thor
8
- def self.banner(command, namespace = nil, subcommand = false)
9
- "#{basename} #{subcommand_prefix} #{command.usage}"
10
- end
11
-
12
- def self.subcommand_prefix
13
- name.gsub(%r{.*::}, "").gsub(%r{^[A-Z]}) { |match| match[0].downcase }.gsub(%r{[A-Z]}) { |match| "-#{match[0].downcase}" }
14
- end
15
- end
16
-
17
- class Lesson < SubCommandBase
18
- desc "next", "Proceed to the next lesson of the workshop"
19
-
20
- def next
21
- say "Proceeding to next lesson..."
22
- client.next
23
- end
24
-
25
- desc "complete", "Mark the current lesson as complete"
26
-
27
- def complete
28
- say "Marked current lesson as complete"
29
- client.complete
30
- end
31
-
32
- desc "list", "Show all available workshop lessons"
33
-
34
- def list
35
- say "All available workshop lessons:"
36
- client.list.each do |lesson|
37
- puts "#{" " * lesson["indent"]}[#{lesson["position"]}]: #{lesson["title"]}"
38
- end
39
- end
40
-
41
- desc "download [CONTENT | all]", "Download one or all workshop contents"
42
-
43
- def download(content)
44
- client.download(content)
45
- end
46
-
47
- desc "show [CONTENT]", "Show any workshop lesson"
48
-
49
- def show(content)
50
- client.show(content)
51
- end
52
-
53
- private
54
-
55
- def client
56
- @client ||= RPW::Client.new
57
- end
58
- end
59
-
60
- class Progress < SubCommandBase
61
- desc "set [LESSON]", "Set current lesson to a particular lesson"
62
-
63
- def set(lesson)
64
- client.set_progress(lesson)
65
- end
66
-
67
- desc "reset", "Erase all progress and start over"
68
-
69
- def reset
70
- yes? "Are you sure you want to reset your progress? (Y/N)"
71
- client.reset_progress
72
- end
73
-
74
- desc "show", "Show current workshop progress"
75
- def show
76
- data = client.progress
77
- say "The Rails Performance Workshop"
78
- say "You have completed #{data[:completed]} out of #{data[:total]} total sections."
79
- say "Current lesson: #{data[:current_lesson]["title"]}"
80
- say "Progress by Section (X == completed, O == current):"
81
- data[:sections].each do |section|
82
- say "#{section[:title]}: #{section[:progress]}"
83
- end
84
- end
85
-
86
- private
87
-
88
- def client
89
- @client ||= RPW::Client.new
90
- end
91
-
92
- default_task :show
93
- end
94
-
95
- class CLI < Thor
96
- desc "lesson [SUBCOMMAND]", "View and download lessons"
97
- subcommand "lesson", Lesson
98
- desc "progress [SUBCOMMAND]", "View and set progress"
99
- subcommand "progress", Progress
100
-
101
- def self.exit_on_failure?
102
- true
103
- end
104
-
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)"
109
-
110
- say "We'll create a .rpw_key file in the current directory to save your purchase key and course data."
111
- key = ask("Your Purchase Key: ")
112
-
113
- client.setup(key)
114
-
115
- 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."
117
-
118
- client.directory_setup
119
-
120
- say "Setup complete!"
121
- end
122
-
123
- desc "support", "Create a new support ticket, report a bug or issue"
124
-
125
- def support
126
- end
127
-
128
- private
129
-
130
- def client
131
- @client ||= RPW::Client.new
132
- end
133
- end
134
- end
3
+ require_relative "../lib/rpw/cli"
135
4
 
136
5
  RPW::CLI.start(ARGV)
data/lib/rpw.rb CHANGED
@@ -1,289 +1,8 @@
1
- require "typhoeus"
2
- require "json"
1
+ require "rpw/version"
2
+ require "rpw/client"
3
+ require "rpw/client_data"
4
+ require "rpw/gateway"
3
5
 
4
6
  module RPW
5
7
  class Error < StandardError; end
6
-
7
- class Gateway
8
- attr_accessor :domain
9
-
10
- def initialize(domain, key)
11
- @domain = domain
12
- @key = key
13
- end
14
-
15
- class Error < StandardError; end
16
-
17
- def authenticate_key(key)
18
- Typhoeus.get(domain + "/license", userpwd: key + ":").success?
19
- end
20
-
21
- def get_content_by_position(position)
22
- response = Typhoeus.get(domain + "/contents/positional?position=#{position}", userpwd: @key + ":")
23
- if response.success?
24
- JSON.parse(response.body)
25
- else
26
- puts response.inspect
27
- raise Error, "There was a problem fetching this content."
28
- end
29
- end
30
-
31
- def list_content
32
- response = Typhoeus.get(domain + "/contents", userpwd: @key + ":")
33
- if response.success?
34
- JSON.parse(response.body)
35
- else
36
- puts response.inspect
37
- raise Error, "There was a problem fetching this content."
38
- end
39
- end
40
-
41
- def download_content(content, folder:)
42
- puts "Downloading #{content["title"]}..."
43
- downloaded_file = File.open("#{folder}/#{content["s3_key"]}", "w")
44
- request = Typhoeus::Request.new(content["url"])
45
- request.on_body do |chunk|
46
- downloaded_file.write(chunk)
47
- printf(".") if rand(10) == 0 # lol
48
- end
49
- request.on_complete { |response| downloaded_file.close }
50
- request
51
- end
52
- end
53
-
54
- class Client
55
- RPW_SERVER_DOMAIN = ENV["RPW_SERVER_DOMAIN"] || "https://rpw-licensor.speedshop.co"
56
-
57
- def setup(key)
58
- gateway.authenticate_key(key)
59
- keyfile["key"] = key
60
- end
61
-
62
- def directory_setup
63
- ["video", "quiz", "lab", "text", "cgrp"].each do |path|
64
- FileUtils.mkdir_p(path) unless File.directory?(path)
65
- end
66
-
67
- client_data["completed"] = [] # just to write the file
68
-
69
- unless File.exist?(".gitignore") && File.read(".gitignore").match(/rpw_key/)
70
- File.open(".gitignore", "a") do |f|
71
- f.puts "\n"
72
- f.puts ".rpw_key\n"
73
- f.puts ".rpw_info\n"
74
- f.puts "video\n"
75
- f.puts "quiz\n"
76
- f.puts "lab\n"
77
- f.puts "text\n"
78
- f.puts "cgrp\n"
79
- end
80
- end
81
- end
82
-
83
- def next
84
- content = next_content
85
- unless File.exist?(content["style"] + "/" + content["s3_key"])
86
- gateway.download_content(content, folder: content["style"]).run
87
- extract_content(content) if content["s3_key"].end_with?(".tar.gz")
88
- end
89
- client_data["current_lesson"] = content["position"]
90
- display_content(content)
91
- end
92
-
93
- def complete
94
- client_data["completed"] ||= []
95
- client_data["completed"] += [client_data["current_lesson"]]
96
- end
97
-
98
- def list
99
- gateway.list_content
100
- end
101
-
102
- def show(content_pos)
103
- content = gateway.get_content_by_position(content_pos)
104
- unless File.exist?(content["style"] + "/" + content["s3_key"])
105
- gateway.download_content(content, folder: content["style"]).run
106
- extract_content(content) if content["s3_key"].end_with?(".tar.gz")
107
- end
108
- client_data["current_lesson"] = content["position"]
109
- display_content(content)
110
- end
111
-
112
- def download(content_pos)
113
- if content_pos.downcase == "all"
114
- to_download = gateway.list_content
115
- hydra = Typhoeus::Hydra.new(max_concurrency: 5)
116
- to_download.each do |content|
117
- unless File.exist?(content["style"] + "/" + content["s3_key"])
118
- hydra.queue gateway.download_content(content, folder: content["style"])
119
- end
120
- end
121
- hydra.run
122
- to_download.each { |content| extract_content(content) if content["s3_key"].end_with?(".tar.gz") }
123
- else
124
- content = gateway.get_content_by_position(content_pos)
125
- unless File.exist?(content["style"] + "/" + content["s3_key"])
126
- gateway.download_content(content, folder: content["style"]).run
127
- extract_content(content) if content["s3_key"].end_with?(".tar.gz")
128
- end
129
- end
130
- end
131
-
132
- def progress
133
- contents = gateway.list_content
134
- {
135
- completed: client_data["completed"].size,
136
- total: contents.size,
137
- current_lesson: contents.find { |c| c["position"] == client_data["current_lesson"] },
138
- sections: chart_section_progress(contents)
139
- }
140
- end
141
-
142
- def set_progress(lesson)
143
- client_data["current_lesson"] = lesson.to_i
144
- end
145
-
146
- def reset_progress
147
- client_data["current_lesson"] = 0
148
- client_data["completed"] = []
149
- end
150
-
151
- private
152
-
153
- def chart_section_progress(contents)
154
- contents.group_by { |c| c["position"] / 100 }
155
- .each_with_object([]) do |(_, c), memo|
156
- completed_str = c.map { |l|
157
- if l["position"] == client_data["current_lesson"]
158
- "O"
159
- elsif client_data["completed"].include?(l["position"])
160
- "X"
161
- else
162
- "."
163
- end
164
- }.join
165
- memo << {
166
- title: c[0]["title"],
167
- progress: completed_str
168
- }
169
- end
170
- end
171
-
172
- def next_content
173
- contents = gateway.list_content
174
- return contents.first unless client_data["completed"]
175
- contents.delete_if { |c| client_data["completed"].include? c["position"] }
176
- contents.min_by { |c| c["position"] }
177
- end
178
-
179
- def client_data
180
- @client_data ||= ClientData.new
181
- end
182
-
183
- def keyfile
184
- @keyfile ||= Keyfile.new
185
- end
186
-
187
- def gateway
188
- @gateway ||= Gateway.new(RPW_SERVER_DOMAIN, keyfile["key"])
189
- end
190
-
191
- def extract_content(content)
192
- folder = content["style"]
193
- `tar -C #{folder} -xvzf #{folder}/#{content["s3_key"]}`
194
- end
195
-
196
- def display_content(content)
197
- case content["style"]
198
- when "video"
199
- puts "Opening video: #{content["title"]}"
200
- exec("open video/#{content["s3_key"]}")
201
- when "quiz"
202
- Quiz.start(["give_quiz", "quiz/" + content["s3_key"]])
203
- 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"
206
- when "text"
207
- puts "Opening in your editor: #{content["title"]}"
208
- exec("$EDITOR text/#{content["s3_key"]}")
209
- when "cgrp"
210
- puts "The Complete Guide to Rails Performance has been downloaded and extracted to the cgrp directory."
211
- puts "All source code for the CGRP is in the src directory, PDF and other compiled formats are in the release directory."
212
- end
213
- end
214
- end
215
-
216
- require "fileutils"
217
- require "yaml"
218
-
219
- class ClientData
220
- DOTFILE_NAME = ".rpw_info"
221
-
222
- def [](key)
223
- make_sure_dotfile_exists
224
- data[key]
225
- end
226
-
227
- def []=(key, value)
228
- data[key] = value
229
- begin
230
- File.open(self.class::DOTFILE_NAME, "w") { |f| f.write(YAML.dump(data)) }
231
- rescue
232
- raise Error, "The RPW data file in this directory is not writable. \
233
- Check your file permissions."
234
- end
235
- end
236
-
237
- private
238
-
239
- def data
240
- @data ||= begin
241
- yaml = begin
242
- YAML.safe_load(File.read(self.class::DOTFILE_NAME))
243
- rescue
244
- nil
245
- end
246
- yaml || {}
247
- end
248
- end
249
-
250
- def make_sure_dotfile_exists
251
- return true if File.exist?(self.class::DOTFILE_NAME)
252
- begin
253
- FileUtils.touch(self.class::DOTFILE_NAME)
254
- rescue
255
- raise Error, "Could not create the RPW data file in this directory \
256
- Check your file permissions."
257
- end
258
- end
259
- end
260
-
261
- class Keyfile < ClientData
262
- DOTFILE_NAME = ".rpw_key"
263
- end
264
-
265
- require "digest"
266
-
267
- class Quiz < Thor
268
- desc "give_quiz FILENAME", ""
269
- def give_quiz(filename)
270
- @quiz_data = YAML.safe_load(File.read(filename))
271
- @quiz_data["questions"].each { |q| question(q) }
272
- end
273
-
274
- private
275
-
276
- def question(data)
277
- puts data["prompt"]
278
- data["answer_choices"].each { |ac| puts ac }
279
- provided_answer = ask("Your answer?")
280
- answer_digest = Digest::MD5.hexdigest(data["prompt"] + provided_answer)
281
- if answer_digest == data["answer_digest"]
282
- say "Correct!"
283
- else
284
- say "Incorrect."
285
- say "I encourage you to try reviewing the material to see what the correct answer is."
286
- end
287
- end
288
- end
289
8
  end