daigaku 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +7 -4
  3. data/CODE_OF_CONDUCT.md +77 -0
  4. data/README.md +11 -11
  5. data/bin/daigaku +6 -2
  6. data/daigaku.gemspec +22 -26
  7. data/lib/daigaku.rb +0 -1
  8. data/lib/daigaku/chapter.rb +3 -4
  9. data/lib/daigaku/coloring.rb +22 -27
  10. data/lib/daigaku/configuration.rb +25 -28
  11. data/lib/daigaku/congratulator.rb +17 -5
  12. data/lib/daigaku/course.rb +9 -8
  13. data/lib/daigaku/exceptions.rb +0 -2
  14. data/lib/daigaku/generator.rb +13 -15
  15. data/lib/daigaku/github_client.rb +5 -5
  16. data/lib/daigaku/loadable.rb +23 -14
  17. data/lib/daigaku/loading/chapters.rb +0 -2
  18. data/lib/daigaku/loading/courses.rb +0 -2
  19. data/lib/daigaku/loading/units.rb +0 -2
  20. data/lib/daigaku/markdown.rb +1 -0
  21. data/lib/daigaku/markdown/printer.rb +89 -0
  22. data/lib/daigaku/markdown/ruby_doc.rb +15 -15
  23. data/lib/daigaku/solution.rb +23 -15
  24. data/lib/daigaku/storeable.rb +11 -12
  25. data/lib/daigaku/task.rb +1 -1
  26. data/lib/daigaku/terminal.rb +3 -4
  27. data/lib/daigaku/terminal/cli.rb +8 -8
  28. data/lib/daigaku/terminal/courses.rb +22 -19
  29. data/lib/daigaku/terminal/output.rb +46 -53
  30. data/lib/daigaku/terminal/setup.rb +13 -18
  31. data/lib/daigaku/terminal/solutions.rb +27 -32
  32. data/lib/daigaku/terminal/welcome.rb +9 -11
  33. data/lib/daigaku/test.rb +7 -10
  34. data/lib/daigaku/test_result.rb +54 -21
  35. data/lib/daigaku/unit.rb +2 -4
  36. data/lib/daigaku/version.rb +1 -1
  37. data/lib/daigaku/views.rb +29 -33
  38. data/lib/daigaku/views/chapters_menu.rb +16 -20
  39. data/lib/daigaku/views/courses_menu.rb +12 -15
  40. data/lib/daigaku/views/main_menu.rb +23 -23
  41. data/lib/daigaku/views/menu.rb +14 -18
  42. data/lib/daigaku/views/splash.rb +11 -13
  43. data/lib/daigaku/views/subscriber.rb +38 -0
  44. data/lib/daigaku/views/task_view.rb +97 -80
  45. data/lib/daigaku/views/top_bar.rb +4 -10
  46. data/lib/daigaku/views/units_menu.rb +16 -21
  47. data/lib/daigaku/window.rb +12 -70
  48. data/spec/daigaku/chapter_spec.rb +23 -18
  49. data/spec/daigaku/coloring_spec.rb +0 -1
  50. data/spec/daigaku/configuration_spec.rb +54 -50
  51. data/spec/daigaku/congratulator_spec.rb +11 -8
  52. data/spec/daigaku/course_spec.rb +75 -52
  53. data/spec/daigaku/generator_spec.rb +24 -25
  54. data/spec/daigaku/github_client_spec.rb +17 -18
  55. data/spec/daigaku/loading/chapters_spec.rb +2 -3
  56. data/spec/daigaku/loading/courses_spec.rb +2 -3
  57. data/spec/daigaku/loading/units_spec.rb +4 -5
  58. data/spec/daigaku/markdown/ruby_doc_spec.rb +12 -6
  59. data/spec/daigaku/reference_solution_spec.rb +8 -10
  60. data/spec/daigaku/solution_spec.rb +21 -22
  61. data/spec/daigaku/storeable_spec.rb +12 -10
  62. data/spec/daigaku/task_spec.rb +3 -4
  63. data/spec/daigaku/terminal/cli_spec.rb +29 -21
  64. data/spec/daigaku/terminal/courses_spec.rb +104 -99
  65. data/spec/daigaku/terminal/output_spec.rb +44 -39
  66. data/spec/daigaku/terminal/setup_spec.rb +1 -3
  67. data/spec/daigaku/terminal/solutions_spec.rb +0 -2
  68. data/spec/daigaku/terminal/welcome_spec.rb +0 -2
  69. data/spec/daigaku/terminal_spec.rb +5 -7
  70. data/spec/daigaku/test_example_spec.rb +16 -14
  71. data/spec/daigaku/test_result_spec.rb +21 -25
  72. data/spec/daigaku/test_spec.rb +11 -12
  73. data/spec/daigaku/unit_spec.rb +24 -27
  74. data/spec/daigaku/views/chapters_menu_spec.rb +0 -1
  75. data/spec/daigaku/views/courses_menu_spec.rb +0 -1
  76. data/spec/daigaku/views/menu_spec.rb +1 -2
  77. data/spec/daigaku/views/task_view_spec.rb +0 -2
  78. data/spec/daigaku/views/units_menu_spec.rb +0 -1
  79. data/spec/daigaku/views_spec.rb +0 -1
  80. data/spec/daigaku_spec.rb +9 -12
  81. data/spec/path_helpers_spec.rb +11 -12
  82. data/spec/resource_helpers_spec.rb +11 -12
  83. data/spec/spec_helper.rb +3 -4
  84. data/spec/support/macros/content_helpers.rb +16 -17
  85. data/spec/support/macros/mock_helpers.rb +6 -6
  86. data/spec/support/macros/path_helpers.rb +15 -15
  87. data/spec/support/macros/resource_helpers.rb +34 -35
  88. metadata +32 -44
