belajar 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +4 -0
  5. data/Guardfile +5 -0
  6. data/README.md +5 -0
  7. data/Rakefile +5 -0
  8. data/belajar.gemspec +38 -0
  9. data/bin/belajar +12 -0
  10. data/lib/belajar.rb +24 -0
  11. data/lib/belajar/chapter.rb +23 -0
  12. data/lib/belajar/configuration.rb +89 -0
  13. data/lib/belajar/congratulator.rb +13 -0
  14. data/lib/belajar/course.rb +80 -0
  15. data/lib/belajar/exceptions.rb +19 -0
  16. data/lib/belajar/generator.rb +60 -0
  17. data/lib/belajar/github_client.rb +30 -0
  18. data/lib/belajar/loadable.rb +23 -0
  19. data/lib/belajar/loading/chapters.rb +9 -0
  20. data/lib/belajar/loading/courses.rb +9 -0
  21. data/lib/belajar/loading/units.rb +9 -0
  22. data/lib/belajar/reference_solution.rb +18 -0
  23. data/lib/belajar/solution.rb +53 -0
  24. data/lib/belajar/storeable.rb +32 -0
  25. data/lib/belajar/task.rb +10 -0
  26. data/lib/belajar/terminal.rb +12 -0
  27. data/lib/belajar/terminal/cli.rb +59 -0
  28. data/lib/belajar/terminal/courses.rb +179 -0
  29. data/lib/belajar/terminal/output.rb +78 -0
  30. data/lib/belajar/terminal/setup.rb +115 -0
  31. data/lib/belajar/terminal/solutions.rb +46 -0
  32. data/lib/belajar/terminal/texts/about.txt +19 -0
  33. data/lib/belajar/terminal/texts/congratulations.txt +12 -0
  34. data/lib/belajar/terminal/texts/courses_empty.txt +3 -0
  35. data/lib/belajar/terminal/texts/hint_course_download.txt +13 -0
  36. data/lib/belajar/terminal/texts/welcome.txt +12 -0
  37. data/lib/belajar/terminal/welcome.rb +98 -0
  38. data/lib/belajar/test.rb +46 -0
  39. data/lib/belajar/test_result.rb +86 -0
  40. data/lib/belajar/unit.rb +28 -0
  41. data/lib/belajar/version.rb +3 -0
  42. data/lib/belajar/views.rb +51 -0
  43. data/lib/belajar/views/chapters_menu.rb +60 -0
  44. data/lib/belajar/views/courses_menu.rb +52 -0
  45. data/lib/belajar/views/main_menu.rb +37 -0
  46. data/lib/belajar/views/menu.rb +89 -0
  47. data/lib/belajar/views/splash.rb +57 -0
  48. data/lib/belajar/views/task_view.rb +236 -0
  49. data/lib/belajar/views/top_bar.rb +40 -0
  50. data/lib/belajar/views/units_menu.rb +61 -0
  51. data/lib/belajar/window.rb +215 -0
  52. data/spec/belajar/chapter_spec.rb +76 -0
  53. data/spec/belajar/configuration_spec.rb +161 -0
  54. data/spec/belajar/congratulator_spec.rb +24 -0
  55. data/spec/belajar/course_spec.rb +201 -0
  56. data/spec/belajar/generator_spec.rb +82 -0
  57. data/spec/belajar/github_client_spec.rb +53 -0
  58. data/spec/belajar/loading/chapters_spec.rb +16 -0
  59. data/spec/belajar/loading/courses_spec.rb +16 -0
  60. data/spec/belajar/loading/units_spec.rb +21 -0
  61. data/spec/belajar/reference_solution_spec.rb +41 -0
  62. data/spec/belajar/solution_spec.rb +86 -0
  63. data/spec/belajar/storeable_spec.rb +35 -0
  64. data/spec/belajar/task_spec.rb +23 -0
  65. data/spec/belajar/terminal/cli_spec.rb +51 -0
  66. data/spec/belajar/terminal/courses_spec.rb +293 -0
  67. data/spec/belajar/terminal/output_spec.rb +151 -0
  68. data/spec/belajar/terminal/setup_spec.rb +10 -0
  69. data/spec/belajar/terminal/solutions_spec.rb +8 -0
  70. data/spec/belajar/terminal/welcome_spec.rb +12 -0
  71. data/spec/belajar/terminal_spec.rb +24 -0
  72. data/spec/belajar/test_example_spec.rb +54 -0
  73. data/spec/belajar/test_result_spec.rb +91 -0
  74. data/spec/belajar/test_spec.rb +48 -0
  75. data/spec/belajar/unit_spec.rb +85 -0
  76. data/spec/belajar/views/chapters_menu_spec.rb +6 -0
  77. data/spec/belajar/views/courses_menu_spec.rb +6 -0
  78. data/spec/belajar/views/menu_spec.rb +19 -0
  79. data/spec/belajar/views/task_view_spec.rb +7 -0
  80. data/spec/belajar/views/units_menu_spec.rb +6 -0
  81. data/spec/belajar/views_spec.rb +21 -0
  82. data/spec/belajar_spec.rb +51 -0
  83. data/spec/path_helpers_spec.rb +60 -0
  84. data/spec/resource_helpers_spec.rb +33 -0
  85. data/spec/spec_helper.rb +28 -0
  86. data/spec/support/macros/content_helpers.rb +129 -0
  87. data/spec/support/macros/mock_helpers.rb +25 -0
  88. data/spec/support/macros/path_helpers.rb +139 -0
  89. data/spec/support/macros/resource_helpers.rb +157 -0
  90. data/spec/support/macros/test_helpers.rb +6 -0
  91. metadata +385 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ceb199e902cae1cbf1d2fdac248726a72cf77b8e6fc96b358357a50e2adcbb41
