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,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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
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
|
-
|
7
|
+
new.run
|
9
8
|
end
|
10
9
|
|
11
10
|
def self.about
|
12
|
-
|
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
|
18
|
+
say 'For now, let’s 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
|
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
|
-
|
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
|
-
|
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
|
-
|
67
|
+
'Oh! You don’t 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
|
-
|
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
|
-
|
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
|
-
|
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
|
11
|
+
@path = Dir[File.join(path, '*spec.rb')].first
|
13
12
|
end
|
14
13
|
|
15
14
|
def run(solution_code)
|
16
|
-
spec_code
|
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 =
|
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.
|
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
|
data/lib/daigaku/test_result.rb
CHANGED
@@ -1,25 +1,33 @@
|
|
1
|
-
|
1
|
+
require 'json'
|
2
2
|
|
3
|
+
module Daigaku
|
3
4
|
class TestResult
|
4
|
-
|
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
|
16
|
-
@failure_count = @result
|
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
|
21
|
-
exception
|
22
|
-
message
|
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
|
-
|
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:
|
62
|
-
exception: { message: "
|
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 =
|
107
|
+
EXAMPLE_PASSED_MESSAGE = 'Your code passed this requirement.'.freeze
|
74
108
|
|
75
|
-
def initialize(
|
76
|
-
@description =
|
77
|
-
@status
|
78
|
-
@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 ==
|
116
|
+
@status == PASSED
|
83
117
|
end
|
84
118
|
end
|
85
|
-
|
86
119
|
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
|
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
|
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
|
data/lib/daigaku/version.rb
CHANGED
data/lib/daigaku/views.rb
CHANGED
@@ -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
|
-
|
6
|
+
include Curses
|
8
7
|
|
9
|
-
|
10
|
-
|
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
|
-
|
19
|
-
init_screen
|
12
|
+
private
|
20
13
|
|
21
|
-
|
22
|
-
|
23
|
-
curs_set(0) # invisible cursor
|
14
|
+
def default_window(height = nil, width = nil, top = 0, left = 0)
|
15
|
+
init_screen
|
24
16
|
|
25
|
-
|
26
|
-
|
17
|
+
noecho
|
18
|
+
crmode
|
19
|
+
curs_set(0) # invisible cursor
|
27
20
|
|
28
|
-
|
21
|
+
height ||= lines
|
22
|
+
width ||= cols + 1
|
29
23
|
|
30
|
-
|
31
|
-
window.setpos(line, 0)
|
32
|
-
window.clear_line
|
33
|
-
end
|
24
|
+
window = Daigaku::Window.new(height, width, top, left)
|
34
25
|
|
35
|
-
|
36
|
-
window.
|
37
|
-
window.
|
38
|
-
window
|
26
|
+
Curses.lines.times do |line|
|
27
|
+
window.setpos(line, 0)
|
28
|
+
window.clear_line
|
39
29
|
end
|
40
30
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
16
|
-
@chapter
|
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
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|