@@ -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 = solution_path(unit_path)
11
- @code = File.read(@path).strip if File.file?(@path)
12
- @verified = get_store_state
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
- result = Daigaku::Test.new(@unit_path).run(self.code)
17
- set_store_state(result.passed?)
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 = path.split('/')[-3..-1].join('/').gsub(FILE_SUFFIX, '')
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 = Daigaku.config.solutions_path
38
- sub_dirs = Storeable.key(path.split('/')[-3..-2].join('/').gsub(FILE_SUFFIX, ''))
39
- file = Storeable.key(File.basename(path)) + FILE_SUFFIX
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
- File.join(local_path, sub_dirs, file)
48
+ def directory_from(path)
49
+ path.split('/')[-3..-2].join('/').gsub(FILE_SUFFIX, '')
42
50
  end
43
51
 
44
- def set_store_state(verified)
52
+ def store_state=(verified)
45
53
  @verified = verified
46
54
  QuickStore.store.set(store_key, verified?)
47
55
  end
48
56
 
49
- def get_store_state
57
+ def store_state
50
58
  QuickStore.store.get(store_key)
51
59
  end
52
60
  end
53
- end
61
+ end
@@ -1,15 +1,14 @@
1
1
  module Daigaku
2
2
  module Storeable
3
-
4
3
  LEADING_NUMBERS = /^\d+[\_\-\s]+/
5
- PART_JOINTS = /[\_\-\s]+/
4
+ PART_JOINTS = /[\_\-\s]+/
6
5
 
7
6
  class << self
8
7
  def key(text, options = {})
