eturem 0.4.0 → 0.5.0

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
2
  SHA256:
3
- metadata.gz: c96c7ae5b183ce6fc301daa33a6279158dbe2e4426dce6dde798834dc84901dc
4
- data.tar.gz: 1bfe20edbb9da9e037b7ebe0896c62f4975631f5760fde2900ff814cc99ea3de
3
+ metadata.gz: 767768a9b608e0cf83064badb5717aecec8d95d729cd6f578944e69240410145
4
+ data.tar.gz: 91f5d7e50048c588b5ec8ee922ee7db379173b64e731382f0f4e14f07c1b81ec
5
5
  SHA512:
6
- metadata.gz: 5f929faa102e9a9b297d4732dd36cfe19a83ae3cb89b54b7ff1f60db16c4d30c872524fe6717f39120bd9096670f90c77244afa36d1e0088efe6c89b46f3becc
7
- data.tar.gz: 044c90b8262a37e838c197002c479f913a767344065f551a9973b5cff46d904193d38f5def3d2180021452166e3c05a120be06519a95e7a8c03e6f0a1885b100
6
+ metadata.gz: 288c49a8a1093d696fcc897f076a21c800640ee9b6229a48325f68ed769acf366fcdb4c085b09ebb2f0f3a1386f9f95d2d417f2f99a96733c50bb238d030b834
7
+ data.tar.gz: c658ff6c2fa82566e5052b515306b78cd096bca286d2a41875f2ef0aec1271dcb7a0f44962e7bb51c8d466485f37d4d86a5f79a840d471569b31dbf4689a16d9
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
  ## 使用するとどうなるか
@@ -129,7 +133,7 @@ Eturem を使用すると、次のようなエラー表示になります。
129
133
  => 2: puts "a" if a = 1
130
134
  ```
131
135
 
132
- 現状「条件式内での = 使用」のみにしか対応していませんが、上記のように日本語での警告に加えて該当行を表示することができます。
136
+ 現状あまり多くの種類の警告に対応できてはいませんが、上記のように日本語での警告に加えて該当行を表示することができます。
133
137
 
134
138
  ### 例5:エラー発生時に自動で binding.irb
135
139
 
@@ -139,9 +143,33 @@ Eturem を使用すると、次のようなエラー表示になります。
139
143
 
140
144
  なお、この機能だけを切り出した gem「[autoirb](https://github.com/nodai2hITC/autoirb)」もあります。
141
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
+
142
170
  ## .eturem ファイルによる設定
143
171
 
144
- HOME に「.eturem」というファイルを用意すると、詳細設定ができます。
172
+ HOME またはカレントディレクトリに「.eturem」というファイルを用意すると、詳細設定ができます。(下の例の設定が、各項目を省略した際に使用される初期値です。)
145
173
 
146
174
  ```
147
175
  enable: true
@@ -150,6 +178,7 @@ lang: en
150
178
  output_backtrace: true
151
179
  output_original: true
152
180
  output_script: true
181
+ override_warning: true
153
182
  use_coderay: false
154
183
  before_line_num: 2
155
184
  after_line_num: 2
