eturem 0.3.2 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: cafde349114833d7d2ec857ff349c9b90be5d1ec
4
- data.tar.gz: d80d846f4b2e628dab97a0e81cec9f64d3a162f9
2
+ SHA256:
3
+ metadata.gz: 02f58a271e2dc5649f9a44f03c338609d7484a21c117765f015b37b2fa987a0e
4
+ data.tar.gz: f64968cfff79d69874addbe967624ee89b3c064d5039f6b97878037f52c38337
5
5
  SHA512:
6
- metadata.gz: e7614e84b0e772b45d733c2fa25587855c314c7b10bdaf5052a4647ab2a666da4c08b233dfafb921ca7f8a3bcfd47833232a9488146d14221d242adf7d81b884
7
- data.tar.gz: 2c6cc910538e61fb935788b9f47390129f7d1896caeebb170bd14dde8c986232b04eaa1d91d2c190deb3a7f42d941a9d47bbf4b7afbc24e872d1bee0953c7131
6
+ metadata.gz: 6c4caaf3140eff2837ed1aa13517a6955d4a7039fc2266d46420c14738d7eae20ce418cb470b1fdaabdad3f4f17c4a454fb4bcab1483223a7ea4c0af1eee4fc6
7
+ data.tar.gz: f3b5c7db9d90865be0705b7193d1fd7dd9feeb103c2eba394342486eef6e9a08ce460bae42d9e4bea07b385a82b6f9974c91fc7b788b8aa84e8477b786fb5c39
data/Gemfile CHANGED
@@ -1,6 +1,7 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
-
5
3
  # Specify your gem's dependencies in eturem.gemspec
6
4
  gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "minitest", "~> 5.0"
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2018 nodai2hITC
3
+ Copyright (c) 2018-2020 nodai2hITC
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -12,6 +12,10 @@ Easy To Understand Ruby Error Message の略。
12
12
 
13
13
  $ ruby -returem/ja <your_script.rb>
14
14
 
15
+ または
16
+
17
+ $ eturem lang=ja <your_script.rb>
18
+
15
19
  と使用すればよいのですが、最初に書いたとおり初心者が使用することを想定した gem ですので、そんなことを初心者に強いるのは酷というもの。だれか詳しい人が、事前に ```gem install eturem``` した上で、環境変数 RUBYOPT に ```-returem/ja``` を追加しておいてあげましょう。
16
20
 
17
21
  ## 使用するとどうなるか
@@ -41,7 +45,7 @@ example1.rb:5: syntax error, unexpected end-of-input, expecting keyword_end
41
45
  Eturem を使用すると、次のようなエラー表示になります。
42
46
 
43
47
  ```
44
- ファイル"example1.rb" 5行目でエラーが発生しました。
48
+ 【エラー】ファイル"example1.rb" 5行目:
45
49
  (ただし、実際のエラーの原因はおそらくもっと前にあります。)
46
50
  構文エラーです。endが足りません。「if」に対応する「end」があるか確認してください。
47
51
  3: puts "なんたらかんたら"
@@ -74,7 +78,7 @@ did_you_mean のおかげで昔より格段にわかりやすくなったとは
74
78
  Eturem を使用すると、次のようなエラー表示になります。(実際には色付き)
75
79
 
76
80
  ```
77
- ファイル"example2.rb" 5行目でエラーが発生しました。
81
+ 【エラー】ファイル"example2.rb" 5行目:
78
82
  変数/メソッド「player_life」は存在しません。「prayer_life」の入力ミスではありませんか?
79
83
  1: prayer_life = 100
80
84
  :
@@ -110,7 +114,7 @@ example3.rb:1:in `foo': wrong number of arguments (given 1, expected 2) (Argumen
110
114
  Eturem を使用すると、次のようなエラー表示になります。
111
115
 
112
116
  ```
113
- ファイル"example3.rb" 4行目でエラーが発生しました。
117
+ 【エラー】ファイル"example3.rb" 4行目:
114
118
  引数の数が正しくありません。「foo」は本来2個の引数を取りますが、1個の引数が渡されています。
115
119
  2: end
116
120
  3: # 中略
@@ -119,6 +123,91 @@ Eturem を使用すると、次のようなエラー表示になります。
119
123
 
120
124
  このように、呼び出し行をエラー発生行として表示してくれます。
121
125
 
