daigaku 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,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
@@ -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 --no-color --order defined --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,25 +1,33 @@
1
- module Daigaku
1
+ require 'json'
2
2
 
3
+ module Daigaku
3
4
  class TestResult
4
- require 'json'
5
+ CODE_ERROR_MESSAGE = ':( You got an error in your code!'.freeze
5
6
 
6
7
  attr_reader :examples, :example_count, :failure_count
7
8
 
8
9
  def initialize(result_json)
9
10
  @result = begin
10
11
  JSON.parse(result_json, symbolize_names: true)
11
- rescue
12
- syntax_error_json
12
+ rescue => error
13
+ syntax_error_json(error)
13
14
  end
14
15
 
15
- @example_count = @result[:summary][:example_count]
16
- @failure_count = @result[:summary][:failure_count]
16
+ @example_count = @result.dig(:summary, :example_count)
17
+ @failure_count = @result.dig(:summary, :failure_count)
18
+ error_count = @result.dig(:summary, :errors_outside_of_examples_count) || 0
19
+
20
+ if error_count > 0
21
+ @failure_count = error_count
22
+ details = error_details(@result)
23
+ @result = error_json(details)
24
+ end
17
25
 
18
26
  @examples = @result[:examples].map do |example|
19
27
  description = example[:full_description]
20
- status = example[:status]
21
- exception = example[:exception]
22
- message = exception ? exception[:message] : nil
28
+ status = example[:status]
29
+ exception = example[:exception]
30
+ message = exception ? exception[:message] : nil
23
31
 
24
32
  TestExample.new(description: description, status: status, message: message)
25
33
  end
@@ -47,40 +55,65 @@ module Daigaku
47
55
 
48
56
  def build_failed_summary
49
57
  message = examples.map do |example|
50
- "#{example.description}\n#{example.status}: #{example.message}"
58
+ "#{example.description}\n#{example.status}: #{example.message}".strip
51
59
  end
52
60
 
53
- summary = message.map(&:strip).join("\n" * 3)
61
+ message.join("\n" * 3)
54
62
  end
55
63
 
56
- def syntax_error_json
64
+ def syntax_error_json(error)
65
+ details = failure_details(error)
66
+ error_json(details)
67
+ end
68
+
69
+ def error_details(result)
70
+ result[:messages]
71
+ .first
72
+ .split('\n')
73
+ .each_with_index
74
+ .select { |line, index| index > 0 && line.matches?(/temp_.+\.rb/) }
75
+ .first.to_s
76
+ end
77
+
78
+ def error_json(details)
57
79
  {
58
80
  summary: {},
59
81
  examples: [
60
82
  {
61
- status: 'failed',
62
- exception: { message: ":( You got a syntax error in your code!" }
83
+ status: TestExample::FAILED,
84
+ exception: { message: "#{CODE_ERROR_MESSAGE}\n\n#{details}" }
63
85
  }
64
86
  ]
65
87
  }
66
88
  end
89
+
90
+ def failure_details(error)
91
+ line = error.backtrace.first
92
+ error_message = remove_colorization(error.message)
93
+ "#{error.class} in #{line}:\n#{error_message}"
94
+ end
95
+
96
+ def remove_colorization(text)
97
+ text.gsub(/\x1b\[[0-9]*m/i, '')
98
+ end
67
99
  end
68
100
 
69
101
  class TestExample
102
+ PASSED = 'passed'.freeze
103
+ FAILED = 'failed'.freeze
70
104
 
71
105
  attr_reader :description, :status, :message
72
106
 
73
- EXAMPLE_PASSED_MESSAGE = "Your code passed this requirement."
107
+ EXAMPLE_PASSED_MESSAGE = 'Your code passed this requirement.'.freeze
74
108
 
75
- def initialize(args = {})
76
- @description = args[:description]
77
- @status = args[:status]
78
- @message = args[:message] || EXAMPLE_PASSED_MESSAGE
109
+ def initialize(status:, description: nil, message: nil)
110
+ @description = description
111
+ @status = status
112
+ @message = message || EXAMPLE_PASSED_MESSAGE
79
113
  end
80
114
 
81
115
  def passed?
82
- @status == 'passed'
116
+ @status == PASSED
83
117
  end
84
118
  end
85
-
86
119
  end
@@ -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
 
@@ -17,12 +16,11 @@ module Daigaku
17
16
  end
18
17
 
19
18
  def solution
20
- @solution = Solution.new(@path)
19
+ @solution ||= Solution.new(@path)
21
20
  end
22
21
 
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.2.0"
2
+ VERSION = '1.0.0'.freeze
3
3
  end
@@ -1,51 +1,47 @@
1
1
  require 'curses'
2
- require 'active_support/concern'
3
2
  require 'daigaku/views/top_bar'
4
3
 
5
4
  module Daigaku
6
5
  module Views
7
- extend ActiveSupport::Concern
6
+ include Curses
8
7
 
9
- included do
10
- include Curses
11
-
12
- def reset_menu_position
13
- @position = 0
14
- end
15
-
16
- private
8
+ def reset_menu_position
9
+ @position = 0
10
+ end
17
11
 
18
- def default_window(height = nil, width = nil, top = 0, left = 0)
19
- init_screen
12
+ private
20
13
 
21
- noecho
22
- crmode
23
- curs_set(0) # invisible cursor
14
+ def default_window(height = nil, width = nil, top = 0, left = 0)
15
+ init_screen
24
16
 
25
- height ||= lines
26
- width ||= cols + 1
17
+ noecho
18
+ crmode
19
+ curs_set(0) # invisible cursor
27
20
 
28
- window = Daigaku::Window.new(height, width, top, left)
21
+ height ||= lines
22
+ width ||= cols + 1
29
23
 
30
- Curses.lines.times do |line|
31
- window.setpos(line, 0)
32
- window.clear_line
33
- end
24
+ window = Daigaku::Window.new(height, width, top, left)
34
25
 
35
- window.keypad(true)
36
- window.scrollok(true)
37
- window.refresh
38
- window
26
+ Curses.lines.times do |line|
27
+ window.setpos(line, 0)
28
+ window.clear_line
39
29
  end
40
30
 
41
- def sub_window_below_top_bar(window, top_bar)
42
- top_bar.show
43
- top = top_bar.height
44
- sub_window = window.subwin(window.maxy - top, window.maxx, top, 0)
45
- sub_window.keypad(true)
46
- sub_window
47
- end
31
+ window.keypad(true)
32
+ window.scrollok(true)
33
+ window.refresh
34
+ window
48
35
  end
49
36
 
37
+ def sub_window_below_top_bar(window, top_bar)
38
+ top_bar.show
39
+
40
+ top = top_bar.height
41
+ sub_window = window.subwin(window.maxy - top, window.maxx, top, 0)
42
+
43
+ sub_window.keypad(true)
44
+ sub_window
45
+ end
50
46
  end
51
47
  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