@@ -157,13 +186,27 @@ repl: nil
157
186
  ```
158
187
 
159
188
  - enable: eturem の使用/不使用を切り替えます。
160
- - debug: eturem 自体をデバッグするための機能なので、使う必要はありません。
161
- - lang: 表示言語を切り替えます。ただし、`-returem/ja` のように言語を指定して使用した場合、この設定よりもそちらが優先されます。
189
+ - debug: eturem 自体をデバッグするための機能なので、通常は使う必要はありません。
190
+ - lang: 表示言語を切り替えます。ただし `-returem/ja` のように言語を指定して使用した場合、この設定よりもそちらが優先されます。
162
191
  - output_backtrace: バックトレース表示の有無を切り替えます。
163
192
  - output_original: Ruby 本来のエラーメッセージの表示の有無を切り替えます。
164
193
  - output_script: エラーの起きた個所のスクリプト表示の有無を切り替えます。
194
+ - override_warning: 警告に対してもわかりやすいメッセージを表示するかを設定します。
165
195
  - before_line_num / after_line_num: スクリプト表示の際、エラー行の前後何行を表示するかを設定します。
166
- - repl: irb または pry と書いておくと、エラー発生時に binding.irb / binding.pry します。
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 等も取得できるようになっています。
167
210
 
168
211
  ## Contributing
169
212
 
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.
@@ -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"
@@ -4,39 +4,59 @@ lang = "en"
4
4
  output_backtrace = true
5
5
  output_original = true
6
6
  output_script = true
7
+ override_warning = true
7
8
  use_coderay = false
8
9
  before_line_num = 2
9
10
  after_line_num = 2
10
11
  repl = nil
11
12
 
12
- if File.exist?($PROGRAM_NAME)
13
- config_file = File.join(Dir.home, ".eturem")
14
- if File.exist?(config_file)
15
- config = File.read(config_file)
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)
18
- lang = Regexp.last_match(:lang) if config.match(/^lang\s*\:\s*(?<lang>\S+)/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
- use_coderay = true if config.match( /^use_coderay\s*\:\s*(?:true|on|1)/i)
23
- before_line_num = Regexp.last_match(:num).to_i if config.match(/^before_line_num\s*\:\s*(?<num>\d+)/i)
24
- after_line_num = Regexp.last_match(:num).to_i if config.match( /^after_line_num\s*\:\s*(?<num>\d+)/i)
25
- repl = Regexp.last_match(:repl).downcase if config.match(/^repl\s*\:\s*(?<repl>irb|pry)/i)
13
+ config_file = File.exist?("./.eturem") ? "./.eturem" : File.join(Dir.home, ".eturem")
14
+ if File.exist?(config_file)
15
+ config = File.read(config_file).gsub(/#.*/, "")
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)
18
+ lang = Regexp.last_match(:lang) if config.match(/^lang\s*\:\s*(?<lang>\S+)/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)
24
+ before_line_num = Regexp.last_match(:num).to_i if config.match(/^before_line_num\s*\:\s*(?<num>\d+)/i)
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)
27
+ end
28
+
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
+ eturem_path = File.expand_path("..", __FILE__)
40
+ last_binding = nil
41
+ tracepoint = TracePoint.trace(:raise) do |tp|
42
+ last_binding = tp.binding unless File.expand_path(tp.path).start_with?(eturem_path)
26
43
  end
27
-
28
- if enable
29
- require "eturem/#{lang}" unless defined?(Eturem)
30
- Eturem.set_config(
31
- output_backtrace: output_backtrace,
32
- output_original: output_original,
33
- output_script: output_script,
34
- use_coderay: use_coderay,
35
- before_line_num: before_line_num,
36
- after_line_num: after_line_num
37
- )
38
-
39
- Eturem.load_and_output($PROGRAM_NAME, repl, debug)
40
- exit
44
+ exception = Eturem.load(File.expand_path(Eturem.program_name))
45
+ tracepoint.disable
46
+
47
+ if exception.is_a?(Exception)
48
+ begin
49
+ Eturem.extend_exception(exception)
50
+ $stderr.write exception.eturem_full_message
51
+ rescue Exception => e
52
+ raise debug ? e : exception
53
+ end
54
+
55
+ repl ||= $eturem_repl if defined?($eturem_repl)
56
+ if repl && last_binding && exception.is_a?(StandardError)
57
+ require repl
58
+ last_binding.public_send(repl)
59
+ end
41
60
  end
61
+ exit
42
62
  end
@@ -1,309 +1,313 @@
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
- eturem = @eturem_class.new(file)
10
- begin
11
- Kernel.load(file)
12
- rescue Exception => exception
13
- raise exception if exception.is_a? SystemExit
14
- eturem.exception = exception
15
- end
16
- eturem.exception ? eturem : nil
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
17
11
  end
18
-
19
- def self.load_and_output(file, repl = nil, debug = false)
20
- eturem = @eturem_class.new(file, true)
21
- last_binding = nil
22
- tp = TracePoint.trace(:raise) do |t|
23
- last_binding = t.binding unless File.expand_path(t.path) == File.expand_path(__FILE__)
24
- end
25
- script = read_script(file)
26
- begin
27
- TOPLEVEL_BINDING.eval(script, file, 1)
28
- tp.disable
29
- eturem.comeback_stderr
30
- rescue Exception => exception
31
- tp.disable
32
- eturem.comeback_stderr
33
- raise exception if exception.is_a? SystemExit
34
- repl ||= $eturem_repl
35
- use_repl = repl && last_binding && exception.is_a?(StandardError)
36
- begin
37
- eturem.exception = exception
38
- $stderr.write eturem.inspect
39
- rescue Exception => e
40
- raise debug ? e : eturem.exception
41
- end
42
- return unless use_repl
43
- require repl
44
- last_binding.public_send(repl)
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)
45
20
  end
46
21
  end
47
-
22
+
48
23
  def self.eval(expr, bind = nil, fname = "(eval)", lineno = 1)
49
- eturem = @eturem_class.new(fname)
50
- begin
24
+ self.rescue do
51
25
  bind ? Kernel.eval(expr, bind, fname, lineno) : Kernel.eval(expr)
52
- rescue Exception => exception
53
- raise exception if exception.is_a? SystemExit
54
- eturem.exception = exception
55
26
  end
56
- return eturem.exception ? eturem : nil
57
27
  end
58
-
59
- def self.set_config(config)
60
- @eturem_class.set_config(config)
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
61
39
  end
62
-
63
- def self.read_script(file)
64
- script = nil
65
- if File.exist?(file)
66
- script = File.binread(file)
67
- encoding = "utf-8"
68
- if script.match(/\A(?:#!.*\R)?#.*coding *[:=] *(?<encoding>[^\s:]+)/)
69
- encoding = Regexp.last_match(:encoding)
70
- end
71
- script.force_encoding(encoding)
72
- end
73
- return script
40
+
41
+ def self.program_name
42
+ @program_name
74
43
  end
75
-
76
- class Base
77
- attr_reader :exception, :backtrace_locations, :path, :lineno, :label
78
-
79
- @@output_backtrace = true
80
- @@output_original = true
81
- @@output_script = true
82
- @@use_coderay = false
83
- @@before_line_num = 2
84
- @@after_line_num = 2
85
-
86
- @inspect_methods = {}
87
-
88
- def self.inspect_methods
89
- return @inspect_methods
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
+
54
+ def self.output_backtrace=(value)
55
+ @@eturem_output_backtrace = value
90
56
  end
91
-
92
- def self.set_config(config)
93
- @@output_backtrace = config[:output_backtrace] if config.has_key?(:output_backtrace)
94
- @@output_original = config[:output_original] if config.has_key?(:output_original)
95
- @@output_script = config[:output_script] if config.has_key?(:output_script)
96
- @@use_coderay = config[:use_coderay] if config.has_key?(:use_coderay)
97
- @@before_line_num = config[:before_line_num] if config.has_key?(:before_line_num)
98
- @@after_line_num = config[:after_line_num] if config.has_key?(:after_line_num)
57
+
58
+ def self.output_original=(value)
59
+ @@eturem_output_original = value
99
60
  end
100
-
101
- def initialize(load_file, replace_stderr = false)
102
- @load_file = load_file.encode("utf-8")
103
- @scripts = {}
104
- @decoration = {}
105
- if replace_stderr
106
- @stderr = $stderr
107
- $stderr = self
108
- end
61
+
62
+ def self.output_script=(value)
63
+ @@eturem_output_script = value
109
64
  end
110
-
111
- def exception=(exception)
112
- @exception = exception
113
- @exception_s = exception.to_s
114
-
115
- eturem_path = File.dirname(File.expand_path(__FILE__))
116
- @backtrace_locations = (@exception.backtrace_locations || []).reject do |location|
117
- path = File.expand_path(location.path)
118
- path.start_with?(eturem_path) || path.end_with?("/rubygems/core_ext/kernel_require.rb")
119
- end
120
-
121
- if @exception.is_a?(SyntaxError) && @exception_s.match(/\A(?<path>.+?)\:(?<lineno>\d+)/)
122
- @path = Regexp.last_match(:path)
123
- @lineno = Regexp.last_match(:lineno).to_i
124
- else
125
- backtrace_locations_shift
126
- end
127
-
128
- @script_lines = read_script(@path) || []
129
- @output_linenos = default_output_linenos
130
- prepare
65
+
66
+ def self.use_coderay=(value)
67
+ @@eturem_use_coderay = value
131
68
  end
132
-
133
- def inspect
134
- str = @@output_backtrace ? backtrace_inspect : ""
135
- error_message = exception_inspect
136
- if error_message.empty?
137
- str << original_exception_inspect
138
- else
139
- str = "#{original_exception_inspect}\n#{str}" if @@output_original
140
- str << "#{error_message}\n"
141
- end
142
- str << script_inspect if @@output_script
143
- return str
69
+
70
+ def self.before_line_num=(value)
71
+ @@eturem_before_line_num = value
144
72
  end
145
-
146
- def backtrace_inspect
147
- return "" if @backtrace_locations.empty?
148
-
149
- str = "#{traceback_most_recent_call_last}\n"
150
- backtraces = []
151
- size = @backtrace_locations.size
152
- format = "%#{8 + size.to_s.length}d: %s\n"
153
- @backtrace_locations.reverse.each_with_index do |location, i|
154
- backtraces.push(sprintf(format, size - i, location_inspect(location)))
155
- end
156
-
157
- if @exception_s == "stack level too deep"
158
- str << backtraces[0..7].join
159
- str << " ... #{backtraces.size - 12} levels...\n"
160
- str << backtraces[-4..-1].join
161
- else
162
- str << backtraces.join
163
- end
164
- return str
73
+
74
+ def self.after_line_num=(value)
75
+ @@eturem_after_line_num = value
165
76
  end
166
-
167
- def exception_inspect
168
- inspect_methods = self.class.inspect_methods
169
- inspect_methods.keys.reverse_each do |key|
170
- if (key.is_a?(Class) && @exception.is_a?(key)) ||
171
- (key.is_a?(String) && @exception.class.to_s == key)
172
- method = inspect_methods[key]
173
- return method ? public_send(method) : ""
174
- end
175
- end
176
- return ""
77
+
78
+ def self.highlight(str, pattern, highlight)
79
+ str.sub!(pattern) { "#{highlight}#{$1 || $&}\e[0m#{$2}" } if str
177
80
  end
178
-
179
- def original_exception_inspect
180
- if @exception.is_a? SyntaxError
181
- return "#{@exception_s.chomp}\n"
182
- else
183
- location_str = "#{@path}:#{@lineno}:in `#{@label}'"
184
- @exception_s.match(/\A(?<first_line>.*)/)
185
- return "#{location_str}: #{Regexp.last_match(:first_line)} (\e[4m#{@exception.class}\e[0m)" +
186
- "#{Regexp.last_match.post_match.chomp}\n"
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)
187
92
  end
