rpw 0.0.2 → 1.0.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 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