daigaku 0.3.0 → 0.4.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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -3
  3. data/README.md +1 -1
  4. data/daigaku.gemspec +23 -26
  5. data/lib/daigaku.rb +0 -1
  6. data/lib/daigaku/chapter.rb +3 -4
  7. data/lib/daigaku/coloring.rb +13 -13
  8. data/lib/daigaku/configuration.rb +25 -28
  9. data/lib/daigaku/congratulator.rb +17 -5
  10. data/lib/daigaku/course.rb +9 -8
  11. data/lib/daigaku/exceptions.rb +0 -2
  12. data/lib/daigaku/generator.rb +13 -13
  13. data/lib/daigaku/github_client.rb +4 -4
  14. data/lib/daigaku/loadable.rb +11 -15
  15. data/lib/daigaku/loading/chapters.rb +0 -2
  16. data/lib/daigaku/loading/courses.rb +0 -2
  17. data/lib/daigaku/loading/units.rb +0 -2
  18. data/lib/daigaku/markdown.rb +1 -0
  19. data/lib/daigaku/markdown/printer.rb +89 -0
  20. data/lib/daigaku/markdown/ruby_doc.rb +6 -8
  21. data/lib/daigaku/solution.rb +18 -15
  22. data/lib/daigaku/storeable.rb +11 -12
  23. data/lib/daigaku/task.rb +1 -1
  24. data/lib/daigaku/terminal.rb +3 -4
  25. data/lib/daigaku/terminal/cli.rb +6 -8
  26. data/lib/daigaku/terminal/courses.rb +19 -16
  27. data/lib/daigaku/terminal/output.rb +0 -2
  28. data/lib/daigaku/terminal/setup.rb +13 -18
  29. data/lib/daigaku/terminal/solutions.rb +27 -32
  30. data/lib/daigaku/terminal/welcome.rb +9 -11
  31. data/lib/daigaku/test.rb +7 -10
  32. data/lib/daigaku/test_result.rb +13 -16
  33. data/lib/daigaku/unit.rb +1 -3
  34. data/lib/daigaku/version.rb +1 -1
  35. data/lib/daigaku/views.rb +4 -3
  36. data/lib/daigaku/views/chapters_menu.rb +16 -20
  37. data/lib/daigaku/views/courses_menu.rb +12 -15
  38. data/lib/daigaku/views/main_menu.rb +23 -23
  39. data/lib/daigaku/views/menu.rb +9 -13
  40. data/lib/daigaku/views/splash.rb +11 -13
  41. data/lib/daigaku/views/subscriber.rb +38 -0
  42. data/lib/daigaku/views/task_view.rb +80 -78
  43. data/lib/daigaku/views/top_bar.rb +4 -10
  44. data/lib/daigaku/views/units_menu.rb +16 -21
  45. data/lib/daigaku/window.rb +12 -70
  46. data/spec/daigaku/chapter_spec.rb +23 -18
  47. data/spec/daigaku/coloring_spec.rb +0 -1
  48. data/spec/daigaku/configuration_spec.rb +54 -50
  49. data/spec/daigaku/congratulator_spec.rb +11 -8
  50. data/spec/daigaku/course_spec.rb +70 -51
  51. data/spec/daigaku/generator_spec.rb +24 -25
  52. data/spec/daigaku/github_client_spec.rb +17 -18
  53. data/spec/daigaku/loading/chapters_spec.rb +2 -3
  54. data/spec/daigaku/loading/courses_spec.rb +2 -3
  55. data/spec/daigaku/loading/units_spec.rb +4 -5
  56. data/spec/daigaku/markdown/ruby_doc_spec.rb +3 -6
  57. data/spec/daigaku/reference_solution_spec.rb +8 -10
  58. data/spec/daigaku/solution_spec.rb +18 -20
  59. data/spec/daigaku/storeable_spec.rb +12 -10
  60. data/spec/daigaku/task_spec.rb +3 -4
  61. data/spec/daigaku/terminal/cli_spec.rb +29 -21
  62. data/spec/daigaku/terminal/courses_spec.rb +104 -99
  63. data/spec/daigaku/terminal/output_spec.rb +44 -39
  64. data/spec/daigaku/terminal/setup_spec.rb +1 -3
  65. data/spec/daigaku/terminal/solutions_spec.rb +0 -2
  66. data/spec/daigaku/terminal/welcome_spec.rb +0 -2
  67. data/spec/daigaku/terminal_spec.rb +5 -7
  68. data/spec/daigaku/test_example_spec.rb +16 -14
  69. data/spec/daigaku/test_result_spec.rb +21 -25
  70. data/spec/daigaku/test_spec.rb +11 -12
  71. data/spec/daigaku/unit_spec.rb +24 -27
  72. data/spec/daigaku/views/chapters_menu_spec.rb +0 -1
  73. data/spec/daigaku/views/courses_menu_spec.rb +0 -1
  74. data/spec/daigaku/views/menu_spec.rb +1 -2
  75. data/spec/daigaku/views/task_view_spec.rb +0 -2
  76. data/spec/daigaku/views/units_menu_spec.rb +0 -1
  77. data/spec/daigaku/views_spec.rb +0 -1
  78. data/spec/daigaku_spec.rb +9 -12
  79. data/spec/path_helpers_spec.rb +11 -12
  80. data/spec/resource_helpers_spec.rb +11 -12
  81. data/spec/spec_helper.rb +3 -4
  82. data/spec/support/macros/content_helpers.rb +16 -17
  83. data/spec/support/macros/mock_helpers.rb +6 -6
  84. data/spec/support/macros/path_helpers.rb +15 -15
  85. data/spec/support/macros/resource_helpers.rb +34 -35
  86. metadata +12 -10