93
+ script.force_encoding(encoding).encode!("utf-8")
94
+ if @@eturem_use_coderay
95
+ require "coderay"
96
+ script = CodeRay.scan(script, :ruby).terminal
97
+ end
98
+ [""] + script.lines(chomp: true)
188
99
  end
189
-
190
- def script_inspect(path = @path, linenos = @output_linenos, lineno = @lineno, decoration = @decoration)
191
- script_lines = read_script(path)
192
- return "" unless script_lines
193
-
100
+
101
+ def self.script(lines, linenos, lineno)
194
102
  str = ""
195
103
  max_lineno_length = linenos.max.to_s.length
196
- last_i = linenos.min - 1
104
+ last_i = linenos.min.to_i - 1
197
105
  linenos.uniq.sort.each do |i|
198
- line = script_lines[i]
199
- line = highlight(line, decoration[i][0], decoration[i][1]) if decoration[i]
200
- str << "\e[0m #{' ' * max_lineno_length} :\n" if last_i + 1 != i
201
- str << (lineno == i ? "\e[0m => \e[1;34m" : "\e[0m \e[1;34m")
202
- str << sprintf("%#{max_lineno_length}d\e[0m: %s\n", i, line)
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)
203
111
  last_i = i
204
112
  end
205
- return str
113
+ str
206
114
  end
