mentor 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ae3511e3b6d5760ea27f80381bec9c917f3c49fe
4
+ data.tar.gz: 0ce7c1629a9530f2e4b2f8435f331c20cc521dbb
5
+ SHA512:
6
+ metadata.gz: ecc7a4768849f6cef68ea37a90b805ece2db7e01bfd401e4968077169c6bd89c70eabc61958494e5b6e0ce2603bd7fa530154eaad5587c204e0ce02b1662ad6c
7
+ data.tar.gz: df14fccab379150b55c91f6298eac72e9d9adcfb10098fdd71996f8ca6f0efb26e0bfeba346b2258008c99105baae0bda994748fbc7e444814b32f7f823094c4
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ /Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,3 @@
1
+ guard :minitest do
2
+ watch(/lib|test|\./) { 'test/mentor_test.rb' }
3
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Sean Lerner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # Mentor
2
+
3
+ Mentor is a command line application for new Ruby developers. It gives descriptive error messages and where possible provides suggestions on how to best fix errors.
4
+
5
+ Instead of using Ruby to run your ruby programs like so:
6
+
7
+ ```
8
+ ruby my_ruby_program.rb
9
+ ```
10
+
11
+ Use Mentor:
12
+
13
+ ```
14
+ mentor my_ruby_program.rb
15
+ ```
16
+
17
+ ## Installation
18
+
19
+ From your command line, type:
20
+
21
+ ```
22
+ gem install mentor
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ From your command line, type:
28
+
29
+ ```
30
+ mentor my_ruby_program.rb
31
+ ```
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitLab at https://gitlab.com/seanlerner/mentor.
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
40
+
41
+ ## Credits
42
+
43
+ Mentor was created by Sean Lerner.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'rake/testtask'
2
+
3
+ task default: :test
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.pattern = 'test/*_test.rb'
7
+ end
data/bin/mentor ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'mentor'
3
+ Mentor.enable
4
+ require_relative Dir.pwd + '/' + ARGV.first
@@ -0,0 +1,52 @@
1
+ module Mentor
2
+
3
+ class MentorError < StandardError
4
+
5
+ include Outputs, Colorize
6
+
7
+ def self.find
8
+ error_classes.find(&:can_handle?)
9
+ end
10
+
11
+ def self.can_handle?
12
+ Mentor.tp.raised_exception.class == RuntimeError
13
+ end
14
+
15
+ def output
16
+ puts
17
+ sections_formatted.each do |section|
18
+ puts section
19
+ puts
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def self.error_classes
26
+ [
27
+ NoMethodDidYouMeanSuggestionError,
28
+ MentorNoMethodError,
29
+ MentorError
30
+ ]
31
+ end
32
+
33
+ def sections
34
+ [
35
+ Header.new,
36
+ RubyErrorComplete.new,
37
+ RelativePath.new,
38
+ LinesOfCode.new
39
+ ]
40
+ end
41
+
42
+ def sections_formatted
43
+ sections.map do |section|
44
+ section.lines.map do |line|
45
+ colorize line
46
+ end
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,24 @@
1
+ module Mentor
2
+
3
+ class MentorNoMethodError < MentorError
4
+
5
+ extend Outputs, Colorize
6
+
7
+ def self.can_handle?
8
+ Mentor.tp.raised_exception.class == NoMethodError
9
+ end
10
+
11
+ private
12
+
13
+ def sections
14
+ [
15
+ Header.new,
16
+ RubyErrorComplete.new,
17
+ RelativePath.new,
18
+ LinesOfCode.new
19
+ ]
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,32 @@
1
+ module Mentor
2
+
3
+ class NoMethodDidYouMeanSuggestionError < MentorNoMethodError
4
+
5
+ def self.can_handle?
6
+ super && Mentor.tp.raised_exception&.corrections.any?
7
+ end
8
+
9
+ def sections
10
+ [
11
+ Header.new,
12
+ RubyErrorComplete.new,
13
+ RelativePath.new,
14
+ LinesOfCode.new,
15
+ ErrorClassSpecificHelp.new(error_class_specific_help),
16
+ Suggestion.new("Try changing the method #{method_name} to #{did_you_mean_word} on #{var_for_method}.")
17
+ ]
18
+ end
19
+
20
+ private
21
+
22
+ def error_class_specific_help
23
+ [
24
+ "#{var_for_method} does not have the method #{method_name}.",
25
+ '',
26
+ 'You may have made a typo.'
27
+ ]
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,156 @@
1
+ module Mentor
2
+
3
+ module Colorize
4
+
5
+ def colorize(line)
6
+
7
+ if line_of_code?(line)
8
+ line = syntax_highlight(line)
9
+ elsif backtrace?(line)
10
+ return rainbow(line, :backtrace_line)
11
+ end
12
+
13
+ apply_colors(line)
14
+ end
15
+
16
+ def colorize_section
17
+ @lines.map! do |line|
18
+ line = rainbow(line, :suggestion)
19
+ colorize(line)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def backtrace?(line)
26
+ line['from ']
27
+ end
28
+
29
+ def line_of_code?(line)
30
+ line[padded_lineno_with_colon_regex]
31
+ end
32
+
33
+ def apply_colors(line)
34
+
35
+ line = color_pattern(line, error_lineno, "#{file_name}:#{error_lineno}", :error_lineno)
36
+ line = color_pattern(line, ruby_error_class, "(#{ruby_error_class})", :ruby_error_class)
37
+ line = color_pattern(line, ruby_error_class, " #{ruby_error_class} ", :ruby_error_class)
38
+
39
+ output_types = %i(
40
+ did_you_mean_text did_you_mean_word
41
+ message horizontal_line ruby_error_text
42
+ calling_method method_class method_name var_for_method
43
+ absolute_base_dir relative_base_dir app_dir file_name
44
+ )
45
+
46
+ output_types.reduce(line) do |line, output_type|
47
+ color_text(line, send(output_type), output_type)
48
+ end
49
+
50
+ end
51
+
52
+ def syntax_highlight(line)
53
+ padded_lineno = line[padded_lineno_with_colon_regex]
54
+ code = line[padded_lineno.size..-1]
55
+ color_padded_lineno(padded_lineno) + color_code(code)
56
+ end
57
+
58
+ def color_code(code)
59
+ formatter = Rouge::Formatters::Terminal256.new
60
+ lexer = Rouge::Lexers::Ruby.new
61
+ formatter.format(lexer.lex(code))
62
+ end
63
+
64
+ def color_padded_lineno(padded_lineno)
65
+ text_to_color = padded_lineno[padded_lineno_regex]
66
+ color = text_to_color['=>'] ? :error_lineno : :subtle
67
+ color_text(padded_lineno, text_to_color, color)
68
+ end
69
+
70
+ def color_pattern(full_text, text_to_color, match_pattern, output_type)
71
+ full_text.gsub(match_pattern, color_text(match_pattern, text_to_color, output_type))
72
+ end
73
+
74
+ def color_text(full_text, text_to_color, output_type)
75
+
76
+ return full_text unless text_to_color && text_to_color.size > 0
77
+
78
+ colorized_text = rainbow(text_to_color, output_type)
79
+ remaining_text = full_text
80
+ text_processed = ''
81
+
82
+ while remaining_text[text_to_color]
83
+ before_text = before_text_to_color(remaining_text, text_to_color)
84
+ reset_color = get_reset_color(before_text).to_s
85
+ text_processed += before_text + colorized_text
86
+ index_after_text_processed = (before_text + text_to_color).size
87
+ remaining_text = reset_color + remaining_text[index_after_text_processed..-1].to_s
88
+ end
89
+
90
+ text_processed + remaining_text
91
+
92
+ end
93
+
94
+ def before_text_to_color(remaining_text, text_to_color)
95
+ index = remaining_text.index(text_to_color)
96
+ return '' if !index || index == 0
97
+ remaining_text[0..index - 1]
98
+ end
99
+
100
+ def get_reset_color(before_text)
101
+ index_of_color_to_reset_to = before_text.rindex(color_regex)
102
+ before_text[index_of_color_to_reset_to..-1][color_regex] if index_of_color_to_reset_to
103
+ end
104
+
105
+ def color_regex
106
+ /\e\[(\d|;)+m/
107
+ end
108
+
109
+ def padded_lineno_regex
110
+ /^\s+(=>)?\s\d+/
111
+ end
112
+
113
+ def padded_lineno_with_colon_regex
114
+ /^\s+(=>)?\s\d+:/
115
+ end
116
+
117
+ def rainbow(str, output_type)
118
+ Rainbow(str).color(colors[output_type])
119
+ end
120
+
121
+ def colors
122
+ error = :tomato
123
+ error_lineno = :gold
124
+ method_name = :deepskyblue
125
+ subtle = :olive
126
+ very_subtle = :dimgray
127
+
128
+ {
129
+ app_dir: subtle,
130
+ backtrace_line: very_subtle,
131
+ absolute_base_dir: subtle,
132
+ relative_base_dir: subtle,
133
+ calling_method: :green,
134
+ did_you_mean_text: :royalblue,
135
+ did_you_mean_word: method_name,
136
+ error_lineno: error_lineno,
137
+ error_lineno_padded: error_lineno,
138
+ file_name: :greenyellow,
139
+ horizontal_line: :red,
140
+ lineno_subtle_padded: subtle,
141
+ message: error,
142
+ method_class: :mediumpurple,
143
+ method_name: method_name,
144
+ other_class_or_module: :darksalmon,
145
+ prominent: :ivory,
146
+ ruby_error_class: :orangered,
147
+ ruby_error_text: error,
148
+ subtle: subtle,
149
+ suggestion: :lightgreen,
150
+ var_for_method: :palevioletred
151
+ }
152
+ end
153
+
154
+ end
155
+
156
+ end
@@ -0,0 +1,27 @@
1
+ module Mentor
2
+
3
+ module Globals
4
+
5
+ DIR = __dir__ + '/../..'
6
+
7
+ attr_accessor :cancel_exit_after_error, :tp
8
+
9
+ def cancel_exit_after_error?
10
+ @cancel_exit_after_error
11
+ end
12
+
13
+ def enable
14
+ @enabled = true
15
+ end
16
+
17
+ def disable
18
+ @enabled = false
19
+ end
20
+
21
+ def enabled?
22
+ @enabled == true
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,37 @@
1
+ module Mentor
2
+
3
+ module OutputHelper
4
+
5
+ def home_to_tilde(path)
6
+ path.sub(ENV['HOME'], '~')
7
+ end
8
+
9
+ def indent_lines(*lines, indent: 2)
10
+ lines.flatten!
11
+
12
+ indent.downto(1).each do |number_of_spaces|
13
+ if lines.all? { |line| line.size + indent * 2 <= terminal_width }
14
+ return lines.map { |line| ' ' * number_of_spaces + line }
15
+ end
16
+ end
17
+
18
+ lines
19
+ end
20
+
21
+ def lines_from_file
22
+ return @lines_from_file if @lines_from_file
23
+ file = File.new(Mentor.tp.path)
24
+ @lines_from_file = file.map { |line| [file.lineno, line.chomp] }.to_h
25
+ end
26
+
27
+ def terminal_width
28
+ `tput cols`.to_i
29
+ end
30
+
31
+ def valid_var_name
32
+ /(@@|@|\$)?[a-zA-Z][a-zA-Z_0-9]*/
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -0,0 +1,140 @@
1
+ module Mentor
2
+
3
+ module Outputs
4
+
5
+ include OutputHelper
6
+
7
+ def absolute_base_dir
8
+ Dir.pwd + '/'
9
+ end
10
+
11
+ def app_dir
12
+ Mentor.tp.path. # Full path
13
+ sub(absolute_base_dir, ''). # Remove path up until this file
14
+ split('/')[0..-2]. # Split path into parts and exclude filename
15
+ join('/') + # Join with '/'
16
+ '/' # Add one more '/' to end of it
17
+ end
18
+
19
+ def backtrace_lines
20
+
21
+ # Mentor.tp.raised_exception.backtrace disappears after accessing it once
22
+ # So we just do it once and then save the result to @@backtrace_lines
23
+ if self.class.class_variable_defined?(:@@backtrace_lines)
24
+ return @@backtrace_lines
25
+ end
26
+
27
+ bt_lines = Mentor.tp.raised_exception.
28
+ backtrace[1..-1]. # Remove first
29
+ grep_v(/bin\/mentor/). # Remove mentor involvement
30
+ map { |line| line.sub!(Dir.pwd + '/', '') }. # Remove path before current dir
31
+ map { |line| 'from ' + line } # Add 'from'
32
+
33
+ return bt_lines if bt_lines.empty?
34
+
35
+ # Make it appear so backtrace came from `<main>' instead of being required by mentor
36
+ bt_lines[-1].gsub!("`<top (required)>'", "`<main>'")
37
+
38
+ @@backtrace_lines = bt_lines
39
+
40
+ end
41
+
42
+ def calling_method
43
+ (Mentor.tp.method_id || '<main>').to_s
44
+ end
45
+
46
+ def did_you_mean_word
47
+ if Mentor.tp.raised_exception.respond_to? :corrections
48
+ Mentor.tp.raised_exception.corrections.first&.to_s
49
+ end
50
+ end
51
+
52
+ def did_you_mean_text
53
+ 'Did you mean?'
54
+ end
55
+
56
+ def error_lineno_padded(width)
57
+ "=> #{@lineno}".rjust(width)
58
+ end
59
+
60
+ def error_lineno
61
+ Mentor.tp.lineno.to_s
62
+ end
63
+
64
+ def file_name
65
+ Mentor.tp.path.split('/').last
66
+ end
67
+
68
+ def horizontal_line
69
+ '─' * terminal_width
70
+ end
71
+
72
+ def instance_methods
73
+ Mentor.tp.raised_exception.receiver.class.instance_methods -
74
+ Mentor.tp.raised_exception.receiver.class.superclass.instance_methods
75
+ end
76
+
77
+ def lineno_subtle_padded(width)
78
+ @lineno.to_s.rjust(width)
79
+ end
80
+
81
+ def message
82
+ if Mentor.tp.raised_exception.respond_to? :original_message
83
+ Mentor.tp.raised_exception.original_message
84
+ else
85
+ Mentor.tp.raised_exception.message
86
+ end
87
+ end
88
+
89
+ def method_name
90
+ if Mentor.tp.raised_exception.respond_to? :spell_checker
91
+ if Mentor.tp.raised_exception.spell_checker.respond_to?(:method_name)
92
+ Mentor.tp.raised_exception.spell_checker.method_name.to_s
93
+ else
94
+ raise "cannot determine method name"
95
+ end
96
+ end
97
+ end
98
+
99
+ def method_class
100
+ if Mentor.tp.raised_exception.respond_to? :receiver
101
+ Mentor.tp.raised_exception.receiver.class.to_s
102
+ end
103
+ end
104
+
105
+ def method_class_plural
106
+ pluralize method_class
107
+ end
108
+
109
+ def method_class_superclass
110
+ Mentor.tp.raised_exception.receiver.class.superclass
111
+ end
112
+
113
+ def method_class_superclasses
114
+ to_sentence(Mentor.tp.raised_exception.receiver.class.ancestors - typical_classes)
115
+ end
116
+
117
+ def relative_base_dir
118
+ home_to_tilde(absolute_base_dir)
119
+ end
120
+
121
+ def ruby_error_class
122
+ Mentor.tp.raised_exception.class.to_s
123
+ end
124
+
125
+ def ruby_error_text
126
+ 'Ruby Error:'
127
+ end
128
+
129
+ def var_for_method
130
+ culprit_line = lines_from_file[Mentor.tp.lineno]
131
+ var = culprit_line[/#{valid_var_name}\.#{method_name}/].to_s.chomp(".#{method_name}")
132
+ if var.empty?
133
+ culprit_line[/#{valid_var_name} method_name/].to_s.chomp(".#{method_name}")
134
+ end
135
+ var
136
+ end
137
+
138
+ end
139
+
140
+ end
@@ -0,0 +1,21 @@
1
+ module Mentor
2
+ gems = [
3
+ 'pry',
4
+ 'rainbow',
5
+ 'rouge',
6
+ ]
7
+
8
+ helpers = [
9
+ 'output_helper',
10
+ 'outputs',
11
+ 'colorize'
12
+ ].map { |file| Globals::DIR + '/lib/helpers/' + file }
13
+
14
+ sections = Dir.glob(Globals::DIR + '/lib/sections/*.rb')
15
+ errors = Dir.glob(Globals::DIR + '/lib/errors/*.rb')
16
+
17
+ (gems + helpers + sections + errors).each { |file| require file }
18
+
19
+ require_relative '../main'
20
+
21
+ end
data/lib/main.rb ADDED
@@ -0,0 +1,23 @@
1
+ module Mentor
2
+
3
+ class Main
4
+
5
+ def initialize
6
+
7
+ error_class = MentorError.find
8
+
9
+ return unless error_class
10
+
11
+ begin
12
+ error = error_class.new
13
+ raise error
14
+ rescue *MentorError.error_classes
15
+ error.output
16
+ exit 1 unless Mentor.cancel_exit_after_error?
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,3 @@
1
+ module Mentor
2
+ VERSION = '0.1.0'
3
+ end
data/lib/mentor.rb ADDED
@@ -0,0 +1,37 @@
1
+ require 'mentor/version'
2
+
3
+ module Mentor
4
+
5
+ class Init
6
+
7
+ def self.setup
8
+ require_relative 'helpers/globals'
9
+ Mentor.extend(Globals)
10
+ setup_trace_point
11
+ Mentor.enable
12
+ end
13
+
14
+ private
15
+
16
+ def self.setup_trace_point
17
+
18
+ TracePoint.trace(:raise) do |tp|
19
+ tp.disable
20
+
21
+ if Mentor.enabled?
22
+ Mentor.disable
23
+ require_relative 'helpers/requires'
24
+ Mentor.tp = tp
25
+ Main.new
26
+ end
27
+
28
+ tp.enable
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+
37
+ Mentor::Init.setup
@@ -0,0 +1,15 @@
1
+ module Mentor
2
+
3
+ class Backtrace
4
+
5
+ include Outputs
6
+
7
+ attr_reader :lines
8
+
9
+ def initialize
10
+ @lines = indent_lines(backtrace_lines, indent: 10)
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,20 @@
1
+ module Mentor
2
+
3
+ class DidYouMeanCorrection
4
+
5
+ include Outputs
6
+
7
+ attr_reader :line
8
+
9
+ def initialize
10
+ @line = "#{did_you_mean_text} #{did_you_mean_word}"
11
+ end
12
+
13
+ def self.can_handle?
14
+ Mentor.tp.raised_exception.respond_to?(:corrections) &&
15
+ Mentor.tp.raised_exception.corrections.any?
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,16 @@
1
+ module Mentor
2
+
3
+ class ErrorClassSpecificHelp
4
+
5
+ include Outputs, Colorize
6
+
7
+ attr_reader :lines
8
+
9
+ def initialize(lines)
10
+ @lines = indent_lines(lines)
11
+ colorize_section
12
+ end
13
+
14
+ end
15
+
16
+ end
@@ -0,0 +1,47 @@
1
+ module Mentor
2
+
3
+ class Header
4
+
5
+ include Outputs, Colorize
6
+
7
+ attr_reader :lines
8
+
9
+ def initialize
10
+ @lines = [
11
+ horizontal_line,
12
+ title_indented,
13
+ horizontal_line,
14
+ ]
15
+
16
+ colorize_section
17
+ end
18
+
19
+ private
20
+
21
+ def title_indented
22
+ if title_bar_spacer_size > 4
23
+ " #{ruby_error_text} #{ruby_error_class}#{title_bar_spacer_with_indent}#{file_name}:#{error_lineno}"
24
+ elsif title_bar_spacer_size > 2
25
+ "#{ruby_error_text} #{ruby_error_class}#{title_bar_spacer_without_indent}#{file_name}:#{error_lineno}"
26
+ else
27
+ "#{ruby_error_text} #{ruby_error_class}\n#{file_name}:#{error_lineno}"
28
+ end
29
+ end
30
+
31
+ def title_bar_spacer_size
32
+ start_of_line = ruby_error_text + ' ' + ruby_error_class
33
+ end_of_line = file_name + ':' + error_lineno
34
+ terminal_width - (start_of_line.length + end_of_line.length)
35
+ end
36
+
37
+ def title_bar_spacer_without_indent
38
+ ' ' * title_bar_spacer_size
39
+ end
40
+
41
+ def title_bar_spacer_with_indent
42
+ ' ' * (title_bar_spacer_size - 2)
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,30 @@
1
+ module Mentor
2
+
3
+ class LineOfCode < String
4
+
5
+ include Outputs
6
+
7
+ def initialize(lineno, line, max_length)
8
+ @lineno = lineno
9
+ @line = line
10
+ @max_length = max_length
11
+ super("#{padded_lineno}: #{@line}")
12
+ end
13
+
14
+ private
15
+
16
+ def padded_lineno
17
+ if error_lineno?
18
+ error_lineno_padded(@max_length)
19
+ else
20
+ lineno_subtle_padded(@max_length)
21
+ end
22
+ end
23
+
24
+ def error_lineno?
25
+ @lineno == Mentor.tp.lineno
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,70 @@
1
+ module Mentor
2
+
3
+ class LinesOfCode
4
+
5
+ include Outputs
6
+
7
+ attr_accessor :lines
8
+
9
+ def initialize
10
+ @lines = indent_lines(lines_of_code_from_method_or_file, indent: 4)
11
+ end
12
+
13
+ private
14
+
15
+ def lines_of_code_from_method_or_file
16
+ if calling_method_can_be_obtained?
17
+ set_first_and_last_from_method
18
+ else
19
+ set_first_and_last_from_file
20
+ end
21
+
22
+ lineno_max_length = "=> #{@last_lineno}".length
23
+
24
+ (@first_lineno..@last_lineno).map do |lineno|
25
+ LineOfCode.new(lineno, lines_from_file[lineno], lineno_max_length)
26
+ end
27
+ end
28
+
29
+ def calling_method_can_be_obtained?
30
+ calling_method != '<main>'
31
+ end
32
+
33
+ def set_first_and_last_from_method
34
+ method_text = Pry::Method.from_str("#{Mentor.tp.defined_class}##{calling_method}").source.split("\n")
35
+ @first_lineno = lines_from_file.select { |lineno, line| line["def #{calling_method}"] }.sort.last.first
36
+ @last_lineno = @first_lineno + method_text.size - 1
37
+ end
38
+
39
+ def set_first_and_last_from_file
40
+ @first_lineno = Mentor.tp.lineno
41
+ @first_lineno -= 1 until first_line_at_start_of_file? || first_line_empty? ||
42
+ line_prior_to_first_line_ends?
43
+ @last_lineno = Mentor.tp.lineno
44
+ @last_lineno += 1 until last_line_at_end_of_file? || last_line_empty?
45
+ end
46
+
47
+ def first_line_at_start_of_file?
48
+ @first_lineno == 1
49
+ end
50
+
51
+ def first_line_empty?
52
+ lines_from_file[@first_lineno].empty?
53
+ end
54
+
55
+ def line_prior_to_first_line_ends?
56
+ lines_from_file[@first_lineno - 1]['end']
57
+ end
58
+
59
+ def last_line_at_end_of_file?
60
+ @last_lineno == lines_from_file.size
61
+ end
62
+
63
+ def last_line_empty?
64
+ lines_from_file[@last_lineno].empty?
65
+ end
66
+
67
+ end
68
+
69
+ end
70
+
@@ -0,0 +1,15 @@
1
+ module Mentor
2
+
3
+ class RelativePath
4
+
5
+ include Outputs
6
+
7
+ attr_accessor :lines
8
+
9
+ def initialize
10
+ @lines = indent_lines(relative_base_dir + app_dir + file_name + ':' + error_lineno)
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,21 @@
1
+ module Mentor
2
+
3
+ class RubyErrorComplete
4
+
5
+ include Outputs, Colorize
6
+
7
+ attr_reader :lines
8
+
9
+ def initialize
10
+ @lines = []
11
+ @lines << RubyErrorMain.new.lines
12
+ @lines << DidYouMeanCorrection.new.line if DidYouMeanCorrection.can_handle?
13
+ @lines << Backtrace.new.lines
14
+ @lines = indent_lines(@lines)
15
+ @lines << ''
16
+ @lines << horizontal_line
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,77 @@
1
+ module Mentor
2
+
3
+ class RubyErrorMain
4
+
5
+ include Outputs
6
+
7
+ attr_reader :lines
8
+
9
+ def initialize
10
+ @lines = ruby_error_main
11
+ end
12
+
13
+ private
14
+
15
+ def single_line
16
+ parts = []
17
+ parts << path_and_lineno
18
+ parts << message
19
+ parts << bracketed_class
20
+ parts.flatten.join(' ')
21
+ end
22
+
23
+ def path_and_lineno
24
+ app_path_and_lineno = "#{app_dir}#{file_name}:#{error_lineno}:in `#{calling_method}':"
25
+
26
+ if backtrace_lines.any?
27
+ absolute_base_dir + app_path_and_lineno
28
+ else
29
+ app_path_and_lineno
30
+ end
31
+ end
32
+
33
+ def bracketed_class
34
+ "(#{ruby_error_class})"
35
+ end
36
+
37
+ def ruby_error_main
38
+ if single_line.size <= terminal_width
39
+ [single_line]
40
+ else
41
+ ruby_error_wrapped
42
+ end
43
+ end
44
+
45
+ def ruby_error_wrapped
46
+ if fit_on_one_or_two_lines?(path_and_lineno, message)
47
+ [path_and_lineno + ' ' + message, bracketed_class]
48
+
49
+ elsif fit_on_one_or_two_lines?(message, bracketed_class)
50
+ [path_and_lineno, message + ' ' + bracketed_class]
51
+
52
+ else
53
+ [path_and_lineno, message, bracketed_class]
54
+
55
+ end
56
+ end
57
+
58
+ def fit_on_one_or_two_lines?(part_a, part_b)
59
+ fit_on_one_line?(part_a, part_b) ||
60
+ does_not_fit_on_one_line?(part_a) && fit_on_two_lines?(part_a, part_b)
61
+ end
62
+
63
+ def fit_on_one_line?(part_a, part_b)
64
+ (part_a + ' ' + part_b).size <= terminal_width
65
+ end
66
+
67
+ def does_not_fit_on_one_line?(part)
68
+ part.size >= terminal_width
69
+ end
70
+
71
+ def fit_on_two_lines?(part_a, part_b)
72
+ (part_a + ' ' + part_b).size <= terminal_width * 2
73
+ end
74
+
75
+ end
76
+
77
+ end
@@ -0,0 +1,23 @@
1
+ module Mentor
2
+
3
+ class Suggestion
4
+
5
+ include Outputs, Colorize
6
+
7
+ attr_reader :lines
8
+
9
+ def initialize(suggestion = '')
10
+ @lines = suggestion
11
+ format_lines
12
+ end
13
+
14
+ private
15
+
16
+ def format_lines
17
+ @lines = indent_lines(@lines)
18
+ colorize_section
19
+ end
20
+
21
+ end
22
+
23
+ end
data/mentor.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mentor/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'mentor'
8
+ spec.version = Mentor::VERSION
9
+ spec.authors = ['Sean Lerner']
10
+ spec.email = ['sean@smallcity.ca']
11
+ spec.summary = 'Helpful error messages for new ruby programmers.'
12
+ spec.homepage = 'https://gitlab.com/seanlerner/mentor'
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
+ f.match(%r{^(test|spec|features)/})
17
+ end
18
+
19
+ spec.executables << 'mentor'
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_dependency 'pry', '~> 0.10'
23
+ spec.add_dependency 'rainbow', '~> 2.2'
24
+ spec.add_dependency 'rouge', '~> 2.0'
25
+
26
+ spec.add_development_dependency 'bundler', '~> 1.13'
27
+ spec.add_development_dependency 'guard', '~> 2.14'
28
+ spec.add_development_dependency 'guard-minitest', '~> 2.4'
29
+ spec.add_development_dependency 'minitest', '~> 5.0'
30
+ spec.add_development_dependency 'rake', '~> 10.0'
31
+ end
metadata ADDED
@@ -0,0 +1,186 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mentor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sean Lerner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-06-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pry
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.10'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rainbow
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rouge
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.13'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.13'
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.14'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.14'
83
+ - !ruby/object:Gem::Dependency
84
+ name: guard-minitest
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.4'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.4'
97
+ - !ruby/object:Gem::Dependency
98
+ name: minitest
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '5.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '5.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '10.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '10.0'
125
+ description:
126
+ email:
127
+ - sean@smallcity.ca
128
+ executables:
129
+ - mentor
130
+ extensions: []
131
+ extra_rdoc_files: []
132
+ files:
133
+ - ".gitignore"
134
+ - Gemfile
135
+ - Guardfile
136
+ - LICENSE.txt
137
+ - README.md
138
+ - Rakefile
139
+ - bin/mentor
140
+ - lib/errors/mentor_error.rb
141
+ - lib/errors/mentor_no_method_error.rb
142
+ - lib/errors/no_method_did_you_mean_suggestion_error.rb
143
+ - lib/helpers/colorize.rb
144
+ - lib/helpers/globals.rb
145
+ - lib/helpers/output_helper.rb
146
+ - lib/helpers/outputs.rb
147
+ - lib/helpers/requires.rb
148
+ - lib/main.rb
149
+ - lib/mentor.rb
150
+ - lib/mentor/version.rb
151
+ - lib/sections/backtrace.rb
152
+ - lib/sections/did_you_mean_correction.rb
153
+ - lib/sections/error_class_specific_help.rb
154
+ - lib/sections/header.rb
155
+ - lib/sections/line_of_code.rb
156
+ - lib/sections/lines_of_codes.rb
157
+ - lib/sections/relative_path.rb
158
+ - lib/sections/ruby_error_complete.rb
159
+ - lib/sections/ruby_error_main.rb
160
+ - lib/sections/suggestion.rb
161
+ - mentor.gemspec
162
+ homepage: https://gitlab.com/seanlerner/mentor
163
+ licenses:
164
+ - MIT
165
+ metadata: {}
166
+ post_install_message:
167
+ rdoc_options: []
168
+ require_paths:
169
+ - lib
170
+ required_ruby_version: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ required_rubygems_version: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ requirements: []
181
+ rubyforge_project:
182
+ rubygems_version: 2.6.11
183
+ signing_key:
184
+ specification_version: 4
185
+ summary: Helpful error messages for new ruby programmers.
186
+ test_files: []