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.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/README.md +5 -0
- data/Rakefile +5 -0
- data/belajar.gemspec +38 -0
- data/bin/belajar +12 -0
- data/lib/belajar.rb +24 -0
- data/lib/belajar/chapter.rb +23 -0
- data/lib/belajar/configuration.rb +89 -0
- data/lib/belajar/congratulator.rb +13 -0
- data/lib/belajar/course.rb +80 -0
- data/lib/belajar/exceptions.rb +19 -0
- data/lib/belajar/generator.rb +60 -0
- data/lib/belajar/github_client.rb +30 -0
- data/lib/belajar/loadable.rb +23 -0
- data/lib/belajar/loading/chapters.rb +9 -0
- data/lib/belajar/loading/courses.rb +9 -0
- data/lib/belajar/loading/units.rb +9 -0
- data/lib/belajar/reference_solution.rb +18 -0
- data/lib/belajar/solution.rb +53 -0
- data/lib/belajar/storeable.rb +32 -0
- data/lib/belajar/task.rb +10 -0
- data/lib/belajar/terminal.rb +12 -0
- data/lib/belajar/terminal/cli.rb +59 -0
- data/lib/belajar/terminal/courses.rb +179 -0
- data/lib/belajar/terminal/output.rb +78 -0
- data/lib/belajar/terminal/setup.rb +115 -0
- data/lib/belajar/terminal/solutions.rb +46 -0
- data/lib/belajar/terminal/texts/about.txt +19 -0
- data/lib/belajar/terminal/texts/congratulations.txt +12 -0
- data/lib/belajar/terminal/texts/courses_empty.txt +3 -0
- data/lib/belajar/terminal/texts/hint_course_download.txt +13 -0
- data/lib/belajar/terminal/texts/welcome.txt +12 -0
- data/lib/belajar/terminal/welcome.rb +98 -0
- data/lib/belajar/test.rb +46 -0
- data/lib/belajar/test_result.rb +86 -0
- data/lib/belajar/unit.rb +28 -0
- data/lib/belajar/version.rb +3 -0
- data/lib/belajar/views.rb +51 -0
- data/lib/belajar/views/chapters_menu.rb +60 -0
- data/lib/belajar/views/courses_menu.rb +52 -0
- data/lib/belajar/views/main_menu.rb +37 -0
- data/lib/belajar/views/menu.rb +89 -0
- data/lib/belajar/views/splash.rb +57 -0
- data/lib/belajar/views/task_view.rb +236 -0
- data/lib/belajar/views/top_bar.rb +40 -0
- data/lib/belajar/views/units_menu.rb +61 -0
- data/lib/belajar/window.rb +215 -0
- data/spec/belajar/chapter_spec.rb +76 -0
- data/spec/belajar/configuration_spec.rb +161 -0
- data/spec/belajar/congratulator_spec.rb +24 -0
- data/spec/belajar/course_spec.rb +201 -0
- data/spec/belajar/generator_spec.rb +82 -0
- data/spec/belajar/github_client_spec.rb +53 -0
- data/spec/belajar/loading/chapters_spec.rb +16 -0
- data/spec/belajar/loading/courses_spec.rb +16 -0
- data/spec/belajar/loading/units_spec.rb +21 -0
- data/spec/belajar/reference_solution_spec.rb +41 -0
- data/spec/belajar/solution_spec.rb +86 -0
- data/spec/belajar/storeable_spec.rb +35 -0
- data/spec/belajar/task_spec.rb +23 -0
- data/spec/belajar/terminal/cli_spec.rb +51 -0
- data/spec/belajar/terminal/courses_spec.rb +293 -0
- data/spec/belajar/terminal/output_spec.rb +151 -0
- data/spec/belajar/terminal/setup_spec.rb +10 -0
- data/spec/belajar/terminal/solutions_spec.rb +8 -0
- data/spec/belajar/terminal/welcome_spec.rb +12 -0
- data/spec/belajar/terminal_spec.rb +24 -0
- data/spec/belajar/test_example_spec.rb +54 -0
- data/spec/belajar/test_result_spec.rb +91 -0
- data/spec/belajar/test_spec.rb +48 -0
- data/spec/belajar/unit_spec.rb +85 -0
- data/spec/belajar/views/chapters_menu_spec.rb +6 -0
- data/spec/belajar/views/courses_menu_spec.rb +6 -0
- data/spec/belajar/views/menu_spec.rb +19 -0
- data/spec/belajar/views/task_view_spec.rb +7 -0
- data/spec/belajar/views/units_menu_spec.rb +6 -0
- data/spec/belajar/views_spec.rb +21 -0
- data/spec/belajar_spec.rb +51 -0
- data/spec/path_helpers_spec.rb +60 -0
- data/spec/resource_helpers_spec.rb +33 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/macros/content_helpers.rb +129 -0
- data/spec/support/macros/mock_helpers.rb +25 -0
- data/spec/support/macros/path_helpers.rb +139 -0
- data/spec/support/macros/resource_helpers.rb +157 -0
- data/spec/support/macros/test_helpers.rb +6 -0
- metadata +385 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/README.md
ADDED
data/Rakefile
ADDED
data/belajar.gemspec
ADDED
@@ -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
|
data/bin/belajar
ADDED
data/lib/belajar.rb
ADDED
@@ -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
|