rpw 0.0.1 → 0.0.6

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: 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