ruby_friendly_error 0.0.1 → 0.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a41f6d667be9cadbacddde143ff345d2d145e6d0e2d5c0c42249eceb1918b2d
4
- data.tar.gz: 1901ec22809369187299906734c171be17ba0e3b4ff2e0a2597b151b1456638c
3
+ metadata.gz: 4f4b1bac560baef1e3575b2d65a0d0cd90c40f3daba2219b2d0b6dc6eef9b06e
4
+ data.tar.gz: d3baae55366188eb7df5d5cd9bbdadfc8273ac90e821cb31aa85404867277668
5
5
  SHA512:
6
- metadata.gz: 8d68f65c5654d7584b6a4231345ad41f2146e2f8f09358b446dafe85fa82627e5ec7e3ab150b0c3a4c3b8496b0849b5bde1d3c48cf81baf36bab28b387861722
7
- data.tar.gz: 565906dd0162aac5eb29094334519600354d527df8bc44d5811342ad5b1157fee14a9fed0a582aee73a4adbc354e388a1a22be4e1f153aa7822c403bee6bb6b7
6
+ metadata.gz: b47bafe16356f7ae7c875350f81c756a3f28cce4d7e58fdf0a7002b0a6688e7fc1824d562f5229fcf35f87441b083f1e18640a218d1ede235b2753ac30bc0df3
7
+ data.tar.gz: d5db46fa860dcaf072d0a9d726088222ce945e7ae10b6c26085d2ac62f94353df8a2a559361997997cd84274e1bf7f03ccdebc644c58ff5978cf074cc772b99b
data/.gitignore CHANGED
@@ -6,5 +6,7 @@
6
6
  /tmp/
7
7
  /vendor/
8
8
 
9
+ ruby_friendly_error-*.gem
10
+
9
11
  # rspec failure tracking
10
12
  .rspec_status
data/.rubocop_todo.yml CHANGED
@@ -1,3 +1,3 @@
1
- Metrics/ModuleLength:
1
+ Metrics/ClassLength:
2
2
  Exclude:
3
- - lib/ruby_friendly_error.rb
3
+ - lib/ruby_friendly_error/renderers/exception_renderer.rb
data/Gemfile.lock CHANGED
@@ -1,10 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby_friendly_error (0.0.1)
4
+ ruby_friendly_error (0.0.2)
5
5
  colorize (~> 0.8)
6
6
  i18n (~> 1.1)
7
- parser (~> 2.5.1)
7
+ parser (~> 2.5)
8
8
 
9
9
  GEM
10
10
  remote: https://rubygems.org/
data/README.adoc CHANGED
@@ -6,7 +6,7 @@
6
6
  :sectnums:
7
7
  :source-highlighter: highlightjs
8
8
  :toc: left
9
- :toclevels: 1
9
+ :toclevels: 2
10
10
 
11
11
  = RubyFriendlyError image:https://img.shields.io/badge/ruby-2.4.4-cc342d.svg["ruby 2.4.4", link="https://www.ruby-lang.org/en/news/2018/03/28/ruby-2-4-4-released/"] image:https://img.shields.io/badge/ruby-2.5.1-cc342d.svg["ruby 2.5.1", link="https://www.ruby-lang.org/en/news/2018/03/28/ruby-2-5-1-released/"]
12
12
 
@@ -24,6 +24,70 @@ gem 'ruby_friendly_error'
24
24
  $ bundle exec ruby_friendly_error your.rb