4
+ data.tar.gz: 1fa6907a4838a2bcfba99975d382635082e58d028164da3c216a1a8ef25214cc
5
+ SHA512:
6
+ metadata.gz: a4db8b51c4ed8bf90fff1c63310e5b2aa61dca71158bffe8754582e46d9530469754cc6b19fd7b743f40fc9d95b941043054c6f96c7f5578f10b6d0303355c3e
7
+ data.tar.gz: 071cca5ab34a02d7067ea8b5d9966716bf7f60786d9179ea62a4c17372878a6d0013747be76e436cf0ef26bb7aa55f262ee9ab1d33858dbfe5b6729b6044a082
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ /.idea
16
+ .project
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.2.0
5
+ - rbx-2
6
+ - ruby-head
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in belajar.gemspec
4
+ gemspec
@@ -0,0 +1,5 @@
1
+ guard 'rspec', cmd: 'rspec', all_after_pass: true do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
@@ -0,0 +1,5 @@
1
+ # Belajar
2
+
3
+ ### License
4
+
5
+ Belajar is released under the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task default: :spec
@@ -0,0 +1,38 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'belajar/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "belajar"
7
+ spec.version = Belajar::VERSION
8
+ spec.required_ruby_version = '~> 2.0'
9
+ spec.authors = ["Ahmad Ainul Rizki"]
10
+ spec.email = ["hey@bejoistic.com"]
11
+ spec.summary = %q{Learning Ruby on the command line.}
12
+ spec.description = %q{Belajar is the Japanese word for university. With Belajar you can interactively learn the Ruby programming language using the command line.}
13
+ spec.homepage = "https://github.com/wong-bejo/belajar"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ if RUBY_VERSION >= '2.1'
22
+ spec.add_runtime_dependency "curses", ">= 1.0", "< 2.0"
23
+ end
24
+
25
+ spec.add_runtime_dependency "activesupport", ">= 4.0", "< 5.0"
26
+ spec.add_runtime_dependency "rspec", ">= 3.0", "< 4.0"
27
+ spec.add_runtime_dependency "thor", "~> 0.19.1"
28
+ spec.add_runtime_dependency "os", "~> 0.9.6"
29
+ spec.add_runtime_dependency "colorize", "~> 0.7.5"
30
+ spec.add_runtime_dependency "rubyzip", ">= 1.0", "< 2.0"
31
+ spec.add_runtime_dependency "wisper", ">= 2.0.0.rc1", "< 3.0"
32
+ spec.add_runtime_dependency "quick_store", "~> 0.1.0"
33
+
34
+ spec.add_development_dependency "bundler", "~> 1.7"
35
+ spec.add_development_dependency "rake", "~> 10.0"
36
+ spec.add_development_dependency "webmock", "~> 1.20.4"
37
+ spec.add_development_dependency "guard-rspec", "~> 4.5.0"
38
+ end
@@ -0,0 +1,12 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+
5
+ begin
6
+ require 'belajar'
7
+ rescue LoadError => e
8
+ warn "could not load \"belajar\"\n#{e}"
9
+ exit -1
10
+ end
11
+
12
+ Belajar::Terminal::CLI.start
@@ -0,0 +1,24 @@
1
+ require 'quick_store'
2
+ require 'belajar/window'
3
+
4
+ Dir[File.join("#{File.dirname(__FILE__)}/**/*.rb")].sort.each do |file|
5
+ require file
6
+ end
7
+
8
+ module Belajar
9
+
10
+ class << self
11
+ def config
12
+ Configuration.instance
13
+ end
14
+
15
+ def configure
16
+ yield(config) if block_given?
17
+ end
18
+
19
+ def start
20
+ Views::Splash.new
21
+ Views::MainMenu.new
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ module Belajar
2
+ class Chapter
3
+
4
+ attr_reader :title, :path
5
+
6
+ def initialize(path)
7
+ @path = path
8
+ @title = File.basename(path).gsub(/\_+/, ' ')
9
+ end
10
+
11
+ def units
12
+ @units ||= Loading::Units.load(@path)
13
+ end
14
+
15
+ def started?
16
+ units.reduce(false) { |started, unit| started ||= unit.mastered? }
17
+ end
18
+
19
+ def mastered?
20
+ units.reduce(true) { |mastered, unit| mastered &&= unit.mastered? }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,89 @@
1
+ module Belajar
2
+ require 'singleton'
3
+ require 'fileutils'
4
+
5
+ class Configuration
6
+ include Singleton
7
+
8
+ LOCAL_DIR = '.belajar'
9
+ COURSES_DIR = 'courses'
10
+ SOLUTIONS_DIR = 'solutions'
11
+ STORAGE_FILE = 'belajar.db.yml'
12
+ BELAJAR_INITIAL_COURSE = 'wong-bejo/Get_started_with_Ruby'
13
+
14
+ attr_accessor :courses_path
15
+ attr_reader :storage_file
16
+
17
+ def initialize
18
+ @courses_path = local_path_to(COURSES_DIR)
19
+ @storage_file = local_path_to(STORAGE_FILE)
20
+
21
+ QuickStore.configure do |config|
22
+ config.file_path = @storage_file
23
+ end
24
+
25
+ yield if block_given?
26
+ end
27
+
28
+ def solutions_path
29
+ @solutions_path || raise(Belajar::ConfigurationError, 'Solutions path is not set.')
30
+ end
31
+
32
+ def solutions_path=(path)
33
+ full_path = File.expand_path(path, Dir.pwd)
34
+
35
+ unless Dir.exist?(full_path)
36
+ error = [
37
+ Belajar::ConfigurationError,
38
+ "Solutions path \"#{path}\" isn't an existing directory."
39
+ ]
40
+
41
+ raise(*error)
42
+ end
43
+
44
+ @solutions_path = full_path
45
+ end
46
+
47
+ def save
48
+ settings = self.instance_variables
49
+ settings.delete(:@storage_file)
50
+
51
+ settings.each do |variable|
52
+ key = variable.to_s.delete('@')
53
+ value = self.instance_variable_get(variable.to_sym)
54
+ QuickStore.store.set(key, value)
55
+ end
56
+ end
57
+
58
+ def import!
59
+ @courses_path = QuickStore.store.courses_path || @courses_path
60
+ @solutions_path = QuickStore.store.solutions_path || @solutions_path
61
+ self
62
+ end
63
+
64
+ def summary
65
+ settings = self.instance_variables
66
+ settings.delete(:@storage_file)
67
+
68
+ lines = settings.map do |variable|
69
+ key = variable.to_s.delete('@').gsub('_', ' ')
70
+ value = self.instance_variable_get(variable.to_sym)
71
+ "* #{key}: #{value}"
72
+ end
73
+
74
+ lines.join("\n")
75
+ end
76
+
77
+ def initial_course
78
+ BELAJAR_INITIAL_COURSE
79
+ end
80
+
81
+ private
82
+
83
+ def local_path_to(*resource)
84
+ path = File.join('~', LOCAL_DIR, resource)
85
+ File.expand_path(path, __FILE__)
86
+ end
87
+ end
88
+
89
+ end
@@ -0,0 +1,13 @@
1
+ module Belajar
2
+
3
+ class Congratulator
4
+
5
+ def self.message
6
+ lines = Terminal.text(:congratulations).lines.map(&:strip).compact
7
+ count = lines.count.zero? ? 0 : (lines.count - 1)
8
+ random_value = rand(0..count)
9
+ lines[random_value]
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,80 @@
1
+ require 'fileutils'
2
+
3
+ module Belajar
4
+ class Course
5
+
6
+ attr_reader :title, :path, :author, :link
7
+
8
+ def initialize(path)
9
+ @path = path
10
+ @title = File.basename(path).gsub(/\_+/, ' ')
11
+ @author = QuickStore.store.get(key(:author))
12
+ end
13
+
14
+ def chapters
15
+ @chapters ||= Loading::Chapters.load(@path)
16
+ end
17
+
18
+ def started?
19
+ chapters.reduce(false) { |started, chapter| started ||= chapter.started? }
20
+ end
21
+
22
+ def mastered?
23
+ chapters.reduce(true) { |mastered, chapter| mastered &&= chapter.mastered? }
24
+ end
25
+
26
+ def key(key_name)
27
+ Storeable.key(title, prefix: 'courses', suffix: key_name)
28
+ end
29
+
30
+ # Unzips a zipped file and removes the zipped file.
31
+ # Returns a Belajar::Course of the unzipped content.
32
+ #
33
+ # Example:
34
+ # Belajar::Course.unzip('/path/to/file.zip')
35
+ # # => Belajar::Course
36
+ #
37
+ # Belajar::Course.unzip(/path/to/master.zip, github_repo: true)
38
+ # # => Belajar::Course
39
+ #
40
+ class << self
41
+ def unzip(file_path, options = {})
42
+ target_dir = File.dirname(file_path)
43
+ course_dir = nil
44
+
45
+ Zip::File.open(file_path) do |zip_file|
46
+ zip_file.each do |entry|
47
+
48
+ if options[:github_repo]
49
+ first, *others = entry.to_s.split('/')
50
+ directory = File.join(first.split('-')[0..-2].join('-'), others)
51
+ else
52
+ directory = entry.to_s
53
+ end
54
+
55
+ if course_dir.nil? && directory != '/'
56
+ course_dir = File.join(target_dir, directory.gsub(/\/$/, ''))
57
+
58
+ if Dir.exist?(course_dir)
59
+ FileUtils.copy_entry("#{course_dir}/", "#{course_dir}_old/", true)
60
+ FileUtils.rm_rf("#{course_dir}/.", secure: true)
61
+ end
62
+ end
63
+
64
+ zip_file.extract(entry, "#{target_dir}/#{directory}") { true }
65
+ end
66
+ end
67
+
68
+ FileUtils.rm(file_path)
69
+ rescue Exception => e
70
+ puts e
71
+ old_dir = "#{course_dir}_old/"
72
+ FileUtils.copy_entry(old_dir, "#{course_dir}/", true) if Dir.exist?(old_dir)
73
+ ensure
74
+ old_dir = "#{course_dir}_old/"
75
+ FileUtils.rm_r(old_dir) if Dir.exist?(old_dir)
76
+ return Course.new(course_dir) if course_dir
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,19 @@
1
+ module Belajar
2
+
3
+ class Error < StandardError; end
4
+ class CourseNotFoundError < Error; end
5
+ class ChaptersNotFoundError < Error; end
6
+ class UnitsNotFoundError < Error; end
7
+ class TaskNotFoundError < Error; end
8
+ class ReferenceSolutionNotFoundError; end
9
+ class SolutionNotFoundError < Error; end
10
+ class ScaffoldError < Error; end
11
+
12
+ class ConfigurationError < Error; end
13
+
14
+ module Download
15
+ class NoUrlError < Error; end
16
+ class NoZipFileUrlError < Error; end
17
+ end
18
+
19
+ end
@@ -0,0 +1,60 @@
1
+ module Belajar
2
+ require 'fileutils'
3
+ require 'active_support'
4
+ require 'active_support/core_ext'
5
+
6
+ class Generator
7
+
8
+ def scaffold(courses_path, target_path)
9
+ Dir[File.join(courses_path, "*/*/*/*.md")].each do |file|
10
+ content_dir_parts = file.split('/')[-4..-2].map do |part|
11
+ clean_up_path_part(part)
12
+ end
13
+
14
+ content_dir = File.join(content_dir_parts)
15
+ directory = File.join(target_path, File.dirname(content_dir))
16
+
17
+ solution_file = File.basename(content_dir) + Solution::FILE_SUFFIX
18
+ file_path = File.join(directory, solution_file)
19
+
20
+ create_dir(directory)
21
+ create_file(file_path)
22
+ end
23
+ end
24
+
25
+ def prepare
26
+ begin
27
+ solutions_path = Belajar.config.solutions_path
28
+ rescue ConfigurationError => e
29
+ base_dir = File.dirname(Belajar.config.courses_path)
30
+ solutions_dir = Belajar::Configuration::SOLUTIONS_DIR
31
+ solutions_path = File.join(base_dir, solutions_dir)
32
+ end
33
+
34
+ create_dir(Belajar.config.courses_path)
35
+ create_dir(solutions_path)
36
+
37
+ Belajar.config.solutions_path = solutions_path
38
+ Belajar.config.save
39
+ end
40
+
41
+ private
42
+
43
+ def create_dir(path)
44
+ return if path.blank?
45
+ FileUtils.makedirs(path) unless Dir.exist?(path)
46
+ end
47
+
48
+ def create_file(path)
49
+ return if path.blank?
50
+ create_dir(File.dirname(path))
51
+ FileUtils.touch(path) unless File.exist?(path)
52
+ end
53
+
54
+ def clean_up_path_part(text)
55
+ leading_numbers = /^\d+[\_\-\s]/
56
+ part_joints = /[\_\-\s]+/
57
+ text.gsub(leading_numbers, '').gsub(part_joints, '_').downcase
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,30 @@
1
+ require 'open-uri'
2
+ require 'json'
3
+ require 'date'
4
+
5
+ module Belajar
6
+ module GithubClient
7
+
8
+ # Returns the url to the master zip file of the Github repo.
9
+ def self.master_zip_url(user_and_repo)
10
+ "https://github.com/#{user_and_repo}/archive/master.zip"
11
+ end
12
+
13
+ # Returns the timestamp of updated_at for the repo from the Github API.
14
+ def self.updated_at(user_and_repo)
15
+ url = "https://api.github.com/repos/#{user_and_repo}"
16
+ JSON.parse(open(url).read)['updated_at']
17
+ end
18
+
19
+ # Returns whether the pushed_at time from Github API is newer than the
20
+ # stored one.
21
+ def self.updated?(user_and_repo)
22
+ return false unless user_and_repo
23
+
24
+ course = Course.new(user_and_repo.split('/').last)
25
+ stored_time = QuickStore.store.get(course.key(:updated_at))
26
+ current_time = self.updated_at(user_and_repo)
27
+ DateTime.parse(stored_time) < DateTime.parse(current_time)
28
+ end
29
+ end
30
+ end