├── Rakefile ├── .gitignore ├── Gemfile ├── lib ├── better_errors │ ├── version.rb │ ├── rails.rb │ ├── repl │ │ ├── basic.rb │ │ └── pry.rb │ ├── repl.rb │ ├── templates │ │ ├── variable_info.erb │ │ └── main.erb │ ├── core_ext │ │ └── exception.rb │ ├── middleware.rb │ ├── code_formatter.rb │ ├── error_page.rb │ └── stack_frame.rb └── better_errors.rb ├── spec ├── spec_helper.rb └── better_errors │ ├── support │ └── my_source.rb │ ├── repl │ └── basic_spec.rb │ ├── middleware_spec.rb │ ├── code_formatter_spec.rb │ ├── error_page_spec.rb │ └── stack_frame_spec.rb ├── LICENSE.txt ├── better_errors.gemspec └── README.md /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | tmp 3 | Gemfile.lock 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /lib/better_errors/version.rb: -------------------------------------------------------------------------------- 1 | module BetterErrors 2 | VERSION = "0.1.1" 3 | end 4 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $: << File.expand_path("../../lib", __FILE__) 2 | require "better_errors" 3 | require "ostruct" 4 | -------------------------------------------------------------------------------- /spec/better_errors/support/my_source.rb: -------------------------------------------------------------------------------- 1 | one 2 | two 3 | three 4 | four 5 | five 6 | six 7 | seven 8 | eight 9 | nine 10 | ten 11 | eleven 12 | twelve 13 | thirteen 14 | fourteen 15 | fifteen 16 | sixteen 17 | seventeen 18 | eighteen 19 | nineteen 20 | twenty 21 | -------------------------------------------------------------------------------- /lib/better_errors/rails.rb: -------------------------------------------------------------------------------- 1 | module BetterErrors 2 | class Railtie < Rails::Railtie 3 | initializer "better_errors.configure_rails_initialization" do 4 | unless Rails.env.production? 5 | Rails.application.middleware.use BetterErrors::Middleware 6 | BetterErrors.logger = Rails.logger 7 | BetterErrors.application_root = Rails.root.to_s 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/better_errors/repl/basic.rb: -------------------------------------------------------------------------------- 1 | module BetterErrors 2 | module REPL 3 | class Basic 4 | def initialize(binding) 5 | @binding = binding 6 | end 7 | 8 | def send_input(str) 9 | [execute(str), ">>"] 10 | end 11 | 12 | private 13 | def execute(str) 14 | "=> #{@binding.eval(str).inspect}\n" 15 | rescue Exception => e 16 | "!! #{e.inspect rescue e.class.to_s rescue "Exception"}\n" 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/better_errors/repl.rb: -------------------------------------------------------------------------------- 1 | module BetterErrors 2 | module REPL 3 | PROVIDERS = [ 4 | { impl: "better_errors/repl/basic", 5 | const: :Basic }, 6 | ] 7 | 8 | def self.provider 9 | @provider ||= const_get detect[:const] 10 | end 11 | 12 | def self.provider=(prov) 13 | @provider = prov 14 | end 15 | 16 | def self.detect 17 | PROVIDERS.find do |prov| 18 | test_provider prov 19 | end 20 | end 21 | 22 | def self.test_provider(provider) 23 | require provider[:impl] 24 | true 25 | rescue LoadError 26 | false 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/better_errors/templates/variable_info.erb: -------------------------------------------------------------------------------- 1 |
| <%= name %> | <%= value.inspect %> |
| <%= name %> | <%= value.inspect %> |
Source unavailable
" 27 | end 28 | 29 | def coderay_scanner 30 | ext = File.extname(filename) 31 | FILE_TYPES[ext] || :text 32 | end 33 | 34 | def formatted_lines 35 | line_range.zip(highlighted_lines).map do |current_line, str| 36 | class_name = current_line == line ? "highlight" : "" 37 | sprintf '%5d %s', class_name, current_line, str 38 | end 39 | end 40 | 41 | def highlighted_lines 42 | CodeRay.scan(context_lines.join, coderay_scanner).div(wrap: nil).lines 43 | end 44 | 45 | def context_lines 46 | range = line_range 47 | source_lines[(range.begin - 1)..(range.end - 1)] or raise Errno::EINVAL 48 | end 49 | 50 | def source_lines 51 | @source_lines ||= File.readlines(filename) 52 | end 53 | 54 | def line_range 55 | min = [line - context, 1].max 56 | max = [line + context, source_lines.count].min 57 | min..max 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/better_errors/repl/pry.rb: -------------------------------------------------------------------------------- 1 | require "fiber" 2 | require "pry" 3 | 4 | module BetterErrors 5 | module REPL 6 | class Pry 7 | class Input 8 | def readline 9 | Fiber.yield 10 | end 11 | end 12 | 13 | class Output 14 | def initialize 15 | @buffer = "" 16 | end 17 | 18 | def puts(*args) 19 | args.each do |arg| 20 | @buffer << "#{arg.chomp}\n" 21 | end 22 | end 23 | 24 | def tty? 25 | false 26 | end 27 | 28 | def read_buffer 29 | @buffer 30 | ensure 31 | @buffer = "" 32 | end 33 | end 34 | 35 | def initialize(binding) 36 | @fiber = Fiber.new do 37 | @pry.repl binding 38 | end 39 | @input = Input.new 40 | @output = Output.new 41 | @pry = ::Pry.new input: @input, output: @output 42 | @pry.hooks.clear_all 43 | @continued_expression = false 44 | @pry.hooks.add_hook :after_read, "better_errors hacky hook" do 45 | @continued_expression = false 46 | end 47 | @fiber.resume 48 | end 49 | 50 | def pry_indent 51 | @pry.instance_variable_get(:@indent) 52 | end 53 | 54 | def send_input(str) 55 | old_pry_config_color = ::Pry.config.color 56 | ::Pry.config.color = false 57 | @continued_expression = true 58 | @fiber.resume "#{str}\n" 59 | # TODO - indent with `pry_indent.current_prefix` 60 | # TODO - use proper pry prompt 61 | [@output.read_buffer, @continued_expression ? ".." : ">>"] 62 | ensure 63 | ::Pry.config.color = old_pry_config_color 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/better_errors/error_page_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | module BetterErrors 4 | describe ErrorPage do 5 | let(:exception) { raise ZeroDivisionError, "you divided by zero you silly goose!" rescue $! } 6 | 7 | let(:error_page) { ErrorPage.new exception, { "REQUEST_PATH" => "/some/path" } } 8 | 9 | let(:response) { error_page.render } 10 | 11 | let(:empty_binding) { 12 | local_a = :value_for_local_a 13 | local_b = :value_for_local_b 14 | 15 | @inst_c = :value_for_inst_c 16 | @inst_d = :value_for_inst_d 17 | 18 | binding 19 | } 20 | 21 | it "should include the error message" do 22 | response.should include("you divided by zero you silly goose!") 23 | end 24 | 25 | it "should include the request path" do 26 | response.should include("/some/path") 27 | end 28 | 29 | it "should include the exception class" do 30 | response.should include("ZeroDivisionError") 31 | end 32 | 33 | context "variable inspection" do 34 | let(:exception) { empty_binding.eval("raise") rescue $! } 35 | 36 | it "should show local variables" do 37 | html = error_page.do_variables("index" => 0)[:html] 38 | html.should include("local_a") 39 | html.should include(":value_for_local_a") 40 | html.should include("local_b") 41 | html.should include(":value_for_local_b") 42 | end 43 | 44 | it "should show instance variables" do 45 | html = error_page.do_variables("index" => 0)[:html] 46 | html.should include("inst_c") 47 | html.should include(":value_for_inst_c") 48 | html.should include("inst_d") 49 | html.should include(":value_for_inst_d") 50 | end 51 | end 52 | 53 | it "should not die if the source file is not a real filename" do 54 | exception.stub!(:backtrace).and_return([ 55 | "
<%= exception_message %>
653 |