25
25
  ```
26
26
 
27
+ == Samples
28
+
29
+ === miss spell
30
+
31
+ [source,ruby]
32
+ .sample.rb
33
+ ----
34
+ # frozen_string_literal: true
35
+
36
+ def hoge prayer_life = 100 , player_lifee = 200
37
+ puts 'hoge' if player_life > 0
38
+ end
39
+
40
+ hoge
41
+ ----
42
+
43
+ ```sh
44
+ $ bundle exec ruby_friendly_error sample.rb
45
+ ```
46
+
47
+ image:https://raw.githubusercontent.com/isuke/ruby_friendly_error/images/name_error_with_did_you_mean.png["name_error_with_did_you_mean", caption="output"]
48
+
49
+ === miss args num
50
+
51
+ [source,ruby]
52
+ .sample.rb
53
+ ----
54
+ # frozen_string_literal: true
55
+
56
+ def hoge arg1, arg2 = 'foobar'
57
+ puts arg1
58
+ puts arg2
59
+ end
60
+
61
+ hoge 'piyo', 'fuga', 'what!?'
62
+ ----
63
+
64
+ ```sh
65
+ $ bundle exec ruby_friendly_error sample.rb
66
+ ```
67
+
68
+ image:https://raw.githubusercontent.com/isuke/ruby_friendly_error/images/wrong_number_of_arguments_error.png["wrong_number_of_arguments_error", caption="output"]
69
+
70
+ == Options
71
+
72
+ [cols="1,1,1", options="header"]
73
+ |===
74
+ | key
75
+ | description
76
+ | values
77
+
78
+ | RUBY_FRIENDLY_ERROR_LANG
79
+ | message language.
80
+ | en, ja
81
+ |===
82
+
83
+ === RUBY_FRIENDLY_ERROR_LANG
84
+
85
+ ```sh
86
+ $ RUBY_FRIENDLY_ERROR_LANG=ja bundle exec ruby_friendly_error sample.rb
87
+ ```
88
+
89
+ image:https://raw.githubusercontent.com/isuke/ruby_friendly_error/images/wrong_number_of_arguments_error_ja.png["wrong_number_of_arguments_error_ja", caption="output"]
90
+
27
91
  == Development
28
92
 
29
93
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -3,4 +3,4 @@
3
3
 
4
4
  require 'ruby_friendly_error'
5
5
 
6
- RubyFriendlyError.load ARGV[0]
6
+ RubyFriendlyError.load ARGV[0], ENV['RUBY_FRIENDLY_ERROR_LANG']&.to_sym
@@ -35,6 +35,20 @@ class Parser::AST::Node
35
35
  KEYWORD_REST_ARG_TYPE,
36
36
  ].freeze
37
37
 
38
+ REQURED_ARG_TYPES = [
39
+ ARG_TYPE,
40
+ ].freeze
41
+
42
+ OPTIONAL_ARG_TYPES = [
43
+ OPTION_ARG_TYPE,
44
+ KEYWORD_ARG_OPTION_TYPE,
45
+ ].freeze
46
+
47
+ DEF_TYPE = :def
48
+ SEND_TYPE = :send
49
+
50
+ NOT_HAVE_LINE_TYPES = %i[args].freeze
51
+
38
52
  def find_by_variable_name variable_name
39
53
  to_a.each do |node|
40
54
  next unless node.is_a? Parser::AST::Node
@@ -42,9 +56,47 @@ class Parser::AST::Node
42
56
  return node if node.to_a[0].to_s.sub(/^@*/, '') == variable_name.to_s
43
57
  end
44
58
 
45
- result = node.find_by_variable_name(variable_name)
59
+ result = node.find_by_variable_name variable_name
60
+ return result if result
61
+ end
62
+ nil
63
+ end
64
+
65
+ def find_by_line_number line_number
66
+ to_a.each do |node|
67
+ next unless node.is_a? Parser::AST::Node
68
+ next if NOT_HAVE_LINE_TYPES.include? node.type
69
+ return node if node.location.line == line_number
70
+ result = node.find_by_line_number line_number
71
+ return result if result
72
+ end
73
+ nil
74
+ end
75
+
76
+ def find_send_by_def_name def_name
77
+ to_a.each do |node|
78
+ next unless node.is_a? Parser::AST::Node
79
+
80
+ return node if node.type == SEND_TYPE && node.to_a[1].to_s == def_name.to_s
81
+
82
+ result = node.find_send_by_def_name def_name
46
83
  return result if result
47
84
  end
48
85
  nil
49
86
  end
87
+
88
+ def def_args_num_range
89
+ return nil unless type == DEF_TYPE
90
+
91
+ required_arg_num = to_a[1].to_a.select { |n| REQURED_ARG_TYPES.include? n.type }.size
92
+ optionarl_arg_num = to_a[1].to_a.select { |n| OPTIONAL_ARG_TYPES.include? n.type }.size
93
+
94
+ (required_arg_num..(required_arg_num + optionarl_arg_num))
95
+ end
96
+
97
+ def send_args_num
98
+ return nil unless type == SEND_TYPE
99
+
100
+ to_a.size - 2
101
+ end
50
102
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyFriendlyError::Renderers
4
+ class ArgumentErrorRenderer < StandardErrorRenderer
5
+ private
6
+
7
+ def exception_i18n_name
8
+ I18n.t 'standard_error.argument_error.title'
9
+ end
10
+
11
+ def def_node
12
+ @def_node ||= ast.find_by_line_number error_line_number
13
+ end
14
+
15
+ def def_name
16
+ @def_name ||= def_node.to_a[0]
17
+ end
18
+
19
+ def send_node
20
+ @send_node ||= ast.find_send_by_def_name def_name
21
+ end
22
+
23
+ def error_message
24
+ args_num_range = def_node.def_args_num_range
25
+
26
+ args_num_str = args_num_range.size == 1 ? args_num_range.first.to_s : args_num_range.to_s
27
+
28
+ I18n.t 'standard_error.argument_error.wrong_number_of_arguments', def_name: def_name, arg_range: args_num_str, send_arg: send_node.send_args_num
29
+ end
30
+
31
+ def display_error_detail
32
+ display_error_line_string def_node.location.line, strong_pos_range: ((def_node.location.column + 1)..def_node.to_a[1].location.last_column)
33
+
34
+ display_error_line_string send_node.location.line, strong_pos_range: ((send_node.location.column + 1)..send_node.location.last_column)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyFriendlyError::Renderers
4
+ class ExceptionRenderer
5
+ attr_reader :exception
6
+ attr_reader :eval_file_content
7
+
8
+ def initialize exception, eval_file_content
9
+ @exception = exception
10
+ @eval_file_content = eval_file_content
11
+ end
12
+
13
+ def render
14
+ display_backtrace exception.backtrace unless eval?
15
+
16
+ display_main_message
17
+
18
+ display_error_detail
19
+
20
+ display_message
21
+ end
22
+
23
+ private
24
+
25
+ def file_name
26
+ @file_name ||= File.basename(file_path)
27
+ end
28
+
29
+ def file_path
30
+ raise NotImplementedError, "You must implement #{__method__}"
31
+ end
32
+
33
+ def file_expand_path
34
+ @file_expand_path ||= File.expand_path(file_path)
35
+ end
36
+
37
+ def file_content
38
+ raise NotImplementedError, "You must implement #{__method__}"
39
+ end
40
+
41
+ def eval?
42
+ file_name == '(eval)'
43
+ end
44
+
45
+ def exception_i18n_name
46
+ raise NotImplementedError, "You must implement #{__method__}"
47
+ end
48
+
49
+ def error_line_number
50
+ raise NotImplementedError, "You must implement #{__method__}"
51
+ end
52
+
53
+ def error_message
54
+ raise NotImplementedError, "You must implement #{__method__}"
55
+ end
56
+
57
+ def display_backtrace backtrace
58
+ fake_backtrace = backtrace
59
+ .dup
60
+ .delete_if { |b| b.match(/ruby_friendly_error/) }
61
+ .delete_if { |b| b.match(/`block in exec'/) }
62
+ .map do |b|
63
+ if b.match? Regexp.new("#{@file_name}.*`exec'")
64
+ b.sub("`exec'", "`<main>'")
65
+ else
66
+ b
67
+ end
68
+ end
69
+
70
+ fake_backtrace_size = fake_backtrace.size
71
+ fake_backtrace_digits = fake_backtrace_size.to_s.length
72
+
73
+ fake_backtrace.reverse_each.with_index do |b, i|
74
+ num = fake_backtrace_size - i
75
+ num_digits = num.to_s.length
76
+ color = b.include?(@file_name) ? :light_red : :light_yellow
77
+ STDERR.puts "#{' ' * (fake_backtrace_digits - num_digits)}#{num}: #{b}".colorize(color)
78
+ end
79
+ STDERR.puts
80
+ end
81
+
82
+ def display_main_message
83
+ STDERR.puts "#{exception_i18n_name.underline}#{I18n.t('main_message')}: #{file_path}:#{error_line_number}".colorize(:light_red)
84
+ STDERR.puts
85
+ end
86
+
87
+ def display_error_detail
88
+ raise NotImplementedError, "You must implement #{__method__}"
89
+ end
90
+
91
+ def display_message
92
+ STDERR.puts "#{exception_i18n_name.underline}:".colorize(:light_red)
93
+ STDERR.puts format_line_strings(error_message) { |l, _i| " #{l}" }.colorize(:light_red)
94
+ end
95
+
96
+ def display_error_line_string line_number = error_line_number, strong_pos_range: nil, window: RubyFriendlyError::WINDOW
97
+ line_strings = file_content.split("\n", -1)
98
+ file_line_size = line_strings.size
99
+ window_start = [line_number - window, 1].max
100
+ window_end = [line_number + window, file_line_size].min
101
+
102
+ STDERR.puts RubyFriendlyError::DISPLAY_START.colorize(:light_yellow)
103
+
104
+ display_error_line_string_window line_strings, window_start, line_number - 1 if line_number > 1
105
+
106
+ lint_string =
107
+ if strong_pos_range
108
+ RubyFriendlyError::Utils.replace_string_at(line_strings[line_number - 1], strong_pos_range, &:underline)
109
+ else
110
+ line_strings[line_number - 1]
111
+ end
112
+ STDERR.puts "#{line_number}: #{lint_string}".rstrip.colorize(:light_red)
113
+
114
+ display_error_line_string_window line_strings, line_number + 1, window_end if line_number < file_line_size
115
+
116
+ STDERR.puts RubyFriendlyError::DISPLAY_END.colorize(:light_yellow)
117
+ STDERR.puts
118
+ end
119
+
120
+ def display_error_line_string_window line_strings, start_line_number, end_line_number
121
+ STDERR.puts format_line_strings(line_strings, start_line_number, end_line_number) { |l, i| "#{start_line_number + i}: #{l}" }&.colorize(:light_yellow)
122
+ end
123
+
124
+ def format_line_strings line_strings, start_line = 1, end_line = 0
125
+ line_strings =
126
+ case line_strings
127
+ when String then line_strings.split("\n", -1)
128
+ when Array then line_strings
129
+ end
130
+ line_strings[(start_line - 1)..(end_line - 1)]
131
+ &.map
132
+ &.with_index { |l, i| block_given? ? yield(l, i).rstrip : l.rstrip }
133
+ &.join("\n")
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyFriendlyError::Renderers
4
+ class MissingEndErrorRenderer < SyntaxErrorRenderer
5
+ private
6
+
7
+ def error_message
8
+ I18n.t('syntax_error.missing_end')
9
+ end
10
+
11
+ def display_error_detail
12
+ display_error_line_string
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyFriendlyError::Renderers
4
+ class NameEndErrorRenderer < StandardErrorRenderer
5
+ private
6
+
7
+ def exception_i18n_name
8
+ I18n.t 'standard_error.name_error.title'
9
+ end
10
+
11
+ def corrections
12
+ @corrections ||= exception.spell_checker.corrections
13
+ end
14
+
15
+ def error_message
16
+ var_name_str = exception.spell_checker.name.underline
17
+ corrections_str = corrections.map { |c| "`#{c.to_s.underline}`" }.join(', ')
18
+ I18n.t('standard_error.name_error.with_did_you_mean', var_name: var_name_str, corrections: corrections_str)
19
+ end
20
+
21
+ def display_error_detail
22
+ corrections.each do |var_name|
23
+ node_location = ast.find_by_variable_name(var_name).location
24
+ display_error_line_string node_location.line, strong_pos_range: ((node_location.column + 1)..node_location.last_column)
25
+ end
26
+
27
+ display_error_line_string strong_pos_range: ((error_node.location.column + 1)..error_node.location.last_column)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyFriendlyError::Renderers
4
+ class StandardErrorRenderer < ExceptionRenderer
5
+ private
6
+
7
+ def ast
8
+ @ast ||= RubyFriendlyError::Utils.suppress_error_display { Parser::CurrentRuby.parse file_content }
9
+ end
10
+
11
+ def error_node
12
+ @error_node ||= ast.find_by_line_number error_line_number
13
+ end
14
+
15
+ def file_path
16
+ @file_path ||= exception.backtrace.first.match(/(.+):[0-9]+:/)[1]
17
+ end
18
+
19
+ def file_content
20
+ @file_content ||= eval? ? eval_file_content : File.read(file_path)
21
+ end
22
+
23
+ def error_line_number
24
+ exception.backtrace.first.match(/:([0-9]+):/)[1].to_i
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyFriendlyError::Renderers
4
+ class SyntaxErrorRenderer < ExceptionRenderer
5
+ private
6
+
7
+ def file_path
8
+ @file_path ||= exception.message.match(/(.+):[0-9]+:/)[1]
9
+ end
10
+
11
+ def file_content
12
+ @file_content ||= eval? ? eval_file_content : File.read(file_path)
13
+ end
14
+
15
+ def error_line_number
16
+ exception.message.match(/:([0-9]+):/)[1].to_i
17
+ end
18
+
19
+ def exception_i18n_name
20
+ I18n.t 'syntax_error.title'
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyFriendlyError::Renderers
4
+ class UnnecessaryEndErrorRenderer < SyntaxErrorRenderer
5
+ private
6
+
7
+ def error_message
8
+ I18n.t('syntax_error.unnecessary_end')
9
+ end
10
+
11
+ def display_error_detail
12
+ display_error_line_string
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyFriendlyError
4
+ class Utils
5
+ class << self
6
+ def suppress_error_display
7
+ original_stdout = $stderr
8
+ $stderr = StringIO.new
9
+
10
+ yield
11
+ ensure
12
+ $stderr = original_stdout
13
+ end
14
+
15
+ def replace_string_at string, range
16
+ regexp = Regexp.new "\\A(.{#{range.first - 1}})(.{#{range.size}})(.*)\\Z"
17
+ match_data = string.match regexp
18
+ replaced_string = yield match_data[2]
19
+ match_data[1] + replaced_string + match_data[3]
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyFriendlyError
4
- VERSION = '0.0.1'
4
+ VERSION = '0.0.2'
5
5
  end
