learn-open 1.2.20 → 1.2.21

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.
Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +4 -0
  3. data/Guardfile +70 -0
  4. data/learn-open.gemspec +7 -1
  5. data/lib/learn_open.rb +66 -0
  6. data/lib/learn_open/adapters/io_adapter.rb +24 -0
  7. data/lib/learn_open/adapters/learn_web_adapter.rb +103 -0
  8. data/lib/learn_open/adapters/system_adapter.rb +30 -0
  9. data/lib/learn_open/environments.rb +35 -0
  10. data/lib/learn_open/environments/base_environment.rb +70 -0
  11. data/lib/learn_open/environments/generic_environment.rb +9 -0
  12. data/lib/learn_open/environments/ide_environment.rb +63 -0
  13. data/lib/learn_open/environments/jupyter_container_environment.rb +28 -0
  14. data/lib/learn_open/environments/linux_environment.rb +17 -0
  15. data/lib/learn_open/environments/mac_environment.rb +75 -0
  16. data/lib/learn_open/lessons.rb +24 -0
  17. data/lib/learn_open/lessons/base_lesson.rb +57 -0
  18. data/lib/learn_open/lessons/ios_lesson.rb +14 -0
  19. data/lib/learn_open/lessons/jupyter_lesson.rb +14 -0
  20. data/lib/learn_open/lessons/lab_lesson.rb +9 -0
  21. data/lib/learn_open/lessons/readme_lesson.rb +13 -0
  22. data/lib/learn_open/opener.rb +26 -469
  23. data/lib/learn_open/services/dependency_installers.rb +19 -0
  24. data/lib/learn_open/services/dependency_installers/base_installer.rb +21 -0
  25. data/lib/learn_open/services/dependency_installers/gem_installer.rb +14 -0
  26. data/lib/learn_open/services/dependency_installers/jupyter_pip_installer.rb +14 -0
  27. data/lib/learn_open/services/dependency_installers/node_package_installer.rb +20 -0
  28. data/lib/learn_open/services/dependency_installers/pip_installer.rb +14 -0
  29. data/lib/learn_open/services/file_backup_starter.rb +20 -0
  30. data/lib/learn_open/services/lesson_downloader.rb +82 -0
  31. data/lib/learn_open/services/logger.rb +23 -0
  32. data/lib/learn_open/version.rb +1 -1
  33. data/spec/fakes/fake_git.rb +20 -0
  34. data/spec/fakes/fake_learn_client.rb +214 -0
  35. data/spec/fixtures/learn-config +3 -0
  36. data/spec/learn_open/environments/ide_environment_spec.rb +62 -0
  37. data/spec/learn_open/opener_spec.rb +789 -14
  38. data/spec/spec_helper.rb +41 -0
  39. metadata +121 -5
  40. data/spec/home_dir/.learn-config +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: f1f69010fe0e78ce9ea308799cc36239e7d77603
4
- data.tar.gz: eee935ee7c0a6f6db0d7efbcc76ed290568ac493
2
+ SHA256:
3
+ metadata.gz: d0d170cc7e2457803701a5055afdd38f3ec5022f23f37b7e1a7d25a994fd5a80
4
+ data.tar.gz: d598a192eb8111e123d7e9d4fb7b6d82575d86dd48d4c5ca5c40ed1c65aee468
5
5
  SHA512:
6
- metadata.gz: ded2346040ba1f197e1803afa95d7ddfc405c3f7894c50fde7193e0f42cdb07ece60c747480d359f193048b86d87ae65adf96d18f3f5b592eac1d5a299dec8aa
7
- data.tar.gz: 9cd45be0f1435ac08a9fa4a52a38e3c5ee03f8c9fe3a2e0dbc26ae2756f293695930350494420be5126b600c34f097e081f764e990bf05a7b5b39428bbcfba2f
6
+ metadata.gz: 240272e5222130bc341d4e28de8edf12448fcb9ac2562437e0a0d1f39521274a585c17a222807a27cf36dfa74adbf727791490f11e0304c4ae3f981a25eb90b3
7
+ data.tar.gz: 79ec8c12d8b4f03048a1ec8def1a08a49b02f1a95b001bc3b195272ae006da4dfbda72828074da245fcb2f34194c2ed56c04b01182d49a2620dadb126f4e6619
data/.gitignore CHANGED
@@ -13,3 +13,7 @@
13
13
  *.a
