daigaku 0.2.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +7 -4
- data/CODE_OF_CONDUCT.md +77 -0
- data/README.md +11 -11
- data/bin/daigaku +6 -2
- data/daigaku.gemspec +22 -26
- data/lib/daigaku.rb +0 -1
- data/lib/daigaku/chapter.rb +3 -4
- data/lib/daigaku/coloring.rb +22 -27
- data/lib/daigaku/configuration.rb +25 -28
- data/lib/daigaku/congratulator.rb +17 -5
- data/lib/daigaku/course.rb +9 -8
- data/lib/daigaku/exceptions.rb +0 -2
- data/lib/daigaku/generator.rb +13 -15
- data/lib/daigaku/github_client.rb +5 -5
- data/lib/daigaku/loadable.rb +23 -14
- data/lib/daigaku/loading/chapters.rb +0 -2
- data/lib/daigaku/loading/courses.rb +0 -2
- data/lib/daigaku/loading/units.rb +0 -2
- data/lib/daigaku/markdown.rb +1 -0
- data/lib/daigaku/markdown/printer.rb +89 -0
- data/lib/daigaku/markdown/ruby_doc.rb +15 -15
- data/lib/daigaku/solution.rb +23 -15
- data/lib/daigaku/storeable.rb +11 -12
- data/lib/daigaku/task.rb +1 -1
- data/lib/daigaku/terminal.rb +3 -4
- data/lib/daigaku/terminal/cli.rb +8 -8
- data/lib/daigaku/terminal/courses.rb +22 -19
- data/lib/daigaku/terminal/output.rb +46 -53
- data/lib/daigaku/terminal/setup.rb +13 -18
- data/lib/daigaku/terminal/solutions.rb +27 -32
- data/lib/daigaku/terminal/welcome.rb +9 -11
- data/lib/daigaku/test.rb +7 -10
- data/lib/daigaku/test_result.rb +54 -21
- data/lib/daigaku/unit.rb +2 -4
- data/lib/daigaku/version.rb +1 -1
- data/lib/daigaku/views.rb +29 -33
- data/lib/daigaku/views/chapters_menu.rb +16 -20
- data/lib/daigaku/views/courses_menu.rb +12 -15
- data/lib/daigaku/views/main_menu.rb +23 -23
- data/lib/daigaku/views/menu.rb +14 -18
- data/lib/daigaku/views/splash.rb +11 -13
- data/lib/daigaku/views/subscriber.rb +38 -0
- data/lib/daigaku/views/task_view.rb +97 -80
- data/lib/daigaku/views/top_bar.rb +4 -10
- data/lib/daigaku/views/units_menu.rb +16 -21
- data/lib/daigaku/window.rb +12 -70
- data/spec/daigaku/chapter_spec.rb +23 -18
- data/spec/daigaku/coloring_spec.rb +0 -1
- data/spec/daigaku/configuration_spec.rb +54 -50
- data/spec/daigaku/congratulator_spec.rb +11 -8
- data/spec/daigaku/course_spec.rb +75 -52
- data/spec/daigaku/generator_spec.rb +24 -25
- data/spec/daigaku/github_client_spec.rb +17 -18
- data/spec/daigaku/loading/chapters_spec.rb +2 -3
- data/spec/daigaku/loading/courses_spec.rb +2 -3
- data/spec/daigaku/loading/units_spec.rb +4 -5
- data/spec/daigaku/markdown/ruby_doc_spec.rb +12 -6
- data/spec/daigaku/reference_solution_spec.rb +8 -10
- data/spec/daigaku/solution_spec.rb +21 -22
- data/spec/daigaku/storeable_spec.rb +12 -10
- data/spec/daigaku/task_spec.rb +3 -4
- data/spec/daigaku/terminal/cli_spec.rb +29 -21
- data/spec/daigaku/terminal/courses_spec.rb +104 -99
- data/spec/daigaku/terminal/output_spec.rb +44 -39
- data/spec/daigaku/terminal/setup_spec.rb +1 -3
- data/spec/daigaku/terminal/solutions_spec.rb +0 -2
- data/spec/daigaku/terminal/welcome_spec.rb +0 -2
- data/spec/daigaku/terminal_spec.rb +5 -7
- data/spec/daigaku/test_example_spec.rb +16 -14
- data/spec/daigaku/test_result_spec.rb +21 -25
- data/spec/daigaku/test_spec.rb +11 -12
- data/spec/daigaku/unit_spec.rb +24 -27
- data/spec/daigaku/views/chapters_menu_spec.rb +0 -1
- data/spec/daigaku/views/courses_menu_spec.rb +0 -1
- data/spec/daigaku/views/menu_spec.rb +1 -2
- data/spec/daigaku/views/task_view_spec.rb +0 -2
- data/spec/daigaku/views/units_menu_spec.rb +0 -1
- data/spec/daigaku/views_spec.rb +0 -1
- data/spec/daigaku_spec.rb +9 -12
- data/spec/path_helpers_spec.rb +11 -12
- data/spec/resource_helpers_spec.rb +11 -12
- data/spec/spec_helper.rb +3 -4
- data/spec/support/macros/content_helpers.rb +16 -17
- data/spec/support/macros/mock_helpers.rb +6 -6
- data/spec/support/macros/path_helpers.rb +15 -15
- data/spec/support/macros/resource_helpers.rb +34 -35
- metadata +32 -44
data/lib/daigaku/solution.rb
CHANGED
@@ -1,20 +1,20 @@
|
|
1
1
|
module Daigaku
|
2
2
|
class Solution
|
3
|
-
|
4
|
-
FILE_SUFFIX = '_solution.rb'
|
3
|
+
FILE_SUFFIX = '_solution.rb'.freeze
|
5
4
|
|
6
5
|
attr_reader :code, :path, :errors
|
7
6
|
|
8
7
|
def initialize(unit_path)
|
9
8
|
@unit_path = unit_path
|
10
|
-
@path
|
11
|
-
@code
|
12
|
-
@verified
|
9
|
+
@path = solution_path(unit_path)
|
10
|
+
@code = load_code(@path)
|
11
|
+
@verified = store_state
|
13
12
|
end
|
14
13
|
|
15
14
|
def verify!
|
16
|
-
|
17
|
-
|
15
|
+
@code = load_code(@path)
|
16
|
+
result = Daigaku::Test.new(@unit_path).run(@code)
|
17
|
+
self.store_state = result.passed?
|
18
18
|
result
|
19
19
|
end
|
20
20
|
|
@@ -24,7 +24,7 @@ module Daigaku
|
|
24
24
|
|
25
25
|
def store_key
|
26
26
|
unless @store_key
|
27
|
-
part_path
|
27
|
+
part_path = path.split('/')[-3..-1].join('/').gsub(FILE_SUFFIX, '')
|
28
28
|
@store_key = Storeable.key(part_path, prefix: 'verified')
|
29
29
|
end
|
30
30
|
|
@@ -33,21 +33,29 @@ module Daigaku
|
|
33
33
|
|
34
34
|
private
|
35
35
|
|
36
|
+
def load_code(path)
|
37
|
+
File.read(path).strip if File.file?(path)
|
38
|
+
end
|
39
|
+
|
36
40
|
def solution_path(path)
|
37
|
-
local_path
|
38
|
-
|
39
|
-
file
|
41
|
+
local_path = Daigaku.config.solutions_path
|
42
|
+
sub_directory = Storeable.key(directory_from(path))
|
43
|
+
file = Storeable.key(File.basename(path)) + FILE_SUFFIX
|
44
|
+
|
45
|
+
File.join(local_path, sub_directory, file)
|
46
|
+
end
|
40
47
|
|
41
|
-
|
48
|
+
def directory_from(path)
|
49
|
+
path.split('/')[-3..-2].join('/').gsub(FILE_SUFFIX, '')
|
42
50
|
end
|
43
51
|
|
44
|
-
def
|
52
|
+
def store_state=(verified)
|
45
53
|
@verified = verified
|
46
54
|
QuickStore.store.set(store_key, verified?)
|
47
55
|
end
|
48
56
|
|
49
|
-
def
|
57
|
+
def store_state
|
50
58
|
QuickStore.store.get(store_key)
|
51
59
|
end
|
52
60
|
end
|
53
|
-
end
|
61
|
+
end
|
data/lib/daigaku/storeable.rb
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
module Daigaku
|
2
2
|
module Storeable
|
3
|
-
|
4
3
|
LEADING_NUMBERS = /^\d+[\_\-\s]+/
|
5
|
-
PART_JOINTS
|
4
|
+
PART_JOINTS = /[\_\-\s]+/
|
6
5
|
|
7
6
|
class << self
|
8
7
|
def key(text, options = {})
|
9
|
-
separator
|
10
|
-
prefix
|
11
|
-
suffix
|
12
|
-
suffixes
|
8
|
+
separator = QuickStore.config.key_separator
|
9
|
+
prefix = options[:prefix]
|
10
|
+
suffix = clean(options[:suffix])
|
11
|
+
suffixes = options[:suffixes]
|
13
12
|
suffixes_items = suffixes ? suffixes.map { |s| clean(s) }.compact : nil
|
14
13
|
|
15
14
|
[prefix, clean(text), suffix || suffixes_items].compact.join(separator)
|
@@ -18,15 +17,15 @@ module Daigaku
|
|
18
17
|
private
|
19
18
|
|
20
19
|
def clean(text)
|
21
|
-
if text
|
22
|
-
|
23
|
-
|
24
|
-
end
|
20
|
+
return if text.nil?
|
21
|
+
parts(text.to_s).join(QuickStore.config.key_separator)
|
22
|
+
end
|
25
23
|
|
26
|
-
|
24
|
+
def parts(text)
|
25
|
+
text.split(QuickStore.config.key_separator).map do |key|
|
26
|
+
key.gsub(LEADING_NUMBERS, '').gsub(PART_JOINTS, '_').downcase
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
30
|
-
|
31
30
|
end
|
32
31
|
end
|
data/lib/daigaku/task.rb
CHANGED
data/lib/daigaku/terminal.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
module Daigaku
|
2
2
|
module Terminal
|
3
|
-
|
4
3
|
# text should be of a width of 70 columns or less
|
5
4
|
def self.text(file_name)
|
6
5
|
texts_path = File.expand_path('../terminal/texts', __FILE__)
|
7
|
-
file
|
8
|
-
(File.exist?(file) ? File.read(file).to_s : '')
|
9
|
-
end
|
6
|
+
file = File.join(texts_path, "#{file_name}.txt")
|
10
7
|
|
8
|
+
File.exist?(file) ? File.read(file).to_s : ''
|
9
|
+
end
|
11
10
|
end
|
12
11
|
end
|
data/lib/daigaku/terminal/cli.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
require 'thor'
|
2
|
+
require_relative 'courses'
|
3
|
+
require_relative 'solutions'
|
4
|
+
require_relative 'setup'
|
5
|
+
require_relative 'output'
|
2
6
|
|
3
7
|
module Daigaku
|
4
8
|
module Terminal
|
5
|
-
|
6
|
-
require_relative 'courses'
|
7
|
-
require_relative 'solutions'
|
8
|
-
require_relative 'setup'
|
9
|
-
require_relative 'output'
|
10
|
-
|
11
9
|
class CLI < Thor
|
12
10
|
include Terminal::Output
|
13
11
|
|
12
|
+
package_name 'Daigaku'
|
13
|
+
|
14
14
|
desc 'courses [COMMAND]', 'Handle daigaku courses'
|
15
15
|
subcommand 'courses', Terminal::Courses
|
16
16
|
|
@@ -21,7 +21,7 @@ module Daigaku
|
|
21
21
|
subcommand 'setup', Terminal::Setup
|
22
22
|
|
23
23
|
def self.start
|
24
|
-
Daigaku.config.import
|
24
|
+
Daigaku.config.import
|
25
25
|
super
|
26
26
|
end
|
27
27
|
|
@@ -40,7 +40,7 @@ module Daigaku
|
|
40
40
|
generator = Generator.new
|
41
41
|
generator.prepare
|
42
42
|
|
43
|
-
courses_path
|
43
|
+
courses_path = Daigaku.config.courses_path
|
44
44
|
solutions_path = Daigaku.config.solutions_path
|
45
45
|
|
46
46
|
generator.scaffold(courses_path, solutions_path)
|
@@ -1,14 +1,18 @@
|
|
1
|
+
require 'os'
|
2
|
+
require 'open-uri'
|
3
|
+
require 'zip'
|
4
|
+
require_relative 'output'
|
5
|
+
|
1
6
|
module Daigaku
|
2
7
|
module Terminal
|
3
|
-
|
4
|
-
require 'os'
|
5
|
-
require 'open-uri'
|
6
|
-
require 'zip'
|
7
|
-
require_relative 'output'
|
8
|
-
|
9
8
|
class Courses < Thor
|
10
9
|
include Terminal::Output
|
11
10
|
|
11
|
+
GITHUB = /github\.com/
|
12
|
+
MASTER_ZIP_URL = %r{github.com\/(.*)\/archive\/master.zip}
|
13
|
+
URL = /\A#{URI.regexp(%w(http https))}\z/
|
14
|
+
ZIP_FILE = /\.zip/
|
15
|
+
|
12
16
|
desc 'list', 'List your available daigaku courses'
|
13
17
|
def list
|
14
18
|
courses = Loading::Courses.load(Daigaku.config.courses_path)
|
@@ -22,22 +26,22 @@ module Daigaku
|
|
22
26
|
url = GithubClient.master_zip_url(Daigaku.config.initial_course) if use_initial_course
|
23
27
|
url = GithubClient.master_zip_url(options[:github]) if options[:github]
|
24
28
|
|
25
|
-
url_given = (url =~
|
26
|
-
github = use_initial_course || options[:github] || url.match(
|
29
|
+
url_given = (url =~ URL)
|
30
|
+
github = use_initial_course || options[:github] || url.match(GITHUB)
|
27
31
|
|
28
32
|
raise Download::NoUrlError unless url_given
|
29
|
-
raise Download::NoZipFileUrlError unless File.basename(url) =~
|
33
|
+
raise Download::NoZipFileUrlError unless File.basename(url) =~ ZIP_FILE
|
30
34
|
|
31
35
|
courses_path = Daigaku.config.courses_path
|
32
36
|
FileUtils.makedirs(courses_path) unless Dir.exist?(courses_path)
|
33
37
|
|
34
38
|
file_name = File.join(courses_path, url.split('/').last)
|
35
39
|
|
36
|
-
File.open(file_name, 'w') { |file| file << open(url).read }
|
40
|
+
File.open(file_name, 'w') { |file| file << URI.open(url).read }
|
37
41
|
course = Course.unzip(file_name, github_repo: github)
|
38
42
|
|
39
43
|
if github
|
40
|
-
user_and_repo = url.match(
|
44
|
+
user_and_repo = url.match(MASTER_ZIP_URL).captures.first
|
41
45
|
store_repo_data(options[:github] || user_and_repo)
|
42
46
|
end
|
43
47
|
|
@@ -46,11 +50,11 @@ module Daigaku
|
|
46
50
|
scaffold_solutions
|
47
51
|
|
48
52
|
say_info "Successfully #{action} the course \"#{course.title}\"!"
|
49
|
-
rescue Download::NoUrlError
|
53
|
+
rescue Download::NoUrlError
|
50
54
|
print_download_warning(url, "\"#{url}\" is not a valid URL!")
|
51
|
-
rescue Download::NoZipFileUrlError
|
55
|
+
rescue Download::NoZipFileUrlError
|
52
56
|
print_download_warning(url, "\"#{url}\" is not a URL of a *.zip file!")
|
53
|
-
rescue
|
57
|
+
rescue StandardError => e
|
54
58
|
print_download_warning(url, e.message)
|
55
59
|
ensure
|
56
60
|
FileUtils.rm(file_name) if File.exist?(file_name.to_s)
|
@@ -124,9 +128,9 @@ module Daigaku
|
|
124
128
|
end
|
125
129
|
|
126
130
|
def store_repo_data(user_and_repo)
|
127
|
-
parts
|
128
|
-
author = parts
|
129
|
-
course = parts
|
131
|
+
parts = (user_and_repo ||= Daigaku.config.initial_course).split('/')
|
132
|
+
author = parts[0]
|
133
|
+
course = parts[1]
|
130
134
|
|
131
135
|
course = Course.new(course)
|
132
136
|
QuickStore.store.set(course.key(:author), author)
|
@@ -164,7 +168,7 @@ module Daigaku
|
|
164
168
|
def print_course_not_available(course_name)
|
165
169
|
text = [
|
166
170
|
"The course \"#{course_name}\" is not available in",
|
167
|
-
"\"#{Daigaku.config.courses_path}\".\n"
|
171
|
+
"\"#{Daigaku.config.courses_path}\".\n"
|
168
172
|
]
|
169
173
|
|
170
174
|
say_warning text.join("\n")
|
@@ -174,6 +178,5 @@ module Daigaku
|
|
174
178
|
end
|
175
179
|
end
|
176
180
|
end
|
177
|
-
|
178
181
|
end
|
179
182
|
end
|
@@ -1,78 +1,71 @@
|
|
1
1
|
require 'thor'
|
2
|
-
require 'active_support/concern'
|
3
2
|
require 'colorize'
|
4
3
|
|
5
4
|
module Daigaku
|
6
5
|
module Terminal
|
7
|
-
|
8
6
|
module Output
|
9
|
-
|
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
|
7
|
+
private
|
18
8
|
|
19
|
-
|
20
|
-
|
21
|
-
|
9
|
+
def say(text)
|
10
|
+
output = text.split("\n").map { |line| "\t#{line}" }.join("\n")
|
11
|
+
$stdout.puts output
|
12
|
+
end
|
22
13
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
14
|
+
def empty_line
|
15
|
+
$stdout.puts ''
|
16
|
+
end
|
27
17
|
|
28
|
-
|
29
|
-
|
30
|
-
|
18
|
+
def get(string)
|
19
|
+
$stdout.print "\n\t#{string} "
|
20
|
+
$stdin.gets.strip
|
21
|
+
end
|
31
22
|
|
32
|
-
|
33
|
-
|
34
|
-
|
23
|
+
def say_info(text)
|
24
|
+
say_box(text, ' ℹ', :light_blue)
|
25
|
+
end
|
35
26
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
empty_line
|
27
|
+
def say_warning(text)
|
28
|
+
say_box(text, '⚠ ', :light_red)
|
29
|
+
end
|
40
30
|
|
41
|
-
|
42
|
-
|
31
|
+
def say_box(text, symbol, color)
|
32
|
+
empty_line
|
33
|
+
say line.send(color)
|
34
|
+
empty_line
|
43
35
|
|
44
|
-
|
45
|
-
|
46
|
-
empty_line
|
47
|
-
end
|
36
|
+
indented_text = text.split("\n").join("\n#{' ' * (symbol.length + 1)}")
|
37
|
+
say indented_text.prepend("#{symbol} ").send(color)
|
48
38
|
|
49
|
-
|
50
|
-
|
51
|
-
|
39
|
+
empty_line
|
40
|
+
say line.send(color)
|
41
|
+
empty_line
|
42
|
+
end
|
52
43
|
|
53
|
-
|
54
|
-
|
44
|
+
def line(symbol = '-')
|
45
|
+
symbol * 70
|
46
|
+
end
|
55
47
|
|
56
|
-
|
57
|
-
|
48
|
+
def get_command(command, description)
|
49
|
+
say description
|
58
50
|
|
59
|
-
|
60
|
-
|
61
|
-
next
|
62
|
-
end
|
51
|
+
loop do
|
52
|
+
cmd = get '>'
|
63
53
|
|
64
|
-
|
65
|
-
|
54
|
+
unless cmd == command
|
55
|
+
say "This was something else. Try \"#{command}\"."
|
56
|
+
next
|
66
57
|
end
|
67
|
-
end
|
68
58
|
|
69
|
-
|
70
|
-
|
71
|
-
confirm = get '(yes|no)'
|
72
|
-
yield if confirm == 'yes' && block_given?
|
59
|
+
system cmd
|
60
|
+
break
|
73
61
|
end
|
74
62
|
end
|
75
|
-
end
|
76
63
|
|
64
|
+
def get_confirm(description)
|
65
|
+
say_warning description
|
66
|
+
confirm = get '(yes|no)'
|
67
|
+
yield if confirm == 'yes' && block_given?
|
68
|
+
end
|
69
|
+
end
|
77
70
|
end
|
78
71
|
end
|
@@ -1,10 +1,8 @@
|
|
1
1
|
require 'thor'
|
2
|
+
require_relative 'output'
|
2
3
|
|
3
4
|
module Daigaku
|
4
5
|
module Terminal
|
5
|
-
|
6
|
-
require_relative 'output'
|
7
|
-
|
8
6
|
class Setup < Thor
|
9
7
|
include Terminal::Output
|
10
8
|
|
@@ -19,7 +17,7 @@ module Daigaku
|
|
19
17
|
path = get 'path:'
|
20
18
|
|
21
19
|
begin
|
22
|
-
@daigaku_path = File.expand_path(
|
20
|
+
@daigaku_path = File.expand_path(path.to_s, Dir.pwd)
|
23
21
|
rescue
|
24
22
|
say_warning "#{path} is no valid path name. Try another!"
|
25
23
|
next
|
@@ -29,7 +27,7 @@ module Daigaku
|
|
29
27
|
say "\"#{@daigaku_path}\""
|
30
28
|
|
31
29
|
confirmation = get '(yes|no)'
|
32
|
-
break if confirmation.
|
30
|
+
break if confirmation.casecmp('yes').zero?
|
33
31
|
|
34
32
|
empty_line
|
35
33
|
say 'No Problem. Just type another one!'
|
@@ -61,8 +59,8 @@ module Daigaku
|
|
61
59
|
solutions_path = options[:paths] || options[:solutions_path]
|
62
60
|
|
63
61
|
if courses_path.nil? && solutions_path.nil?
|
64
|
-
say_warning
|
65
|
-
say
|
62
|
+
say_warning 'Please specify options when using this command!'
|
63
|
+
say `daigaku setup help set`
|
66
64
|
return
|
67
65
|
end
|
68
66
|
|
@@ -76,11 +74,12 @@ module Daigaku
|
|
76
74
|
private
|
77
75
|
|
78
76
|
def prepare_directories(path)
|
79
|
-
courses_dir
|
77
|
+
courses_dir = Daigaku::Configuration::COURSES_DIR
|
80
78
|
courses_path = File.join(path, courses_dir)
|
79
|
+
|
81
80
|
Daigaku.config.courses_path = courses_path
|
82
81
|
|
83
|
-
solutions_dir
|
82
|
+
solutions_dir = Daigaku::Configuration::SOLUTIONS_DIR
|
84
83
|
solutions_path = File.join(path, solutions_dir)
|
85
84
|
|
86
85
|
if Dir.exist? solutions_path
|
@@ -92,7 +91,7 @@ module Daigaku
|
|
92
91
|
|
93
92
|
text = [
|
94
93
|
"Your Daigaku directory is now set up.\n",
|
95
|
-
|
94
|
+
'Daigaku created/updated following two paths for you:',
|
96
95
|
courses_path,
|
97
96
|
solutions_path
|
98
97
|
]
|
@@ -101,15 +100,11 @@ module Daigaku
|
|
101
100
|
end
|
102
101
|
|
103
102
|
def update_config(attribute, value)
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
say_warning e.message
|
109
|
-
end
|
103
|
+
path = File.expand_path(value, Dir.pwd)
|
104
|
+
Daigaku.config.send("#{attribute}=", path)
|
105
|
+
rescue StandardError => e
|
106
|
+
say_warning e.message
|
110
107
|
end
|
111
|
-
|
112
108
|
end
|
113
|
-
|
114
109
|
end
|
115
110
|
end
|