@@ -5,8 +5,17 @@ require 'i18n'
5
5
  require 'parser/current'
6
6
 
7
7
  require 'ruby_friendly_error/ast'
8
+ require 'ruby_friendly_error/utils'
8
9
  require 'ruby_friendly_error/version'
9
10
 
11
+ require 'ruby_friendly_error/renderers/exception_renderer'
12
+ require 'ruby_friendly_error/renderers/syntax_error_renderer'
13
+ require 'ruby_friendly_error/renderers/missing_end_error_renderer'
14
+ require 'ruby_friendly_error/renderers/unnecessary_end_error_renderer'
15
+ require 'ruby_friendly_error/renderers/standard_error_renderer'
16
+ require 'ruby_friendly_error/renderers/name_error_renderer'
17
+ require 'ruby_friendly_error/renderers/argument_error_renderer'
18
+
10
19
  Bundler.require(:development)
11
20
 
12
21
  Parser::Builders::Default.emit_lambda = true
@@ -15,138 +24,55 @@ Parser::Builders::Default.emit_encoding = true
15
24
  Parser::Builders::Default.emit_index = true
16
25
 
17
26
  module RubyFriendlyError
18
- ROOT_PATH = Pathname.new(__FILE__).dirname.parent.to_s
19
- WINDOW = 2
27
+ module Renderers; end
28
+
29
+ ROOT_PATH = Pathname.new(__FILE__).dirname.parent.to_s
30
+ WINDOW = 2
31
+ DISPLAY_START = '<' * 80
32
+ DISPLAY_END = '>' * 80
20
33
 
