learn-open 1.2.20 → 1.2.21

Sign up to get free protection for your applications and to get access to all the features.
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