lib/sus/output/backtrace.rb



# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2022-2024, by Samuel Williams.

module Sus
	module Output
		# Print out a backtrace relevant to the given test identity if provided.
		class Backtrace
			def self.first(identity = nil)
				# This implementation could be a little more efficient.
				self.new(caller_locations(1), identity&.path, 1)
			end
			
			def self.for(exception, identity = nil)
				# I've disabled the root filter here, because partial backtraces are not very useful.
				# We might want to do something to improve presentation of the backtrace based on the root instead.
				self.new(extract_stack(exception), identity&.path)
			end
			
			Location = Struct.new(:path, :lineno, :label)
			
			def self.extract_stack(exception)
				if stack = exception.backtrace_locations
					return stack
				elsif stack = exception.backtrace
					return stack.map do |line|
						Location.new(*line.split(":", 3))
					end
				else
					[]
				end
			end
			
			def initialize(stack, root = nil, limit = nil)
				@stack = stack
				@root = root
				@limit = limit
			end
			
			attr :stack
			attr :root
			attr :limit
			
			def filter(root: @root, limit: @limit)
				if root
					if limit
						return @stack.lazy.select do |frame|
							frame.path.start_with?(root)
						end.first(limit)
					else
						return up_to_and_matching(@stack) do |frame|
							frame.path.start_with?(root)
						end
					end
				elsif limit
					return @stack.first(limit)
				else
					return @stack
				end
			end
			
			def print(output)
				if @limit == 1
					filter.each do |frame|
						output.write " ", :path, frame.path, :line, ":", frame.lineno
					end
				else
					output.indented do
						filter.each do |frame|
							output.puts :indent, :path, frame.path, :line, ":", frame.lineno, :reset, " ", frame.label
						end
					end
				end
			end
			
			private def up_to_and_matching(things, &block)
				preface = true
				things.select do |thing|
					if preface
						if yield(thing)
							preface = false
						end
						true
					elsif yield(thing)
						true
					else
						false
					end
				end
			end
		end
	end
end