126
+ ### 例4:warning
127
+
128
+ 0.4.0 から、warning に対してもサポートができるようになりました。
129
+
130
+ ```
131
+ 【警告】ファイル"example4.rb" 2行目:
132
+ 条件式部分で「 = 」が使われています。「 == 」の間違いではありませんか?
133
+ => 2: puts "a" if a = 1
134
+ ```
135
+
136
+ 現状あまり多くの種類の警告に対応できてはいませんが、上記のように日本語での警告に加えて該当行を表示することができます。
137
+
138
+ ### 例5:エラー発生時に自動で binding.irb
139
+
140
+ 変数の値が不適切だったためにエラーが起きた可能性がある場合、エラーが起きた瞬間の変数の値を調べる必要があります。そうした際には「エラー発生箇所の直前に binding.irb を入れて調べる」という手がありますが、そうするとたまにしか起きないエラーの場合何度も binding.irb が動いてしまって面倒です。
141
+
142
+ そこで、後述する設定ファイル .eturem で設定するか、あるいはプログラム中に `$eturem_repl = "irb"` と入れておくことで、エラー発生時に自動的に binding.irb することができます。
143
+
144
+ なお、この機能だけを切り出した gem「[autoirb](https://github.com/nodai2hITC/autoirb)」もあります。
145
+
146
+ ### その他細かなこと
147
+
148
+ #### 全角空白・全角記号による NameError
149
+
150
+ 日本人ならきっと誰もがやったことがあり、そして意外と気付けない全角空白や全角記号によるエラー。通常の NameError の場合とは異なるメッセージでわかりやすくしています。(下の表示ではわかりませんが、実際には全角空白部分に下線が引かれています。)
151
+
152
+ ```
153
+ 【エラー】ファイル"example5.rb" 1行目:(NoMethodError)
154
+ スクリプト中に全角空白が混じっています。
155
+ => 1: puts "こんにちは世界"
156
+ ```
157
+
158
+ #### 大文字・小文字を間違えた場合の NameError
159
+
160
+ did_you_mean は、例えば `Time.now` と書くべきところを `time.now` と間違えて小文字で書いてしまった場合、 `Time` をサジェストしないようになっています。(サジェスト範囲を広げると誤反応が多くなるため、敢えてそうしているのだそうです。)
161
+
162
+ とはいえ、初心者は大文字小文字が区別されるという意識が無く、つい小文字で入力してしまうことが少なからずあるように思えます。そこでスペルが同一で大文字小文字のみが異なる場合に限り、定数もサジェスト対象になるようにしています。
163
+
164
+ ```
165
+ 【エラー】ファイル"example6.rb" 1行目:(NameError)
166
+ 変数/メソッド「time」は存在しません。「Time」の入力ミスではありませんか?
167
+ => 1: start_time = time.now
168
+ ```
169
+
170
+ ## .eturem ファイルによる設定
171
+
172
+ HOME またはカレントディレクトリに「.eturem」というファイルを用意すると、詳細設定ができます。(下の例の設定が、各項目を省略した際に使用される初期値です。)
173
+
174
+ ```
175
+ enable: true
176
+ debug: false
177
+ lang: en
178
+ output_backtrace: true
179
+ output_original: true
180
+ output_script: true
181
+ override_warning: true
182
+ use_coderay: false
183
+ before_line_num: 2
184
+ after_line_num: 2
185
+ repl: nil
186
+ ```
187
+
188
+ - enable: eturem の使用/不使用を切り替えます。
189
+ - debug: eturem 自体をデバッグするための機能なので、通常は使う必要はありません。
190
+ - lang: 表示言語を切り替えます。ただし `-returem/ja` のように言語を指定して使用した場合、この設定よりもそちらが優先されます。
191
+ - output_backtrace: バックトレース表示の有無を切り替えます。
192
+ - output_original: Ruby 本来のエラーメッセージの表示の有無を切り替えます。
193
+ - output_script: エラーの起きた個所のスクリプト表示の有無を切り替えます。
194
+ - override_warning: 警告に対してもわかりやすいメッセージを表示するかを設定します。
195
+ - before_line_num / after_line_num: スクリプト表示の際、エラー行の前後何行を表示するかを設定します。
196
+ - repl: irb または pry と書いておくと、エラー発生時に自動的に binding.irb / binding.pry します。
197
+
198
+ ## 簡単な仕組み
199
+
200
+ ```ruby
201
+ begin
202
+ load($PROGRAM_NAME)
203
+ rescue Exception => e
204
+ # エラー表示処理
205
+ end
206
+ exit
207
+ ```
208
+
209
+ 実際はもう少しいろいろやっていますが、このように本来のスクリプト実行が始まる前に `-returem` オプションで eturem を呼び出し、そこから `load` でスクリプトを実行しているのがポイントです。これにより、SyntaxError 等も取得できるようになっています。
210
+
122
211
  ## Contributing
123
212
 
124
213
  「こう表示した方がよりわかりやすいのでは?」等のご意見ありましたら、よろしく御願いします。
data/README.md CHANGED
@@ -14,6 +14,10 @@ install it yourself as:
14
14
 
15
15
  $ ruby -returem <your_script.rb>
16
16
 
17
+ or
18
+
19
+ $ eturem <your_script.rb>
20
+
17
21
  ## Contributing
18
22
 
19
23
  Bug reports and pull requests are welcome on GitHub at https://github.com/nodai2hITC/eturem. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
data/Rakefile CHANGED
@@ -5,6 +5,7 @@ Rake::TestTask.new(:test) do |t|
5
5
  t.libs << "test"
6
6
  t.libs << "lib"
7
7
  t.test_files = FileList["test/**/*_test.rb"]
8
+ t.warning = false
8
9
  end
9
10
 
10
11
  task :default => :test
@@ -1,7 +1,5 @@
1
1
 
2
- lib = File.expand_path("../lib", __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "eturem/version"
2
+ require_relative 'lib/eturem/version'
5
3
 
6
4
  Gem::Specification.new do |spec|
7
5
  spec.name = "eturem"
@@ -13,24 +11,20 @@ Gem::Specification.new do |spec|
13
11
  spec.description = %q{Easy To Understand Ruby Error Message.}
14
12
  spec.homepage = "https://github.com/nodai2hITC/eturem"
15
13
  spec.license = "MIT"
14
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
16
15
 
17
- # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
- # to allow pushing to a single host or delete this section to allow pushing to any host.
19
- if spec.respond_to?(:metadata)
20
- # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
21
- else
22
- raise "RubyGems 2.0 or newer is required to protect against " \
23
- "public gem pushes."
24
- end
16
+ # spec.metadata["allowed_push_host"] = "Set to 'http://mygemserver.com'"
17
+
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = spec.homepage
20
+ # spec.metadata["changelog_uri"] = spec.homepage
25
21
 
26
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
27
- f.match(%r{^(test|spec|features)/})
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
25
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
28
26
  end
29
27
  spec.bindir = "exe"
30
28
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
29
  spec.require_paths = ["lib"]
32
-
33
- spec.add_development_dependency "bundler", "~> 1.16"
34
- spec.add_development_dependency "rake", "~> 10.0"
35
- spec.add_development_dependency "minitest", "~> 5.0"
36
30
  end
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lang = nil
4
+ if ARGV.first.to_s =~ /^lang=(.+)$/
5
+ lang = $1
6
+ ARGV.shift
7
+ end
8
+
9
+ if ARGV.empty?
10
+ puts "usage: eturem (lang=**) script.rb"
11
+ exit
12
+ end
13
+
14
+ $PROGRAM_NAME = ARGV.shift
15
+ require lang ? "eturem/#{lang}" : "eturem"
@@ -1,39 +1,79 @@
1
1
  enable = true
2
+ debug = false
2
3
  lang = "en"
3
4
  output_backtrace = true
4
5
  output_original = true
5
6
  output_script = true
7
+ override_warning = true
6
8
  use_coderay = false
7
- max_backtrace = 16
8
9
  before_line_num = 2
9
10
  after_line_num = 2
11
+ repl = nil
10
12
 
11
- config_file = File.join(Dir.home, ".eturem")
13
+ config_file = File.exist?("./.eturem") ? "./.eturem" : File.join(Dir.home, ".eturem")
12
14
  if File.exist?(config_file)
13
- config = File.read(config_file)
15
+ config = File.read(config_file).gsub(/#.*/, "")
14
16
  enable = false if config.match(/^enable\s*\:\s*(?:false|off|0)/i)
17
+ debug = true if config.match(/^debug\s*\:\s*(?:true|on|1)/i)
15
18
  lang = Regexp.last_match(:lang) if config.match(/^lang\s*\:\s*(?<lang>\S+)/i)
16
- output_backtrace = false if config.match(/^output_backtrace\s*\:\s*(?:false|off|0)/i)
17
- output_original = false if config.match( /^output_original\s*\:\s*(?:false|off|0)/i)
18
- output_script = false if config.match( /^output_script\s*\:\s*(?:false|off|0)/i)
19
- use_coderay = true if config.match( /^use_coderay\s*\:\s*(?:true|on|1)/i)
20
- max_backtrace = Regexp.last_match(:num).to_i if config.match( /^max_backtrace\s*\:\s*(?<num>\d+)/i)
19
+ output_backtrace = false if config.match(/^output_backtrace\s*\:\s*(?:false|off|0)/i)
20
+ output_original = false if config.match( /^output_original\s*\:\s*(?:false|off|0)/i)
21
+ output_script = false if config.match( /^output_script\s*\:\s*(?:false|off|0)/i)
22
+ override_warning = false if config.match(/^override_warning\s*\:\s*(?:false|off|0)/i)
23
+ use_coderay = true if config.match( /^use_coderay\s*\:\s*(?:true|on|1)/i)
21
24
  before_line_num = Regexp.last_match(:num).to_i if config.match(/^before_line_num\s*\:\s*(?<num>\d+)/i)
22
25
  after_line_num = Regexp.last_match(:num).to_i if config.match( /^after_line_num\s*\:\s*(?<num>\d+)/i)
26
+ repl = Regexp.last_match(:repl).downcase if config.match(/^repl\s*\:\s*(?<repl>irb|pry)/i)
23
27
  end
24
- require "eturem/#{lang}" if enable && !defined?(Eturem)
25
28
 
26
- if defined? Eturem
27
- Eturem.eturem_class.output_backtrace = output_backtrace
28
- Eturem.eturem_class.output_original = output_original
29
- Eturem.eturem_class.output_script = output_script
30
- Eturem.eturem_class.use_coderay = use_coderay
31
- Eturem.eturem_class.max_backtrace = max_backtrace
32
- Eturem.eturem_class.before_line_num = before_line_num
33
- Eturem.eturem_class.after_line_num = after_line_num
34
-
35
- require "coderay" if use_coderay
36
- exception = Eturem.load($0)
37
- exception.output if exception
29
+ if enable
30
+ require "eturem/#{lang}/main" unless defined?(Eturem)
31
+ require "eturem/warning" if override_warning
32
+ Eturem::Base.output_backtrace = output_backtrace
33
+ Eturem::Base.output_original = output_original
34
+ Eturem::Base.output_script = output_script
35
+ Eturem::Base.use_coderay = use_coderay
36
+ Eturem::Base.before_line_num = before_line_num
37
+ Eturem::Base.after_line_num = after_line_num
38
+
39
+ if File.exist?($PROGRAM_NAME)
40
+ program_file = File.open($PROGRAM_NAME, "rb")
41
+ script = program_file.read
42
+ if script.match(/^__END__\R/)
43
+ program_file.pos = Regexp.last_match.end(0)
44
+ encoding = "utf-8"
45
+ if script.match(/\A(?:#!.*\R)?#.*coding *[:=] *(?<encoding>[^\s:]+)/)
46
+ encoding = Regexp.last_match(:encoding)
47
+ end
48
+ program_file.set_encoding(encoding)
49
+ Object.const_set(:DATA, program_file)
50
+ program_file
51
+ else
52
+ program_file.close
53
+ end
54
+ end
55
+
56
+ eturem_path = File.expand_path("..", __FILE__)
57
+ last_binding = nil
58
+ tracepoint = TracePoint.trace(:raise) do |tp|
59
+ last_binding = tp.binding unless File.expand_path(tp.path).start_with?(eturem_path)
60
+ end
61
+ exception = Eturem.load(File.expand_path(Eturem.program_name))
62
+ tracepoint.disable
63
+
64
+ if exception.is_a?(Exception)
65
+ begin
66
+ Eturem.extend_exception(exception)
67
+ $stderr.write exception.eturem_full_message
68
+ rescue Exception => e
69
+ raise debug ? e : exception
70
+ end
71
+
72
+ repl ||= $eturem_repl if defined?($eturem_repl)
73
+ if repl && last_binding && exception.is_a?(StandardError)
74
+ require repl
75
+ last_binding.public_send(repl)
76
+ end
77
+ end
38
78
  exit
39
79
  end
@@ -1,280 +1,312 @@
1
1
  require "eturem/version"
2
2
 
3
3
  module Eturem
4
- # load script and return eturem_exception if exception raised
5
- # @param [String] file script file
6
- # @return [Eturem::Base] if exception raised
7
- # @return [nil] if exception did not raise
8
- def self.load(file)
9
- begin
10
- Kernel.load(File.expand_path(file))
11
- rescue Exception => exception
12
- return @eturem_class.new(exception) unless exception.is_a? SystemExit
4
+ @program_name = $PROGRAM_NAME.encode("utf-8")
5
+
6
+ def self.rescue
7
+ yield
8
+ rescue Exception => exception
9
+ raise exception if exception.is_a? SystemExit
10
+ exception
11
+ end
12
+
13
+ # load script and return exception if exception raised
14
+ # @param [String] filename script file
15
+ # @return [Exception] if exception raised
16
+ # @return [true] if exception did not raise
17
+ def self.load(filename, wrap = false)
18
+ self.rescue do
19
+ Kernel.load(filename, wrap)
13
20
  end
14
- return nil
15
21
  end
16
-
22
+
17
23
  def self.eval(expr, bind = nil, fname = "(eval)", lineno = 1)
18
- begin
24
+ self.rescue do
19
25
  bind ? Kernel.eval(expr, bind, fname, lineno) : Kernel.eval(expr)
20
- rescue Exception => exception
21
- return @eturem_class.new(exception) unless exception.is_a? SystemExit
22
26
  end
23
- return nil
24
27
  end
25
-
26
- def self.eturem_class
27
- @eturem_class
28
+
29
+ def self.extend_exception(exception)
30
+ ext = _extend_exception(exception) ||
31
+ case exception
32
+ when SyntaxError then SyntaxErrorExt
33
+ when NameError then NameErrorExt
34
+ when ArgumentError then ArgumentErrorExt
35
+ else ExceptionExt
36
+ end
37
+ exception.extend ext
38
+ exception.eturem_prepare
28
39
  end
29
-
30
- def self.eturem_class=(klass)
31
- @eturem_class = klass
40
+
41
+ def self.program_name
42
+ @program_name
32
43
  end
33
-
34
- class Base
35
- attr_reader :exception
36
-
37
- @@output_backtrace = true
38
- @@output_original = true
39
- @@output_script = true
40
- @@use_coderay = false
41
- @@max_backtrace = 16
42
- @@before_line_num = 2
43
- @@after_line_num = 2
44
-
45
- @inspect_methods = {}
46
-
47
- def self.inspect_methods
48
- return @inspect_methods
49
- end
50
-
44
+
45
+
46
+ module Base
47
+ @@eturem_output_backtrace = true
48
+ @@eturem_output_original = true
49
+ @@eturem_output_script = true
50
+ @@eturem_use_coderay = false
51
+ @@eturem_before_line_num = 2
52
+ @@eturem_after_line_num = 2
53
+
51
54
  def self.output_backtrace=(value)
52
- @@output_backtrace = value
55
+ @@eturem_output_backtrace = value
53
56
  end
54
-
57
+
55
58
  def self.output_original=(value)
56
- @@output_original = value
59
+ @@eturem_output_original = value
57
60
  end
58
-
61
+
59
62
  def self.output_script=(value)
60
- @@output_script = value
63
+ @@eturem_output_script = value
61
64
  end
62
-
65
+
63
66
  def self.use_coderay=(value)
64
- @@use_coderay = value
65
- end
66
-
67
- def self.max_backtrace=(value)
68
- @@max_backtrace = value
67
+ @@eturem_use_coderay = value
69
68
  end
70
-
69
+
71
70
  def self.before_line_num=(value)
72
- @@before_line_num = value
71
+ @@eturem_before_line_num = value
73
72
  end
74
-
73
+
75
74
  def self.after_line_num=(value)
76
- @@after_line_num = value
75
+ @@eturem_after_line_num = value
77
76
  end
78
-
79
- def initialize(exception)
80
- @exception = exception
81
- @exception_s = exception.to_s
82
-
83
- eturem_path = File.dirname(File.absolute_path(__FILE__))
84
- @backtrace_locations = (@exception.backtrace_locations || []).reject do |location|
85
- path = File.absolute_path(location.path)
86
- path.start_with?(eturem_path) || path.end_with?("/rubygems/core_ext/kernel_require.rb")
87
- end
88
- @backtrace_locations.each do |location|
89
- if $0 == location.path
90
- def location.label
91
- super.sub("<top (required)>", "<main>")
92
- end
93
- end
77
+
78
+ def self.highlight(str, pattern, highlight)
79
+ str.sub!(pattern) { "#{highlight}#{$1 || $&}\e[0m#{$2}" } if str
80
+ end
81
+
82
+ def self.unhighlight(str)
83
+ str.gsub(/\e\[[0-9;]*m/, "")
84
+ end
85
+
86
+ def self.read_script(filename)
87
+ return [] unless File.exist?(filename)
88
+ script = File.binread(filename)
89
+ encoding = "utf-8"
90
+ if script.match(/\A(?:#!.*\R)?#.*coding *[:=] *(?<encoding>[^\s:]+)/)
91
+ encoding = Regexp.last_match(:encoding)
94
92
  end
95
-
96
- if @exception.is_a?(SyntaxError) && @exception_s.match(/\A(?<path>.+?)\:(?<lineno>\d+)/)
97
- @path = Regexp.last_match(:path)
98
- @lineno = Regexp.last_match(:lineno).to_i
99
- else
100
- backtrace_locations_shift
93
+ script.force_encoding(encoding).encode!("utf-8")
94
+ if @@eturem_use_coderay
95
+ require "coderay"
96
+ script = CodeRay.scan(script, :ruby).terminal
101
97
  end
102
-
103
- load_script
104
- prepare
98
+ [""] + script.lines(chomp: true)
105
99
  end
106
-
107
- # output backtrace + error message + script
108
- def output
109
- output_backtrace if @@output_backtrace
110
- error_message = exception_inspect
111
- if error_message.to_s.empty?
112
- puts original_exception_inspect
113
- else
114
- puts original_exception_inspect if @@output_original
115
- puts error_message
100
+
101
+ def self.script(lines, linenos, lineno)
102
+ str = ""
103
+ max_lineno_length = linenos.max.to_s.length
104
+ last_i = linenos.min.to_i - 1
105
+ linenos.uniq.sort.each do |i|
106
+ line = lines[i]
107
+ next unless line
108
+ str += " #{' ' * max_lineno_length} :\n" if last_i + 1 != i
109
+ str += (lineno == i ? " => " : " ")
110
+ str += sprintf("\e[1;34m%#{max_lineno_length}d\e[0m: %s\e[0m\n", i, line)
111
+ last_i = i
116
112
  end
117
- output_script if @@output_script
113
+ str
118
114
  end
119
-
120
- def output_backtrace
121
- return if @backtrace_locations.empty?
122
-
123
- puts traceback_most_recent_call_last
124
- @backtrace_locations[0, @@max_backtrace].reverse.each_with_index do |location, i|
125
- puts sprintf("%9d: %s", @backtrace_locations.size - i, location_inspect(location))
115
+
116
+
117
+ def eturem_prepare
118
+ this_dirpath = File.dirname(File.expand_path(__FILE__))
119
+ @eturem_backtrace_locations = (self.backtrace_locations || []).reject do |location|
120
+ File.expand_path(location.path).start_with?(this_dirpath) ||
121
+ location.path.end_with?("/rubygems/core_ext/kernel_require.rb")
126
122
  end
127
- end
128
-
129
- def exception_inspect
130
- inspect_methods = self.class.inspect_methods
131
- inspect_methods.keys.reverse_each do |key|
132
- if (key.is_a?(Class) && @exception.is_a?(key)) ||
133
- (key.is_a?(String) && @exception.class.to_s == key)
134
- method = inspect_methods[key]
135
- return method ? public_send(method) : nil
123
+
124
+ program_filepath = File.expand_path(Eturem.program_name)
125
+ @eturem_backtrace_locations.each do |location|
126
+ if File.expand_path(location.path) == program_filepath
127
+ def location.path; Eturem.program_name; end
128
+ if location.label == "<top (required)>"
129
+ def location.label; "<main>"; end
130
+ end
136
131
  end
137
132
  end
138
- return nil
139
- end
140
-
141
- def original_exception_inspect
142
- if @exception.is_a? SyntaxError
143
- return @exception_s
144
- else
145
- location_str = "#{@path}:#{@lineno}:in `#{@label}'"
146
- if @exception_s.match(/\A(?<first_line>.*?)\n/)
147
- return "#{location_str}: #{Regexp.last_match(:first_line)} (#{@exception.class})\n" +
148
- "#{Regexp.last_match.post_match}"
149
- else
150
- return "#{location_str}: #{@exception_s} (#{@exception.class})"
133
+
134
+ @eturem_message = self.message
135
+ unless @eturem_message.encoding == Encoding::UTF_8
136
+ @eturem_message.force_encoding("utf-8")
137
+ unless @eturem_message.valid_encoding?
138
+ @eturem_message.force_encoding(Encoding.locale_charmap).encode!("utf-8")
151
139
  end
152
140
  end
153
- end
154
-
155
- def output_script
156
- return if @script.empty?
157
-
158
- max_lineno_length = @output_lines.compact.max.to_s.length
159
- @output_lines.each do |i|
160
- if @lineno == i
161
- puts sprintf("\e[0m => \e[1;34m%#{max_lineno_length}d\e[0m: %s", i, @script_lines[i])
162
- elsif i
163
- puts sprintf("\e[0m \e[1;34m%#{max_lineno_length}d\e[0m: %s", i, @script_lines[i])
164
- else
165
- puts "\e[0m #{" " * max_lineno_length} :"
166
- end
141
+
142
+ if self.is_a?(SyntaxError) && @eturem_message.match(/\A(?<path>.+?)\:(?<lineno>\d+):\s*/)
143
+ @eturem_path = Regexp.last_match(:path)
144
+ @eturem_lineno = Regexp.last_match(:lineno).to_i
145
+ @eturem_message = Regexp.last_match.post_match
146
+ @eturem_path = Eturem.program_name if @eturem_path == program_filepath
147
+ else
148
+ eturem_backtrace_locations_shift
167
149
  end
150
+
151
+ @eturem_script_lines = Eturem::Base.read_script(@eturem_path)
152
+ @eturem_output_linenos = eturem_default_output_linenos
168
153
  end
169
-
170
- private
171
-
172
- def prepare
173
- case @exception
174
- when SyntaxError then prepare_syntax_error
175
- when NameError then prepare_name_error
176
- when ArgumentError then prepare_argument_error
177
- when TypeError then prepare_type_error
178
- end
154
+
155
+ def eturem_original_error_message()
156
+ @eturem_message.match(/\A(?<first_line>.*)/)
157
+ "#{@eturem_path}:#{@eturem_lineno}:in `#{@eturem_label}': " +
158
+ "\e[1m#{Regexp.last_match(:first_line)} (\e[4m#{self.class}\e[0;1m)" +
159
+ "#{Regexp.last_match.post_match.chomp}\e[0m\n"
179
160
  end
180
-
181
- def prepare_syntax_error
182
- @unexpected = @exception_s.match(/unexpected (?<unexpected>(?:','|[^,])+)/) ?
183
- Regexp.last_match(:unexpected) : nil
184
- @expected = @exception_s.match(/[,\s]expecting (?<expected>\S+)/) ?
185
- Regexp.last_match(:expected) : nil
186
- if !@expected && @exception_s.match(/(?<invalid>(?:break|next|retry|redo|yield))/)
187
- @invalid = Regexp.last_match(:invalid)
188
- end
161
+
162
+ def eturem_backtrace_str(order = :bottom)
163
+ str = @eturem_backtrace_locations.empty? ? "" : eturem_traceback(order)
164
+ str + (order == :top ? eturem_backtrace_str_top : eturem_backtrace_str_bottom)
189
165
  end
190
-
191
- def prepare_name_error
192
- highlight!(@script_lines[@lineno], @exception.name.to_s, "\e[1;31m\e[4m")
193
- return unless @exception_s.match(/Did you mean\?/)
194
- @did_you_mean = Regexp.last_match.post_match.strip.scan(/\S+/)
195
- return if @script.empty?
196
-
197
- new_range = []
198
- @did_you_mean.each do |name|
199
- index = @script_lines.index { |line| line.include?(name) }
200
- next unless index
201
- highlight!(@script_lines[index], name, "\e[1;33m")
202
- new_range.push(index)
203
- end
204
- new_range.sort!
205
- before = new_range.select { |i| i < @output_lines.first }
206
- after = new_range.select { |i| i > @output_lines.last }
207
- unless before.empty?
208
- @output_lines.unshift(nil) if before.last + 1 != @output_lines.first
209
- @output_lines.unshift(*before)
210
- end
211
- unless after.empty?
212
- @output_lines.push(nil) if @output_lines.last + 1 != after.first
213
- @output_lines.push(*after)
166
+
167
+ def eturem_backtrace
168
+ eturem_backtrace_locations.map do |location|
169
+ eturem_location_to_s(location)
214
170
  end
215
171
  end
216
-
217
- def prepare_argument_error
218
- @method = @label
219
- old_path = @path
220
- backtrace_locations_shift
221
- load_script unless old_path == @path
222
- @output_lines = default_output_lines
223
- if @exception_s.match(/given (?<given>\d+)\, expected (?<expected>[^)]+)/)
224
- @given = Regexp.last_match(:given).to_i
225
- @expected = Regexp.last_match(:expected)
172
+
173
+ def eturem_location_to_s(location)
174
+ "#{location.path}:#{location.lineno}:in `#{location.label}'"
175
+ end
176
+
177
+ def eturem_backtrace_locations
178
+ @eturem_backtrace_locations
179
+ end
180
+
181
+ def eturem_message
182
+ ""
183
+ end
184
+
185
+ def eturem_script
186
+ Eturem::Base.script(@eturem_script_lines, @eturem_output_linenos, @eturem_lineno)
187
+ end
188
+
189
+ def eturem_full_message(highlight: true, order: :bottom)
190
+ unless $stderr == STDERR && $stderr.tty?
191
+ highlight = false
192
+ order = :top
226
193
  end
194
+
195
+ str = @@eturem_output_backtrace ? eturem_backtrace_str(order) : ""
196
+ ext_message = eturem_message
197
+ if ext_message.empty?
198
+ str += eturem_original_error_message
199
+ else
200
+ str = "#{eturem_original_error_message}\n#{str}" if @@eturem_output_original
201
+ str += "#{ext_message}\n"
202
+ end
203
+ str += eturem_script if @@eturem_output_script
204
+
205
+ highlight ? str : Eturem::Base.unhighlight(str)
227
206
  end
228
-
229
- def prepare_type_error
230
- @method = @label
207
+
208
+ private
209
+
210
+ def eturem_backtrace_locations_shift
211
+ location = @eturem_backtrace_locations.shift
212
+ @eturem_label = location.label
213
+ @eturem_path = location.path
214
+ @eturem_lineno = location.lineno
231
215
  end
232
-
233
- def backtrace_locations_shift
234
- @label = @backtrace_locations.first.label
235
- @path = @backtrace_locations.first.path
236
- @lineno = @backtrace_locations.first.lineno
237
- @backtrace_locations.shift
216
+
217
+ def eturem_default_output_linenos
218
+ from = [1, @eturem_lineno - @@eturem_before_line_num].max
219
+ to = [@eturem_script_lines.size - 1, @eturem_lineno + @@eturem_after_line_num].min
220
+ (from..to).to_a
238
221
  end
239
-
240
- def traceback_most_recent_call_last
241
- "Traceback (most recent call last):"
222
+
223
+ def eturem_traceback(order = :bottom)
224
+ order == :top ? "" : "\e[1mTraceback\e[0m (most recent call last):\n"
242
225
  end
243
-
244
- def location_inspect(location)
245
- "from #{location.path}:#{location.lineno}:in `#{location.label}'"
226
+
227
+ def eturem_backtrace_str_bottom
228
+ lines = []
229
+ backtrace = eturem_backtrace
230
+ size = backtrace.size
231
+ format = "%#{8 + size.to_s.length}d: %s\n"
232
+ backtrace.reverse.each_with_index do |bt, i|
233
+ lines.push(sprintf(format, size - i, bt))
234
+ end
235
+
236
+ if @eturem_message == "stack level too deep"
237
+ lines = lines[-4..-1] +
238
+ [" ... #{lines.size - 12} levels...\n"] +
239
+ lines[0..7]
240
+ end
241
+ lines.join
246
242
  end
247
-
248
- def load_script
249
- @script ||= ""
250
- if @path && File.exist?(@path)
251
- @script = File.binread(@path)
252
- encoding = "utf-8"
253
- if @script.match(/\A(?:#!.*\R)?#.*coding *[:=] *(?<encoding>[^\s:]+)/)
254
- encoding = Regexp.last_match(:encoding)
255
- end
256
- @script.force_encoding(encoding)
243
+
244
+ def eturem_backtrace_str_top
245
+ lines = eturem_backtrace.map do |bt|
246
+ " from #{bt}\n"
247
+ end
248
+ if @eturem_message == "stack level too deep"
249
+ lines = lines[0..7] +
250
+ [" ... #{lines.size - 12} levels...\n"] +
251
+ lines[-4..-1]
257
252
  end
258
- @script = CodeRay.scan(@script, :ruby).terminal if @@use_coderay
259
- @script_lines = @script.lines
260
- @script_lines.unshift("")
261
- @output_lines = default_output_lines
253
+ lines.join
262
254
  end
263
-
264
- def highlight(str, keyword, color)
265
- str.to_s.gsub(keyword){ color + ($1 || $&) + "\e[0m" }
255
+ end
256
+
257
+
258
+ module ExceptionExt
259
+ include Base
260
+ end
261
+
262
+
263
+ module NameErrorExt
264
+ include ExceptionExt
265
+
266
+ def eturem_prepare()
267
+ @eturem_corrections = self.respond_to?(:corrections) ? self.corrections : []
268
+ @eturem_corrections += Object.constants.map(&:to_s).select do |const|
269
+ const.casecmp?(self.name)
270
+ end
271
+ @eturem_corrections.uniq!
272
+ def self.corrections; @eturem_corrections; end
273
+ super
274
+ uname = self.name.to_s.encode("utf-8")
275
+ if @eturem_script_lines[@eturem_lineno]
276
+ Eturem::Base.highlight(@eturem_script_lines[@eturem_lineno], uname, "\e[1;4;31m")
277
+ end
278
+ @eturem_corrections.each do |name|
279
+ index = @eturem_script_lines.index { |line| line.include?(name.to_s.encode("utf-8")) }
280
+ next unless index
281
+ Eturem::Base.highlight(@eturem_script_lines[index], name.to_s.encode("utf-8"), "\e[1;33m")
282
+ @eturem_output_linenos.push(index)
283
+ end
266
284
  end
267
-
268
- def highlight!(str, keyword, color)
269
- str.gsub!(keyword){ color + ($1 || $&) + "\e[0m" } if str
285
+ end
286
+
287
+
288
+ module ArgumentErrorExt
289
+ include ExceptionExt
290
+
291
+ def eturem_prepare()
292
+ super
293
+ @eturem_method = @eturem_label
294
+ eturem_backtrace_locations_shift
295
+ @eturem_script_lines = Eturem::Base.read_script(@eturem_path)
296
+ @eturem_output_linenos = eturem_default_output_linenos
270
297
  end
271
-
272
- def default_output_lines
273
- from = [1, @lineno - @@before_line_num].max
274
- to = [@script_lines.size - 1, @lineno + @@after_line_num].min
275
- (from..to).to_a
298
+ end
299
+
300
+
301
+ module SyntaxErrorExt
302
+ include ExceptionExt
303
+
304
+ def eturem_original_error_message()
305
+ ret = "#{@eturem_path}:#{@eturem_lineno}: #{@eturem_message}"
306
+ unless @eturem_path == Eturem.program_name
307
+ ret = "\e[1m#{ret} (\e[4m#{self.class}\e[0;1m)\e[0m"
308
+ end
309
+ ret + "\n"
276
310
  end
277
-
278
- Eturem.eturem_class = self
279
311
  end
280
312
  end