daigaku 0.2.0 → 1.0.0
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 +5 -5
- data/.travis.yml +7 -4
- data/CODE_OF_CONDUCT.md +77 -0
- data/README.md +11 -11
- data/bin/daigaku +6 -2
- data/daigaku.gemspec +22 -26
- data/lib/daigaku.rb +0 -1
- data/lib/daigaku/chapter.rb +3 -4
- data/lib/daigaku/coloring.rb +22 -27
- data/lib/daigaku/configuration.rb +25 -28
- data/lib/daigaku/congratulator.rb +17 -5
- data/lib/daigaku/course.rb +9 -8
- data/lib/daigaku/exceptions.rb +0 -2
- data/lib/daigaku/generator.rb +13 -15
- data/lib/daigaku/github_client.rb +5 -5
- data/lib/daigaku/loadable.rb +23 -14
- data/lib/daigaku/loading/chapters.rb +0 -2
- data/lib/daigaku/loading/courses.rb +0 -2
- data/lib/daigaku/loading/units.rb +0 -2
- data/lib/daigaku/markdown.rb +1 -0
- data/lib/daigaku/markdown/printer.rb +89 -0
- data/lib/daigaku/markdown/ruby_doc.rb +15 -15
- data/lib/daigaku/solution.rb +23 -15
- data/lib/daigaku/storeable.rb +11 -12
- data/lib/daigaku/task.rb +1 -1
- data/lib/daigaku/terminal.rb +3 -4
- data/lib/daigaku/terminal/cli.rb +8 -8
- data/lib/daigaku/terminal/courses.rb +22 -19
- data/lib/daigaku/terminal/output.rb +46 -53
- data/lib/daigaku/terminal/setup.rb +13 -18
- data/lib/daigaku/terminal/solutions.rb +27 -32
- data/lib/daigaku/terminal/welcome.rb +9 -11
- data/lib/daigaku/test.rb +7 -10
- data/lib/daigaku/test_result.rb +54 -21
- data/lib/daigaku/unit.rb +2 -4
- data/lib/daigaku/version.rb +1 -1
- data/lib/daigaku/views.rb +29 -33
- data/lib/daigaku/views/chapters_menu.rb +16 -20
- data/lib/daigaku/views/courses_menu.rb +12 -15
- data/lib/daigaku/views/main_menu.rb +23 -23
- data/lib/daigaku/views/menu.rb +14 -18
- data/lib/daigaku/views/splash.rb +11 -13
- data/lib/daigaku/views/subscriber.rb +38 -0
- data/lib/daigaku/views/task_view.rb +97 -80
- data/lib/daigaku/views/top_bar.rb +4 -10
- data/lib/daigaku/views/units_menu.rb +16 -21
- data/lib/daigaku/window.rb +12 -70
- data/spec/daigaku/chapter_spec.rb +23 -18
- data/spec/daigaku/coloring_spec.rb +0 -1
- data/spec/daigaku/configuration_spec.rb +54 -50
- data/spec/daigaku/congratulator_spec.rb +11 -8
- data/spec/daigaku/course_spec.rb +75 -52
- data/spec/daigaku/generator_spec.rb +24 -25
- data/spec/daigaku/github_client_spec.rb +17 -18
- data/spec/daigaku/loading/chapters_spec.rb +2 -3
- data/spec/daigaku/loading/courses_spec.rb +2 -3
- data/spec/daigaku/loading/units_spec.rb +4 -5
- data/spec/daigaku/markdown/ruby_doc_spec.rb +12 -6
- data/spec/daigaku/reference_solution_spec.rb +8 -10
- data/spec/daigaku/solution_spec.rb +21 -22
- data/spec/daigaku/storeable_spec.rb +12 -10
- data/spec/daigaku/task_spec.rb +3 -4
- data/spec/daigaku/terminal/cli_spec.rb +29 -21
- data/spec/daigaku/terminal/courses_spec.rb +104 -99
- data/spec/daigaku/terminal/output_spec.rb +44 -39
- data/spec/daigaku/terminal/setup_spec.rb +1 -3
- data/spec/daigaku/terminal/solutions_spec.rb +0 -2
- data/spec/daigaku/terminal/welcome_spec.rb +0 -2
- data/spec/daigaku/terminal_spec.rb +5 -7
- data/spec/daigaku/test_example_spec.rb +16 -14
- data/spec/daigaku/test_result_spec.rb +21 -25
- data/spec/daigaku/test_spec.rb +11 -12
- data/spec/daigaku/unit_spec.rb +24 -27
- data/spec/daigaku/views/chapters_menu_spec.rb +0 -1
- data/spec/daigaku/views/courses_menu_spec.rb +0 -1
- data/spec/daigaku/views/menu_spec.rb +1 -2
- data/spec/daigaku/views/task_view_spec.rb +0 -2
- data/spec/daigaku/views/units_menu_spec.rb +0 -1
- data/spec/daigaku/views_spec.rb +0 -1
- data/spec/daigaku_spec.rb +9 -12
- data/spec/path_helpers_spec.rb +11 -12
- data/spec/resource_helpers_spec.rb +11 -12
- data/spec/spec_helper.rb +3 -4
- data/spec/support/macros/content_helpers.rb +16 -17
- data/spec/support/macros/mock_helpers.rb +6 -6
- data/spec/support/macros/path_helpers.rb +15 -15
- data/spec/support/macros/resource_helpers.rb +34 -35
- metadata +32 -44
@@ -1,13 +1,25 @@
|
|
1
1
|
module Daigaku
|
2
|
-
|
3
2
|
class Congratulator
|
4
|
-
|
5
3
|
def self.message
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
new.message
|
5
|
+
end
|
6
|
+
|
7
|
+
def message
|
9
8
|
lines[random_value]
|
10
9
|
end
|
11
10
|
|
11
|
+
private
|
12
|
+
|
13
|
+
def lines
|
14
|
+
@lines ||= Terminal.text(:congratulations).lines.map(&:strip).compact
|
15
|
+
end
|
16
|
+
|
17
|
+
def random_value
|
18
|
+
rand(0..lines_count)
|
19
|
+
end
|
20
|
+
|
21
|
+
def lines_count
|
22
|
+
[lines.count - 1, 0].max
|
23
|
+
end
|
12
24
|
end
|
13
25
|
end
|
data/lib/daigaku/course.rb
CHANGED
@@ -2,12 +2,11 @@ require 'fileutils'
|
|
2
2
|
|
3
3
|
module Daigaku
|
4
4
|
class Course
|
5
|
-
|
6
5
|
attr_reader :title, :path, :author, :link
|
7
6
|
|
8
7
|
def initialize(path)
|
9
|
-
@path
|
10
|
-
@title
|
8
|
+
@path = path
|
9
|
+
@title = File.basename(path).gsub(/\_+/, ' ')
|
11
10
|
@author = QuickStore.store.get(key(:author))
|
12
11
|
end
|
13
12
|
|
@@ -16,11 +15,11 @@ module Daigaku
|
|
16
15
|
end
|
17
16
|
|
18
17
|
def started?
|
19
|
-
chapters.
|
18
|
+
chapters.any?(&:started?)
|
20
19
|
end
|
21
20
|
|
22
21
|
def mastered?
|
23
|
-
chapters.
|
22
|
+
chapters.all?(&:mastered?)
|
24
23
|
end
|
25
24
|
|
26
25
|
def key(key_name)
|
@@ -44,7 +43,6 @@ module Daigaku
|
|
44
43
|
|
45
44
|
Zip::File.open(file_path) do |zip_file|
|
46
45
|
zip_file.each do |entry|
|
47
|
-
|
48
46
|
if options[:github_repo]
|
49
47
|
first, *others = entry.to_s.split('/')
|
50
48
|
directory = File.join(first.split('-')[0..-2].join('-'), others)
|
@@ -66,10 +64,13 @@ module Daigaku
|
|
66
64
|
end
|
67
65
|
|
68
66
|
FileUtils.rm(file_path)
|
69
|
-
rescue
|
67
|
+
rescue StandardError => e
|
70
68
|
puts e
|
71
69
|
old_dir = "#{course_dir}_old/"
|
72
|
-
|
70
|
+
|
71
|
+
if Dir.exist?(old_dir)
|
72
|
+
FileUtils.copy_entry(old_dir, "#{course_dir}/", true)
|
73
|
+
end
|
73
74
|
ensure
|
74
75
|
old_dir = "#{course_dir}_old/"
|
75
76
|
FileUtils.rm_r(old_dir) if Dir.exist?(old_dir)
|
data/lib/daigaku/exceptions.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
module Daigaku
|
2
|
-
|
3
2
|
class Error < StandardError; end
|
4
3
|
class CourseNotFoundError < Error; end
|
5
4
|
class ChaptersNotFoundError < Error; end
|
@@ -15,5 +14,4 @@ module Daigaku
|
|
15
14
|
class NoUrlError < Error; end
|
16
15
|
class NoZipFileUrlError < Error; end
|
17
16
|
end
|
18
|
-
|
19
17
|
end
|
data/lib/daigaku/generator.rb
CHANGED
@@ -1,21 +1,21 @@
|
|
1
|
-
|
2
|
-
require 'fileutils'
|
3
|
-
require 'active_support'
|
4
|
-
require 'active_support/core_ext'
|
1
|
+
require 'fileutils'
|
5
2
|
|
3
|
+
module Daigaku
|
6
4
|
class Generator
|
5
|
+
LEADING_NUMBERS = /^\d+[\_\-\s]/
|
6
|
+
PART_JOINTS = /[\_\-\s]+/
|
7
7
|
|
8
8
|
def scaffold(courses_path, target_path)
|
9
|
-
Dir[File.join(courses_path,
|
9
|
+
Dir[File.join(courses_path, '*/*/*/*.md')].each do |file|
|
10
10
|
content_dir_parts = file.split('/')[-4..-2].map do |part|
|
11
11
|
clean_up_path_part(part)
|
12
12
|
end
|
13
13
|
|
14
14
|
content_dir = File.join(content_dir_parts)
|
15
|
-
directory
|
15
|
+
directory = File.join(target_path, File.dirname(content_dir))
|
16
16
|
|
17
17
|
solution_file = File.basename(content_dir) + Solution::FILE_SUFFIX
|
18
|
-
file_path
|
18
|
+
file_path = File.join(directory, solution_file)
|
19
19
|
|
20
20
|
create_dir(directory)
|
21
21
|
create_file(file_path)
|
@@ -25,9 +25,9 @@ module Daigaku
|
|
25
25
|
def prepare
|
26
26
|
begin
|
27
27
|
solutions_path = Daigaku.config.solutions_path
|
28
|
-
rescue ConfigurationError
|
29
|
-
base_dir
|
30
|
-
solutions_dir
|
28
|
+
rescue ConfigurationError
|
29
|
+
base_dir = File.dirname(Daigaku.config.courses_path)
|
30
|
+
solutions_dir = Daigaku::Configuration::SOLUTIONS_DIR
|
31
31
|
solutions_path = File.join(base_dir, solutions_dir)
|
32
32
|
end
|
33
33
|
|
@@ -41,20 +41,18 @@ module Daigaku
|
|
41
41
|
private
|
42
42
|
|
43
43
|
def create_dir(path)
|
44
|
-
return if path.
|
44
|
+
return if path.nil? || path.empty?
|
45
45
|
FileUtils.makedirs(path) unless Dir.exist?(path)
|
46
46
|
end
|
47
47
|
|
48
48
|
def create_file(path)
|
49
|
-
return if path.
|
49
|
+
return if path.nil? || path.empty?
|
50
50
|
create_dir(File.dirname(path))
|
51
51
|
FileUtils.touch(path) unless File.exist?(path)
|
52
52
|
end
|
53
53
|
|
54
54
|
def clean_up_path_part(text)
|
55
|
-
|
56
|
-
part_joints = /[\_\-\s]+/
|
57
|
-
text.gsub(leading_numbers, '').gsub(part_joints, '_').downcase
|
55
|
+
text.gsub(LEADING_NUMBERS, '').gsub(PART_JOINTS, '_').downcase
|
58
56
|
end
|
59
57
|
end
|
60
58
|
end
|
@@ -4,7 +4,6 @@ require 'date'
|
|
4
4
|
|
5
5
|
module Daigaku
|
6
6
|
module GithubClient
|
7
|
-
|
8
7
|
# Returns the url to the master zip file of the Github repo.
|
9
8
|
def self.master_zip_url(user_and_repo)
|
10
9
|
"https://github.com/#{user_and_repo}/archive/master.zip"
|
@@ -13,7 +12,7 @@ module Daigaku
|
|
13
12
|
# Returns the timestamp of updated_at for the repo from the Github API.
|
14
13
|
def self.updated_at(user_and_repo)
|
15
14
|
url = "https://api.github.com/repos/#{user_and_repo}"
|
16
|
-
JSON.parse(open(url).read)['updated_at']
|
15
|
+
JSON.parse(URI.open(url).read)['updated_at']
|
17
16
|
end
|
18
17
|
|
19
18
|
# Returns whether the pushed_at time from Github API is newer than the
|
@@ -21,9 +20,10 @@ module Daigaku
|
|
21
20
|
def self.updated?(user_and_repo)
|
22
21
|
return false unless user_and_repo
|
23
22
|
|
24
|
-
course
|
25
|
-
stored_time
|
26
|
-
current_time =
|
23
|
+
course = Course.new(user_and_repo.split('/').last)
|
24
|
+
stored_time = QuickStore.store.get(course.key(:updated_at))
|
25
|
+
current_time = updated_at(user_and_repo)
|
26
|
+
|
27
27
|
DateTime.parse(stored_time) < DateTime.parse(current_time)
|
28
28
|
end
|
29
29
|
end
|
data/lib/daigaku/loadable.rb
CHANGED
@@ -1,23 +1,32 @@
|
|
1
1
|
module Daigaku
|
2
2
|
module Loadable
|
3
|
-
|
4
|
-
require 'active_support/inflector'
|
5
|
-
|
6
3
|
def load(path)
|
7
|
-
|
8
|
-
dirs = Dir.entries(path).select do |entry|
|
9
|
-
!entry.match(/\./)
|
10
|
-
end
|
4
|
+
return [] unless Dir.exist?(path)
|
11
5
|
|
12
|
-
|
13
|
-
|
14
|
-
class_name = self.to_s.demodulize.singularize
|
15
|
-
"Daigaku::#{class_name}".constantize.new(dir_path)
|
16
|
-
end
|
17
|
-
else
|
18
|
-
Array.new
|
6
|
+
dirs = Dir.entries(path).select do |entry|
|
7
|
+
!entry.match(/\./)
|
19
8
|
end
|
9
|
+
|
10
|
+
dirs.sort.map do |dir|
|
11
|
+
dir_path = File.join(path, dir)
|
12
|
+
module_name = demodulize(to_s)
|
13
|
+
class_name = singularize(module_name)
|
14
|
+
daigaku_class(class_name).new(dir_path)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def demodulize(string)
|
21
|
+
string.split('::').last
|
20
22
|
end
|
21
23
|
|
24
|
+
def singularize(string)
|
25
|
+
string.end_with?('s') ? string[0..-2] : string
|
26
|
+
end
|
27
|
+
|
28
|
+
def daigaku_class(name)
|
29
|
+
Kernel.const_get("Daigaku::#{name}")
|
30
|
+
end
|
22
31
|
end
|
23
32
|
end
|
data/lib/daigaku/markdown.rb
CHANGED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'curses'
|
2
|
+
require_relative 'ruby_doc'
|
3
|
+
|
4
|
+
module Daigaku
|
5
|
+
module Markdown
|
6
|
+
class Printer
|
7
|
+
H1 = /\A\#{1}[^#]+/ # '# heading'
|
8
|
+
H2 = /\A\#{2}[^#]+/ # '## sub heading'
|
9
|
+
BOLD = /\*[^*]*\*/ # '*text*'
|
10
|
+
LINE = /\A-{3,}/ # '---' vertical line
|
11
|
+
CODE = /`[^`]*`/ # '`code line`'
|
12
|
+
|
13
|
+
attr_reader :window
|
14
|
+
|
15
|
+
def initialize(window:)
|
16
|
+
@window = window
|
17
|
+
end
|
18
|
+
|
19
|
+
def print(text)
|
20
|
+
text_line = RubyDoc.parse(text)
|
21
|
+
|
22
|
+
case text_line
|
23
|
+
when H1 then print_h1(text_line)
|
24
|
+
when H2 then print_h2(text_line)
|
25
|
+
when /(#{CODE}|#{BOLD})/ then print_code_or_bold(text_line)
|
26
|
+
when BOLD then print_bold(text_line)
|
27
|
+
when LINE then print_line
|
28
|
+
else print_escaped(text_line)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def print_h1(text)
|
33
|
+
window.heading(text.sub(/\A#\s?/, ''))
|
34
|
+
end
|
35
|
+
|
36
|
+
def print_h2(text)
|
37
|
+
text_decoration = Curses::A_UNDERLINE | Curses::A_NORMAL
|
38
|
+
window.emphasize(text.sub(/\A##\s?/, ''), text_decoration)
|
39
|
+
end
|
40
|
+
|
41
|
+
def print_code_or_bold(text)
|
42
|
+
emphasized = false
|
43
|
+
highlighted = false
|
44
|
+
|
45
|
+
text.chars.each_with_index do |char, index|
|
46
|
+
if char == '*' && text[index - 1] != '\\'
|
47
|
+
emphasized = !emphasized
|
48
|
+
next
|
49
|
+
end
|
50
|
+
|
51
|
+
if char == '`'
|
52
|
+
highlighted = !highlighted
|
53
|
+
next
|
54
|
+
end
|
55
|
+
|
56
|
+
character = text[index..(index + 1)] == '\\*' ? '' : char
|
57
|
+
|
58
|
+
if highlighted
|
59
|
+
window.red(character)
|
60
|
+
elsif emphasized
|
61
|
+
window.emphasize(character)
|
62
|
+
else
|
63
|
+
window.write(character)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def print_bold(text)
|
69
|
+
text.chars.each_with_index do |char, index|
|
70
|
+
if char == '*' && text[index - 1] != '\\'
|
71
|
+
emphasized = !emphasized
|
72
|
+
next
|
73
|
+
end
|
74
|
+
|
75
|
+
character = text[index..(index + 1)].to_s == '\\*' ? '' : char
|
76
|
+
emphasized ? window.emphasize(character) : window.write(character)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def print_line
|
81
|
+
window.write('-' * (Curses.cols - 2))
|
82
|
+
end
|
83
|
+
|
84
|
+
def print_escaped(text)
|
85
|
+
window.write(text.gsub(/(\\#)/, '#'))
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -1,15 +1,14 @@
|
|
1
1
|
require 'cgi'
|
2
2
|
|
3
3
|
module Daigaku
|
4
|
-
|
4
|
+
module Markdown
|
5
5
|
class RubyDoc
|
6
|
-
|
7
|
-
RUBY_DOC_URL = "http://ruby-doc.org".freeze
|
6
|
+
RUBY_DOC_URL = 'http://ruby-doc.org'.freeze
|
8
7
|
CORE_BASE_URL = "#{RUBY_DOC_URL}/core-#{RUBY_VERSION}".freeze
|
9
8
|
STDLIB_BASE_URL = "#{RUBY_DOC_URL}/stdlib-#{RUBY_VERSION}".freeze
|
10
9
|
|
11
|
-
CORE_REGEX = /\(ruby-doc core:\s?(.*)\)
|
12
|
-
STDLIB_REGEX = /\(ruby-doc stdlib:\s?(.*)\)
|
10
|
+
CORE_REGEX = /\(ruby-doc core:\s?(.*)\)/
|
11
|
+
STDLIB_REGEX = /\(ruby-doc stdlib:\s?(.*)\)/
|
13
12
|
|
14
13
|
class << self
|
15
14
|
def parse(text)
|
@@ -43,19 +42,19 @@ module Daigaku
|
|
43
42
|
end
|
44
43
|
|
45
44
|
def doc_regex(type, capture)
|
46
|
-
|
45
|
+
Regexp.new(Regexp.escape("(ruby-doc #{type}: #{capture})"))
|
47
46
|
end
|
48
47
|
|
49
48
|
def core_link(text)
|
50
49
|
constants = ruby_constants(text).join('/')
|
51
|
-
method
|
50
|
+
method = ruby_method(text)
|
52
51
|
|
53
52
|
"#{CORE_BASE_URL}/#{constants}.html#{method}"
|
54
53
|
end
|
55
54
|
|
56
55
|
def stdlib_link(text)
|
57
|
-
constants
|
58
|
-
method
|
56
|
+
constants = ruby_constants(text).join('/')
|
57
|
+
method = ruby_method(text)
|
59
58
|
libdoc_part = "libdoc/#{ruby_stdlib(text)}/rdoc"
|
60
59
|
|
61
60
|
"#{STDLIB_BASE_URL}/#{libdoc_part}/#{constants}.html#{method}"
|
@@ -68,7 +67,7 @@ module Daigaku
|
|
68
67
|
# Else the lib is created from the constants, e.g.
|
69
68
|
# (ruby-doc stdlib: Time) => 'time'
|
70
69
|
def ruby_stdlib(text)
|
71
|
-
parts = text.split
|
70
|
+
parts = text.split
|
72
71
|
|
73
72
|
if parts.length > 1
|
74
73
|
parts.first.strip.downcase
|
@@ -78,7 +77,7 @@ module Daigaku
|
|
78
77
|
end
|
79
78
|
|
80
79
|
def ruby_constants(text)
|
81
|
-
parts = text.split
|
80
|
+
parts = text.split.last.split(/::|#/)
|
82
81
|
select_capitalized(parts)
|
83
82
|
end
|
84
83
|
|
@@ -86,19 +85,20 @@ module Daigaku
|
|
86
85
|
method = text.split(/::|#/).last
|
87
86
|
return '' unless downcased?(method)
|
88
87
|
|
89
|
-
method_type = text
|
90
|
-
method_name = CGI.escape(method.strip).
|
88
|
+
method_type = text =~ /#/ ? 'i' : 'c'
|
89
|
+
method_name = CGI.escape(method.strip).tr('%', '-').gsub(/\A-/, '')
|
91
90
|
"#method-#{method_type}-#{method_name}"
|
92
91
|
end
|
93
92
|
|
94
93
|
def select_capitalized(parts)
|
95
|
-
parts.select
|
94
|
+
parts.select do |part|
|
95
|
+
part[0].match(/\w/) && part[0] == part[0].upcase
|
96
|
+
end
|
96
97
|
end
|
97
98
|
|
98
99
|
def downcased?(text)
|
99
100
|
text == text.downcase
|
100
101
|
end
|
101
|
-
|
102
102
|
end
|
103
103
|
end
|
104
104
|
end
|