@@ -1,46 +1,41 @@
1
+ require 'os'
2
+ require_relative 'output'
3
+
1
4
  module Daigaku
2
5
  module Terminal
3
-
4
- require 'os'
5
- require_relative 'output'
6
-
7
6
  class Solutions < Thor
8
7
  include Terminal::Output
9
8
 
10
9
  desc 'solutions open [COURSE NAME]', 'Open the solutions folder of a course in a GUI window'
11
10
  def open(course_name = '')
12
- begin
13
- path = File.join(Daigaku.config.solutions_path, course_name)
14
-
15
- unless Dir.exist?(path)
16
- text = [
17
- "The course directory \"#{File.basename(path)}\" is not available in",
18
- "\"#{File.dirname(path)}\".\n",
19
- 'Hint:',
20
- 'Run "daigaku scaffold" to create empty solution files for all courses.'
21
- ]
22
- say_warning text.join("\n")
23
-
24
- unless Loading::Courses.load(Daigaku.config.courses_path).empty?
25
- Terminal::Courses.new.list
26
- end
27
-
28
- return
11
+ path = File.join(Daigaku.config.solutions_path, course_name)
12
+
13
+ unless Dir.exist?(path)
14
+ text = [
15
+ "The course directory \"#{File.basename(path)}\" is not available in",
16
+ "\"#{File.dirname(path)}\".\n",
17
+ 'Hint:',
18
+ 'Run "daigaku scaffold" to create empty solution files for all courses.'
19
+ ]
20
+ say_warning text.join("\n")
21
+
22
+ unless Loading::Courses.load(Daigaku.config.courses_path).empty?
23
+ Terminal::Courses.new.list
29
24
  end
30
25
 
31
- if OS.windows?
32
- system "explorer '#{path}'"
33
- elsif OS.mac?
34
- system "open '#{path}'"
35
- elsif OS.linux?
36
- system "xdg-open '#{path}'"
37
- end
38
- rescue ConfigurationError => e
39
- say_warning e.message
26
+ return
40
27
  end
41
- end
42
28
 
