lib/learn_open/opener.rb in learn-open-1.2.20 vs lib/learn_open/opener.rb in learn-open-1.2.21

- old
+ new

@@ -1,484 +1,41 @@ module LearnOpen class Opener - HOME_DIR = File.expand_path("~") - attr_reader :editor, :client, :lessons_dir, :file_path, :get_next_lesson, :token - attr_accessor :lesson, :repo_dir, :lesson_is_lab, :lesson_id, :later_lesson, :dot_learn + attr_reader :editor, + :target_lesson, + :get_next_lesson, + :io, + :logger, + :options def self.run(lesson:, editor_specified:, get_next_lesson:) new(lesson, editor_specified, get_next_lesson).run end - def initialize(lesson, editor, get_next_lesson) - _login, @token = Netrc.read['learn-config'] - @client = LearnWeb::Client.new(token: @token) - - @lesson = lesson - @editor = editor + def initialize(target_lesson, editor, get_next_lesson, options = {}) + @target_lesson = target_lesson + @editor = editor @get_next_lesson = get_next_lesson - @lessons_dir = YAML.load(File.read("#{HOME_DIR}/.learn-config"))[:learn_directory] - @file_path = "#{HOME_DIR}/.learn-open-tmp" - end - def run - setup_tmp_file + @io = options.fetch(:io, LearnOpen.default_io) + @logger = options.fetch(:logger, LearnOpen.logger) - set_lesson - - if ide_version_3? - if self.repo_dir != ENV['LAB_NAME'] - home_dir = "/home/#{ENV['CREATED_USER']}" - File.open("#{home_dir}/.custom_commands.log", "a") do |f| - f.puts %Q{{"command": "open_lab", "lab_name": "#{self.repo_dir}"}} - end - exit - end - end - - puts "Looking for lesson..." - - if jupyter_notebook_environment? - git_tasks - file_tasks - restore_files - watch_for_changes - jupyter_pip_install - completion_tasks - else - warn_if_necessary - if lesson_is_readme? - open_readme - else - git_tasks - file_tasks - setup_backup_if_needed - dependency_tasks - completion_tasks - end - end - + @options = options end - def repo_exists? - File.exists?("#{lessons_dir}/#{repo_dir}/.git") - end + def run + logger.log('Getting lesson...') + io.puts "Looking for lesson..." - private + lesson_data = LearnOpen::Adapters::LearnWebAdapter + .new(options) + .fetch_lesson_data( + target_lesson: target_lesson, + fetch_next_lesson: get_next_lesson + ) - def setup_backup_if_needed - if ide_environment? && ide_git_wip_enabled? - restore_files - watch_for_changes - end - end - - def ping_fork_completion(retries=3) - begin - Timeout::timeout(15) do - client.submit_event( - event: 'fork', - learn_oauth_token: token, - repo_name: repo_dir, - base_org_name: lesson.split('/')[0], - forkee: { full_name: nil } - ) - end - rescue Timeout::Error - if retries > 0 - puts "There was a problem forking and cloning this lesson. Retrying..." - ping_fork_completion(retries-1) - else - puts "There is an issue connecting to Learn. Please try again." - File.write(file_path, 'ERROR: Error connecting to Learn') - exit - end - end - end - - def warn_if_necessary - temp_args = nil - - if self.later_lesson - puts 'WARNING: You are attempting to open a lesson that is beyond your current lesson.' - print 'Are you sure you want to continue? [Yn]: ' - - if ARGV.any? - temp_args = ARGV - ARGV.clear - end - - warn_response = gets.chomp.downcase - - if !warn_response.empty? && !['yes', 'y'].include?(warn_response) - exit - end - end - - if temp_args - temp_args.each do |arg| - ARGV << arg - end - end - end - - def setup_tmp_file - FileUtils.touch(file_path) - File.write(file_path, '') - end - - def cleanup_tmp_file - File.write(file_path, 'Done.') - end - - def set_lesson - File.write(file_path, 'Getting lesson...') - - if !lesson && !get_next_lesson - self.lesson = get_current_lesson_forked_repo - self.lesson_is_lab = current_lesson.lab - self.lesson_id = current_lesson.id - self.later_lesson = false - self.dot_learn = current_lesson.dot_learn - elsif !lesson && get_next_lesson - self.lesson = get_next_lesson_forked_repo - self.lesson_is_lab = next_lesson.lab - self.lesson_id = next_lesson.id - self.later_lesson = false - self.dot_learn = next_lesson.dot_learn - else - self.lesson = ensure_correct_lesson.clone_repo - self.lesson_is_lab = correct_lesson.lab - self.lesson_id = correct_lesson.lesson_id - self.later_lesson = correct_lesson.later_lesson - self.dot_learn = correct_lesson.dot_learn - end - - self.repo_dir = lesson.split('/').last - end - - def current_lesson - @current_lesson ||= client.current_lesson - end - - def next_lesson - @next_lesson ||= client.next_lesson - end - - def get_current_lesson_forked_repo(retries=3) - begin - Timeout::timeout(15) do - current_lesson.clone_repo - end - rescue Timeout::Error - if retries > 0 - puts "There was a problem getting your lesson from Learn. Retrying..." - get_current_lesson_forked_repo(retries-1) - else - puts "There seems to be a problem connecting to Learn. Please try again." - File.write(file_path, 'ERROR: Error connecting to Learn') - exit - end - end - end - - def get_next_lesson_forked_repo(retries=3) - begin - Timeout::timeout(15) do - next_lesson.clone_repo - end - rescue Timeout::Error - if retries > 0 - puts "There was a problem getting your next lesson from Learn. Retrying..." - get_next_lesson_forked_repo(retries-1) - else - puts "There seems to be a problem connecting to Learn. Please try again." - File.write(file_path, 'ERROR: Error connecting to Learn') - exit - end - end - end - - def ensure_correct_lesson - correct_lesson - end - - def correct_lesson(retries=3) - @correct_lesson ||= begin - Timeout::timeout(15) do - client.validate_repo_slug(repo_slug: lesson) - end - rescue Timeout::Error - if retries > 0 - puts "There was a problem connecting to Learn. Retrying..." - correct_lesson(retries-1) - else - puts "Cannot connect to Learn right now. Please try again." - File.write(file_path, 'ERROR: Error connecting to Learn') - exit - end - end - end - - def fork_repo(retries=3) - if !repo_exists? - File.write(file_path, 'Forking repository...') - puts "Forking lesson..." - - if !github_disabled? - begin - Timeout::timeout(15) do - client.fork_repo(repo_name: repo_dir) - end - rescue Timeout::Error - if retries > 0 - puts "There was a problem forking this lesson. Retrying..." - fork_repo(retries-1) - else - puts "There is an issue connecting to Learn. Please try again." - File.write(file_path, 'ERROR: Error connecting to Learn') - exit - end - end - end - end - end - - def clone_repo(retries=3) - if !repo_exists? - File.write(file_path, 'Cloning to your machine...') - puts "Cloning lesson..." - begin - Timeout::timeout(15) do - Git.clone("git@github.com:#{lesson}.git", repo_dir, path: lessons_dir) - end - rescue Git::GitExecuteError - if retries > 0 - puts "There was a problem cloning this lesson. Retrying..." if retries > 1 - sleep(1) - clone_repo(retries-1) - else - puts "Cannot clone this lesson right now. Please try again." - File.write(file_path, 'ERROR: Error cloning. Try again.') - exit - end - rescue Timeout::Error - if retries > 0 - puts "There was a problem cloning this lesson. Retrying..." - clone_repo(retries-1) - else - puts "Cannot clone this lesson right now. Please try again." - File.write(file_path, 'ERROR: Error cloning. Try again.') - exit - end - end - end - - if github_disabled? - ping_fork_completion - end - end - - def open_with_editor - if ios_lesson? - open_ios_lesson - elsif editor - system("#{editor} .") - end - end - - def ios_lesson? - begin - languages = YAML.load(File.read("#{lessons_dir}/#{repo_dir}/.learn"))['languages'] - ios_lang = languages.any? {|l| ['objc', 'swift'].include?(l)} - - ios_lang || xcodeproj_file? || xcworkspace_file? - rescue Psych::SyntaxError - if xcodeproj_file? || xcworkspace_file? - true - else - puts "Sorry, there seems to be a problem with this lesson. Please submit a bug report to bugs@learn.co and try again later." - puts "If you'd like to work on your next lesson now, type: learn next" - File.write(file_path, 'ERROR: Problem parsing lesson data. Try again.') - exit - end - rescue NoMethodError, Errno::ENOENT => e - if xcodeproj_file? || xcworkspace_file? - true - elsif e.message.match(/for false:FalseClass/) || e.message.match(/No such file or directory/) - false - else - puts "Sorry, there seems to be a problem with this lesson. Please submit a bug report to bugs@learn.co and try again later." - puts "If you'd like to work on your next lesson now, type: learn next" - File.write(file_path, 'ERROR: Problem parsing lesson data. Try again.') - exit - end - end - end - - def open_ios_lesson - if can_open_ios_lesson? - open_xcode - else - puts "You need to be on a Mac to work on iOS lessons." - exit - end - end - - def can_open_ios_lesson? - on_mac? - end - - def open_xcode - if xcworkspace_file? - system("cd #{lessons_dir}/#{repo_dir} && open *.xcworkspace") - elsif xcodeproj_file? - system("cd #{lessons_dir}/#{repo_dir} && open *.xcodeproj") - end - end - - def xcodeproj_file? - Dir.glob("#{lessons_dir}/#{repo_dir}/*.xcodeproj").any? - end - - def xcworkspace_file? - Dir.glob("#{lessons_dir}/#{repo_dir}/*.xcworkspace").any? - end - - def cd_to_lesson - puts "Opening lesson..." - Dir.chdir("#{lessons_dir}/#{repo_dir}") - end - - def pip_install - if !ios_lesson? && File.exists?("#{lessons_dir}/#{repo_dir}/requirements.txt") - puts "Installing pip dependencies..." - system("python -m pip install -r requirements.txt") - end - end - - def jupyter_pip_install - if !ios_lesson? && File.exists?("#{lessons_dir}/#{repo_dir}/requirements.txt") - puts "Installing pip dependencies..." - system("/opt/conda/bin/python -m pip install -r requirements.txt") - end - end - - def bundle_install - if !ios_lesson? && File.exists?("#{lessons_dir}/#{repo_dir}/Gemfile") - puts "Bundling..." - system("bundle install") - end - end - - def npm_install - if !ios_lesson? && File.exists?("#{lessons_dir}/#{repo_dir}/package.json") - puts 'Installing npm dependencies...' - - if ide_environment? - system("yarn install --no-lockfile") - else - system("npm install") - end - end - end - - def lesson_is_readme? - !lesson_is_lab - end - - def open_readme - if ide_environment? - puts "Opening readme..." - File.open(".custom_commands.log", "a") do |f| - f.puts %Q{{"command": "browser_open", "url": "https://learn.co/lessons/#{lesson_id}"}} - end - elsif can_open_readme? - puts "Opening readme..." - launch_browser - else - puts "It looks like this lesson is a Readme. Please open it in your browser." - exit - end - end - - def launch_browser - if chrome_installed? - open_chrome - else - open_safari - end - end - - def chrome_installed? - File.exists?('/Applications/Google Chrome.app') - end - - def open_chrome - system("open -a 'Google Chrome' https://learn.co/lessons/#{lesson_id}") - end - - def open_safari - system("open -a Safari https://learn.co/lessons/#{lesson_id}") - end - - def can_open_readme? - on_mac? - end - - def on_mac? - !!RUBY_PLATFORM.match(/darwin/) - end - - def github_disabled? - !dot_learn.nil? && dot_learn[:github] == false - end - - def ide_environment? - ENV['IDE_CONTAINER'] == "true" - end - - def ide_git_wip_enabled? - return false if github_disabled? - - ENV['IDE_GIT_WIP'] == "true" - end - - def ide_version_3? - ENV['IDE_VERSION'] == "3" - end - - def jupyter_notebook_environment? - ENV['JUPYTER_CONTAINER'] == "true" - end - - def git_tasks - fork_repo - clone_repo - end - - def file_tasks - cd_to_lesson - open_with_editor - end - - def dependency_tasks - bundle_install - npm_install - pip_install - end - - def restore_files - pid = Process.spawn("restore-lab", [:out, :err] => File::NULL) - Process.waitpid(pid) - end - - def watch_for_changes - Process.spawn("while inotifywait -e close_write,create,moved_to -r #{lessons_dir}/#{repo_dir}; do backup-lab; done", [:out, :err] => File::NULL) - end - - def completion_tasks - cleanup_tmp_file - puts "Done." - exec("#{ENV['SHELL']} -l") + lesson = Lessons.classify(lesson_data, options) + environment = LearnOpen::Environments.classify(options) + lesson.open(environment, editor) end end end