rpw 0.0.1 → 0.0.6

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: 7b470a6daefa536118bc45ad650345ae904890cfdc21340f4241fe95585840ca
4
- data.tar.gz: 4055764a0110865d0bd1faa4616a269c16beeceee6868ad9f1b5a8188b172f30
3
+ metadata.gz: 796740f7cd5cfebc0f46f72669da6d1b2941436c984d2a624a18200ae3466495
4
+ data.tar.gz: 3b986dbab69c20f2e840b2197175119e428d19b117378ef9192a5ce31178cbac
5
5
  SHA512:
6
- metadata.gz: 797f1c5528477c5befc5bed08a2dba98aabf2968be757a682a5f5dda81ae9815bbfa572f4974504eecb478a7bf106abef1562945f3865f09a8429badf68f61b4
7
- data.tar.gz: a948446a6b948793d63dad89880c1e958f21532eaa278b9e232248a2b298a59c8b103e21c4f0ec537fb003d066abf79da56d8d3e97fde3aad49c9c99fbfaf7f3
6
+ metadata.gz: 6b5530f032ece3e44819374cb7001530359a913e5b025e04f19313c6afeb1ec2450667783c8d44e20a18b1d0c16220a19f60d29275b1f3e9852a5401a2f2b7ba
7
+ data.tar.gz: 1389deea5fe3705527d841abc84d9f6f33b112d5a8d02502bb8d8f8a5df90ffaedc520f8449da2891405d7702a31d65ac193087c12a5a3588afe53cf6f4dee14
@@ -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.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
@@ -1,2 +1,6 @@
1
1
  bundle
2
- *.gem
2
+ *.gem
3
+ .rpw_key
4
+ .rpw_info
5
+ smoketest
6
+ scripts
@@ -1,46 +1,47 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rpw (0.0.1)
4
+ rpw (0.0.6)
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
+ ## 0.0.6
2
+
3
+ * more bugfixes from beta testers
4
+ * CI
5
+
6
+ ## 0.0.5
7
+
8
+ * more bugfixes from beta testers
9
+
10
+ ## 0.0.4
11
+
12
+ * bugfixes
13
+
14
+ ## 0.0.3
15
+
16
+ * beta
17
+
18
+ ## 0.0.2
19
+
20
+ * first command-complete version
21
+
1
22
  ## 0.0.1
2
23
 
3
24
  * setup command works
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,61 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "thor"
4
- require_relative "../lib/rpw"
5
-
6
- module RPW
7
- class CLI < Thor
8
- class_option :verbose, type: :boolean, aliases: "-v"
9
-
10
- desc "next", "Proceed to the next section of the workshop"
11
-
12
- def next
13
- # ...
14
- end
15
-
16
- desc "setup", "Set up your purchase key in order to download workshop sections"
17
-
18
- def setup
19
- say "We'll create a .rpw_key file in the current directory to save your key for future use."
20
- key = ask("Purchase Key: ")
21
-
22
- client.setup(key)
23
-
24
- say "Successfully authenticated with the RPW server and saved your key."
25
- end
26
-
27
- desc "status", "Show your current workshop progression"
28
-
29
- def status
30
- # ...
31
- end
32
-
33
- desc "support", "Create a new support ticket, report a bug or issue"
34
-
35
- def support
36
- end
37
-
38
- desc "show CONTENT", "Show any workshop section (use list to see all section names)"
39
-
40
- def show(content)
41
- end
42
-
43
- desc "list", "Show all available workshop content"
44
-
45
- def list
46
- end
47
-
48
- desc "download [CONTENT | all]", "Download one or all workshop contents"
49
-
50
- def download(content)
51
- end
52
-
53
- private
54
-
55
- def client
56
- @client ||= RPW::Client.new
57
- end
58
- end
59
- end
3
+ require_relative "../lib/rpw/cli"
60
4
 
61
5
  RPW::CLI.start(ARGV)
data/lib/rpw.rb CHANGED
@@ -1,63 +1,8 @@
1
- require "typhoeus"
2
- require "base64"
1
+ require "rpw/version"
2
+ require "rpw/client"
3
+ require "rpw/client_data"
4
+ require "rpw/gateway"
3
5
 
4
6
  module RPW