29
+ if OS.windows?
30
+ system "explorer '#{path}'"
31
+ elsif OS.mac?
32
+ system "open '#{path}'"
33
+ elsif OS.linux?
34
+ system "xdg-open '#{path}'"
35
+ end
36
+ rescue ConfigurationError => e
37
+ say_warning e.message
38
+ end
43
39
  end
44
-
45
40
  end
46
41
  end
@@ -1,22 +1,21 @@
1
1
  module Daigaku
2
2
  module Terminal
3
-
4
3
  class Welcome
5
4
  include Terminal::Output
6
5
 
7
6
  def self.run
8
- self.new.run
7
+ new.run
9
8
  end
10
9
 
11
10
  def self.about
12
- self.new.about
11
+ new.about
13
12
  end
14
13
 
15
14
  def run
16
15
  empty_line
17
16
  say Terminal.text :welcome
18
17
  empty_line
19
- say "For now, let's setup the daigaku paths."
18
+ say 'For now, lets setup the daigaku paths.'
20
19
  Daigaku::Terminal::Setup.new.init
21
20
 
22
21
  show_setup_list_announcement
@@ -37,7 +36,7 @@ module Daigaku
37
36
  empty_line
38
37
  say Terminal.text :about
39
38
  empty_line
40
- say %x{daigaku help}
39
+ say `daigaku help`
41
40
  end
42
41
 
43
42
  private
@@ -45,7 +44,7 @@ module Daigaku
45
44
  def show_setup_list_announcement
46
45
  command = 'daigaku setup list'
47
46
  text = [
48
- "The courses path and solutions path have been added to your settings.",
47
+ 'The courses path and solutions path have been added to your settings.',
49
48
  "Just list your current settings with the \"#{command}\" command:"
50
49
  ].join("\n")
51
50
 
@@ -56,7 +55,7 @@ module Daigaku
56
55
  command = 'daigaku courses list'
57
56
  text = [
58
57
  "Well done. Now, type \"#{command}\" to see what courses are",
59
- "available in your daigaku folder:"
58
+ 'available in your daigaku folder:'
60
59
  ].join("\n")
61
60
 
62
61
  get_command(command, text)
@@ -65,7 +64,7 @@ module Daigaku
65
64
  def show_courses_download_announcement
66
65
  command = 'daigaku courses download'
67
66
  text = [
68
- "Oh! You don't have any courses, yet?",
67
+ 'Oh! You dont have any courses, yet?',
69
68
  "Just enter \"#{command}\" to download the basic Daigaku course:"
70
69
  ].join("\n")
71
70
 
@@ -75,7 +74,7 @@ module Daigaku
75
74
  def show_solutions_open_announcement
76
75
  command = 'daigaku solutions open'
77
76
  text = [
78
- "When downloading a course, Daigaku scaffolds empty solution files",
77
+ 'When downloading a course, Daigaku scaffolds empty solution files',
79
78
  "for your code on the fly.\n",
80
79
  "Type \"#{command}\" to open your solutions folder:"
81
80
  ].join("\n")
@@ -86,13 +85,12 @@ module Daigaku
86
85
  def show_learn_announcement
87
86
  command = 'daigaku learn'
88
87
  text = [
89
- "Congratulations! You learned the first steps of using daigaku.",
88
+ 'Congratulations! You learned the first steps of using daigaku.',
90
89
  "To continue and start learning Ruby type \"#{command}\":"
91
90
  ].join("\n")
92
91
 
93
92
  get_command(command, text)
94
93
  end
95
94
  end
96
-
97
95
  end
98
96
  end
data/lib/daigaku/test.rb CHANGED
@@ -1,25 +1,24 @@
1
- module Daigaku
2
- require 'fileutils'
1
+ require 'fileutils'
3
2
 
3
+ module Daigaku
4
4
  class Test
5
+ CODE_REGEX = /\[\['solution::code'\]\]/
5
6
 
