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.
Files changed (88) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +7 -4
  3. data/CODE_OF_CONDUCT.md +77 -0
  4. data/README.md +11 -11
  5. data/bin/daigaku +6 -2
  6. data/daigaku.gemspec +22 -26
  7. data/lib/daigaku.rb +0 -1
  8. data/lib/daigaku/chapter.rb +3 -4
  9. data/lib/daigaku/coloring.rb +22 -27
  10. data/lib/daigaku/configuration.rb +25 -28
  11. data/lib/daigaku/congratulator.rb +17 -5
  12. data/lib/daigaku/course.rb +9 -8
  13. data/lib/daigaku/exceptions.rb +0 -2
  14. data/lib/daigaku/generator.rb +13 -15
  15. data/lib/daigaku/github_client.rb +5 -5
  16. data/lib/daigaku/loadable.rb +23 -14
  17. data/lib/daigaku/loading/chapters.rb +0 -2
  18. data/lib/daigaku/loading/courses.rb +0 -2
  19. data/lib/daigaku/loading/units.rb +0 -2
  20. data/lib/daigaku/markdown.rb +1 -0
  21. data/lib/daigaku/markdown/printer.rb +89 -0
  22. data/lib/daigaku/markdown/ruby_doc.rb +15 -15
  23. data/lib/daigaku/solution.rb +23 -15
  24. data/lib/daigaku/storeable.rb +11 -12
  25. data/lib/daigaku/task.rb +1 -1
  26. data/lib/daigaku/terminal.rb +3 -4
  27. data/lib/daigaku/terminal/cli.rb +8 -8
  28. data/lib/daigaku/terminal/courses.rb +22 -19
  29. data/lib/daigaku/terminal/output.rb +46 -53
  30. data/lib/daigaku/terminal/setup.rb +13 -18
  31. data/lib/daigaku/terminal/solutions.rb +27 -32
  32. data/lib/daigaku/terminal/welcome.rb +9 -11
  33. data/lib/daigaku/test.rb +7 -10
  34. data/lib/daigaku/test_result.rb +54 -21
  35. data/lib/daigaku/unit.rb +2 -4
  36. data/lib/daigaku/version.rb +1 -1
  37. data/lib/daigaku/views.rb +29 -33
  38. data/lib/daigaku/views/chapters_menu.rb +16 -20
  39. data/lib/daigaku/views/courses_menu.rb +12 -15
  40. data/lib/daigaku/views/main_menu.rb +23 -23
  41. data/lib/daigaku/views/menu.rb +14 -18
  42. data/lib/daigaku/views/splash.rb +11 -13
  43. data/lib/daigaku/views/subscriber.rb +38 -0
  44. data/lib/daigaku/views/task_view.rb +97 -80
  45. data/lib/daigaku/views/top_bar.rb +4 -10
  46. data/lib/daigaku/views/units_menu.rb +16 -21
  47. data/lib/daigaku/window.rb +12 -70
  48. data/spec/daigaku/chapter_spec.rb +23 -18
  49. data/spec/daigaku/coloring_spec.rb +0 -1
  50. data/spec/daigaku/configuration_spec.rb +54 -50
  51. data/spec/daigaku/congratulator_spec.rb +11 -8
  52. data/spec/daigaku/course_spec.rb +75 -52
  53. data/spec/daigaku/generator_spec.rb +24 -25
  54. data/spec/daigaku/github_client_spec.rb +17 -18
  55. data/spec/daigaku/loading/chapters_spec.rb +2 -3
  56. data/spec/daigaku/loading/courses_spec.rb +2 -3
  57. data/spec/daigaku/loading/units_spec.rb +4 -5
  58. data/spec/daigaku/markdown/ruby_doc_spec.rb +12 -6
  59. data/spec/daigaku/reference_solution_spec.rb +8 -10
  60. data/spec/daigaku/solution_spec.rb +21 -22
  61. data/spec/daigaku/storeable_spec.rb +12 -10
  62. data/spec/daigaku/task_spec.rb +3 -4
  63. data/spec/daigaku/terminal/cli_spec.rb +29 -21
  64. data/spec/daigaku/terminal/courses_spec.rb +104 -99
  65. data/spec/daigaku/terminal/output_spec.rb +44 -39
  66. data/spec/daigaku/terminal/setup_spec.rb +1 -3
  67. data/spec/daigaku/terminal/solutions_spec.rb +0 -2
  68. data/spec/daigaku/terminal/welcome_spec.rb +0 -2
  69. data/spec/daigaku/terminal_spec.rb +5 -7
  70. data/spec/daigaku/test_example_spec.rb +16 -14
  71. data/spec/daigaku/test_result_spec.rb +21 -25
  72. data/spec/daigaku/test_spec.rb +11 -12
  73. data/spec/daigaku/unit_spec.rb +24 -27
  74. data/spec/daigaku/views/chapters_menu_spec.rb +0 -1
  75. data/spec/daigaku/views/courses_menu_spec.rb +0 -1
  76. data/spec/daigaku/views/menu_spec.rb +1 -2
  77. data/spec/daigaku/views/task_view_spec.rb +0 -2
  78. data/spec/daigaku/views/units_menu_spec.rb +0 -1
  79. data/spec/daigaku/views_spec.rb +0 -1
  80. data/spec/daigaku_spec.rb +9 -12
  81. data/spec/path_helpers_spec.rb +11 -12
  82. data/spec/resource_helpers_spec.rb +11 -12
  83. data/spec/spec_helper.rb +3 -4
  84. data/spec/support/macros/content_helpers.rb +16 -17
  85. data/spec/support/macros/mock_helpers.rb +6 -6
  86. data/spec/support/macros/path_helpers.rb +15 -15
  87. data/spec/support/macros/resource_helpers.rb +34 -35
  88. metadata +32 -44