5
- class Gateway
6
- attr_accessor :domain
7
-
8
- def initialize(domain)
9
- @domain = domain
10
- end
11
-
12
- class Error < StandardError; end
13
-
14
- def authenticate_key(key)
15
- request = Typhoeus::Request.new(
16
- domain + "/license",
17
- method: :get,
18
- headers: { Authorization: "Basic #{Base64.encode64(key + ':')}" }
19
- )
20
-
21
- request.on_complete do |response|
22
- if response.success?
23
- true
24
- else
25
- raise Error, "Server responded: #{response.code} #{response.response_body}"
26
- end
27
- end
28
-
29
- request.run
30
- end
31
-
32
- def get_resource(resource)
33
- end
34
- end
35
-
36
- class Client
37
- DOTFILE_NAME = ".rpw_key"
38
- RPW_SERVER_DOMAIN = "https://rpw-licensor.speedshop.co"
39
-
40
- class Error < StandardError; end
41
-
42
- def setup(key)
43
- # authenticate against server
44
- gateway.authenticate_key(key)
45
-
46
- # write authenticated key
47
- begin
48
- File.open(DOTFILE_NAME, "w") { |f| f.write(key) }
49
- rescue
50
- raise Error.new "Could not create dotfile in this directory \
51
- to save your key. Check your file permissions."
52
- end
53
-
54
- key
55
- end
56
-
57
- private
58
-
59
- def gateway
60
- @gateway ||= Gateway.new(RPW_SERVER_DOMAIN)
61
- end
62
- end
7
+ class Error < StandardError; end
63
8
  end