6
7
  attr_reader :path
7
8
 
8
- CODE_REGEX = /\[\['solution::code'\]\]/
9
-
10
9
  def initialize(path)
11
10
  @unit_path = path
12
- @path = Dir[File.join(path, '*spec.rb')].first
11
+ @path = Dir[File.join(path, '*spec.rb')].first
13
12
  end
14
13
 
15
14
  def run(solution_code)
16
- spec_code = File.read(@path)
15
+ spec_code = File.read(@path)
17
16
  patched_spec_code = insert_code(spec_code, solution_code.to_s)
18
17
 
19
18
  temp_spec = File.join(File.dirname(@path), "temp_#{File.basename(@path)}")
20
19
  create_temp_spec(temp_spec, patched_spec_code)
21
20
 
22
- result = %x{ rspec --color --format j #{temp_spec} }
21
+ result = `rspec --color --format j #{temp_spec}`
23
22
  remove_file(temp_spec)
24
23
 
25
24
  TestResult.new(result)
@@ -33,14 +32,12 @@ module Daigaku
33
32
 
34
33
  def create_temp_spec(path, content)
35
34
  base_path = File.dirname(path)
36
- FileUtils.mkdir_p(base_path) unless Dir.exist?(base_path)
35
+ FileUtils.makedirs(base_path) unless Dir.exist?(base_path)
37
36
  File.open(path, 'w') { |f| f.puts content }
38
37
  end
39
38
 
40
39
  def remove_file(path)
41
40
  FileUtils.rm(path) if File.exist?(path)
42
41
  end
43
-
44
42
  end
45
-
46
43
  end
@@ -1,8 +1,7 @@
1
- module Daigaku
1
+ require 'json'
2
2
 
3
+ module Daigaku
3
4
  class TestResult
4
- require 'json'
5
-
6
5
  attr_reader :examples, :example_count, :failure_count
7
6
 
8
7
  def initialize(result_json)
@@ -17,9 +16,9 @@ module Daigaku
17
16
 
18
17
  @examples = @result[:examples].map do |example|
19
18
  description = example[:full_description]
20
- status = example[:status]
21
- exception = example[:exception]
22
- message = exception ? exception[:message] : nil
19
+ status = example[:status]
20
+ exception = example[:exception]
21
+ message = exception ? exception[:message] : nil
23
22
 
24
23
  TestExample.new(description: description, status: status, message: message)
25
24
  end
@@ -47,10 +46,10 @@ module Daigaku
47
46
 
48
47
  def build_failed_summary
49
48
  message = examples.map do |example|
50
- "#{example.description}\n#{example.status}: #{example.message}"
49
+ "#{example.description}\n#{example.status}: #{example.message}".strip
51
50
  end
52
51
 
53
- summary = message.map(&:strip).join("\n" * 3)
52
+ message.join("\n" * 3)
54
53
  end
55
54
 
56
55
  def syntax_error_json
@@ -59,7 +58,7 @@ module Daigaku
59
58
  examples: [
60
59
  {
61
60
  status: 'failed',
62
- exception: { message: ":( You got an error in your code!" }
61
+ exception: { message: ':( You got an error in your code!' }
63
62
  }
64
63
  ]
65
64
  }
@@ -67,20 +66,18 @@ module Daigaku
67
66
  end
68
67
 
69
68
  class TestExample
70
-
71
69
  attr_reader :description, :status, :message
72
70
 
73
- EXAMPLE_PASSED_MESSAGE = "Your code passed this requirement."
71
+ EXAMPLE_PASSED_MESSAGE = 'Your code passed this requirement.'.freeze
74
72
 
75
- def initialize(args = {})
76
- @description = args[:description]
77
- @status = args[:status]
78
- @message = args[:message] || EXAMPLE_PASSED_MESSAGE
73
+ def initialize(description:, status:, message: nil)
74
+ @description = description
75
+ @status = status
76
+ @message = message || EXAMPLE_PASSED_MESSAGE
79
77
  end
