daigaku 0.0.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.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/Gemfile +4 -0
  4. data/Guardfile +5 -0
  5. data/README.md +62 -0
  6. data/Rakefile +2 -0
  7. data/bin/daigaku +12 -0
  8. data/daigaku.gemspec +37 -0
  9. data/lib/daigaku.rb +27 -0
  10. data/lib/daigaku/chapter.rb +23 -0
  11. data/lib/daigaku/configuration.rb +86 -0
  12. data/lib/daigaku/course.rb +23 -0
  13. data/lib/daigaku/database.rb +64 -0
  14. data/lib/daigaku/exceptions.rb +19 -0
  15. data/lib/daigaku/generator.rb +53 -0
  16. data/lib/daigaku/loadable.rb +23 -0
  17. data/lib/daigaku/loading/chapters.rb +9 -0
  18. data/lib/daigaku/loading/courses.rb +9 -0
  19. data/lib/daigaku/loading/units.rb +9 -0
  20. data/lib/daigaku/reference_solution.rb +10 -0
  21. data/lib/daigaku/solution.rb +42 -0
  22. data/lib/daigaku/task.rb +10 -0
  23. data/lib/daigaku/terminal.rb +11 -0
  24. data/lib/daigaku/terminal/cli.rb +59 -0
  25. data/lib/daigaku/terminal/courses.rb +114 -0
  26. data/lib/daigaku/terminal/output.rb +72 -0
  27. data/lib/daigaku/terminal/setup.rb +115 -0
  28. data/lib/daigaku/terminal/solutions.rb +46 -0
  29. data/lib/daigaku/terminal/texts/about.txt +19 -0
  30. data/lib/daigaku/terminal/texts/courses_empty.txt +3 -0
  31. data/lib/daigaku/terminal/texts/hint_course_download.txt +13 -0
  32. data/lib/daigaku/terminal/texts/welcome.txt +12 -0
  33. data/lib/daigaku/terminal/welcome.rb +98 -0
  34. data/lib/daigaku/test.rb +46 -0
  35. data/lib/daigaku/test_result.rb +69 -0
  36. data/lib/daigaku/unit.rb +28 -0
  37. data/lib/daigaku/version.rb +3 -0
  38. data/lib/daigaku/views.rb +59 -0
  39. data/lib/daigaku/views/chapters_menu.rb +91 -0
  40. data/lib/daigaku/views/courses_menu.rb +87 -0
  41. data/lib/daigaku/views/main_menu.rb +37 -0
  42. data/lib/daigaku/views/splash.rb +57 -0
  43. data/lib/daigaku/views/task_view.rb +206 -0
  44. data/lib/daigaku/views/top_bar.rb +48 -0
  45. data/lib/daigaku/views/units_menu.rb +92 -0
  46. data/lib/daigaku/window.rb +160 -0
  47. data/spec/daigaku/chapter_spec.rb +76 -0
  48. data/spec/daigaku/configuration_spec.rb +161 -0
  49. data/spec/daigaku/course_spec.rb +75 -0
  50. data/spec/daigaku/database_spec.rb +79 -0
  51. data/spec/daigaku/generator_spec.rb +82 -0
  52. data/spec/daigaku/loading/chapters_spec.rb +16 -0
  53. data/spec/daigaku/loading/courses_spec.rb +16 -0
  54. data/spec/daigaku/loading/units_spec.rb +21 -0
  55. data/spec/daigaku/reference_solution_spec.rb +23 -0
  56. data/spec/daigaku/solution_spec.rb +79 -0
  57. data/spec/daigaku/task_spec.rb +23 -0
  58. data/spec/daigaku/terminal/cli_spec.rb +51 -0
  59. data/spec/daigaku/terminal/courses_spec.rb +60 -0
  60. data/spec/daigaku/terminal/output_spec.rb +123 -0
  61. data/spec/daigaku/terminal/setup_spec.rb +10 -0
  62. data/spec/daigaku/terminal/solutions_spec.rb +8 -0
  63. data/spec/daigaku/terminal/welcome_spec.rb +12 -0
  64. data/spec/daigaku/terminal_spec.rb +14 -0
  65. data/spec/daigaku/test_example_spec.rb +54 -0
  66. data/spec/daigaku/test_result_spec.rb +81 -0
  67. data/spec/daigaku/test_spec.rb +48 -0
  68. data/spec/daigaku/unit_spec.rb +85 -0
  69. data/spec/daigaku/views/chapters_menu_spec.rb +8 -0
  70. data/spec/daigaku/views/courses_menu_spec.rb +8 -0
  71. data/spec/daigaku/views/task_view_spec.rb +7 -0
  72. data/spec/daigaku/views/units_menu_spec.rb +8 -0
  73. data/spec/daigaku/views_spec.rb +23 -0
  74. data/spec/daigaku_spec.rb +57 -0
  75. data/spec/path_helpers_spec.rb +60 -0
  76. data/spec/resource_helpers_spec.rb +33 -0
  77. data/spec/spec_helper.rb +28 -0
  78. data/spec/support/macros/content_helpers.rb +129 -0
  79. data/spec/support/macros/mock_helpers.rb +20 -0
  80. data/spec/support/macros/path_helpers.rb +133 -0
  81. data/spec/support/macros/resource_helpers.rb +119 -0
  82. data/spec/support/macros/test_helpers.rb +6 -0
  83. metadata +361 -0
