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,23 @@
1
+ module Daigaku
2
+ module Loadable
3
+
4
+ require 'active_support/inflector'
5
+
6
+ def load(path)
7
+ if Dir.exist?(path)
8
+ dirs = Dir.entries(path).select do |entry|
9
+ !entry.match(/\./)
10
+ end
11
+
12
+ dirs.sort.map do |dir|
13
+ dir_path = File.join(path, dir)
14
+ class_name = self.to_s.demodulize.singularize
15
+ "Daigaku::#{class_name}".constantize.new(dir_path)
16
+ end
17
+ else
18
+ Array.new
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ module Daigaku
2
+ module Loading
3
+
4
+ class Chapters
5
+ extend Daigaku::Loadable
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Daigaku
2
+ module Loading
3
+
4
+ class Courses
5
+ extend Daigaku::Loadable
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Daigaku
2
+ module Loading
3
+
4
+ class Units
5
+ extend Daigaku::Loadable
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ module Daigaku
2
+ class ReferenceSolution
3
+ attr_reader :code, :path
4
+
5
+ def initialize(path)
6
+ @path = Dir[File.join(path, '*solution.rb')].first
7
+ @code = File.read(@path).strip if @path
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,42 @@
1
+ module Daigaku
2
+ class Solution
3
+
4
+ attr_reader :code, :path, :errors
5
+
6
+ def initialize(unit_path)
7
+ @unit_path = unit_path
8
+ @path = solution_path(unit_path)
9
+ @code = File.read(@path).strip if File.file?(@path)
10
+ @verified = get_database_state
11
+ end
12
+
13
+ def verify!
14
+ result = Daigaku::Test.new(@unit_path).run(self.code)
15
+ set_database_state(result.passed?)
16
+ result
17
+ end
18
+
19
+ def verified?
20
+ !!@verified
21
+ end
22
+
23
+ private
24
+
25
+ def solution_path(path)
26
+ local_path = Daigaku.config.solutions_path
27
+ sub_dirs = path.split('/')[-3..-2]
28
+ file = File.basename(path).gsub(/(\_+|\-+|\.+)/, '_') + '_solution.rb'
29
+
30
+ File.join(local_path, sub_dirs, file)
31
+ end
32
+
33
+ def set_database_state(verified)
34
+ @verified = verified
35
+ Daigaku.database.set(@path, verified?)
36
+ end
37
+
38
+ def get_database_state
39
+ Daigaku.database.get(@path)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,10 @@
1
+ module Daigaku
2
+ class Task
3
+ attr_reader :markdown, :path
4
+
5
+ def initialize(path)
6
+ @path = Dir[File.join(path, '*.md')].first
7
+ @markdown = File.read(@path).strip
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ module Daigaku
2
+ module Terminal
3
+
4
+ # text should be of a width of 70 columns or less
5
+ def self.text(file_name)
6
+ texts_path = File.expand_path('../terminal/texts', __FILE__)
7
+ File.read(File.join(texts_path, "#{file_name.to_s}.txt"))
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,59 @@
1
+ require 'thor'
2
+
3
+ module Daigaku
4
+ module Terminal
5
+
6
+ require_relative 'courses'
7
+ require_relative 'solutions'
8
+ require_relative 'setup'
9
+ require_relative 'output'
10
+
11
+ class CLI < Thor
12
+ include Terminal::Output
13
+
14
+ desc 'courses [COMMAND]', 'Handle daigaku courses'
15
+ subcommand 'courses', Terminal::Courses
16
+
17
+ desc 'solutions [COMMAND]', 'Handle your solutions'
18
+ subcommand 'solutions', Terminal::Solutions
19
+
20
+ desc 'setup [COMMAND]', 'Change daigaku setup'
21
+ subcommand 'setup', Terminal::Setup
22
+
23
+ def self.start
24
+ Daigaku.config.import!
25
+ super
26
+ end
27
+
28
+ desc 'about', 'About daigaku'
29
+ def about
30
+ Welcome.about
31
+ end
32
+
33
+ desc 'welcome', 'Setup daigaku the first time and learn some important commands.'
34
+ def welcome
35
+ Welcome.run
36
+ end
37
+
38
+ desc 'scaffold', 'Scaffold solution files for your courses.'
39
+ def scaffold
40
+ generator = Generator.new
41
+ generator.prepare
42
+
43
+ courses_path = Daigaku.config.courses_path
44
+ solutions_path = Daigaku.config.solutions_path
45
+
46
+ generator.scaffold(courses_path, solutions_path)
47
+
48
+ say_info "You will find your solution files in\n#{solutions_path}."
49
+ end
50
+
51
+ desc 'learn', 'Go to daigaku to learn Ruby!'
52
+ def learn
53
+ courses = Loading::Courses.load(Daigaku.config.courses_path)
54
+ courses.empty? ? Courses.new.list : Daigaku.start
55
+ end
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,114 @@
1
+ module Daigaku
2
+ module Terminal
3
+
4
+ require 'os'
5
+ require 'open-uri'
6
+ require 'zip'
7
+ require_relative 'output'
8
+
9
+ class Courses < Thor
10
+ include Terminal::Output
11
+
12
+ desc 'list', 'List your available daigaku courses'
13
+ def list
14
+ courses = Loading::Courses.load(Daigaku.config.courses_path)
15
+ say_info courses_list_text(courses)
16
+ end
17
+
18
+ method_option :github,
19
+ type: :string,
20
+ aliases: '-g',
21
+ desc: 'Download Github repository'
22
+ desc 'download [URL] [OPTIONS]', 'Download a new daigaku course from [URL]'
23
+ def download(url = nil)
24
+ use_initial_course = url.nil? && options[:github].nil?
25
+ url = github_repo(Daigaku.config.initial_course) if use_initial_course
26
+ url = github_repo(options[:github]) if options[:github]
27
+
28
+ url_given = (url =~ /\A#{URI::regexp(['http', 'https'])}\z/)
29
+ github = use_initial_course || options[:github] || url.match(/github\.com/)
30
+
31
+ raise Download::NoUrlError unless url_given
32
+ raise Download::NoZipFileUrlError unless File.basename(url) =~ /\.zip/
33
+
34
+ courses_path = Daigaku.config.courses_path
35
+ FileUtils.makedirs(courses_path) unless Dir.exist?(courses_path)
36
+
37
+ file_name = File.join(courses_path, url.split('/').last)
38
+
39
+ File.open(file_name, 'w') { |file| file << open(url).read }
40
+ course_name = unzip(file_name, github)
41
+ scaffold_solutions
42
+
43
+ say_info "Successfully downloaded the course \"#{course_name}\"!"
44
+ rescue Download::NoUrlError => e
45
+ print_download_warning(url, "\"#{url}\" is not a valid URL!")
46
+ rescue Download::NoZipFileUrlError => e
47
+ print_download_warning(url, "\"#{url}\" is not a URL of a *.zip file!")
48
+ rescue Exception => e
49
+ print_download_warning(url, e.message)
50
+ ensure
51
+ FileUtils.rm(file_name) if File.exist?(file_name.to_s)
52
+ end
53
+
54
+ private
55
+
56
+ def courses_list_text(courses)
57
+ if courses.empty?
58
+ text = Terminal.text :courses_empty
59
+ else
60
+ text = [
61
+ "Available daigaku courses:\n",
62
+ *courses.map { |course| "* #{File.basename(course.path)}\n" }
63
+ ].join("\n")
64
+ end
65
+
66
+ "#{text}\n#{Terminal.text :hint_course_download}"
67
+ end
68
+
69
+ def github_repo(user_and_repo)
70
+ "https://github.com/#{user_and_repo}/archive/master.zip"
71
+ end
72
+
73
+ def unzip(file_path, github = false)
74
+ target_dir = File.dirname(file_path)
75
+ course_name = nil
76
+
77
+ Zip::File.open(file_path) do |zip_file|
78
+ zip_file.each do |entry|
79
+
80
+ if github
81
+ first, *others = entry.to_s.split('/')
82
+ directory = File.join(first.split('-')[0..-2].join('-'), others)
83
+ else
84
+ directory = entry.to_s
85
+ end
86
+
87
+ course_name ||= directory.split('/').first.gsub(/_+/, ' ')
88
+ zip_file.extract(entry, "#{target_dir}/#{directory}") { true }
89
+ end
90
+ end
91
+
92
+ FileUtils.rm(file_path)
93
+ course_name
94
+ end
95
+
96
+ def scaffold_solutions
97
+ generator = Generator.new
98
+ generator.prepare
99
+ generator.scaffold(Daigaku.config.courses_path, Daigaku.config.solutions_path)
100
+ end
101
+
102
+ def print_download_warning(url, text)
103
+ message = [
104
+ "Error while downloading course from URL \"#{url}\"",
105
+ "#{text}\n",
106
+ Terminal.text(:hint_course_download)
107
+ ].join("\n")
108
+
109
+ say_warning message
110
+ end
111
+ end
112
+
113
+ end
114
+ end
@@ -0,0 +1,72 @@
1
+ require 'thor'
2
+ require 'active_support/concern'
3
+ require 'colorize'
4
+
5
+ module Daigaku
6
+ module Terminal
7
+
8
+ module Output
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ private
13
+
14
+ def say(text)
15
+ output = text.split("\n").map {|line| "\t#{line}" }.join("\n")
16
+ $stdout.puts output
17
+ end
18
+
19
+ def empty_line
20
+ $stdout.puts ''
21
+ end
22
+
23
+ def get(string)
24
+ $stdout.print "\n\t#{string} "
25
+ $stdin.gets.strip
26
+ end
27
+
28
+ def say_info(text)
29
+ say_box(text, ' ℹ', :light_blue)
30
+ end
31
+
32
+ def say_warning(text)
33
+ say_box(text, '⚠ ', :light_red)
34
+ end
35
+
36
+ def say_box(text, symbol, color)
37
+ empty_line
38
+ say line.send(color)
39
+ empty_line
40
+
41
+ indented_text = text.split("\n").join("\n#{' ' * (symbol.length + 1)}")
42
+ say indented_text.prepend("#{symbol} ").send(color)
43
+
44
+ empty_line
45
+ say line.send(color)
46
+ empty_line
47
+ end
48
+
49
+ def line(symbol = '-')
50
+ symbol * 70
51
+ end
52
+
53
+ def get_command(command, description)
54
+ say description
55
+
56
+ loop do
57
+ cmd = get '>'
58
+
59
+ unless cmd == command
60
+ say "This was something else. Try \"#{command}\"."
61
+ next
62
+ end
63
+
64
+ system cmd
65
+ break
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,115 @@
1
+ require 'thor'
2
+
3
+ module Daigaku
4
+ module Terminal
5
+
6
+ require_relative 'output'
7
+
8
+ class Setup < Thor
9
+ include Terminal::Output
10
+
11
+ desc 'init', 'Initially setup daigaku paths'
12
+ def init
13
+ empty_line
14
+ say 'Please type the base path in which you want to save your daigaku'
15
+ say 'files. The "courses" folder and "solutions" folder will be created'
16
+ say 'automatically.'
17
+
18
+ loop do
19
+ path = get 'path:'
20
+
21
+ begin
22
+ @daigaku_path = File.expand_path("#{path}", Dir.pwd)
23
+ rescue
24
+ say_warning "#{path} is no valid path name. Try another!"
25
+ next
26
+ end
27
+
28
+ say_warning 'Do you want to use the following path as your daigaku base path?'
29
+ say "\"#{@daigaku_path}\""
30
+
31
+ confirmation = get '(yes|no)'
32
+ break if confirmation.downcase == 'yes'
33
+
34
+ empty_line
35
+ say 'No Problem. Just type another one!'
36
+ end
37
+
38
+ prepare_directories(@daigaku_path)
39
+ end
40
+
41
+ desc 'list', 'List the current daigaku setup'
42
+ def list
43
+ say_info "Your current daigaku setup is:\n\n#{Daigaku.config.summary}"
44
+ end
45
+
46
+ desc 'set [OPTIONS]', 'Update the settings of your daigaku environment'
47
+ method_option :courses_path,
48
+ type: :string,
49
+ aliases: '-c',
50
+ desc: 'Set courses_path directory'
51
+ method_option :solutions_path,
52
+ type: :string,
53
+ aliases: '-s',
54
+ desc: 'Set solutions_path directory'
55
+ method_option :paths,
56
+ type: :string,
57
+ aliases: '-p',
58
+ desc: 'Set all daigaku paths to a certain path'
59
+ def set
60
+ courses_path = options[:paths] || options[:courses_path]
61
+ solutions_path = options[:paths] || options[:solutions_path]
62
+
63
+ if courses_path.nil? && solutions_path.nil?
64
+ say_warning "Please specify options when using this command!"
65
+ say %x{ daigaku setup help set }
66
+ return
67
+ end
68
+
69
+ update_config(:courses_path, courses_path) if courses_path
70
+ update_config(:solutions_path, solutions_path) if solutions_path
71
+
72
+ Daigaku.config.save
73
+ list
74
+ end
75
+
76
+ private
77
+
78
+ def prepare_directories(path)
79
+ courses_dir = Daigaku::Configuration::COURSES_DIR
80
+ courses_path = File.join(path, courses_dir)
81
+ Daigaku.config.courses_path = courses_path
82
+
83
+ solutions_dir = Daigaku::Configuration::SOLUTIONS_DIR
84
+ solutions_path = File.join(path, solutions_dir)
85
+
86
+ if Dir.exist? solutions_path
87
+ Daigaku.config.solutions_path = solutions_path
88
+ end
89
+
90
+ generator = Daigaku::Generator.new
91
+ generator.prepare
92
+
93
+ text = [
94
+ "Your Daigaku directory is now set up.\n",
95
+ "Daigaku created/updated following two paths for you:",
96
+ courses_path,
97
+ solutions_path
98
+ ]
99
+
100
+ say_info text.join("\n")
101
+ end
102
+
103
+ def update_config(attribute, value)
104
+ begin
105
+ path = File.expand_path(value, Dir.pwd)
106
+ Daigaku.config.send("#{attribute}=", path)
107
+ rescue Exception => e
108
+ say_warning e.message
109
+ end
110
+ end
111
+
112
+ end
113
+
114
+ end
115
+ end