14
14
  *.gem
15
15
  mkmf.log
16
+ notes
17
+ *swp
18
+ *swo
19
+ .idea/
data/Guardfile ADDED
@@ -0,0 +1,70 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ # Note: The cmd option is now required due to the increasing number of ways
19
+ # rspec may be run, below are examples of the most common uses.
20
+ # * bundler: 'bundle exec rspec'
21
+ # * bundler binstubs: 'bin/rspec'
22
+ # * spring: 'bin/rspec' (This will use spring if running and you have
23
+ # installed the spring binstubs per the docs)
24
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
25
+ # * 'just' rspec: 'rspec'
26
+
27
+ guard :rspec, cmd: "bundle exec rspec" do
28
+ require "guard/rspec/dsl"
29
+ dsl = Guard::RSpec::Dsl.new(self)
30
+
31
+ # Feel free to open issues for suggestions and improvements
32
+
33
+ # RSpec files
34
+ rspec = dsl.rspec
35
+ watch(rspec.spec_helper) { rspec.spec_dir }
36
+ watch(rspec.spec_support) { rspec.spec_dir }
37
+ watch(rspec.spec_files)
38
+
39
+ # Ruby files
40
+ ruby = dsl.ruby
41
+ dsl.watch_spec_files_for(ruby.lib_files)
42
+
43
+ # Rails files
44
+ rails = dsl.rails(view_extensions: %w(erb haml slim))
45
+ dsl.watch_spec_files_for(rails.app_files)
46
+ dsl.watch_spec_files_for(rails.views)
47
+
48
+ watch(rails.controllers) do |m|
49
+ [
50
+ rspec.spec.call("routing/#{m[1]}_routing"),
51
+ rspec.spec.call("controllers/#{m[1]}_controller"),
52
+ rspec.spec.call("acceptance/#{m[1]}")
53
+ ]
54
+ end
55
+
56
+ # Rails config changes
57
+ watch(rails.spec_helper) { rspec.spec_dir }
58
+ watch(rails.routes) { "#{rspec.spec_dir}/routing" }
59
+ watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
60
+
61
+ # Capybara features specs
62
+ watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
63
+ watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
64
+
65
+ # Turnip features and steps
66
+ watch(%r{^spec/acceptance/(.+)\.feature$})
67
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
68
+ Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
69
+ end
70
+ end
data/learn-open.gemspec CHANGED
@@ -18,7 +18,13 @@ Gem::Specification.new do |spec|
18
18
  spec.require_paths = ["lib", "bin"]
19
19
 
20
20
  spec.add_development_dependency "bundler", "~> 1.7"
21
- spec.add_development_dependency "rake", "~> 10.0"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency "fakefs", "~> 0.14.2"
23
+ spec.add_development_dependency "pry", "~> 0.11.1"
24
+ spec.add_development_dependency "rspec-core","~> 3.7.1"
25
+ spec.add_development_dependency "rspec-mocks","~> 3.7.0"
26
+ spec.add_development_dependency "diff-lcs", "~> 1.3"
27
+ spec.add_development_dependency "guard-rspec", "~> 4.7.0"
22
28
 
23
29
  spec.add_runtime_dependency "netrc"
24
30
  spec.add_runtime_dependency "git"
data/lib/learn_open.rb CHANGED
@@ -3,10 +3,76 @@ require 'netrc'
3
3
  require 'git'
