belajar 0.1.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.
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