daigaku 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|