4
4
  require 'learn_web'
5
5
  require 'timeout'
6
+ require 'json'
6
7
 
7
8
  require 'learn_open/version'
8
9
  require 'learn_open/opener'
9
10
  require 'learn_open/argument_parser'
11
+ require 'learn_open/adapters/system_adapter'
12
+ require 'learn_open/adapters/learn_web_adapter'
13
+ require 'learn_open/adapters/io_adapter'
14
+ require 'learn_open/environments'
15
+ require 'learn_open/environments/base_environment'
16
+ require 'learn_open/environments/mac_environment'
17
+ require 'learn_open/environments/linux_environment'
18
+ require 'learn_open/environments/generic_environment'
19
+ require 'learn_open/environments/ide_environment'
20
+ require 'learn_open/environments/jupyter_container_environment'
21
+ require 'learn_open/services/dependency_installers'
22
+ require 'learn_open/services/dependency_installers/base_installer'
23
+ require 'learn_open/services/dependency_installers/gem_installer'
24
+ require 'learn_open/services/dependency_installers/jupyter_pip_installer'
25
+ require 'learn_open/services/dependency_installers/node_package_installer'
26
+ require 'learn_open/services/dependency_installers/pip_installer'
27
+ require 'learn_open/services/lesson_downloader'
28
+ require 'learn_open/services/file_backup_starter'
29
+ require 'learn_open/services/logger'
30
+ require 'learn_open/lessons'
31
+ require 'learn_open/lessons/base_lesson'
32
+ require 'learn_open/lessons/jupyter_lesson'
33
+ require 'learn_open/lessons/readme_lesson'
34
+ require 'learn_open/lessons/ios_lesson'
35
+ require 'learn_open/lessons/lab_lesson'
10
36
 
11
37
  module LearnOpen
38
+ def self.learn_web_client
39
+ @client ||= begin
40
+ _login, token = Netrc.read['learn-config']
41
+ LearnWeb::Client.new(token: token)
42
+ end
43
+ end
44
+
45
+ def self.logger
46
+ @logger ||= begin
47
+ home_dir = File.expand_path("~")
48
+ Logger.new("#{home_dir}/.learn-open-tmp")
49
+ end
50
+ end
51
+
52
+ def self.default_io
53
+ LearnOpen::Adapters::IOAdapter.new(input: STDIN, output: Kernel)
54
+ end
55
+
56
+ def self.git_adapter
57
+ Git
58
+ end
59
+
60
+ def self.environment_vars
61
+ ENV
62
+ end
63
+
64
+ def self.system_adapter
65
+ LearnOpen::Adapters::SystemAdapter
66
+ end
67
+
68
+ def self.platform
69
+ RbConfig::CONFIG['host_os']
70
+ end
71
+
72
+ def self.lessons_directory
73
+ @lesson_directory ||= begin
74
+ home_dir = File.expand_path("~")
75
+ YAML.load(File.read("#{home_dir}/.learn-config"))[:learn_directory]
76
+ end
77
+ end
12
78
  end