80
78
 
81
79
  def passed?
82
80
  @status == 'passed'
83
81
  end
84
82
  end
85
-
86
83
  end
data/lib/daigaku/unit.rb CHANGED
@@ -1,10 +1,9 @@
1
1
  module Daigaku
2
2
  class Unit
3
-
4
3
  attr_reader :title, :task
5
4
 
6
5
  def initialize(path)
7
- @path = path
6
+ @path = path
8
7
  @title = File.basename(path).gsub(/\_+/, ' ')
9
8
  end
10
9
 
@@ -23,6 +22,5 @@ module Daigaku
23
22
  def mastered?
24
23
  solution.verified?
25
24
  end
26
-
27
25
  end
28
26
  end
@@ -1,3 +1,3 @@
1
1
  module Daigaku
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.0'.freeze
3
3
  end
data/lib/daigaku/views.rb CHANGED
@@ -23,7 +23,7 @@ module Daigaku
23
23
  curs_set(0) # invisible cursor
24
24
 
25
25
  height ||= lines
26
- width ||= cols + 1
26
+ width ||= cols + 1
27
27
 
28
28
  window = Daigaku::Window.new(height, width, top, left)
29
29
 
@@ -40,12 +40,13 @@ module Daigaku
40
40
 
41
41
  def sub_window_below_top_bar(window, top_bar)
42
42
  top_bar.show
43
- top = top_bar.height
43
+
44
+ top = top_bar.height
44
45
  sub_window = window.subwin(window.maxy - top, window.maxx, top, 0)
46
+
45
47
  sub_window.keypad(true)
46
48
  sub_window
47
49
  end
48
50
  end
49
-
50
51
  end
51
52
  end
@@ -2,9 +2,7 @@ require 'daigaku/views/menu'
2
2
 
3
3
  module Daigaku
4
4
  module Views
5
-
6
5
  class ChaptersMenu < Menu
7
-
8
6
  private
9
7
 
10
8
  def before_enter(*args)
@@ -12,8 +10,8 @@ module Daigaku
12
10
  end
13
11
 
14
12
  def before_reenter(*args)
15
- @course = args[0]
16
- @chapter = args[1]
13
+ @course = args[0]
14
+ @chapter = args[1]
17
15
  @position = @course.chapters.find_index(@chapter)
18
16
  end
19
17
 
@@ -24,20 +22,20 @@ module Daigaku
24
22
  def interact_with(window)
25
23
  while char = window.getch
26
24
  case char
27
- when KEY_UP
28
- @position -= 1
29
- broadcast(:reset_menu_position)
30
- when KEY_DOWN
31
- @position += 1
32
- broadcast(:reset_menu_position)
33
- when 10 # Enter
34
- broadcast(:enter, @course, models[@position])
35
- return
36
- when 263 # Backspace
37
- broadcast(:reenter, @course)
38
- return
39
- when 27 # ESC
40
- exit
25
+ when KEY_UP
26
+ @position -= 1
27
+ broadcast(:reset_menu_position)
28
+ when KEY_DOWN
29
+ @position += 1
30
+ broadcast(:reset_menu_position)
31
+ when 10 # Enter
32
+ broadcast(:enter, @course, models[@position])
33
+ return
34
+ when 263 # Backspace
35
+ broadcast(:reenter, @course)
36
+ return
37
+ when 27 # ESC
38
+ exit
41
39
  end
42
40
 
43
41
  @position = items.length - 1 if @position < 0
@@ -53,8 +51,6 @@ module Daigaku
53
51
  def items
54
52
  models.map(&:title)
55
53
  end
56
-
57
54
  end
58
-
59
55
  end
60
56
  end
@@ -2,9 +2,7 @@ require 'daigaku/views/menu'
2
2
 
3
3
  module Daigaku
4
4
  module Views
