rpw 0.0.3 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/.github/workflows/test.yml +109 -0
 - data/.gitignore +1 -0
 - data/Gemfile.lock +12 -16
 - data/HISTORY.md +21 -0
 - data/exe/rpw +1 -252
 - data/lib/rpw.rb +4 -374
 - data/lib/rpw/README.md +58 -0
 - data/lib/rpw/cli.rb +94 -0
 - data/lib/rpw/cli/bannerlord.rb +59 -0
 - data/lib/rpw/cli/key.rb +15 -0
 - data/lib/rpw/cli/lesson.rb +101 -0
 - data/lib/rpw/cli/progress.rb +34 -0
 - data/lib/rpw/cli/quiz.rb +28 -0
 - data/lib/rpw/cli/sub_command_base.rb +30 -0
 - data/lib/rpw/client.rb +178 -0
 - data/lib/rpw/client_data.rb +73 -0
 - data/lib/rpw/gateway.rb +67 -0
 - data/lib/rpw/version.rb +1 -1
 - data/rpw.gemspec +1 -1
 - metadata +15 -3
 
| 
         @@ -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
         
     | 
    
        data/lib/rpw/cli/key.rb
    ADDED
    
    | 
         @@ -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 support@speedshop.co"
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,101 @@ 
     | 
|
| 
      
 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 :"no-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.complete(content["position"])
         
     | 