@@ -0,0 +1,24 @@
1
+ module LearnOpen
2
+ module Adapters
3
+ class IOAdapter
4
+ attr_reader :input, :output
5
+
6
+ def initialize(input:, output:)
7
+ @input = input
8
+ @output = output
9
+ end
10
+
11
+ def puts(*message)
12
+ output.puts(*message)
13
+ end
14
+
15
+ def print(*message)
16
+ output.print(*message)
17
+ end
18
+
19
+ def gets
20
+ input.gets
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,103 @@
1
+ module LearnOpen
2
+ module Adapters
3
+ class LearnWebAdapter
4
+ attr_reader :client
5
+
6
+ def initialize(options = {})
7
+ @client = options.fetch(:learn_web_client) {LearnOpen.learn_web_client}
8
+ end
9
+
10
+ def fetch_lesson_data(target_lesson: false, fetch_next_lesson: false)
11
+ if opening_current_lesson?(target_lesson, fetch_next_lesson)
12
+ load_current_lesson
13
+ {
14
+ lesson: current_lesson,
15
+ id: current_lesson.id,
16
+ later_lesson: false
17
+ }
18
+ elsif opening_next_lesson?(target_lesson, fetch_next_lesson)
19
+ load_next_lesson
20
+ {
21
+ lesson: next_lesson,
22
+ id: next_lesson.id,
23
+ later_lesson: false
24
+ }
25
+ else
26
+ lesson = get_lesson(target_lesson)
27
+ {
28
+ lesson: lesson,
29
+ id: lesson.lesson_id,
30
+ later_lesson: lesson.later_lesson
31
+ }
32
+ end
33
+ end
34
+
35
+ def opening_current_lesson?(target_lesson, fetch_next_lesson)
36
+ !target_lesson && !fetch_next_lesson
37
+ end
38
+
39
+ def opening_next_lesson?(target_lesson, fetch_next_lesson)
40
+ !target_lesson && fetch_next_lesson
41
+ end
42
+
43
+ def current_lesson
44
+ @current_lesson ||= client.current_lesson
45
+ end
46
+
47
+ def next_lesson
48
+ @next_lesson ||= client.next_lesson
49
+ end
50
+
51
+ def load_current_lesson(retries = 3)
52
+ begin
53
+ Timeout::timeout(15) do
54
+ current_lesson
55
+ end
56
+ rescue Timeout::Error
57
+ if retries > 0
58
+ io.puts "There was a problem getting your lesson from Learn. Retrying..."
59
+ load_current_lesson(retries - 1)
60
+ else
61
+ io.puts "There seems to be a problem connecting to Learn. Please try again."
62
+ logger.log('ERROR: Error connecting to Learn')
63
+ exit
64
+ end
65
+ end
66
+ end
67
+
68
+ def load_next_lesson(retries = 3)
69
+ begin
70
+ Timeout::timeout(15) do
71
+ next_lesson
72
+ end
73
+ rescue Timeout::Error
74
+ if retries > 0
75
+ io.puts "There was a problem getting your next lesson from Learn. Retrying..."
76
+ load_next_lesson(retries - 1)
77
+ else
78
+ io.puts "There seems to be a problem connecting to Learn. Please try again."
79
+ logger.log('ERROR: Error connecting to Learn')
80
+ exit
81
+ end
82
+ end
83
+ end
84
+
85
+ def get_lesson(target_lesson, retries = 3)
86
+ @correct_lesson ||= begin
87
+ Timeout::timeout(15) do
88
+ client.validate_repo_slug(repo_slug: target_lesson)
89
+ end
90
+ rescue Timeout::Error
91
+ if retries > 0
92
+ io.puts "There was a problem connecting to Learn. Retrying..."
93
+ get_lesson(target_lesson, retries - 1)
94
+ else
95
+ io.puts "Cannot connect to Learn right now. Please try again."
96
+ logger.log('ERROR: Error connecting to Learn')
97
+ exit
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,30 @@
1
+ module LearnOpen
2
+ module Adapters
3
+ class SystemAdapter
4
+ def self.open_editor(editor, path:)
5
+ system("#{editor} .")
6
+ end
7
+
8
+ def self.open_login_shell(shell)
9
+ exec("#{shell} -l")
10
+ end
11
+
12
+ def self.watch_dir(dir, action)
13
+ spawn("while inotifywait -qre create,delete,move,close_write #{dir}; do #{action}; done")
14
+ end
15
+
16
+ def self.spawn(command, block: false)
17
+ pid = Process.spawn(command, [:out, :err] => File::NULL)
18
+ Process.waitpid(pid) if block
19
+ end
20
+
21
+ def self.run_command(command)
22
+ system(command)
23
+ end
24
+
25
+ def self.change_context_directory(dir)
26
+ Dir.chdir(dir)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,35 @@
1
+ module LearnOpen
2
+ module Environments
3
+ def self.classify(options)
4
+ environment_vars = options.fetch(:environment_vars, LearnOpen.environment_vars)
5
+ platform = options.fetch(:platform, LearnOpen.platform)
6
+ if jupyter_container?(environment_vars)
7
+ JupyterContainerEnvironment.new(options)
8
+ elsif ide_environment?(environment_vars)
9
+ IDEEnvironment.new(options)
10
+ elsif on_mac?(platform)
11
+ MacEnvironment.classify(options)
12
+ elsif on_linux?(platform)
13
+ LinuxEnvironment.new(options)
14
+ else
15
+ GenericEnvironment.new(options)
16
+ end
17
+ end
18
+
19
+ def self.jupyter_container?(environment_vars)
20
+ environment_vars['JUPYTER_CONTAINER'] == "true"
21
+ end
22
+
23
+ def self.ide_environment?(environment_vars)
24
+ environment_vars['IDE_CONTAINER'] == "true"
25
+ end
26
+
27
+ def self.on_mac?(platform)
28
+ !!platform.match(/darwin/)
29
+ end
30
+
31
+ def self.on_linux?(platform)
32
+ !!platform.match(/linux/)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,70 @@
1
+ module LearnOpen
2
+ module Environments
3
+ class BaseEnvironment
4
+
5
+ attr_reader :io, :environment_vars, :system_adapter, :options, :logger
6
+
7
+ def initialize(options={})
8
+ @io = options.fetch(:io) { LearnOpen.default_io }
9
+ @environment_vars = options.fetch(:environment_vars) { LearnOpen.environment_vars }
10
+ @system_adapter = options.fetch(:system_adapter) { LearnOpen.system_adapter }
11
+ @logger = options.fetch(:logger) { LearnOpen.logger }
12
+ @options = options
13
+ end
14
+
15
+ def open_jupyter_lab(lesson, location, editor)
16
+ :noop
17
+ end
18
+
19
+ def open_lab(lesson, location, editor)
20
+ case lesson
21
+ when LearnOpen::Lessons::IosLesson
22
+ io.puts "You need to be on a Mac to work on iOS lessons."
23
+ else
24
+ download_lesson(lesson, location)
25
+ open_editor(lesson, location, editor)
26
+ install_dependencies(lesson, location)
27
+ notify_of_completion
28
+ open_shell
29
+ end
30
+ end
31
+
32
+ def install_dependencies(lesson, location)
33
+ DependencyInstallers.run_installers(lesson, location, self, options)
34
+ end
35
+
36
+ def download_lesson(lesson, location)
37
+ LessonDownloader.call(lesson, location, options)
38
+ end
39
+
40
+ def open_editor(lesson, location, editor)
41
+ io.puts "Opening lesson..."
42
+ system_adapter.change_context_directory(lesson.to_path)
43
+ system_adapter.open_editor(editor, path: ".")
44
+ end
45
+
46
+ def start_file_backup(lesson, location)
47
+ FileBackupStarter.call(lesson, location, options)
48
+ end
49
+
50
+ def open_shell
51
+ system_adapter.open_login_shell(environment_vars['SHELL'])
52
+ end
53
+
54
+ def notify_of_completion
55
+ logger.log("Done.")
56
+ io.puts "Done."
57
+ end
58
+
59
+ def warn_if_necessary(lesson)
60
+ return unless lesson.later_lesson
61
+
62
+ io.puts 'WARNING: You are attempting to open a lesson that is beyond your current lesson.'
63
+ io.print 'Are you sure you want to continue? [Yn]: '
64
+
65
+ warn_response = io.gets.chomp.downcase
66
+ exit if !['yes', 'y'].include?(warn_response)
67
+ end
68
+ end
69
+ end
70
+ end