207
-
208
- def write(*str)
209
- message = nil
210
- if str.join.force_encoding("utf-8").match(/^(.+?):(\d+):\s*warning:\s*/)
211
- path, lineno, warning = $1, $2.to_i, $'.strip
212
- message = warning_message(path, lineno, warning)
115
+
116
+
117
+ def eturem_prepare
118
+ this_filepath = File.expand_path(__FILE__)
119
+ @eturem_backtrace_locations = self.backtrace_locations || []
120
+ index = @eturem_backtrace_locations.index do |location|
121
+ File.expand_path(location.path) == this_filepath
122
+ end
123
+ @eturem_backtrace_locations = @eturem_backtrace_locations[0...index] if index
124
+
125
+ program_filepath = File.expand_path(Eturem.program_name)
126
+ @eturem_backtrace_locations.each do |location|
127
+ if File.expand_path(location.path) == program_filepath
128
+ def location.path; Eturem.program_name; end
129
+ if location.label == "<top (required)>"
130
+ def location.label; "<main>"; end
131
+ end
132
+ end
133
+ end
134
+
135
+ @eturem_message = self.message
136
+ unless @eturem_message.encoding == Encoding::UTF_8
137
+ @eturem_message.force_encoding("utf-8")
138
+ unless @eturem_message.valid_encoding?
139
+ @eturem_message.force_encoding(Encoding.locale_charmap).encode!("utf-8")
140
+ end
213
141
  end