21
34
  I18n.load_path = Dir[File.join(ROOT_PATH, 'locales', '*.yml')]
22
35
  I18n.backend.load_translations
23
- I18n.backend.store_translations(:en , YAML.load_file(File.join(ROOT_PATH, 'locales', 'en.yml')))
24
36
 
25
37
  class << self
26
- def load file_path
27
- exec File.read(file_path), file_path
38
+ def load file_path, lang = :en
39
+ exec File.read(file_path), File.expand_path(file_path), lang
28
40
  end
29
41
 
30
- def exec file_content, file_name = '(eval)'
31
- eval file_content, nil, file_name # rubocop:disable Security/Eval
32
- rescue Exception => ex # rubocop:disable Lint/RescueException
33
- exception_file_name = file_name == '(eval)' ? file_name : ex.backtrace.first.match(/(.+):[0-9]+:/)[1]
34
- exception_file_content = file_name == '(eval)' ? file_content : File.read(exception_file_name)
35
- exception_handling ex, exception_file_name, exception_file_content, file_name != '(eval)'
42
+ def exec file_content, file_name = '(eval)', lang = :en
43
+ load_i18n lang
44
+
45
+ eval file_content, nil, file_name, 1 # rubocop:disable Security/Eval
46
+ rescue Exception => exception # rubocop:disable Lint/RescueException
47
+ renderer_class = renderer_class(exception)
48
+
49
+ raise exception unless renderer_class
50
+
51
+ renderer_class.new(exception, file_content).render
52
+ exit false
36
53
  end
