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.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/README.md +62 -0
- data/Rakefile +2 -0
- data/bin/daigaku +12 -0
- data/daigaku.gemspec +37 -0
- data/lib/daigaku.rb +27 -0
- data/lib/daigaku/chapter.rb +23 -0
- data/lib/daigaku/configuration.rb +86 -0
- data/lib/daigaku/course.rb +23 -0
- data/lib/daigaku/database.rb +64 -0
- data/lib/daigaku/exceptions.rb +19 -0
- data/lib/daigaku/generator.rb +53 -0
- data/lib/daigaku/loadable.rb +23 -0
- data/lib/daigaku/loading/chapters.rb +9 -0
- data/lib/daigaku/loading/courses.rb +9 -0
- data/lib/daigaku/loading/units.rb +9 -0
- data/lib/daigaku/reference_solution.rb +10 -0
- data/lib/daigaku/solution.rb +42 -0
- data/lib/daigaku/task.rb +10 -0
- data/lib/daigaku/terminal.rb +11 -0
- data/lib/daigaku/terminal/cli.rb +59 -0
- data/lib/daigaku/terminal/courses.rb +114 -0
- data/lib/daigaku/terminal/output.rb +72 -0
- data/lib/daigaku/terminal/setup.rb +115 -0
- data/lib/daigaku/terminal/solutions.rb +46 -0
- data/lib/daigaku/terminal/texts/about.txt +19 -0
- data/lib/daigaku/terminal/texts/courses_empty.txt +3 -0
- data/lib/daigaku/terminal/texts/hint_course_download.txt +13 -0
- data/lib/daigaku/terminal/texts/welcome.txt +12 -0
- data/lib/daigaku/terminal/welcome.rb +98 -0
- data/lib/daigaku/test.rb +46 -0
- data/lib/daigaku/test_result.rb +69 -0
- data/lib/daigaku/unit.rb +28 -0
- data/lib/daigaku/version.rb +3 -0
- data/lib/daigaku/views.rb +59 -0
- data/lib/daigaku/views/chapters_menu.rb +91 -0
- data/lib/daigaku/views/courses_menu.rb +87 -0
- data/lib/daigaku/views/main_menu.rb +37 -0
- data/lib/daigaku/views/splash.rb +57 -0
- data/lib/daigaku/views/task_view.rb +206 -0
- data/lib/daigaku/views/top_bar.rb +48 -0
- data/lib/daigaku/views/units_menu.rb +92 -0
- data/lib/daigaku/window.rb +160 -0
- data/spec/daigaku/chapter_spec.rb +76 -0
- data/spec/daigaku/configuration_spec.rb +161 -0
- data/spec/daigaku/course_spec.rb +75 -0
- data/spec/daigaku/database_spec.rb +79 -0
- data/spec/daigaku/generator_spec.rb +82 -0
- data/spec/daigaku/loading/chapters_spec.rb +16 -0
- data/spec/daigaku/loading/courses_spec.rb +16 -0
- data/spec/daigaku/loading/units_spec.rb +21 -0
- data/spec/daigaku/reference_solution_spec.rb +23 -0
- data/spec/daigaku/solution_spec.rb +79 -0
- data/spec/daigaku/task_spec.rb +23 -0
- data/spec/daigaku/terminal/cli_spec.rb +51 -0
- data/spec/daigaku/terminal/courses_spec.rb +60 -0
- data/spec/daigaku/terminal/output_spec.rb +123 -0
- data/spec/daigaku/terminal/setup_spec.rb +10 -0
- data/spec/daigaku/terminal/solutions_spec.rb +8 -0
- data/spec/daigaku/terminal/welcome_spec.rb +12 -0
- data/spec/daigaku/terminal_spec.rb +14 -0
- data/spec/daigaku/test_example_spec.rb +54 -0
- data/spec/daigaku/test_result_spec.rb +81 -0
- data/spec/daigaku/test_spec.rb +48 -0
- data/spec/daigaku/unit_spec.rb +85 -0
- data/spec/daigaku/views/chapters_menu_spec.rb +8 -0
- data/spec/daigaku/views/courses_menu_spec.rb +8 -0
- data/spec/daigaku/views/task_view_spec.rb +7 -0
- data/spec/daigaku/views/units_menu_spec.rb +8 -0
- data/spec/daigaku/views_spec.rb +23 -0
- data/spec/daigaku_spec.rb +57 -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 +20 -0
- data/spec/support/macros/path_helpers.rb +133 -0
- data/spec/support/macros/resource_helpers.rb +119 -0
- data/spec/support/macros/test_helpers.rb +6 -0
- 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,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
|
data/lib/daigaku/task.rb
ADDED
@@ -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
|