@@ -0,0 +1,22 @@
1
+ ## Installation Requirements
2
+
3
+ This client assumes you have `tar` installed.
4
+
5
+ ## Important Commands
6
+
7
+ Here are some important commands for you to know:
8
+
9
+ $ rpw lesson next | Proceed to the next part of the workshop.
10
+ $ rpw lesson complete | Mark current lesson as complete.
11
+ $ rpw lesson list | List all workshop lessons. Note each lesson is preceded with an ID.
12
+ $ rpw lesson download | Download any or all lessons.
13
+ $ rpw lesson show | Show any particular workshop lesson.
14
+ $ rpw progress | Show where you're currently at in the workshop.
15
+ $ rpw help | Help! You can also ask in Slack.
16
+
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
@@ -0,0 +1,59 @@
1
+ module RPW
2
+ class Bannerlord
3
+ class << self
4
+ def print_banner
5
+ puts r
6
+ if `tput cols 80`.to_i < 80
7
+ puts small_banner
8
+ else
9
+ puts banner
10
+ end
11
+ puts reset
12
+ end
13
+
14
+ def r
15
+ "\e[31m"
16
+ end
17
+
18
+ def reset
19
+ "\e[0m"
20
+ end
21
+
22
+ def small_banner
23
+ %(
24
+ _____ _ _____ _ _
25
+ |_ _| |_ ___ | __ |___|_| |___
26
+ | | | | -_| | -| .'| | |_ -|
27
+ |_| |_|_|___| |__|__|__,|_|_|___|
28
+ _____ ___
29
+ | _ |___ ___| _|___ ___ _____ ___ ___ ___ ___
30
+ | __| -_| _| _| . | _| | .'| | _| -_|
31
+ |__| |___|_| |_| |___|_| |_|_|_|__,|_|_|___|___|
32
+ _ _ _ _ _
33
+ | | | |___ ___| |_ ___| |_ ___ ___
34
+ | | | | . | _| '_|_ -| | . | . |
35
+ |_____|___|_| |_,_|___|_|_|___| _|
36
+ |_|
37
+ #{reset})
38
+ end
39
+
40
+ def banner
41
+ %(
42
+ _____ _ _____ _ _
43
+ +hmNMMMMMm/` -ymMMNh/ |_ _| |_ ___ | __ |___|_| |___
44
+ sMMMMMMMMMy +MMMMMMMMy | | | | -_| | -| .'| | |_ -|
45
+ yMMMMMMMMMMy` yMMMMMMMMN |_| |_|_|___| |__|__|__,|_|_|___|
46
+ `dMMMMMMMMMMm:-dMMMMMMm: _____ ___
47
+ `sNMMMMMMMMMMs.:+sso:` | _ |___ ___| _|___ ___ _____ ___ ___ ___ ___
48
+ :dMMMMMMMMMMm/ | __| -_| _| _| . | _| | .'| | _| -_|
49
+ :oss+:.sNMMMMMMMMMMy` |__| |___|_| |_| |___|_| |_|_|_|__,|_|_|___|___|
50
+ /mMMMMMMd-:mMMMMMMMMMMd. _ _ _ _ _
51
+ NMMMMMMMMy `hMMMMMMMMMMh | | | |___ ___| |_ ___| |_ ___ ___
52
+ yMMMMMMMM+ `dMMMMMMMMMy | | | | . | _| '_|_ -| | . | . |
53
+ /hNMMmy- `/mMMMMMNmy/ |_____|___|_| |_,_|___|_|_|___| _|
54
+ |_|
55
+ #{reset})
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,15 @@
1
+ module RPW
2
+ class Key < SubCommandBase
3
+ class_before :exit_with_no_key
4
+
5
+ desc "register [EMAIL_ADDRESS]", "Change email registered with Speedshop. One-time only."
6
+ def register(email)
7
+ if client.register_email(email)
8
+ say "Key registered with #{email}. You should receive a Slack invite soon."
9
+ else
10
+ say "Key has already been registered. If you believe this is in error,"\
11
+ " please email nate.berkopec@speedshop.co"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,99 @@
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
@@ -0,0 +1,32 @@
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
@@ -0,0 +1,28 @@
1
+ require "digest"
2
+ require "thor"
3
+
4
+ module RPW
5
+ class Quiz < Thor
6
+ desc "give_quiz FILENAME", ""
7
+ def give_quiz(filename)
8
+ @quiz_data = YAML.safe_load(File.read(filename))
9
+ @quiz_data["questions"].each { |q| question(q) }
10
+ end
11
+
12
+ private
13
+
14
+ def question(data)
15
+ puts data["prompt"]
16
+ data["answer_choices"].each { |ac| puts ac }
17
+ provided_answer = ask("Your answer?")
18
+ answer_digest = Digest::MD5.hexdigest(data["prompt"] + provided_answer.upcase)
19
+ if answer_digest == data["answer_digest"]
20
+ say "Correct!"
21
+ else
22
+ say "Incorrect."
23
+ say "I encourage you to try reviewing the material to see what the correct answer is."
24
+ end
25
+ say ""
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ module RPW
2
+ class SubCommandBase < Thor
3
+ def self.banner(command, namespace = nil, subcommand = false)
4
+ "#{basename} #{subcommand_prefix} #{command.usage}"
5
+ end
6
+
7
+ def self.subcommand_prefix
8
+ name.gsub(%r{.*::}, "").gsub(%r{^[A-Z]}) { |match| match[0].downcase }
9
+ .gsub(%r{[A-Z]}) { |match| "-#{match[0].downcase}" }
10
+ end
11
+
12
+ no_commands do
13
+ def client
14
+ @client ||= RPW::Client.new
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
+ end
29
+ end
30
+ end
@@ -0,0 +1,168 @@
1
+ require "fileutils"
2
+ require "rpw/cli/quiz"
3
+
4
+ module RPW
5
+ class Client
6
+ RPW_SERVER_DOMAIN = ENV["RPW_SERVER_DOMAIN"] || "https://rpw-licensor.speedshop.co"
7
+ attr_reader :gateway
8
+
9
+ def initialize(gateway = nil)
10
+ @gateway = gateway || Gateway.new(RPW_SERVER_DOMAIN, client_data["key"])
11
+ end
12
+
13
+ def setup(key)
14
+ success = gateway.authenticate_key(key)
15
+ client_data["key"] = key if success
16
+ success
17
+ end
18
+
19
+ def register_email(email)
20
+ gateway.register_email(email)
21
+ end
22
+
23
+ def next
24
+ contents = gateway.list_content
25
+ return contents.first unless client_data["completed"]
26
+ contents.delete_if { |c| client_data["completed"].include? c["position"] }
27
+ contents.sort_by { |c| c["position"] }[1] # 0 would be the current lesson
28
+ end
29
+
30
+ def list
31
+ gateway.list_content
32
+ end
33
+
34
+ def show(content_pos)
35
+ content_pos = client_data["current_lesson"] if content_pos == :current
36
+ gateway.get_content_by_position(content_pos)
37
+ end
38
+
39
+ def directory_setup(home_dir_ok = true)
40
+ ["video", "quiz", "lab", "text", "cgrp"].each do |path|
41
+ FileUtils.mkdir_p(path) unless File.directory?(path)
42
+ end
43
+
44
+ if home_dir_ok
45
+ ClientData.create_in_home!
46
+ else
47
+ ClientData.create_in_pwd!
48
+ end
49
+
50
+ unless File.exist?(".gitignore") && File.read(".gitignore").match(/rpw_info/)
51
+ File.open(".gitignore", "a") do |f|
52
+ f.puts "\n"
53
+ f.puts ".rpw_info\n"
54
+ f.puts "video\n"
55
+ f.puts "quiz\n"
56
+ f.puts "lab\n"
57
+ f.puts "text\n"
58
+ f.puts "cgrp\n"
59
+ end
60
+ end
61
+
62
+ File.open("README.md", "w+") do |f|
63
+ f.puts File.read(File.join(File.dirname(__FILE__), "README.md"))
64
+ end
65
+ end
66
+
67
+ def increment_current_lesson!(position)
68
+ mark_current_lesson_as_completed
69
+ client_data["current_lesson"] = position
70
+ end
71
+
72
+ def progress
73
+ contents = gateway.list_content
74
+ completed_lessons = client_data["completed"] || []
75
+ {
76
+ completed: completed_lessons.size,
77
+ total: contents.size,
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"] = []
90
+ end
91
+
92
+ def latest_version?
93
+ return true unless ClientData.exists?
94
+
95
+ if client_data["last_version_check"]
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
99
+
100
+ begin
101
+ latest = gateway.latest_version?
102
+ rescue
103
+ return true
104
+ end
105
+
106
+ client_data["last_version_check"] = if latest
107
+ Time.now
108
+ else
109
+ false
110
+ end
111
+ end
112
+
113
+ def setup?
114
+ return false unless ClientData.exists?
115
+ client_data["key"]
116
+ end
117
+
118
+ def directories_ready?
119
+ ["video", "quiz", "lab", "text", "cgrp"].all? do |path|
120
+ File.directory?(path)
121
+ end
122
+ end
123
+
124
+ def download_and_extract(content)
125
+ location = content["style"] + "/" + content["s3_key"]
126
+ unless File.exist?(location)
127
+ gateway.download_content(content, folder: content["style"])
128
+ extract_content(content) if content["s3_key"].end_with?(".tar.gz")
129
+ end
130
+ end
131
+
132
+ private
133
+
134
+ def mark_current_lesson_as_completed
135
+ reset_progress unless client_data["current_lesson"] && client_data["completed"]
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
157
+ end
158
+
159
+ def client_data
160
+ @client_data ||= ClientData.new
161
+ end
162
+
163
+ def extract_content(content)
164
+ folder = content["style"]
165
+ `tar -C #{folder} -xvzf #{folder}/#{content["s3_key"]}`
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,73 @@
1
+ require "fileutils"
2
+ require "yaml"
3
+
4
+ module RPW
5
+ class ClientData
6
+ DOTFILE_NAME = ".rpw_info"
7
+
8
+ def initialize
9
+ data # access file to load
10
+ end
11
+
12
+ def [](key)
13
+ data[key]
14
+ end
15
+
16
+ def []=(key, value)
17
+ data
18
+ data[key] = value
19
+
20
+ begin
21
+ File.open(filestore_location, "w") { |f| f.write(YAML.dump(data)) }
22
+ rescue
23
+ raise Error, "The RPW data at #{filestore_location} is not writable. \
24
+ Check your file permissions."
25
+ end
26
+ end
27
+
28
+ def self.create_in_pwd!
29
+ FileUtils.touch(File.expand_path("./" + DOTFILE_NAME))
30
+ end
31
+
32
+ def self.create_in_home!
33
+ unless File.directory?(File.expand_path("~/.rpw/"))
34
+ FileUtils.mkdir(File.expand_path("~/.rpw/"))
35
+ end
36
+
37
+ FileUtils.touch(File.expand_path("~/.rpw/" + DOTFILE_NAME))
38
+ end
39
+
40
+ def self.delete_filestore
41
+ return unless File.exist?(filestore_location)
42
+ FileUtils.remove(filestore_location)
43
+ end
44
+
45
+ def self.exists?
46
+ File.exist? filestore_location
47
+ end
48
+
49
+ def self.filestore_location
50
+ if File.exist?(File.expand_path("./" + DOTFILE_NAME))
51
+ File.expand_path("./" + DOTFILE_NAME)
52
+ else
53
+ File.expand_path("~/.rpw/" + DOTFILE_NAME)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def filestore_location
60
+ self.class.filestore_location
61
+ end
62
+
63
+ def data
64
+ @data ||= begin
65
+ begin
66
+ YAML.safe_load(File.read(filestore_location), permitted_classes: [Time]) || {}
67
+ rescue Errno::ENOENT
68
+ {}
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,63 @@
1
+ require "excon"
2
+ require "json"
3
+
4
+ module RPW
5
+ class Gateway
6
+ attr_accessor :domain
7
+
8
+ def initialize(domain, key)
9
+ @domain = domain
10
+ @key = key
11
+ end
12
+
13
+ class Error < StandardError; end
14
+
15
+ def authenticate_key(key)
16
+ Excon.get(domain + "/license", user: key).status == 200
17
+ end
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
+ def list_content
30
+ response = Excon.get(domain + "/contents", user: @key)
31
+ if response.status == 200
32
+ JSON.parse(response.body)
33
+ else
34
+ puts response.inspect
35
+ raise Error, "There was a problem fetching this content."
36
+ end
37
+ end
38
+
39
+ def download_content(content, folder:)
40
+ puts "Downloading #{content["title"]}..."
41
+ downloaded_file = File.open("#{folder}/#{content["s3_key"]}.partial", "w")
42
+ streamer = lambda do |chunk, remaining_bytes, total_bytes|
43
+ downloaded_file.write(chunk)
44
+ print 13.chr
45
+ print "Remaining: #{(remaining_bytes.to_f / total_bytes * 100).round(2).to_s.rjust(8)}%"
46
+ 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
+ end
52
+
53
+ def latest_version?
54
+ resp = Excon.get("https://rubygems.org/api/v1/gems/rpw.json")
55
+ data = JSON.parse resp.body
56
+ Gem::Version.new(RPW::VERSION) >= Gem::Version.new(data["version"])
57
+ end
58
+
59
+ def register_email(email)
60
+ Excon.put(domain + "/license?email=#{email}&key=#{@key}").status == 200
61
+ end
62
+ end
63
+ end
@@ -1,3 +1,3 @@
1
- module RPW
2
- VERSION = "0.0.1"
3
- end
1
+ module RPW
2
+ VERSION = "0.0.6"
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 "typhoeus"
28
+ spec.add_dependency "thor-hollaback"
29
+ spec.add_dependency "excon"
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.1
4
+ version: 0.0.6
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-05 00:00:00.000000000 Z
11
+ date: 2020-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -25,7 +25,21 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: typhoeus
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'
41
+ - !ruby/object:Gem::Dependency
42
+ name: excon
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - ">="
@@ -46,6 +60,7 @@ executables:
46
60
  extensions: []
47
61
  extra_rdoc_files: []
48
62
  files:
63
+ - ".github/workflows/test.yml"
49
64
  - ".gitignore"
50
65
  - ".ruby_version"
51
66
  - CODE_OF_CONDUCT.md
@@ -57,6 +72,17 @@ files:
57
72
  - Rakefile
58
73
  - exe/rpw
59
74
  - lib/rpw.rb
75
+ - lib/rpw/README.md
76
+ - lib/rpw/cli.rb
77
+ - lib/rpw/cli/bannerlord.rb
78
+ - lib/rpw/cli/key.rb
79
+ - lib/rpw/cli/lesson.rb
80
+ - lib/rpw/cli/progress.rb
81
+ - lib/rpw/cli/quiz.rb
82
+ - lib/rpw/cli/sub_command_base.rb
83
+ - lib/rpw/client.rb
84
+ - lib/rpw/client_data.rb
85
+ - lib/rpw/gateway.rb
60
86
  - lib/rpw/version.rb
61
87
  - rpw.gemspec
62
88
  homepage: https://speedshop.co