214
- if message
215
- @stderr.write(message)
142
+
143
+ if self.is_a?(SyntaxError) && @eturem_message.match(/\A(?<path>.+?)\:(?<lineno>\d+):\s*/)
144
+ @eturem_path = Regexp.last_match(:path)
145
+ @eturem_lineno = Regexp.last_match(:lineno).to_i
146
+ @eturem_message = Regexp.last_match.post_match
147
+ @eturem_path = Eturem.program_name if @eturem_path == program_filepath
216
148
  else
217
- @stderr.write(*str)
149
+ eturem_backtrace_locations_shift
218
150
  end
151
+
152
+ @eturem_script_lines = Eturem::Base.read_script(@eturem_path)
153
+ @eturem_output_linenos = eturem_default_output_linenos
219
154
  end
220
-
221
- def comeback_stderr
222
- $stderr = @stderr || STDERR
155
+
156
+ def eturem_original_error_message()
157
+ @eturem_message.match(/\A(?<first_line>.*)/)
158
+ "#{@eturem_path}:#{@eturem_lineno}:in `#{@eturem_label}': " +
159
+ "\e[1m#{Regexp.last_match(:first_line)} (\e[4m#{self.class}\e[0;1m)" +
160
+ "#{Regexp.last_match.post_match.chomp}\e[0m\n"
223
161
  end
224
-
225
- def to_s
226
- @exception_s
162
+
163
+ def eturem_backtrace_str(order = :bottom)
164
+ str = @eturem_backtrace_locations.empty? ? "" : eturem_traceback(order)
165
+ str + (order == :top ? eturem_backtrace_str_top : eturem_backtrace_str_bottom)
227
166
  end