9
- separator = QuickStore.config.key_separator
10
- prefix = options[:prefix]
11
- suffix = clean(options[:suffix])
12
- suffixes = options[: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
- parts = text.to_s.split(QuickStore.config.key_separator).map do |key|
23
- key.gsub(LEADING_NUMBERS, '').gsub(PART_JOINTS, '_').downcase
24
- end
20
+ return if text.nil?
21
+ parts(text.to_s).join(QuickStore.config.key_separator)
22
+ end
25
23
 
26
- parts.join(QuickStore.config.key_separator)
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
@@ -3,7 +3,7 @@ module Daigaku
3
3
  attr_reader :markdown, :path
4
4
 
5
5
  def initialize(path)
6
- @path = Dir[File.join(path, '*.md')].first
6
+ @path = Dir[File.join(path, '*.md')].first
7
7
  @markdown = File.read(@path).strip
8
8
  end
9
9
  end
@@ -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 = File.join(texts_path, "#{file_name.to_s}.txt")
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
@@ -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 = Daigaku.config.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 =~ /\A#{URI::regexp(['http', 'https'])}\z/)
26
- github = use_initial_course || options[:github] || url.match(/github\.com/)
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) =~ /\.zip/
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(/github.com\/(.*)\/archive\/master.zip/).captures.first
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 => e
53
+ rescue Download::NoUrlError
50
54
  print_download_warning(url, "\"#{url}\" is not a valid URL!")
51
- rescue Download::NoZipFileUrlError => e
55
+ rescue Download::NoZipFileUrlError
52
56
  print_download_warning(url, "\"#{url}\" is not a URL of a *.zip file!")
53
- rescue Exception => e
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 = (user_and_repo ||= Daigaku.config.initial_course).split('/')
128
- author = parts.first
129
- course = parts.second
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
- 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
7
+ private
18
8
 
19
- def empty_line
20
- $stdout.puts ''
21
- end
9
+ def say(text)
10
+ output = text.split("\n").map { |line| "\t#{line}" }.join("\n")
11
+ $stdout.puts output
12
+ end
22
13
 
23
- def get(string)
24
- $stdout.print "\n\t#{string} "
25
- $stdin.gets.strip
26
- end
14
+ def empty_line
15
+ $stdout.puts ''
16
+ end
27
17
 
28
- def say_info(text)
29
- say_box(text, ' ℹ', :light_blue)
30
- end
18
+ def get(string)
19
+ $stdout.print "\n\t#{string} "
20
+ $stdin.gets.strip
21
+ end
31
22
 
32
- def say_warning(text)
33
- say_box(text, ' ', :light_red)
34
- end
23
+ def say_info(text)
24
+ say_box(text, ' ', :light_blue)
25
+ end
35
26
 
36
- def say_box(text, symbol, color)
37
- empty_line
38
- say line.send(color)
39
- empty_line
27
+ def say_warning(text)
28
+ say_box(text, '⚠ ', :light_red)
29
+ end
40
30
 
41
- indented_text = text.split("\n").join("\n#{' ' * (symbol.length + 1)}")
42
- say indented_text.prepend("#{symbol} ").send(color)
31
+ def say_box(text, symbol, color)
32
+ empty_line
33
+ say line.send(color)
34
+ empty_line
43
35
 
44
- empty_line
45
- say line.send(color)
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
- def line(symbol = '-')
50
- symbol * 70
51
- end
39
+ empty_line
40
+ say line.send(color)
41
+ empty_line
42
+ end
52
43
 
53
- def get_command(command, description)
54
- say description
44
+ def line(symbol = '-')
45
+ symbol * 70
46
+ end
55
47
 
56
- loop do
57
- cmd = get '>'
48
+ def get_command(command, description)
49
+ say description
58
50
 
59
- unless cmd == command
60
- say "This was something else. Try \"#{command}\"."
61
- next
62
- end
51
+ loop do
52
+ cmd = get '>'
63
53
 
64
- system cmd
65
- break
54
+ unless cmd == command
55
+ say "This was something else. Try \"#{command}\"."
56
+ next
66
57
  end
67
- end
68
58
 
69
- def get_confirm(description)
70
- say_warning description
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("#{path}", Dir.pwd)
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.downcase == 'yes'
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 "Please specify options when using this command!"
65
- say %x{ daigaku setup help set }
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 = Daigaku::Configuration::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 = Daigaku::Configuration::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
- "Daigaku created/updated following two paths for you:",
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
- 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
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