37
54
 
38
55
  private
39
56
 
40
- def exception_handling exception, file_name, file_content, displayed_backtrace
41
- display_backtrace file_name, exception.backtrace if displayed_backtrace
57
+ def load_i18n lang
58
+ I18n.backend.store_translations(lang, YAML.load_file(File.join(ROOT_PATH, 'locales', "#{lang}.yml")))
59
+ I18n.locale = lang
60
+ end
42
61
 
62
+ def renderer_class exception
43
63
  case exception
44
64
  when SyntaxError
45
65
  case exception.message
46
66
  when /unnecessary `end`/
47
- render_unnecessary_end_error file_content, exception
67
+ RubyFriendlyError::Renderers::UnnecessaryEndErrorRenderer
48
68
  when /unexpected end-of-input/
49
- render_missing_end_error file_content, exception
69
+ RubyFriendlyError::Renderers::MissingEndErrorRenderer
50
70
  end
51
71
  when NameError
52
- ast = suppress_error_display { Parser::CurrentRuby.parse file_content }
53
- render_name_error_with_did_you_mean file_content, exception, ast
54
- end
55
-
56
- raise exception
57
- end
58
-
59
- def render_missing_end_error file_content, ex
60
- line = ex.message.match(/:([0-9]+):/)[1].to_i
61
- display_error_line file_content, line
62
- STDERR.puts I18n.t('syntax_error.title').colorize(:light_red) + ':'
63
- STDERR.puts format_lines(I18n.t('syntax_error.missing_end')) { |l, _i| " #{l}" }.colorize(:light_red)
64
- end
65
-
66
- def render_unnecessary_end_error file_content, ex
67
- line = ex.message.match(/:([0-9]+):/)[1].to_i
68
- display_error_line file_content, line
69
- STDERR.puts I18n.t('syntax_error.title').colorize(:light_red) + ':'
70
- STDERR.puts format_lines(I18n.t('syntax_error.unnecessary_end')) { |l, _i| " #{l}" }.colorize(:light_red)
71
- end
72
-
73
- def render_name_error_with_did_you_mean file_content, ex, ast
74
- corrections = ex.spell_checker.corrections
75
- corrections.each do |var_name|
76
- node = ast.find_by_variable_name var_name
77
- display_error_line file_content, node.loc.line
78
- end
79
-
80
- STDERR.puts I18n.t('name_error.title').colorize(:light_red) + ':'
81
- var_name_str = ex.spell_checker.name.underline
82
- corrections_str = corrections.map { |c| "`#{c.to_s.underline}`" }.join(', ')
83
- message = I18n.t('name_error.with_did_you_mean', var_name: var_name_str, corrections: corrections_str)
84
- STDERR.puts format_lines(message) { |l, _i| " #{l}" }.colorize(:light_red)
85
- end
86
-
87
- def suppress_error_display
88
- original_stdout = $stderr
89
- $stderr = StringIO.new
90
-
91
- yield
92
- ensure
93
- $stderr = original_stdout
94
- end
95
-
96
- def display_error_line file_content, error_line, window = WINDOW
97
- lines = file_content.split("\n", -1)
98
- line_size = lines.size
99
- window_start = [error_line - window, 1].max
100
- window_end = [error_line + window, line_size].min
101
-
102
- if error_line > 1
103
- start_line = window_start
104
- end_line = error_line - 1
105
- STDERR.puts format_lines(lines, start_line, end_line) { |l, i| "#{start_line + i}: #{l}" }&.colorize(:light_yellow)
106
- end
107
- STDERR.puts "#{error_line}: #{lines[error_line - 1]}".rstrip.colorize(:light_red)
108
- if error_line < line_size
109
- start_line = error_line + 1
110
- end_line = window_end
111
- STDERR.puts format_lines(lines, error_line + 1, end_line) { |l, i| "#{start_line + i}: #{l}" }&.colorize(:light_yellow)
112
- end
113
- STDERR.puts
114
- end
115
-
116
- def format_lines lines, start_line = 1, end_line = 0
117
- lines =
118
- case lines
119
- when String then lines.split("\n", -1)
120
- when Array then lines
121
- end
122
- lines[(start_line - 1)..(end_line - 1)]
123
- &.map
124
- &.with_index { |l, i| block_given? ? yield(l, i).rstrip : l.rstrip }
125
- &.join("\n")
126
- end
127
-
128
- def display_backtrace file_name, backtrace
129
- fake_backtrace = backtrace
130
- .dup
131
- .delete_if { |b| b.match(/ruby_friendly_error/) }
132
- .delete_if { |b| b.match(/`block in exec'/) }
133
- .map do |b|
134
- if b.match? Regexp.new("#{file_name}.*`exec'")
135
- b.sub("`exec'", "`<main>'")
136
- else
137
- b
138
- end
139
- end
140
-
141
- fake_backtrace_size = fake_backtrace.size
142
- fake_backtrace_digits = fake_backtrace_size.to_s.length
143
-
144
- fake_backtrace.reverse_each.with_index do |b, i|
145
- num = fake_backtrace_size - i
146
- num_digits = num.to_s.length
147
- STDERR.puts "#{' ' * (fake_backtrace_digits - num_digits)}#{num}:#{b}".colorize(:light_red)
72
+ RubyFriendlyError::Renderers::NameEndErrorRenderer
73
+ when ArgumentError
74
+ RubyFriendlyError::Renderers::ArgumentErrorRenderer
148
75
  end
149
- STDERR.puts
150
76
  end
151
77
  end
152
78
  end
data/locales/en.yml CHANGED
@@ -1,4 +1,5 @@
1
1
  en:
2
+ main_message: " occurred"
2
3
  syntax_error:
3
4
  title: "syntax error"
4
5
  missing_end: |
@@ -7,8 +8,15 @@ en:
7
8
  unnecessary_end: |
8
9
  exist unnecessary `end`.
9
10
  Please remove unnecessary `end`.
10
- name_error:
11
- title: "name error"
12
- with_did_you_mean: |
13
- undefined local variable or method `%{var_name}`
14
- Did you mean? %{corrections}
11
+ standard_error:
12
+ name_error:
13
+ title: "name error"
14
+ with_did_you_mean: |
15
+ undefined local variable or method `%{var_name}`.
16
+ Did you mean? %{corrections}.
17
+ argument_error:
18
+ title: "argument error"
19
+ wrong_number_of_arguments: |
20
+ wrong number of arguments.
21
+ `%{def_name}` expected %{arg_range}.
22
+ but given %{send_arg}.
data/locales/ja.yml ADDED
@@ -0,0 +1,22 @@
1
+ ja:
2
+ main_message: "が発生しました。"
3
+ syntax_error:
4
+ title: "構文エラー"
5
+ missing_end: |
6
+ `end` が欠けています。
7
+ おそらくこの行より前に問題があります。
8
+ unnecessary_end: |
9
+ `end` が余分に存在しています。
10
+ 不要な `end` を取り除いてください。
11
+ standard_error:
12
+ name_error:
13
+ title: "名前エラー"
14
+ with_did_you_mean: |
15
+ `%{var_name}` という名前の変数、もしくはメソッドが定義されていません。
16
+ もしかして? %{corrections}。
17
+ argument_error:
18
+ title: "引数エラー"
19
+ wrong_number_of_arguments: |
20
+ 引数の数が誤っています。
21
+ `%{def_name}` は %{arg_range} 個の引数を期待しています。
22
+ しかし %{send_arg} 個の引数を渡しています。
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.email = ['isuke770@gmail.com']
12
12
 
13
13
  spec.summary = 'make to ruby error messages friendly.'
14
- spec.description = 'make to ruby error messages friendly.'
14
+ spec.description = 'make to ruby error messages friendly. Display multilingual message and error lines'
15
15
  spec.homepage = 'https://github.com/isuke/ruby_friendly_error'
16
16
  spec.license = 'MIT'
17
17
 
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
 
27
27
  spec.add_dependency 'colorize', '~> 0.8'
28
28
  spec.add_dependency 'i18n' , '~> 1.1'
29
- spec.add_dependency 'parser' , '~> 2.5.1'
30
- spec.add_development_dependency 'bundler' , '~> 1.16'
31
- spec.add_development_dependency 'rake' , '~> 10.0'
29
+ spec.add_dependency 'parser' , '~> 2.5'
30
+ spec.add_development_dependency 'bundler', '~> 1.16'
31
+ spec.add_development_dependency 'rake' , '~> 10.0'
32
32
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ def hoge arg1, arg2 = 'foobar'
4
+ puts arg1
5
+ puts arg2
6
+ end
7
+
8
+ hoge 'piyo', 'fuga', 'what!?'
@@ -1,20 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # 1: prayer_life = 100
4
- # 2: player_lifee = 200
5
- # 3:
6
- # 4: puts 'hoge' if player_life > 0
7
-
8
- # 1: prayer_life = 100
9
- # 2: player_lifee = 200
10
- # 3:
11
-
12
- # name error:
13
- # undefined local variable or method `player_life`
14
- # Did you mean? `player_lifee`, `prayer_life`
15
-
16
3
  prayer_life = 100
17
4
 
18
5
  player_lifee = 100
19
6
 
20
- puts 'hoge' if player_life > 0
7
+ 3.times do
8
+ puts 'hoge' if player_life > 0
9
+ end
data/samples/not_error.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'miss_spell1'
4
+
3
5
  player_life = 100
4
6
 
5
7
  puts 'hoge' if player_life > 0
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_friendly_error
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - isuke
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-09-08 00:00:00.000000000 Z
11
+ date: 2018-09-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 2.5.1
47
+ version: '2.5'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 2.5.1
54
+ version: '2.5'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -80,7 +80,8 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '10.0'
83
- description: make to ruby error messages friendly.
83
+ description: make to ruby error messages friendly. Display multilingual message and
84
+ error lines
84
85
  email:
85
86
  - isuke770@gmail.com
86
87
  executables:
@@ -108,9 +109,19 @@ files:
108
109
  - exe/ruby_friendly_error
109
110
  - lib/ruby_friendly_error.rb
110
111
  - lib/ruby_friendly_error/ast.rb
112
+ - lib/ruby_friendly_error/renderers/argument_error_renderer.rb
113
+ - lib/ruby_friendly_error/renderers/exception_renderer.rb
114
+ - lib/ruby_friendly_error/renderers/missing_end_error_renderer.rb
115
+ - lib/ruby_friendly_error/renderers/name_error_renderer.rb
116
+ - lib/ruby_friendly_error/renderers/standard_error_renderer.rb
117
+ - lib/ruby_friendly_error/renderers/syntax_error_renderer.rb
118
+ - lib/ruby_friendly_error/renderers/unnecessary_end_error_renderer.rb
119
+ - lib/ruby_friendly_error/utils.rb
111
120
  - lib/ruby_friendly_error/version.rb
112
121
  - locales/en.yml
122
+ - locales/ja.yml
113
123
  - ruby_friendly_error.gemspec
124
+ - samples/arg_error.rb
114
125
  - samples/mismatch_args.rb
115
126
  - samples/miss_spell1.rb
116
127
  - samples/miss_spell2.rb