5
-
6
5
  class CoursesMenu < Menu
7
-
8
6
  private
9
7
 
10
8
  def header_text
@@ -14,17 +12,17 @@ module Daigaku
14
12
  def interact_with(window)
15
13
  while char = window.getch
16
14
  case char
17
- when KEY_UP
18
- @position -= 1
19
- broadcast(:reset_menu_position)
20
- when KEY_DOWN
21
- @position += 1
22
- broadcast(:reset_menu_position)
23
- when 10 # Enter
24
- broadcast(:enter, models[@position])
25
- return
26
- when 27 # ESC
27
- exit
15
+ when KEY_UP
16
+ @position -= 1
17
+ broadcast(:reset_menu_position)
18
+ when KEY_DOWN
19
+ @position += 1
20
+ broadcast(:reset_menu_position)
21
+ when 10 # Enter
22
+ broadcast(:enter, models[@position])
23
+ return
24
+ when 27 # ESC
25
+ exit
28
26
  end
29
27
 
30
28
  @position = items.length - 1 if @position < 0
@@ -41,12 +39,11 @@ module Daigaku
41
39
  non_empty_courses = models.select { |course| !course.chapters.empty? }
42
40
 
43
41
  non_empty_courses.map do |course|
44
- line = "#{course.title}"
42
+ line = course.title
45
43
  self.items_info <<= [(course.author ? "(by #{course.author})" : '')]
46
44
  line
47
45
  end
48
46
  end
49
47
  end
50
-
51
48
  end
52
49
  end
@@ -1,37 +1,37 @@
1
+ require_relative 'subscriber'
2
+
1
3
  module Daigaku
2
4
  module Views
3
-
5
+ # Subscription: `first.subscribe(second)` means
6
+ # first subscribes second on the first's broadcast.
7
+ # second has to have method that is broadcasted.
4
8
  class MainMenu
5
9
  include Views
6
10
 
7
- def initialize
8
- courses_menu = Views::CoursesMenu.new
9
- chapters_menu = Views::ChaptersMenu.new
10
- units_menu = Views::UnitsMenu.new
11
- task_view = Views::TaskView.new
11
+ attr_reader :courses_menu, :chapters_menu, :units_menu, :task_view
12
12
 
13
- # Subscription: `first.subscribe(second)` means
14
- # first subscribes second on the first's broadcast.
15
- # second has to have method that is broadcasted.
13
+ def initialize
14
+ @courses_menu = Views::CoursesMenu.new
15
+ @chapters_menu = Views::ChaptersMenu.new
16
+ @units_menu = Views::UnitsMenu.new
17
+ @task_view = Views::TaskView.new
16
18
 
17
- # top down navigation
18
- courses_menu.subscribe(chapters_menu, on: :enter)
19
- chapters_menu.subscribe(units_menu, on: :enter)
20
- units_menu.subscribe(task_view, on: :enter)
19
+ subscribe_events
20
+ courses_menu.enter
21
+ end
21
22
 
22
- # bottom up navigation
23
- chapters_menu.subscribe(courses_menu, on: :reenter)
24
- units_menu.subscribe(chapters_menu, on: :reenter)
25
- task_view.subscribe(units_menu, on: :reenter)
23
+ private
26
24
 
27
- # position reset
28
- courses_menu.subscribe(chapters_menu, on: :reset_menu_position)
29
- courses_menu.subscribe(units_menu, on: :reset_menu_position)
30
- chapters_menu.subscribe(units_menu, on: :reset_menu_position)
25
+ def subscribe_events
26
+ subscriber = Subscriber.new(
27
+ courses_menu: courses_menu,
28
+ chapters_menu: chapters_menu,
29
+ units_menu: units_menu,
30
+ task_view: task_view
31
+ )
31
32
 
32
- courses_menu.enter
33
+ subscriber.subscribe_events!
33
34
  end
34
35
  end
35
-
36
36
  end
37
37
  end