228
-
229
- private
230
-
231
- def prepare
232
- case @exception
233
- when NameError then prepare_name_error
234
- when ArgumentError then prepare_argument_error
167
+
168
+ def eturem_backtrace
169
+ eturem_backtrace_locations.map do |location|
170
+ eturem_location_to_s(location)
235
171
  end
236
172
  end
237
-
238
- def prepare_name_error
239
- return unless @exception_s.match(/Did you mean\?/)
240
- @did_you_mean = Regexp.last_match.post_match.strip.scan(/\S+/)
241
- @decoration[@lineno] = [@exception.name.to_s, "\e[1;31m\e[4m"]
242
-
243
- @did_you_mean.each do |name|
244
- index = @script_lines.index { |line| line.include?(name) }
245
- next unless index
246
- @decoration[index] = [name, "\e[1;33m"]
247
- @output_linenos.push(index)
248
- end
173
+
174
+ def eturem_location_to_s(location)
175
+ "#{location.path}:#{location.lineno}:in `#{location.label}'"
249
176
  end
250
-
251
- def prepare_argument_error
252
- @method = @label
253
- backtrace_locations_shift
254
- @script_lines = read_script(@path)
255
- @output_linenos = default_output_linenos
177
+
178
+ def eturem_backtrace_locations
179
+ @eturem_backtrace_locations
256
180
  end
257
-
258
- def backtrace_locations_shift
259
- @label = @backtrace_locations.first.label
260
- @path = @backtrace_locations.first.path
261
- @lineno = @backtrace_locations.first.lineno
262
- @backtrace_locations.shift
181
+
182
+ def eturem_message
183
+ ""
184
+ end
185
+
186
+ def eturem_script
187
+ Eturem::Base.script(@eturem_script_lines, @eturem_output_linenos, @eturem_lineno)
263
188
  end
264
-
265
- def traceback_most_recent_call_last
266
- "Traceback (most recent call last):"
189
+
190
+ def eturem_full_message(highlight: true, order: :bottom)
191
+ unless $stderr == STDERR && $stderr.tty?
192
+ highlight = false
193
+ order = :top
194
+ end
195
+
196
+ str = @@eturem_output_backtrace ? eturem_backtrace_str(order) : ""
197
+ ext_message = eturem_message
198
+ if ext_message.empty?
199
+ str += eturem_original_error_message
200
+ else
201
+ str = "#{eturem_original_error_message}\n#{str}" if @@eturem_output_original
202
+ str += "#{ext_message}\n"
203
+ end
204
+ str += eturem_script if @@eturem_output_script
205
+
206
+ highlight ? str : Eturem::Base.unhighlight(str)
267
207
  end
268
-
269
- def location_inspect(location)
270
- "from #{location.path}:#{location.lineno}:in `#{location.label}'"
208
+
209
+ private
210
+
211
+ def eturem_backtrace_locations_shift
212
+ location = @eturem_backtrace_locations.shift
213
+ @eturem_label = location.label
214
+ @eturem_path = location.path
215
+ @eturem_lineno = location.lineno
271
216
  end
272
-
273
- def default_output_linenos
274
- from = [1, @lineno - @@before_line_num].max
275
- to = [@script_lines.size - 1, @lineno + @@after_line_num].min
217
+
218
+ def eturem_default_output_linenos
219
+ from = [1, @eturem_lineno - @@eturem_before_line_num].max
220
+ to = [@eturem_script_lines.size - 1, @eturem_lineno + @@eturem_after_line_num].min
276
221
  (from..to).to_a
277
222
  end
278
-
279
- def highlight(str, keyword, color)
280
- str.to_s.gsub(keyword){ "#{color}#{$&}\e[0m" }
223
+
224
+ def eturem_traceback(order = :bottom)
225
+ order == :top ? "" : "\e[1mTraceback\e[0m (most recent call last):\n"
281
226
  end