| 
      
 20 
     | 
    
         
            +
                  display_content(content, !options[:"no-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(nil)
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                desc "list", "Show all available workshop lessons"
         
     | 
| 
      
 30 
     | 
    
         
            +
                def list
         
     | 
| 
      
 31 
     | 
    
         
            +
                  say "All available workshop lessons:"
         
     | 
| 
      
 32 
     | 
    
         
            +
                  say "Use [ID] for the show/download command"
         
     | 
| 
      
 33 
     | 
    
         
            +
                  say "[ID]: Lesson Name"
         
     | 
| 
      
 34 
     | 
    
         
            +
                  client.list.each do |lesson|
         
     | 
| 
      
 35 
     | 
    
         
            +
                    puts "[#{lesson["position"]}]:#{"  " * lesson["indent"]} #{lesson["title"]}"
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                desc "download [CONTENT | all]", "Download one or all workshop contents"
         
     | 
| 
      
 40 
     | 
    
         
            +
                def download(content_pos)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  to_download = if content_pos.downcase == "all"
         
     | 
| 
      
 42 
     | 
    
         
            +
                    client.list
         
     | 
| 
      
 43 
     | 
    
         
            +
                  else
         
     | 
| 
      
 44 
     | 
    
         
            +
                    [client.show(content_pos)]
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
                  to_download.each { |content| client.download_and_extract(content) }
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                desc "show [CONTENT]", "Show any workshop lesson, shows current lesson w/no arguments"
         
     | 
| 
      
 50 
     | 
    
         
            +
                option :"no-open"
         
     | 
| 
      
 51 
     | 
    
         
            +
                def show(content_order = :current)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  content = client.show(content_order)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  client.download_and_extract(content)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  display_content(content, !options[:"no-open"])
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                private
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                def display_content(content, open_after)
         
     | 
| 
      
 60 
     | 
    
         
            +
                  say "Current Lesson: #{content["title"]}"
         
     | 
| 
      
 61 
     | 
    
         
            +
                  openable = false
         
     | 
| 
      
 62 
     | 
    
         
            +
                  case content["style"]
         
     | 
| 
      
 63 
     | 
    
         
            +
                  when "video"
         
     | 
| 
      
 64 
     | 
    
         
            +
                    location = "video/#{content["s3_key"]}"
         
     | 
| 
      
 65 
     | 
    
         
            +
                    openable = true
         
     | 
| 
      
 66 
     | 
    
         
            +
                  when "quiz"
         
     | 
| 
      
 67 
     | 
    
         
            +
                    Quiz.start(["give_quiz", "quiz/" + content["s3_key"]])
         
     | 
| 
      
 68 
     | 
    
         
            +
                  when "lab"
         
     | 
| 
      
 69 
     | 
    
         
            +
                    location = "lab/#{content["s3_key"][0..-8]}"
         
     | 
| 
      
 70 
     | 
    
         
            +
                    openable = true
         
     | 
| 
      
 71 
     | 
    
         
            +
                  when "text"
         
     | 
| 
      
 72 
     | 
    
         
            +
                    location = "text/#{content["s3_key"]}"
         
     | 
| 
      
 73 
     | 
    
         
            +
                    openable = true
         
     | 
| 
      
 74 
     | 
    
         
            +
                  when "cgrp"
         
     | 
| 
      
 75 
     | 
    
         
            +
                    say "The Complete Guide to Rails Performance has been downloaded and extracted to the ./cgrp directory."
         
     | 
| 
      
 76 
     | 
    
         
            +
                    say "All source code for the CGRP is in the src directory, PDF and other compiled formats are in the release directory."
         
     | 
| 
      
 77 
     | 
    
         
            +
                    say "You can check it out now, or to continue: $ rpw lesson next "
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
                  if location
         
     | 
| 
      
 80 
     | 
    
         
            +
                    if openable && !open_after
         
     | 
| 
      
 81 
     | 
    
         
            +
                      say "Download complete. Open with: $ #{open_command} #{location}"
         
     | 
| 
      
 82 
     | 
    
         
            +
                    elsif open_after && openable
         
     | 
| 
      
 83 
     | 
    
         
            +
                      exec "#{open_command} #{location}"
         
     | 
| 
      
 84 
     | 
    
         
            +
                    end
         
     | 
| 
      
 85 
     | 
    
         
            +
                  end
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                require "rbconfig"
         
     | 
| 
      
 89 
     | 
    
         
            +
                def open_command
         
     | 
| 
      
 90 
     | 
    
         
            +
                  host_os = RbConfig::CONFIG["host_os"]
         
     | 
| 
      
 91 
     | 
    
         
            +
                  case host_os
         
     | 
| 
      
 92 
     | 
    
         
            +
                  when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
         
     | 
| 
      
 93 
     | 
    
         
            +
                    "start"
         
     | 
| 
      
 94 
     | 
    
         
            +
                  when /darwin|mac os/
         
     | 
| 
      
 95 
     | 
    
         
            +
                    "open"
         
     | 
| 
      
 96 
     | 
    
         
            +
                  else
         
     | 
| 
      
 97 
     | 
    
         
            +
                    "xdg-open"
         
     | 
| 
      
 98 
     | 
    
         
            +
                  end
         
     | 
| 
      
 99 
     | 
    
         
            +
                end
         
     | 
| 
      
 100 
     | 
    
         
            +
              end
         
     | 
| 
      
 101 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,34 @@ 
     | 
|
| 
      
 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(pos)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  lesson = client.set_progress(pos.to_i)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  say "Set current progress to #{lesson["title"]}"
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                desc "reset", "Erase all progress and start over"
         
     | 
| 
      
 12 
     | 
    
         
            +
                def reset
         
     | 
| 
      
 13 
     | 
    
         
            +
                  return unless yes? "Are you sure you want to reset your progress? (Y/N)"
         
     | 
| 
      
 14 
     | 
    
         
            +
                  say "Resetting progress."
         
     | 
| 
      
 15 
     | 
    
         
            +
                  client.set_progress(nil)
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                desc "show", "Show current workshop progress"
         
     | 
| 
      
 19 
     | 
    
         
            +
                def show
         
     | 
| 
      
 20 
     | 
    
         
            +
                  data = client.progress
         
     | 
| 
      
 21 
     | 
    
         
            +
                  say "The Rails Performance Workshop"
         
     | 
| 
      
 22 
     | 
    
         
            +
                  say "You have completed #{data[:completed]} out of #{data[:total]} total sections."
         
     | 
| 
      
 23 
     | 
    
         
            +
                  say "Current lesson: #{data[:current_lesson]["title"]}" if data[:current_lesson]
         
     | 
| 
      
 24 
     | 
    
         
            +
                  say "Progress by Section (X == completed, O == current):"
         
     | 
| 
      
 25 
     | 
    
         
            +
                  data[:sections].each do |section|
         
     | 
| 
      
 26 
     | 
    
         
            +
                    say "#{section[:title]}: #{section[:progress]}"
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                private
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                default_task :show
         
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/rpw/cli/quiz.rb
    ADDED
    
    | 
         @@ -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
         
     | 
    
        data/lib/rpw/client.rb
    ADDED
    
    | 
         @@ -0,0 +1,178 @@ 
     | 
|
| 
      
 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 
     | 
    
         
            +
                  return list.first unless client_data["completed"]
         
     | 
| 
      
 25 
     | 
    
         
            +
                  list.sort_by { |c| c["position"] }.find { |c| c["position"] > current_position }
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def list
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @list ||= begin
         
     | 
| 
      
 30 
     | 
    
         
            +
                    if client_data["content_cache_generated"] &&
         
     | 
| 
      
 31 
     | 
    
         
            +
                        client_data["content_cache_generated"] >= Time.now - 60 * 60
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                      client_data["content_cache"]
         
     | 
| 
      
 34 
     | 
    
         
            +
                    else
         
     | 
| 
      
 35 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 36 
     | 
    
         
            +
                        client_data["content_cache"] = gateway.list_content
         
     | 
| 
      
 37 
     | 
    
         
            +
                        client_data["content_cache_generated"] = Time.now
         
     | 
| 
      
 38 
     | 
    
         
            +
                        client_data["content_cache"]
         
     | 
| 
      
 39 
     | 
    
         
            +
                      rescue
         
     | 
| 
      
 40 
     | 
    
         
            +
                        client_data["content_cache"] || (raise Error.new("No internet connection"))
         
     | 
| 
      
 41 
     | 
    
         
            +
                      end
         
     | 
| 
      
 42 
     | 
    
         
            +
                    end
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                def show(content_pos)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  content_pos = current_position if content_pos == :current
         
     | 
| 
      
 48 
     | 
    
         
            +
                  gateway.get_content_by_position(content_pos)
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def directory_setup(home_dir_ok = true)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  ["video", "quiz", "lab", "text", "cgrp"].each do |path|
         
     | 
| 
      
 53 
     | 
    
         
            +
                    FileUtils.mkdir_p(path) unless File.directory?(path)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                  if home_dir_ok
         
     | 
| 
      
 57 
     | 
    
         
            +
                    ClientData.create_in_home!
         
     | 
| 
      
 58 
     | 
    
         
            +
                  else
         
     | 
| 
      
 59 
     | 
    
         
            +
                    ClientData.create_in_pwd!
         
     | 
| 
      
 60 
     | 
    
         
            +
                  end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                  unless File.exist?(".gitignore") && File.read(".gitignore").match(/rpw_info/)
         
     | 
| 
      
 63 
     | 
    
         
            +
                    File.open(".gitignore", "a") do |f|
         
     | 
| 
      
 64 
     | 
    
         
            +
                      f.puts "\n"
         
     | 
| 
      
 65 
     | 
    
         
            +
                      f.puts ".rpw_info\n"
         
     | 
| 
      
 66 
     | 
    
         
            +
                      f.puts "video\n"
         
     | 
| 
      
 67 
     | 
    
         
            +
                      f.puts "quiz\n"
         
     | 
| 
      
 68 
     | 
    
         
            +
                      f.puts "lab\n"
         
     | 
| 
      
 69 
     | 
    
         
            +
                      f.puts "text\n"
         
     | 
| 
      
 70 
     | 
    
         
            +
                      f.puts "cgrp\n"
         
     | 
| 
      
 71 
     | 
    
         
            +
                    end
         
     | 
| 
      
 72 
     | 
    
         
            +
                  end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                  File.open("README.md", "w+") do |f|
         
     | 
| 
      
 75 
     | 
    
         
            +
                    f.puts File.read(File.join(File.dirname(__FILE__), "README.md"))
         
     | 
| 
      
 76 
     | 
    
         
            +
                  end
         
     | 
| 
      
 77 
     | 
    
         
            +
                end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                def progress
         
     | 
| 
      
 80 
     | 
    
         
            +
                  completed_lessons = client_data["completed"] || []
         
     | 
| 
      
 81 
     | 
    
         
            +
                  {
         
     | 
| 
      
 82 
     | 
    
         
            +
                    completed: completed_lessons.size,
         
     | 
| 
      
 83 
     | 
    
         
            +
                    total: list.size,
         
     | 
| 
      
 84 
     | 
    
         
            +
                    current_lesson: list.find { |c| c["position"] == current_position },
         
     | 
| 
      
 85 
     | 
    
         
            +
                    sections: chart_section_progress(list, completed_lessons)
         
     | 
| 
      
 86 
     | 
    
         
            +
                  }
         
     | 
| 
      
 87 
     | 
    
         
            +
                end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                def set_progress(pos)
         
     | 
| 
      
 90 
     | 
    
         
            +
                  client_data["completed"] = [] && return if pos.nil?
         
     | 
| 
      
 91 
     | 
    
         
            +
                  lesson = list.find { |l| l["position"] == pos }
         
     | 
| 
      
 92 
     | 
    
         
            +
                  raise Error.new("No such lesson - use the IDs in $ rpw lesson list") unless lesson
         
     | 
| 
      
 93 
     | 
    
         
            +
                  client_data["completed"] += [pos]
         
     | 
| 
      
 94 
     | 
    
         
            +
                  lesson
         
     | 
| 
      
 95 
     | 
    
         
            +
                end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                def latest_version?
         
     | 
| 
      
 98 
     | 
    
         
            +
                  return true unless ClientData.exists?
         
     | 
| 
      
 99 
     | 
    
         
            +
                  return true if client_data["last_version_check"] &&
         
     | 
| 
      
 100 
     | 
    
         
            +
                    client_data["last_version_check"] >= Time.now - (60 * 60)
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 103 
     | 
    
         
            +
                    latest = gateway.latest_version?
         
     | 
| 
      
 104 
     | 
    
         
            +
                  rescue
         
     | 
| 
      
 105 
     | 
    
         
            +
                    return true
         
     | 
| 
      
 106 
     | 
    
         
            +
                  end
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                  client_data["last_version_check"] = if latest
         
     | 
| 
      
 109 
     | 
    
         
            +
                    Time.now
         
     | 
| 
      
 110 
     | 
    
         
            +
                  else
         
     | 
| 
      
 111 
     | 
    
         
            +
                    false
         
     | 
| 
      
 112 
     | 
    
         
            +
                  end
         
     | 
| 
      
 113 
     | 
    
         
            +
                end
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                def setup?
         
     | 
| 
      
 116 
     | 
    
         
            +
                  return false unless ClientData.exists?
         
     | 
| 
      
 117 
     | 
    
         
            +
                  client_data["key"]
         
     | 
| 
      
 118 
     | 
    
         
            +
                end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                def directories_ready?
         
     | 
| 
      
 121 
     | 
    
         
            +
                  ["video", "quiz", "lab", "text", "cgrp"].all? do |path|
         
     | 
| 
      
 122 
     | 
    
         
            +
                    File.directory?(path)
         
     | 
| 
      
 123 
     | 
    
         
            +
                  end
         
     | 
| 
      
 124 
     | 
    
         
            +
                end
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                def download_and_extract(content)
         
     | 
| 
      
 127 
     | 
    
         
            +
                  location = content["style"] + "/" + content["s3_key"]
         
     | 
| 
      
 128 
     | 
    
         
            +
                  unless File.exist?(location)
         
     | 
| 
      
 129 
     | 
    
         
            +
                    gateway.download_content(content, folder: content["style"])
         
     | 
| 
      
 130 
     | 
    
         
            +
                    extract_content(content) if content["s3_key"].end_with?(".tar.gz")
         
     | 
| 
      
 131 
     | 
    
         
            +
                  end
         
     | 
| 
      
 132 
     | 
    
         
            +
                end
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                def complete(position)
         
     | 
| 
      
 135 
     | 
    
         
            +
                  if client_data["completed"]
         
     | 
| 
      
 136 
     | 
    
         
            +
                    # we actually have to put the _next_ lesson on the completed stack
         
     | 
| 
      
 137 
     | 
    
         
            +
                    set_progress(self.next["position"])
         
     | 
| 
      
 138 
     | 
    
         
            +
                  else
         
     | 
| 
      
 139 
     | 
    
         
            +
                    client_data["completed"] = []
         
     | 
| 
      
 140 
     | 
    
         
            +
                    set_progress(position)
         
     | 
| 
      
 141 
     | 
    
         
            +
                  end
         
     | 
| 
      
 142 
     | 
    
         
            +
                end
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
                private
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                def current_position
         
     | 
| 
      
 147 
     | 
    
         
            +
                  @current_position ||= client_data["completed"]&.last || 0
         
     | 
| 
      
 148 
     | 
    
         
            +
                end
         
     | 
| 
      
 149 
     | 
    
         
            +
             
     | 
| 
      
 150 
     | 
    
         
            +
                def chart_section_progress(contents, completed)
         
     | 
| 
      
 151 
     | 
    
         
            +
                  contents.group_by { |c| c["position"] / 100 }
         
     | 
| 
      
 152 
     | 
    
         
            +
                    .each_with_object([]) do |(_, c), memo|
         
     | 
| 
      
 153 
     | 
    
         
            +
                      completed_str = c.map { |l|
         
     | 
| 
      
 154 
     | 
    
         
            +
                        if l["position"] == current_position
         
     | 
| 
      
 155 
     | 
    
         
            +
                          "O"
         
     | 
| 
      
 156 
     | 
    
         
            +
                        elsif completed.include?(l["position"])
         
     | 
| 
      
 157 
     | 
    
         
            +
                          "X"
         
     | 
| 
      
 158 
     | 
    
         
            +
                        else
         
     | 
| 
      
 159 
     | 
    
         
            +
                          "."
         
     | 
| 
      
 160 
     | 
    
         
            +
                        end
         
     | 
| 
      
 161 
     | 
    
         
            +
                      }.join
         
     | 
| 
      
 162 
     | 
    
         
            +
                      memo << {
         
     | 
| 
      
 163 
     | 
    
         
            +
                        title: c[0]["title"],
         
     | 
| 
      
 164 
     | 
    
         
            +
                        progress: completed_str
         
     | 
| 
      
 165 
     | 
    
         
            +
                      }
         
     | 
| 
      
 166 
     | 
    
         
            +
                    end
         
     | 
| 
      
 167 
     | 
    
         
            +
                end
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
                def client_data
         
     | 
| 
      
 170 
     | 
    
         
            +
                  @client_data ||= ClientData.new
         
     | 
| 
      
 171 
     | 
    
         
            +
                end
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                def extract_content(content)
         
     | 
| 
      
 174 
     | 
    
         
            +
                  folder = content["style"]
         
     | 
| 
      
 175 
     | 
    
         
            +
                  `tar -C #{folder} -xvzf #{folder}/#{content["s3_key"]}`
         
     | 
| 
      
 176 
     | 
    
         
            +
                end
         
     | 
| 
      
 177 
     | 
    
         
            +
              end
         
     | 
| 
      
 178 
     | 
    
         
            +
            end
         
     |