@@ -1,13 +1,25 @@
1
1
  module Daigaku
2
-
3
2
  class Congratulator
4
-
5
3
  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)
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
@@ -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 = path
10
- @title = File.basename(path).gsub(/\_+/, ' ')
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.reduce(false) { |started, chapter| started ||= chapter.started? }
18
+ chapters.any?(&:started?)
20
19
  end
21
20
 
22
21
  def mastered?
23
- chapters.reduce(true) { |mastered, chapter| mastered &&= chapter.mastered? }
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 Exception => e
67
+ rescue StandardError => e
70
68
  puts e
71
69
  old_dir = "#{course_dir}_old/"
72
- FileUtils.copy_entry(old_dir, "#{course_dir}/", true) if Dir.exist?(old_dir)
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)
@@ -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
@@ -1,21 +1,21 @@
1
- module Daigaku
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, "*/*/*/*.md")].each do |file|
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 = File.join(target_path, File.dirname(content_dir))
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 = File.join(directory, solution_file)
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 => e
29
- base_dir = File.dirname(Daigaku.config.courses_path)
30
- solutions_dir = Daigaku::Configuration::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.blank?
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.blank?
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
- leading_numbers = /^\d+[\_\-\s]/
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 = 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)
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
@@ -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
- if Dir.exist?(path)
8
- dirs = Dir.entries(path).select do |entry|
9
- !entry.match(/\./)
10
- end
4
+ return [] unless Dir.exist?(path)
11
5
 
12
- dirs.sort.map do |dir|
13
- dir_path = File.join(path, dir)
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
@@ -1,9 +1,7 @@
1
1
  module Daigaku
2
2
  module Loading
3
-
4
3
  class Chapters
5
4
  extend Daigaku::Loadable
6
5
  end
7
-
8
6
  end
9
7
  end
@@ -1,9 +1,7 @@
1
1
  module Daigaku
2
2
  module Loading
3
-
4
3
  class Courses
5
4
  extend Daigaku::Loadable
6
5
  end
7
-
8
6
  end
9
7
  end
@@ -1,9 +1,7 @@
1
1
  module Daigaku
2
2
  module Loading
3
-
4
3
  class Units
5
4
  extend Daigaku::Loadable
6
5
  end
7
-
8
6
  end
9
7
  end
@@ -1 +1,2 @@
1
1
  require 'daigaku/markdown/ruby_doc'
2
+ require 'daigaku/markdown/printer'
@@ -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
- class Markdown
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?(.*)\)/.freeze
12
- STDLIB_REGEX = /\(ruby-doc stdlib:\s?(.*)\)/.freeze
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
- /\(ruby-doc #{type}: #{capture}\)/
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 = ruby_method(text)
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 = ruby_constants(text).join('/')
58
- method = ruby_method(text)
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(' ').last.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.match(/#/) ? 'i' : 'c'
90
- method_name = CGI.escape(method.strip).gsub('%', '-').gsub(/\A-/, '')
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 { |part| part[0] == part[0].upcase }
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