belajar 0.1.1
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 +7 -0
- data/.gitignore +16 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/README.md +5 -0
- data/Rakefile +5 -0
- data/belajar.gemspec +38 -0
- data/bin/belajar +12 -0
- data/lib/belajar.rb +24 -0
- data/lib/belajar/chapter.rb +23 -0
- data/lib/belajar/configuration.rb +89 -0
- data/lib/belajar/congratulator.rb +13 -0
- data/lib/belajar/course.rb +80 -0
- data/lib/belajar/exceptions.rb +19 -0
- data/lib/belajar/generator.rb +60 -0
- data/lib/belajar/github_client.rb +30 -0
- data/lib/belajar/loadable.rb +23 -0
- data/lib/belajar/loading/chapters.rb +9 -0
- data/lib/belajar/loading/courses.rb +9 -0
- data/lib/belajar/loading/units.rb +9 -0
- data/lib/belajar/reference_solution.rb +18 -0
- data/lib/belajar/solution.rb +53 -0
- data/lib/belajar/storeable.rb +32 -0
- data/lib/belajar/task.rb +10 -0
- data/lib/belajar/terminal.rb +12 -0
- data/lib/belajar/terminal/cli.rb +59 -0
- data/lib/belajar/terminal/courses.rb +179 -0
- data/lib/belajar/terminal/output.rb +78 -0
- data/lib/belajar/terminal/setup.rb +115 -0
- data/lib/belajar/terminal/solutions.rb +46 -0
- data/lib/belajar/terminal/texts/about.txt +19 -0
- data/lib/belajar/terminal/texts/congratulations.txt +12 -0
- data/lib/belajar/terminal/texts/courses_empty.txt +3 -0
- data/lib/belajar/terminal/texts/hint_course_download.txt +13 -0
- data/lib/belajar/terminal/texts/welcome.txt +12 -0
- data/lib/belajar/terminal/welcome.rb +98 -0
- data/lib/belajar/test.rb +46 -0
- data/lib/belajar/test_result.rb +86 -0
- data/lib/belajar/unit.rb +28 -0
- data/lib/belajar/version.rb +3 -0
- data/lib/belajar/views.rb +51 -0
- data/lib/belajar/views/chapters_menu.rb +60 -0
- data/lib/belajar/views/courses_menu.rb +52 -0
- data/lib/belajar/views/main_menu.rb +37 -0
- data/lib/belajar/views/menu.rb +89 -0
- data/lib/belajar/views/splash.rb +57 -0
- data/lib/belajar/views/task_view.rb +236 -0
- data/lib/belajar/views/top_bar.rb +40 -0
- data/lib/belajar/views/units_menu.rb +61 -0
- data/lib/belajar/window.rb +215 -0
- data/spec/belajar/chapter_spec.rb +76 -0
- data/spec/belajar/configuration_spec.rb +161 -0
- data/spec/belajar/congratulator_spec.rb +24 -0
- data/spec/belajar/course_spec.rb +201 -0
- data/spec/belajar/generator_spec.rb +82 -0
- data/spec/belajar/github_client_spec.rb +53 -0
- data/spec/belajar/loading/chapters_spec.rb +16 -0
- data/spec/belajar/loading/courses_spec.rb +16 -0
- data/spec/belajar/loading/units_spec.rb +21 -0
- data/spec/belajar/reference_solution_spec.rb +41 -0
- data/spec/belajar/solution_spec.rb +86 -0
- data/spec/belajar/storeable_spec.rb +35 -0
- data/spec/belajar/task_spec.rb +23 -0
- data/spec/belajar/terminal/cli_spec.rb +51 -0
- data/spec/belajar/terminal/courses_spec.rb +293 -0
- data/spec/belajar/terminal/output_spec.rb +151 -0
- data/spec/belajar/terminal/setup_spec.rb +10 -0
- data/spec/belajar/terminal/solutions_spec.rb +8 -0
- data/spec/belajar/terminal/welcome_spec.rb +12 -0
- data/spec/belajar/terminal_spec.rb +24 -0
- data/spec/belajar/test_example_spec.rb +54 -0
- data/spec/belajar/test_result_spec.rb +91 -0
- data/spec/belajar/test_spec.rb +48 -0
- data/spec/belajar/unit_spec.rb +85 -0
- data/spec/belajar/views/chapters_menu_spec.rb +6 -0
- data/spec/belajar/views/courses_menu_spec.rb +6 -0
- data/spec/belajar/views/menu_spec.rb +19 -0
- data/spec/belajar/views/task_view_spec.rb +7 -0
- data/spec/belajar/views/units_menu_spec.rb +6 -0
- data/spec/belajar/views_spec.rb +21 -0
- data/spec/belajar_spec.rb +51 -0
- data/spec/path_helpers_spec.rb +60 -0
- data/spec/resource_helpers_spec.rb +33 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/macros/content_helpers.rb +129 -0
- data/spec/support/macros/mock_helpers.rb +25 -0
- data/spec/support/macros/path_helpers.rb +139 -0
- data/spec/support/macros/resource_helpers.rb +157 -0
- data/spec/support/macros/test_helpers.rb +6 -0
- metadata +385 -0
data/lib/belajar/unit.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Belajar
|
2
|
+
class Unit
|
3
|
+
|
4
|
+
attr_reader :title, :task
|
5
|
+
|
6
|
+
def initialize(path)
|
7
|
+
@path = path
|
8
|
+
@title = File.basename(path).gsub(/\_+/, ' ')
|
9
|
+
end
|
10
|
+
|
11
|
+
def task
|
12
|
+
@task ||= Task.new(@path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def reference_solution
|
16
|
+
@reference_solution ||= ReferenceSolution.new(@path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def solution
|
20
|
+
@solution = Solution.new(@path)
|
21
|
+
end
|
22
|
+
|
23
|
+
def mastered?
|
24
|
+
solution.verified?
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'curses'
|
2
|
+
require 'active_support/concern'
|
3
|
+
require 'belajar/views/top_bar'
|
4
|
+
|
5
|
+
module Belajar
|
6
|
+
module Views
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
include Curses
|
11
|
+
|
12
|
+
def reset_menu_position
|
13
|
+
@position = 0
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def default_window(height = nil, width = nil, top = 0, left = 0)
|
19
|
+
init_screen
|
20
|
+
|
21
|
+
noecho
|
22
|
+
crmode
|
23
|
+
curs_set(0) # invisible cursor
|
24
|
+
|
25
|
+
height ||= lines
|
26
|
+
width ||= cols + 1
|
27
|
+
|
28
|
+
window = Belajar::Window.new(height, width, top, left)
|
29
|
+
|
30
|
+
Curses.lines.times do |line|
|
31
|
+
window.setpos(line, 0)
|
32
|
+
window.clear_line
|
33
|
+
end
|
34
|
+
|
35
|
+
window.keypad(true)
|
36
|
+
window.scrollok(true)
|
37
|
+
window.refresh
|
38
|
+
window
|
39
|
+
end
|
40
|
+
|
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
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'belajar/views/menu'
|
2
|
+
|
3
|
+
module Belajar
|
4
|
+
module Views
|
5
|
+
|
6
|
+
class ChaptersMenu < Menu
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def before_enter(*args)
|
11
|
+
@course = args[0]
|
12
|
+
end
|
13
|
+
|
14
|
+
def before_reenter(*args)
|
15
|
+
@course = args[0]
|
16
|
+
@chapter = args[1]
|
17
|
+
@position = @course.chapters.find_index(@chapter)
|
18
|
+
end
|
19
|
+
|
20
|
+
def header_text
|
21
|
+
"*#{@course.title}* - available chapters:"
|
22
|
+
end
|
23
|
+
|
24
|
+
def interact_with(window)
|
25
|
+
while char = window.getch
|
26
|
+
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
|
41
|
+
end
|
42
|
+
|
43
|
+
@position = items.length - 1 if @position < 0
|
44
|
+
@position = 0 if @position >= items.length
|
45
|
+
draw(window, @position)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def models
|
50
|
+
@course.chapters
|
51
|
+
end
|
52
|
+
|
53
|
+
def items
|
54
|
+
models.map(&:title)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'belajar/views/menu'
|
2
|
+
|
3
|
+
module Belajar
|
4
|
+
module Views
|
5
|
+
|
6
|
+
class CoursesMenu < Menu
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def header_text
|
11
|
+
'Available belajar courses:'
|
12
|
+
end
|
13
|
+
|
14
|
+
def interact_with(window)
|
15
|
+
while char = window.getch
|
16
|
+
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
|
28
|
+
end
|
29
|
+
|
30
|
+
@position = items.length - 1 if @position < 0
|
31
|
+
@position = 0 if @position >= items.length
|
32
|
+
draw(window, @position)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def models
|
37
|
+
Loading::Courses.load(Belajar.config.courses_path)
|
38
|
+
end
|
39
|
+
|
40
|
+
def items
|
41
|
+
non_empty_courses = models.select { |course| !course.chapters.empty? }
|
42
|
+
|
43
|
+
non_empty_courses.map do |course|
|
44
|
+
line = "#{course.title}"
|
45
|
+
self.items_info <<= [(course.author ? "(by #{course.author})" : '')]
|
46
|
+
line
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Belajar
|
2
|
+
module Views
|
3
|
+
|
4
|
+
class MainMenu
|
5
|
+
include Views
|
6
|
+
|
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
|
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.
|
16
|
+
|
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)
|
21
|
+
|
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)
|
26
|
+
|
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)
|
31
|
+
|
32
|
+
courses_menu.enter
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'wisper'
|
2
|
+
|
3
|
+
module Belajar
|
4
|
+
module Views
|
5
|
+
|
6
|
+
class Menu
|
7
|
+
include Views
|
8
|
+
include Wisper::Publisher
|
9
|
+
|
10
|
+
TOP_BAR_TEXT = [
|
11
|
+
'Use *UP KEY* and *DOWN KEY* for menu navigation',
|
12
|
+
'Enter menu with *RETURN*',
|
13
|
+
'Go back with *BACKSPACE*',
|
14
|
+
'Exit with *ESC*'
|
15
|
+
].join(' | ')
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@position = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def enter(*args)
|
22
|
+
if self.class.private_method_defined?(:before_enter)
|
23
|
+
before_enter(*args)
|
24
|
+
end
|
25
|
+
|
26
|
+
@window = default_window
|
27
|
+
top_bar = TopBar.new(@window, TOP_BAR_TEXT)
|
28
|
+
show sub_window_below_top_bar(@window, top_bar)
|
29
|
+
end
|
30
|
+
|
31
|
+
def reenter(*args)
|
32
|
+
if self.class.private_method_defined?(:before_reenter)
|
33
|
+
before_reenter(*args)
|
34
|
+
end
|
35
|
+
|
36
|
+
enter(*args)
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def show(window)
|
42
|
+
draw(window, @position)
|
43
|
+
interact_with(window)
|
44
|
+
end
|
45
|
+
|
46
|
+
def draw(window, active_index = 0)
|
47
|
+
window.attrset(A_NORMAL)
|
48
|
+
window.setpos(0, 1)
|
49
|
+
window.print_markdown header_text
|
50
|
+
|
51
|
+
items.each_with_index do |item, index|
|
52
|
+
window.setpos(index + 2, 1)
|
53
|
+
window.print_indicator(models[index])
|
54
|
+
window.attrset(index == active_index ? A_STANDOUT : A_NORMAL)
|
55
|
+
window.write " #{item.to_s} "
|
56
|
+
window.attrset(A_NORMAL)
|
57
|
+
window.write " #{items_info[index].try(:join, ' ')}"
|
58
|
+
end
|
59
|
+
|
60
|
+
window.refresh
|
61
|
+
end
|
62
|
+
|
63
|
+
def interact_with(window)
|
64
|
+
raise "Please implement the method #interact_with!"
|
65
|
+
end
|
66
|
+
|
67
|
+
def models
|
68
|
+
raise "Please implement the method #models!"
|
69
|
+
end
|
70
|
+
|
71
|
+
def items
|
72
|
+
raise "Please implement the method #items!"
|
73
|
+
end
|
74
|
+
|
75
|
+
def header_text
|
76
|
+
raise "Please implement the method #header_text!"
|
77
|
+
end
|
78
|
+
|
79
|
+
def items_info
|
80
|
+
@items_info || []
|
81
|
+
end
|
82
|
+
|
83
|
+
def items_info=(items_info)
|
84
|
+
@items_info = items_info
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Belajar
|
2
|
+
module Views
|
3
|
+
|
4
|
+
class Splash
|
5
|
+
include Views
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
title = "BELAJAR"
|
9
|
+
subtitle = "Learning the Ruby programming language dead easy."
|
10
|
+
|
11
|
+
panel = default_window
|
12
|
+
|
13
|
+
lines.times do |line|
|
14
|
+
panel.setpos(line, 0)
|
15
|
+
panel.red(' ' * cols, Curses::A_STANDOUT)
|
16
|
+
end
|
17
|
+
|
18
|
+
panel.setpos((lines / 4), (cols - title.length) / 2)
|
19
|
+
panel.red(title, Curses::A_STANDOUT)
|
20
|
+
panel.refresh
|
21
|
+
|
22
|
+
sleep 0.5
|
23
|
+
|
24
|
+
ruby_ascii_art.each_with_index do |line, index|
|
25
|
+
panel.setpos(lines / 4 + 2 + index, (cols - line.length) / 2)
|
26
|
+
panel.red(line, Curses::A_STANDOUT)
|
27
|
+
sleep 0.06
|
28
|
+
panel.refresh
|
29
|
+
end
|
30
|
+
|
31
|
+
panel.setpos(lines / 4 + 11, (cols - subtitle.length) / 2)
|
32
|
+
|
33
|
+
subtitle.chars do |char|
|
34
|
+
panel.red(char, Curses::A_STANDOUT)
|
35
|
+
panel.refresh
|
36
|
+
sleep 0.02
|
37
|
+
end
|
38
|
+
|
39
|
+
sleep 2.5
|
40
|
+
|
41
|
+
close_screen
|
42
|
+
end
|
43
|
+
|
44
|
+
def ruby_ascii_art
|
45
|
+
[
|
46
|
+
" ___________ ",
|
47
|
+
" /.\\ /.\\ /.\\ ",
|
48
|
+
"/___\\/___\\/___\\",
|
49
|
+
" \\ \\ . / . / ",
|
50
|
+
" \\ \\ ./ ./ ",
|
51
|
+
" \\\\ / / ",
|
52
|
+
" \\./ "
|
53
|
+
]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,236 @@
|
|
1
|
+
module Belajar
|
2
|
+
module Views
|
3
|
+
require 'wisper'
|
4
|
+
require 'os'
|
5
|
+
|
6
|
+
class TaskView
|
7
|
+
include Views
|
8
|
+
include Wisper::Publisher
|
9
|
+
|
10
|
+
TOP_BAR_TEXT = [
|
11
|
+
'Scroll with *UP KEY* and *DOWN KEY*',
|
12
|
+
'Verify solution with *v*',
|
13
|
+
'Open solution file with *o*',
|
14
|
+
'Clear validation with *c*',
|
15
|
+
'Exit with *ESC*'
|
16
|
+
].join(' | ')
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@lines = []
|
20
|
+
@top = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def enter(course, chapter, unit)
|
24
|
+
@course = course
|
25
|
+
@chapter = chapter
|
26
|
+
@unit = unit
|
27
|
+
|
28
|
+
@test_result_lines = nil
|
29
|
+
@lines = @unit.task.markdown.lines
|
30
|
+
@top_bar_height = 4
|
31
|
+
@head_height = 2
|
32
|
+
|
33
|
+
initialize_window(@lines.count + @top_bar_height + @head_height)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def set_head(window)
|
39
|
+
window.setpos(0, 1)
|
40
|
+
window.clear_line
|
41
|
+
|
42
|
+
text = "*#{@course.title}* > *#{@chapter.title}* > *#{@unit.title}*:"
|
43
|
+
window.print_markdown(text)
|
44
|
+
|
45
|
+
window.setpos(1, 1)
|
46
|
+
window.clear_line
|
47
|
+
end
|
48
|
+
|
49
|
+
def show(window)
|
50
|
+
window.scrollok(true)
|
51
|
+
draw(window)
|
52
|
+
interact_with(window)
|
53
|
+
end
|
54
|
+
|
55
|
+
def draw(window)
|
56
|
+
@top = 0
|
57
|
+
window.attrset(A_NORMAL)
|
58
|
+
set_head(window)
|
59
|
+
|
60
|
+
@lines.each_with_index do |line, index|
|
61
|
+
window.setpos(index + 2, 1)
|
62
|
+
print_line(window, line, index)
|
63
|
+
end
|
64
|
+
|
65
|
+
window.setpos(0, 1)
|
66
|
+
window.refresh
|
67
|
+
end
|
68
|
+
|
69
|
+
def scroll_up(window)
|
70
|
+
if @top > 0
|
71
|
+
window.scrl(-1)
|
72
|
+
set_head(window)
|
73
|
+
|
74
|
+
@top -= 1
|
75
|
+
line = @lines[@top]
|
76
|
+
|
77
|
+
if line
|
78
|
+
window.setpos(2, 1)
|
79
|
+
print_line(window, line, @top)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def scroll_down(window)
|
85
|
+
if @top + Curses.lines <= @lines.count + @top_bar_height + @head_height
|
86
|
+
window.scrl(1)
|
87
|
+
set_head(window)
|
88
|
+
|
89
|
+
@top += 1
|
90
|
+
line = @lines[@top + window.maxy - 1]
|
91
|
+
window.setpos(window.maxy - 1, 1)
|
92
|
+
|
93
|
+
if line
|
94
|
+
print_line(window, line, @top)
|
95
|
+
else
|
96
|
+
window.clear_line
|
97
|
+
end
|
98
|
+
|
99
|
+
return true
|
100
|
+
else
|
101
|
+
return false
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def print_line(window, line, index)
|
106
|
+
if @test_result_lines && index.between?(0, @test_result_lines.count + 1)
|
107
|
+
if @unit.mastered?
|
108
|
+
window.green(line, A_STANDOUT, full_line: true)
|
109
|
+
else
|
110
|
+
example_index = example_index(@example_heights, index)
|
111
|
+
|
112
|
+
if @examples[example_index].passed?
|
113
|
+
window.green(line, A_STANDOUT, full_line: true)
|
114
|
+
else
|
115
|
+
window.red(line, A_STANDOUT, full_line: true)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
else
|
119
|
+
window.print_markdown(line.strip)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def interact_with(window)
|
124
|
+
while char = window.getch
|
125
|
+
scrollable = true
|
126
|
+
|
127
|
+
case char
|
128
|
+
when 'v' # verify
|
129
|
+
print_test_results(window)
|
130
|
+
return
|
131
|
+
when 'c' # clear
|
132
|
+
reset_screen(window)
|
133
|
+
return
|
134
|
+
when 'o' # open solution file
|
135
|
+
open_editor(@unit.solution.path)
|
136
|
+
when Curses::KEY_DOWN, Curses::KEY_CTRL_N
|
137
|
+
scrollable = scroll_down(window)
|
138
|
+
when Curses::KEY_UP, Curses::KEY_CTRL_P
|
139
|
+
scrollable = scroll_up(window)
|
140
|
+
when Curses::KEY_NPAGE, ?\s # white space
|
141
|
+
0.upto(window.maxy - 2) do |n|
|
142
|
+
scrolled = scroll_down(window)
|
143
|
+
|
144
|
+
unless scrolled
|
145
|
+
scrollable = false if n == 0
|
146
|
+
break
|
147
|
+
end
|
148
|
+
end
|
149
|
+
when Curses::KEY_PPAGE
|
150
|
+
0.upto(window.maxy - 2) do |n|
|
151
|
+
scrolled = scroll_up(window)
|
152
|
+
|
153
|
+
unless scrolled
|
154
|
+
scrollable = false if n == 0
|
155
|
+
break
|
156
|
+
end
|
157
|
+
end
|
158
|
+
when Curses::KEY_LEFT, Curses::KEY_CTRL_T
|
159
|
+
while scroll_up(window)
|
160
|
+
end
|
161
|
+
when Curses::KEY_RIGHT, Curses::KEY_CTRL_B
|
162
|
+
while scroll_down(window)
|
163
|
+
end
|
164
|
+
when Curses::KEY_BACKSPACE
|
165
|
+
broadcast(:reenter, @course, @chapter, @unit)
|
166
|
+
return
|
167
|
+
when 27 # ESC
|
168
|
+
exit
|
169
|
+
end
|
170
|
+
|
171
|
+
Curses.beep unless scrollable
|
172
|
+
window.setpos(0, 1)
|
173
|
+
window.refresh
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def open_editor(file_path)
|
178
|
+
if OS.windows?
|
179
|
+
system "start '' '#{file_path}'"
|
180
|
+
elsif OS.mac?
|
181
|
+
system "open '#{file_path}'"
|
182
|
+
elsif OS.linux?
|
183
|
+
system "xdg-open '#{file_path}'"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def print_test_results(window)
|
188
|
+
result = @unit.solution.verify!
|
189
|
+
@test_result_lines = result.summary_lines
|
190
|
+
|
191
|
+
if result.passed?
|
192
|
+
code_lines = @unit.reference_solution.code_lines
|
193
|
+
|
194
|
+
unless code_lines.empty?
|
195
|
+
code_lines.map! { |line| " #{line}" }
|
196
|
+
code_lines.unshift('', "Reference code:", '')
|
197
|
+
end
|
198
|
+
|
199
|
+
@test_result_lines += code_lines
|
200
|
+
end
|
201
|
+
|
202
|
+
@lines = [''] + @test_result_lines + ['', ''] + @unit.task.markdown.lines
|
203
|
+
@examples = result.examples
|
204
|
+
|
205
|
+
@example_heights = @examples.reduce({}) do |hash, example|
|
206
|
+
start = hash.values.reduce(0) { |sum, r| sum += r.count }
|
207
|
+
range = (start..(start + example.message.split("\n").count) + 2)
|
208
|
+
hash[hash.keys.count] = range
|
209
|
+
hash
|
210
|
+
end
|
211
|
+
|
212
|
+
height = [@lines.count + @top_bar_height + @head_height, Curses.lines].max
|
213
|
+
initialize_window(height)
|
214
|
+
end
|
215
|
+
|
216
|
+
def reset_screen(window)
|
217
|
+
@test_result_lines = nil
|
218
|
+
@lines = @unit.task.markdown.lines
|
219
|
+
height = [@lines.count + @top_bar_height + @head_height, Curses.lines].max
|
220
|
+
initialize_window(height)
|
221
|
+
end
|
222
|
+
|
223
|
+
def initialize_window(height)
|
224
|
+
@window = default_window(height)
|
225
|
+
|
226
|
+
top_bar = TopBar.new(@window, TOP_BAR_TEXT)
|
227
|
+
show sub_window_below_top_bar(@window, top_bar)
|
228
|
+
end
|
229
|
+
|
230
|
+
def example_index(heights, index)
|
231
|
+
heights.values.index { |range| range.include?(index) }
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
236
|
+
end
|