hydraulic_brake 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.rbenv-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3-p194
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3-p194@airbrake --create
data/.yardopts ADDED
@@ -0,0 +1,2 @@
1
+ -
2
+ MIT-LICENSE
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "builder"
4
+
5
+ group "development" do
6
+ gem "bourne", ">= 1.0"
7
+ gem "cucumber", "~> 0.10.6"
8
+ gem "fakeweb", "~> 1.3.0"
9
+ gem "jeweler"
10
+ gem "nokogiri", "~> 1.4.3.1"
11
+ gem "rake"
12
+ gem "rspec", "~> 2.6.0"
13
+ gem "shoulda", "~> 2.11.3"
14
+ gem "simplecov"
15
+ gem "yard"
16
+ end
data/INSTALL ADDED
@@ -0,0 +1,20 @@
1
+ === Configuration
2
+
3
+ You should have something like this in config/initializers/hydraulic_brake.rb.
4
+
5
+ HydraulicBrake.configure do |config|
6
+ config.api_key = '1234567890abcdef'
7
+ end
8
+
9
+ (Please note that this configuration should be in a global configuration, and
10
+ is *not* environment-specific. HydraulicBrake is smart enough to know what
11
+ errors are caused by what environments, so your staging errors don't get mixed
12
+ in with your production errors.)
13
+
14
+ You can test that HydraulicBrake is working in your production environment by
15
+ using this rake task:
16
+
17
+ rake hydraulicbrake:test
18
+
19
+ If everything is configured properly, that task will send a notice to Airbrake
20
+ which will be visible immediately.
data/MIT-LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2007 - 2012, Exceptional DBA Airbrake.io
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,103 @@
1
+ HydraulicBrake
2
+ ========
3
+
4
+ This is a replacement notifier gem for the [Airbrake
5
+ gem](https://github.com/airbrake/airbrake) which is used
6
+ for integrating ruby apps with [Airbrake](http://airbrake.io).
7
+
8
+ ### Transitioning from Airbrake to HydraulicBrake
9
+
10
+ HydraulicBrake is a lighter weight alternative to the [Airbrake
11
+ gem](https://github.com/airbrake/airbrake) and takes a different design
12
+ approach. HydraulicBrake doesn't attempt to integrate with any
13
+ frameworks and has few dependencies. HydraulicBrake doesn't change
14
+ anything outside its own namespace. It doesn't do any automatic
15
+ exception handling or configuration.
16
+
17
+ Configuration
18
+ -------------
19
+
20
+ Configure HydraulicBrake when your app starts with a configuration block
21
+ like this one:
22
+
23
+ HydraulicBrake.configure do |config|
24
+ config.host = "api.airbrake.io"
25
+ config.port = "443"
26
+ config.secure = true
27
+ config.environment_name = "staging"
28
+ config.api_key = "<api-key-from-your-airbrake-server>"
29
+ end
30
+
31
+ Usage
32
+ -----
33
+
34
+ Wherever you want to notify an Airbrake server, just call
35
+ HydraulicBrake#notify
36
+
37
+ begin
38
+ params = {
39
+ # params that you pass to a method that can throw an exception
40
+ }
41
+ my_unpredicable_method(params)
42
+ rescue => e
43
+ HydraulicBrake.notify(
44
+ :error_class => "Special Error",
45
+ :error_message => "Special Error: #{e.message}",
46
+ :parameters => params
47
+ )
48
+ end
49
+
50
+ HydraulicBrake merges the hash you pass with these default options:
51
+
52
+ {
53
+ :api_key => HydraulicBrake.api_key,
54
+ :error_message => 'Notification',
55
+ :backtrace => caller,
56
+ :parameters => {},
57
+ :session => {}
58
+ }
59
+
60
+ You can override any of those parameters.
61
+
62
+ Async Notifications
63
+ -------------------
64
+
65
+ HydraulicBrake doesn't provide anything for async notifications, and
66
+ that's a good thing. Just wrap your calls to Airbrake#notify in the
67
+ async library of your choice.
68
+
69
+ Proxy Support
70
+ -------------
71
+
72
+ The notifier supports using a proxy, if your server is not able to
73
+ directly reach the Airbrake servers. To configure the proxy settings,
74
+ added the following information to your HydraulicBrake configuration
75
+ block.
76
+
77
+ HydraulicBrake.configure do |config|
78
+ config.proxy_host = proxy.host.com
79
+ config.proxy_port = 4038
80
+ config.proxy_user = foo # optional
81
+ config.proxy_pass = bar # optional
82
+
83
+ Logging
84
+ ------------
85
+
86
+ HydraulicBrake uses STDOUT by default. If you don't like HydraulicBrake
87
+ scribbling to your standard output, just pass another `Logger` instance
88
+ inside your configuration:
89
+
90
+ HydraulicBrake.configure do |config|
91
+ ...
92
+ config.logger = Logger.new("path/to/your/log/file")
93
+ end
94
+
95
+ Credits
96
+ -------
97
+
98
+ Thank you to all [the airbrake contributors](https://github.com/airbrake/airbrake/contributors)!
99
+
100
+ License
101
+ -------
102
+
103
+ Airbrake is Copyright © 2008-2012 Airbrake. It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.
data/Rakefile ADDED
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ gem.name = "hydraulic_brake"
17
+ gem.homepage = "http://github.com/stevecrozz/hydraulic_brake"
18
+ gem.license = "MIT"
19
+ gem.summary = "Simple Airbrake client"
20
+ gem.description = %Q{Sends notifications to an Airbrake server}
21
+ gem.email = "stevecrozz@gmail.com"
22
+ gem.authors = ["Stephen Crosby"]
23
+ gem.require_paths = ["lib"]
24
+ end
25
+ Jeweler::RubygemsDotOrgTasks.new
26
+
27
+ require 'rake/testtask'
28
+ Rake::TestTask.new(:test) do |test|
29
+ test.libs << 'lib' << 'test'
30
+ test.pattern = 'test/*_test.rb'
31
+ test.verbose = true
32
+ end
33
+
34
+ require 'yard'
35
+ YARD::Rake::YardocTask.new do |t|
36
+ t.files = ['lib/**/*.rb']
37
+ end
38
+
39
+ task :default => :test
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,27 @@
1
+ Feature: Use the Gem to catch errors in a Rake application
2
+ Background:
3
+ Given I have built and installed the "airbrake" gem
4
+
5
+ Scenario: Catching exceptions in Rake
6
+ When I run rake with airbrake
7
+ Then Airbrake should catch the exception
8
+
9
+ Scenario: Falling back to default handler before Airbrake is configured
10
+ When I run rake with airbrake not yet configured
11
+ Then Airbrake should not catch the exception
12
+
13
+ Scenario: Disabling Rake exception catcher
14
+ When I run rake with airbrake disabled
15
+ Then Airbrake should not catch the exception
16
+
17
+ Scenario: Autodetect, running from terminal
18
+ When I run rake with airbrake autodetect from terminal
19
+ Then Airbrake should not catch the exception
20
+
21
+ Scenario: Autodetect, not running from terminal
22
+ When I run rake with airbrake autodetect not from terminal
23
+ Then Airbrake should catch the exception
24
+
25
+ Scenario: Sending the correct component name
26
+ When I run rake with airbrake
27
+ Then Airbrake should send the rake command line as the component name
@@ -0,0 +1,17 @@
1
+ When /I run rake with (.+)/ do |command|
2
+ @rake_command = "rake #{command.gsub(' ','_')}"
3
+ @rake_result = `cd features/support/rake && GEM_HOME=#{BUILT_GEM_ROOT} #{@rake_command} 2>&1`
4
+ end
5
+
6
+ Then /Airbrake should (|not) ?catch the exception/ do |condition|
7
+ if condition=='not'
8
+ @rake_result.should_not =~ /^airbrake/
9
+ else
10
+ @rake_result.should =~ /^airbrake/
11
+ end
12
+ end
13
+
14
+ Then /Airbrake should send the rake command line as the component name/ do
15
+ component = @rake_result.match(/^airbrake (.*)$/)[1]
16
+ component.should == @rake_command
17
+ end
@@ -0,0 +1,35 @@
1
+ RSpec::Matchers.define :have_content do |xpath, content|
2
+ match do |document|
3
+ @elements = document.search(xpath)
4
+
5
+ if @elements.empty?
6
+ false
7
+ else
8
+ element_with_content = document.at("#{xpath}[contains(.,'#{content}')]")
9
+
10
+ if element_with_content.nil?
11
+ @found = @elements.collect { |element| element.content }
12
+
13
+ false
14
+ else
15
+ true
16
+ end
17
+ end
18
+ end
19
+
20
+ failure_message_for_should do |document|
21
+ if @elements.empty?
22
+ "In XML:\n#{document}\nNo element at #{xpath}"
23
+ else
24
+ "In XML:\n#{document}\nGot content #{@found.inspect} at #{xpath} instead of #{content.inspect}"
25
+ end
26
+ end
27
+
28
+ failure_message_for_should_not do |document|
29
+ unless @elements.empty?
30
+ "In XML:\n#{document}\nExpcted no content #{content.inspect} at #{xpath}"
31
+ end
32
+ end
33
+ end
34
+
35
+ World(RSpec::Matchers)
@@ -0,0 +1,61 @@
1
+ # A test harness for RakeHandler
2
+ #
3
+ require 'rake'
4
+ require 'rubygems'
5
+ require 'hydraulic_brake'
6
+ require 'hydraulic_brake/rake_handler'
7
+
8
+ HydraulicBrake.configure do |c|
9
+ end
10
+
11
+ # Should catch exception
12
+ task :hydraulic_brake do
13
+ HydraulicBrake.configuration.rescue_rake_exceptions = true
14
+ stub_tty_output(true)
15
+ raise_exception
16
+ end
17
+
18
+ # Should not catch exception
19
+ task :hydraulic_brake_disabled do
20
+ HydraulicBrake.configuration.rescue_rake_exceptions = false
21
+ stub_tty_output(true)
22
+ raise_exception
23
+ end
24
+
25
+ # Should not catch exception as tty_output is true
26
+ task :hydraulic_brake_autodetect_from_terminal do
27
+ HydraulicBrake.configuration.rescue_rake_exceptions = nil
28
+ stub_tty_output(true)
29
+ raise_exception
30
+ end
31
+
32
+ # Should catch exception as tty_output is false
33
+ task :hydraulic_brake_autodetect_not_from_terminal do
34
+ HydraulicBrake.configuration.rescue_rake_exceptions = nil
35
+ stub_tty_output(false)
36
+ raise_exception
37
+ end
38
+
39
+ task :hydraulic_brake_not_yet_configured do
40
+ HydraulicBrake.configuration.rescue_rake_exceptions = true
41
+ stub_tty_output(true)
42
+ stub_empty_sender
43
+ raise_exception
44
+ end
45
+
46
+ def stub_empty_sender
47
+ HydraulicBrake.sender = nil
48
+ end
49
+
50
+ def stub_tty_output(value)
51
+ Rake.application.instance_eval do
52
+ @tty_output_stub = value
53
+ def tty_output?
54
+ @tty_output_stub
55
+ end
56
+ end
57
+ end
58
+
59
+ def raise_exception
60
+ raise 'TEST'
61
+ end
@@ -0,0 +1,107 @@
1
+ require 'fileutils'
2
+
3
+ Before do
4
+ @terminal = Terminal.new
5
+ end
6
+
7
+ After do |story|
8
+ if story.failed?
9
+ # puts @terminal.output
10
+ end
11
+ end
12
+
13
+ class Terminal
14
+ attr_reader :output, :status
15
+ attr_accessor :environment_variables, :invoke_heroku_rake_tasks_locally
16
+
17
+ def initialize
18
+ @cwd = FileUtils.pwd
19
+ @output = ""
20
+ @status = 0
21
+ @logger = Logger.new(File.join(TEMP_DIR, 'terminal.log'))
22
+
23
+ @invoke_heroku_rake_tasks_locally = false
24
+
25
+ @environment_variables = {
26
+ "GEM_HOME" => LOCAL_GEM_ROOT,
27
+ "GEM_PATH" => "#{LOCAL_GEM_ROOT}:#{BUILT_GEM_ROOT}",
28
+ "PATH" => "#{gem_bin_path}:#{ENV['PATH']}"
29
+ }
30
+ end
31
+
32
+ def cd(directory)
33
+ @cwd = directory
34
+ end
35
+
36
+ def run(command)
37
+ command = optionally_invoke_heroku_rake_tasks_locally(command)
38
+
39
+ output << "#{command}\n"
40
+ FileUtils.cd(@cwd) do
41
+ # The ; forces ruby to shell out so the env settings work right
42
+ cmdline = "#{environment_settings} #{command} 2>&1 ; "
43
+ logger.debug(cmdline)
44
+ result = `#{cmdline}`
45
+ logger.debug(result)
46
+ output << result
47
+ end
48
+ @status = $?
49
+ end
50
+
51
+ def optionally_invoke_heroku_rake_tasks_locally(command)
52
+ if invoke_heroku_rake_tasks_locally
53
+ command.sub(/^heroku /, '')
54
+ else
55
+ command
56
+ end
57
+ end
58
+
59
+ def echo(string)
60
+ logger.debug(string)
61
+ end
62
+
63
+ def flush!
64
+ @output = ""
65
+ end
66
+
67
+ def build_and_install_gem(gemspec)
68
+ pkg_dir = File.join(TEMP_DIR, 'pkg')
69
+ FileUtils.mkdir_p(pkg_dir)
70
+ output = `gem build #{gemspec} 2>&1`
71
+ gem_file = Dir.glob("*.gem").first
72
+ unless gem_file
73
+ raise "Gem didn't build:\n#{output}"
74
+ end
75
+ target = File.join(pkg_dir, gem_file)
76
+ FileUtils.mv(gem_file, target)
77
+ install_gem_to(LOCAL_GEM_ROOT, target)
78
+ end
79
+
80
+ def install_gem(gem)
81
+ install_gem_to(LOCAL_GEM_ROOT, gem)
82
+ end
83
+
84
+ def uninstall_gem(gem)
85
+ `gem uninstall -i #{LOCAL_GEM_ROOT} #{gem}`
86
+ end
87
+
88
+ def prepend_path(path)
89
+ @environment_variables['PATH'] = path + ":" + @environment_variables['PATH']
90
+ end
91
+
92
+ private
93
+
94
+ def install_gem_to(root, gem)
95
+ `gem install -i #{root} --no-ri --no-rdoc #{gem}`
96
+ end
97
+
98
+ def environment_settings
99
+ @environment_variables.map { |key, value| "#{key}=#{value}" }.join(' ')
100
+ end
101
+
102
+ def gem_bin_path
103
+ File.join(LOCAL_GEM_ROOT, "bin")
104
+ end
105
+
106
+ attr_reader :logger
107
+ end
@@ -0,0 +1,102 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "hydraulic_brake"
8
+ s.version = "0.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Stephen Crosby"]
12
+ s.date = "2013-07-14"
13
+ s.description = "Sends notifications to an Airbrake server"
14
+ s.email = "stevecrozz@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ ".rbenv-version",
20
+ ".rvmrc",
21
+ ".yardopts",
22
+ "Gemfile",
23
+ "INSTALL",
24
+ "MIT-LICENSE",
25
+ "README.md",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "features/rake.feature",
29
+ "features/step_definitions/rake_steps.rb",
30
+ "features/support/matchers.rb",
31
+ "features/support/rake/Rakefile",
32
+ "features/support/terminal.rb",
33
+ "hydraulic_brake.gemspec",
34
+ "lib/hydraulic_brake.rb",
35
+ "lib/hydraulic_brake/backtrace.rb",
36
+ "lib/hydraulic_brake/configuration.rb",
37
+ "lib/hydraulic_brake/notice.rb",
38
+ "lib/hydraulic_brake/sender.rb",
39
+ "lib/hydraulic_brake/version.rb",
40
+ "lib/hydraulic_brake_tasks.rb",
41
+ "resources/README.md",
42
+ "resources/ca-bundle.crt",
43
+ "script/integration_test.rb",
44
+ "test/airbrake_2_3.xsd",
45
+ "test/backtrace_test.rb",
46
+ "test/configuration_test.rb",
47
+ "test/helper.rb",
48
+ "test/logger_test.rb",
49
+ "test/notice_test.rb",
50
+ "test/notifier_test.rb",
51
+ "test/recursion_test.rb",
52
+ "test/sender_test.rb"
53
+ ]
54
+ s.homepage = "http://github.com/stevecrozz/hydraulic_brake"
55
+ s.licenses = ["MIT"]
56
+ s.require_paths = ["lib"]
57
+ s.rubygems_version = "1.8.23"
58
+ s.summary = "Simple Airbrake client"
59
+
60
+ if s.respond_to? :specification_version then
61
+ s.specification_version = 3
62
+
63
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
64
+ s.add_runtime_dependency(%q<builder>, [">= 0"])
65
+ s.add_development_dependency(%q<bourne>, [">= 1.0"])
66
+ s.add_development_dependency(%q<cucumber>, ["~> 0.10.6"])
67
+ s.add_development_dependency(%q<fakeweb>, ["~> 1.3.0"])
68
+ s.add_development_dependency(%q<jeweler>, [">= 0"])
69
+ s.add_development_dependency(%q<nokogiri>, ["~> 1.4.3.1"])
70
+ s.add_development_dependency(%q<rake>, [">= 0"])
71
+ s.add_development_dependency(%q<rspec>, ["~> 2.6.0"])
72
+ s.add_development_dependency(%q<shoulda>, ["~> 2.11.3"])
73
+ s.add_development_dependency(%q<simplecov>, [">= 0"])
74
+ s.add_development_dependency(%q<yard>, [">= 0"])
75
+ else
76
+ s.add_dependency(%q<builder>, [">= 0"])
77
+ s.add_dependency(%q<bourne>, [">= 1.0"])
78
+ s.add_dependency(%q<cucumber>, ["~> 0.10.6"])
79
+ s.add_dependency(%q<fakeweb>, ["~> 1.3.0"])
80
+ s.add_dependency(%q<jeweler>, [">= 0"])
81
+ s.add_dependency(%q<nokogiri>, ["~> 1.4.3.1"])
82
+ s.add_dependency(%q<rake>, [">= 0"])
83
+ s.add_dependency(%q<rspec>, ["~> 2.6.0"])
84
+ s.add_dependency(%q<shoulda>, ["~> 2.11.3"])
85
+ s.add_dependency(%q<simplecov>, [">= 0"])
86
+ s.add_dependency(%q<yard>, [">= 0"])
87
+ end
88
+ else
89
+ s.add_dependency(%q<builder>, [">= 0"])
90
+ s.add_dependency(%q<bourne>, [">= 1.0"])
91
+ s.add_dependency(%q<cucumber>, ["~> 0.10.6"])
92
+ s.add_dependency(%q<fakeweb>, ["~> 1.3.0"])
93
+ s.add_dependency(%q<jeweler>, [">= 0"])
94
+ s.add_dependency(%q<nokogiri>, ["~> 1.4.3.1"])
95
+ s.add_dependency(%q<rake>, [">= 0"])
96
+ s.add_dependency(%q<rspec>, ["~> 2.6.0"])
97
+ s.add_dependency(%q<shoulda>, ["~> 2.11.3"])
98
+ s.add_dependency(%q<simplecov>, [">= 0"])
99
+ s.add_dependency(%q<yard>, [">= 0"])
100
+ end
101
+ end
102
+
@@ -0,0 +1,108 @@
1
+ module HydraulicBrake
2
+ # Front end to parsing the backtrace for each notice
3
+ class Backtrace
4
+
5
+ # Handles backtrace parsing line by line
6
+ class Line
7
+
8
+ # regexp (optionnally allowing leading X: for windows support)
9
+ INPUT_FORMAT = %r{^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$}.freeze
10
+
11
+ # The file portion of the line (such as app/models/user.rb)
12
+ attr_reader :file
13
+
14
+ # The line number portion of the line
15
+ attr_reader :number
16
+
17
+ # The method of the line (such as index)
18
+ attr_reader :method
19
+
20
+ # Parses a single line of a given backtrace
21
+ # @param [String] unparsed_line The raw line from +caller+ or some backtrace
22
+ # @return [Line] The parsed backtrace line
23
+ def self.parse(unparsed_line)
24
+ _, file, number, method = unparsed_line.match(INPUT_FORMAT).to_a
25
+ new(file, number, method)
26
+ end
27
+
28
+ def initialize(file, number, method)
29
+ self.file = file
30
+ self.number = number
31
+ self.method = method
32
+ end
33
+
34
+ # Reconstructs the line in a readable fashion
35
+ def to_s
36
+ "#{file}:#{number}:in `#{method}'"
37
+ end
38
+
39
+ def ==(other)
40
+ to_s == other.to_s
41
+ end
42
+
43
+ def inspect
44
+ "<Line:#{to_s}>"
45
+ end
46
+
47
+ private
48
+
49
+ attr_writer :file, :number, :method
50
+ end
51
+
52
+ # holder for an Array of Backtrace::Line instances
53
+ attr_reader :lines
54
+
55
+ def self.parse(ruby_backtrace, opts = {})
56
+ ruby_lines = split_multiline_backtrace(ruby_backtrace)
57
+
58
+ filters = opts[:filters] || []
59
+ filtered_lines = ruby_lines.to_a.map do |line|
60
+ filters.inject(line) do |line, proc|
61
+ proc.call(line)
62
+ end
63
+ end.compact
64
+
65
+ lines = filtered_lines.collect do |unparsed_line|
66
+ Line.parse(unparsed_line)
67
+ end
68
+
69
+ instance = new(lines)
70
+ end
71
+
72
+ def initialize(lines)
73
+ self.lines = lines
74
+ end
75
+
76
+ def inspect
77
+ "<Backtrace: " + lines.collect { |line| line.inspect }.join(", ") + ">"
78
+ end
79
+
80
+ def to_s
81
+ content = []
82
+ lines.each do |line|
83
+ content << line
84
+ end
85
+ content.join("\n")
86
+ end
87
+
88
+ def ==(other)
89
+ if other.respond_to?(:lines)
90
+ lines == other.lines
91
+ else
92
+ false
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ attr_writer :lines
99
+
100
+ def self.split_multiline_backtrace(backtrace)
101
+ if backtrace.to_a.size == 1
102
+ backtrace.to_a.first.split(/\n\s*/)
103
+ else
104
+ backtrace
105
+ end
106
+ end
107
+ end
108
+ end