282
-
283
- def warning_message(file, line, warning)
284
- case warning
285
- when "found `= literal' in conditional, should be =="
286
- "#{file}:#{line}: warning: #{warning}\n" +
287
- script_inspect(file, [line], line, { line => [/(?<![><!=])=(?!=)/, "\e[1;31m\e[4m"] })
288
- else
289
- nil
227
+
228
+ def eturem_backtrace_str_bottom
229
+ lines = []
230
+ backtrace = eturem_backtrace
231
+ size = backtrace.size
232
+ format = "%#{8 + size.to_s.length}d: %s\n"
233
+ backtrace.reverse.each_with_index do |bt, i|
234
+ lines.push(sprintf(format, size - i, bt))
290
235
  end
236
+
237
+ if @eturem_message == "stack level too deep"
238
+ lines = lines[-4..-1] +
239
+ [" ... #{lines.size - 12} levels...\n"] +
240
+ lines[0..7]
241
+ end
242
+ lines.join
291
243
  end
292
-
293
- def read_script(file)
294
- unless @scripts[file]
295
- script = Eturem.read_script(file)
296
- return nil unless script
297
- script.encode!("utf-8")
298
- if @@use_coderay
299
- require "coderay"
300
- script = CodeRay.scan(script, :ruby).terminal
301
- end
302
- @scripts[file] = [""] + script.lines(chomp: true)
244
+
245
+ def eturem_backtrace_str_top
246
+ lines = eturem_backtrace.map do |bt|
247
+ " from #{bt}\n"
303
248
  end
304
- return @scripts[file]
249
+ if @eturem_message == "stack level too deep"
250
+ lines = lines[0..7] +
251
+ [" ... #{lines.size - 12} levels...\n"] +
252
+ lines[-4..-1]
253
+ end
254
+ lines.join
305
255
  end
306
256
  end
257
+
258
+
259
+ module ExceptionExt
260
+ include Base
261
+ end
262
+
263
+
264
+ module NameErrorExt
265
+ include ExceptionExt
266
+
267
+ def eturem_prepare()
268
+ @eturem_corrections = self.respond_to?(:corrections) ? self.corrections : []
269
+ @eturem_corrections += Object.constants.select do |const|
270
+ const.casecmp?(self.name)
271
+ end
272
+ @eturem_corrections.uniq!
273
+ def self.corrections; @eturem_corrections; end
274
+ super
275
+ uname = self.name.to_s.encode("utf-8")
276
+ if @eturem_script_lines[@eturem_lineno]
277
+ Eturem::Base.highlight(@eturem_script_lines[@eturem_lineno], uname, "\e[1;4;31m")
278
+ end
279
+ @eturem_corrections.each do |name|
280
+ index = @eturem_script_lines.index { |line| line.include?(name.to_s.encode("utf-8")) }
281
+ next unless index
282
+ Eturem::Base.highlight(@eturem_script_lines[index], name.to_s.encode("utf-8"), "\e[1;33m")
283
+ @eturem_output_linenos.push(index)
284
+ end
285
+ end
286
+ end
287
+
288
+
289
+ module ArgumentErrorExt
290
+ include ExceptionExt
291
+
292
+ def eturem_prepare()
293
+ super
294
+ @eturem_method = @eturem_label
295
+ eturem_backtrace_locations_shift
296
+ @eturem_script_lines = Eturem::Base.read_script(@eturem_path)
297
+ @eturem_output_linenos = eturem_default_output_linenos
298
+ end
299
+ end
300
+
301
+
302
+ module SyntaxErrorExt
303
+ include ExceptionExt
307
304
 
308
- @eturem_class = Base
305
+ def eturem_original_error_message()
306
+ ret = "#{@eturem_path}:#{@eturem_lineno}: #{@eturem_message}"
307
+ unless @eturem_path == Eturem.program_name
308
+ ret = "\e[1m#{ret} (\e[4m#{self.class}\e[0;1m)\e[0m"
309
+ end
310
+ ret + "\n"
311
+ end
312
+ end
309
313
  end