@@ -0,0 +1,46 @@
1
+ module Daigaku
2
+ module Terminal
3
+
4
+ require 'os'
5
+ require_relative 'output'
6
+
7
+ class Solutions < Thor
8
+ include Terminal::Output
9
+
10
+ desc 'solutions open [COURSE NAME]', 'Open the solutions folder of a course in a GUI window'
11
+ 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
29
+ end
30
+
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
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,19 @@
1
+ **********************************************************************
2
+ ◇ ABOUT DAIGAKU! ◇
3
+ **********************************************************************
4
+
5
+ Daigaku is the Japanese word for "university".
6
+ With Daigaku you can master your way of learning the Ruby programming
7
+ language with courses that are created by the community.
8
+
9
+ Daigaku is a command line tool and a text based interface and provides
10
+ you with a number of learning tasks and explanations about the Ruby
11
+ programming language. You will learn Ruby step by step by solving small
12
+ language-explaining programming tasks.
13
+
14
+ Daigaku's command line interface provides several commands which you
15
+ can use in your terminal to setup the system, download new courses,
16
+ and navigate through your solutions.
17
+
18
+ By typing "daigaku help" in you terminal you are provided with an
19
+ overview of all available commands.
@@ -0,0 +1,3 @@
1
+ There are no courses available in your current courses path.
2
+
3
+ Go and insert some to your courses path!
@@ -0,0 +1,13 @@
1
+ You can download a new daigaku course by using the
2
+ "daigaku courses download [URL] [OPTIONS]" command, e.g.:
3
+
4
+ $ daigaku courses download https://github.com/daigaku-ruby/Get_started_with_Ruby/archive/master.zip
5
+
6
+ For Github resources you can also use the `--github` (short `-g`) option:
7
+
8
+ $ daigaku courses download -g daigaku-ruby/Get_started_with_Ruby
9
+
10
+ If you want to quick start with Daiaku's "Get started with Ruby"
11
+ course just run:
12
+
13
+ $ daigaku courses download
@@ -0,0 +1,12 @@
1
+ **********************************************************************
2
+ ◇ WELCOME TO DAIGAKU! ◇
3
+ **********************************************************************
4
+
5
+ To get started, you need a folder where you can save your courses and
6
+ solutions. By default, daigaku uses the same base path for both.
7
+ Your courses will be saved to a "courses" folder and your solutions
8
+ can be found in a "solutions" folder.
9
+
10
+ The "solutions" path and "courses" path can be changed later on.
11
+ See which command you can use to update your setup by simply typing
12
+ "daigaku setup help set" in your command line.
@@ -0,0 +1,98 @@
1
+ module Daigaku
2
+ module Terminal
3
+
4
+ class Welcome
5
+ include Terminal::Output
6
+
7
+ def self.run
8
+ self.new.run
9
+ end
10
+
11
+ def self.about
12
+ self.new.about
13
+ end
14
+
15
+ def run
16
+ empty_line
17
+ say Terminal.text :welcome
18
+ empty_line
19
+ say "For now, let's setup the daigaku paths."
20
+ Daigaku::Terminal::Setup.new.init
21
+
22
+ show_setup_list_announcement
23
+ show_courses_list_announcement
24
+
25
+ courses = Loading::Courses.load(Daigaku.config.courses_path)
26
+
27
+ if courses.empty?
28
+ show_courses_download_announcement
29
+ show_solutions_open_announcement
30
+ end
31
+
32
+ empty_line
33
+ show_learn_announcement
34
+ end
35
+
36
+ def about
37
+ empty_line
38
+ say Terminal.text :about
39
+ empty_line
40
+ say %x{daigaku help}
41
+ end
42
+
43
+ private
44
+
45
+ def show_setup_list_announcement
46
+ command = 'daigaku setup list'
47
+ text = [
48
+ "The courses path and solutions path have been added to your settings.",
49
+ "Just list your current settings with the \"#{command}\" command:"
50
+ ].join("\n")
51
+
52
+ get_command(command, text)
53
+ end
54
+
55
+ def show_courses_list_announcement
56
+ command = 'daigaku courses list'
57
+ text = [
58
+ "Well done. Now, type \"#{command}\" to see what courses are",
59
+ "available in your daigaku folder:"
60
+ ].join("\n")
61
+
62
+ get_command(command, text)
63
+ end
64
+
65
+ def show_courses_download_announcement
66
+ command = 'daigaku courses download'
67
+ text = [
68
+ "Oh! You don't have any courses, yet?",
69
+ "Just enter \"#{command}\" to download the basic Daigaku course:"
70
+ ].join("\n")
71
+
72
+ get_command(command, text)
73
+ end
74
+
75
+ def show_solutions_open_announcement
76
+ command = 'daigaku solutions open'
77
+ text = [
78
+ "When downloading a course, Daigaku scaffolds empty solution files",
79
+ "for your code on the fly.\n",
80
+ "Type \"#{command}\" to open your solutions folder:"
81
+ ].join("\n")
82
+
83
+ get_command(command, text)
84
+ end
85
+
86
+ def show_learn_announcement
87
+ command = 'daigaku learn'
88
+ text = [
89
+ "Congratulations! You learned the first steps of using daigaku.",
90
+ "To continue and start learning Ruby type \"#{command}\":"
91
+ ].join("\n")
92
+
93
+ get_command(command, text)
94
+ end
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,46 @@
1
+ module Daigaku
2
+ require 'fileutils'
3
+
4
+ class Test
5
+
6
+ attr_reader :path
7
+
8
+ CODE_REGEX = /\[\['solution::code'\]\]/
9
+
10
+ def initialize(path)
11
+ @unit_path = path
12
+ @path = Dir[File.join(path, '*spec.rb')].first
13
+ end
14
+
15
+ def run(solution_code)
16
+ spec_code = File.read(@path)
17
+ patched_spec_code = insert_code(spec_code, solution_code.to_s)
18
+
19
+ temp_spec = File.join(File.dirname(@path), "temp_#{File.basename(@path)}")
20
+ create_temp_spec(temp_spec, patched_spec_code)
21
+
22
+ result = %x{ rspec --color --format j #{temp_spec} }
23
+ remove_file(temp_spec)
24
+
25
+ TestResult.new(result)
26
+ end
27
+
28
+ private
29
+
30
+ def insert_code(spec, code)
31
+ spec.gsub(CODE_REGEX, code)
32
+ end
33
+
34
+ def create_temp_spec(path, content)
35
+ base_path = File.dirname(path)
36
+ FileUtils.mkdir_p(base_path) unless Dir.exist?(base_path)
37
+ File.open(path, 'w') { |f| f.puts content }
38
+ end
39
+
40
+ def remove_file(path)
41
+ FileUtils.rm(path) if File.exist?(path)
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,69 @@
1
+ module Daigaku
2
+
3
+ class TestResult
4
+ require 'json'
5
+
6
+ attr_reader :examples, :example_count, :failure_count
7
+
8
+ TEST_PASSED_MESSAGE = "Your code passed all tests. Congratulations!"
9
+
10
+ def initialize(result_json)
11
+ @result = JSON.parse(result_json, symbolize_names: true)
12
+
13
+ @example_count = @result[:summary][:example_count]
14
+ @failure_count = @result[:summary][:failure_count]
15
+
16
+ @examples = @result[:examples].map do |example|
17
+ description = example[:full_description]
18
+ status = example[:status]
19
+ exception = example[:exception]
20
+ message = exception ? exception[:message] : nil
21
+
22
+ TestExample.new(description: description, status: status, message: message)
23
+ end
24
+ end
25
+
26
+ def passed?
27
+ @examples.reduce(true) do |passed, example|
28
+ passed && example.passed?
29
+ end
30
+ end
31
+
32
+ def summary
33
+ if passed?
34
+ TEST_PASSED_MESSAGE
35
+ else
36
+ build_failed_summary
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def build_failed_summary
43
+ message = examples.map do |example|
44
+ "#{example.description}\n#{example.status}: #{example.message}"
45
+ end
46
+
47
+ summary = message.map(&:strip).join("\n" * 3)
48
+ end
49
+
50
+ end
51
+
52
+ class TestExample
53
+
54
+ attr_reader :description, :status, :message
55
+
56
+ EXAMPLE_PASSED_MESSAGE = "Your code passed this requirement."
57
+
58
+ def initialize(args = {})
59
+ @description = args[:description]
60
+ @status = args[:status]
61
+ @message = args[:message] || EXAMPLE_PASSED_MESSAGE
62
+ end
63
+
64
+ def passed?
65
+ @status == 'passed'
66
+ end
67
+ end
68
+
69
+ end
@@ -0,0 +1,28 @@
1
+ module Daigaku
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,3 @@
1
+ module Daigaku
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,59 @@
1
+ require 'curses'
2
+ require 'active_support/concern'
3
+ require_relative 'views/top_bar'
4
+
5
+ module Daigaku
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 = Daigaku::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 top_bar(window)
42
+ TopBar.new(window)
43
+ end
44
+
45
+ def main_panel(window)
46
+ top_bar(window).show
47
+ yield(window) if block_given?
48
+ end
49
+
50
+ def sub_window_below_top_bar(window)
51
+ top = top_bar(window).height
52
+ sub_window = window.subwin(window.maxy - top, window.maxx, top, 0)
53
+ sub_window.keypad(true)
54
+ sub_window
55
+ end
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,91 @@
1
+ module Daigaku
2
+ module Views
3
+ require 'wisper'
4
+
5
+ class ChaptersMenu
6
+ include Views
7
+ include Wisper::Publisher
8
+
9
+ def initialize
10
+ @position = 0
11
+ end
12
+
13
+ def enter_chapters_menu(course)
14
+ @window = default_window
15
+ @course = course
16
+
17
+ main_panel(@window) do |window|
18
+ show sub_window_below_top_bar(window)
19
+ end
20
+ end
21
+
22
+ def reenter_chapters_menu(course, chapter)
23
+ @course = course
24
+ @chapter = chapter
25
+
26
+ @position = course.chapters.find_index(chapter)
27
+ enter_chapters_menu(@course)
28
+ end
29
+
30
+ private
31
+
32
+ def show(window)
33
+ draw(window, @position)
34
+ interact_with(window)
35
+ end
36
+
37
+ def draw(window, active_index = 0)
38
+ window.attrset(A_NORMAL)
39
+ window.setpos(0, 1)
40
+ window.emphasize @course.title
41
+ window.write ' - available chapters:'
42
+
43
+ menu_items.each_with_index do |item, index|
44
+ window.setpos(index + 2, 1)
45
+ window.print_indicator(chapters[index])
46
+ window.attrset(index == active_index ? A_STANDOUT : A_NORMAL)
47
+ window.write " #{item.to_s} "
48
+ end
49
+
50
+ window.refresh
51
+ end
52
+
53
+ def interact_with(window)
54
+ while char = window.getch
55
+ case char
56
+ when KEY_UP
57
+ @position -= 1
58
+ broadcast(:reset_menu_position)
59
+ when KEY_DOWN
60
+ @position += 1
61
+ broadcast(:reset_menu_position)
62
+ when 10 # Enter
63
+ broadcast(:enter_units_menu, @course, chapters[@position])
64
+ return
65
+ when 263 # Backspace
66
+ broadcast(:reenter_courses_menu, @course)
67
+ return
68
+ when 27 # ESC
69
+ exit
70
+ end
71
+
72
+ @position = menu_items.length - 1 if @position < 0
73
+ @position = 0 if @position >= menu_items.length
74
+ draw(window, @position)
75
+ end
76
+ end
77
+
78
+ def chapters
79
+ @course.chapters
80
+ end
81
+
82
+ def menu_items
83
+ @menu_items = chapters.map do |chapter|
84
+ chapter.title
85
+ end
86
+ end
87
+
88
+ end
89
+
90
+ end
91
+ end