# typed: strict # frozen_string_literal: true require_relative "d3" require "erb" module Spoom module Coverage class Template extend T::Sig extend T::Helpers abstract! # Create a new template from an Erb file path sig { params(template: String).void } def initialize(template:) @template = template end sig { returns(String) } def erb File.read(@template) end sig { returns(String) } def html ERB.new(erb).result(get_binding) end sig { returns(Binding) } def get_binding # rubocop:disable Naming/AccessorMethodName binding end end class Page < Template extend T::Sig extend T::Helpers abstract! TEMPLATE = T.let("#{Spoom::SPOOM_PATH}/templates/page.erb", String) sig { returns(String) } attr_reader :title sig { returns(D3::ColorPalette) } attr_reader :palette sig { params(title: String, palette: D3::ColorPalette, template: String).void } def initialize(title:, palette:, template: TEMPLATE) super(template: template) @title = title @palette = palette end sig { returns(String) } def header_style D3.header_style end sig { returns(String) } def header_script D3.header_script(palette) end sig { returns(String) } def header_html "

#{title}

" end sig { returns(String) } def body_html cards.map(&:html).join("\n") end sig { abstract.returns(T::Array[Cards::Card]) } def cards; end sig { returns(String) } def footer_html "Generated by spoom on #{Time.now.utc}." end end module Cards class Card < Template extend T::Sig TEMPLATE = T.let("#{Spoom::SPOOM_PATH}/templates/card.erb", String) sig { returns(T.nilable(String)) } attr_reader :title, :body sig { params(template: String, title: T.nilable(String), body: T.nilable(String)).void } def initialize(template: TEMPLATE, title: nil, body: nil) super(template: template) @title = title @body = body end end class Erb < Card extend T::Sig extend T::Helpers abstract! sig { void } def initialize; end sig { override.returns(String) } def html ERB.new(erb).result(get_binding) end sig { abstract.returns(String) } def erb; end end class Snapshot < Card extend T::Sig TEMPLATE = T.let("#{Spoom::SPOOM_PATH}/templates/card_snapshot.erb", String) sig { returns(Coverage::Snapshot) } attr_reader :snapshot sig { params(snapshot: Coverage::Snapshot, title: String).void } def initialize(snapshot:, title: "Snapshot") super(template: TEMPLATE, title: title) @snapshot = snapshot end sig { returns(D3::Pie::Sigils) } def pie_sigils D3::Pie::Sigils.new('pie_sigils', 'Sigils', snapshot) end sig { returns(D3::Pie::Calls) } def pie_calls D3::Pie::Calls.new('pie_calls', 'Calls', snapshot) end sig { returns(D3::Pie::Sigs) } def pie_sigs D3::Pie::Sigs.new('pie_sigs', 'Sigs', snapshot) end end class Map < Card extend T::Sig sig { params(sigils_tree: FileTree, title: String).void } def initialize(sigils_tree:, title: "Strictness Map") super(title: title, body: D3::CircleMap::Sigils.new("map_sigils", sigils_tree).html) end end class Timeline < Card extend T::Sig sig { params(title: String, timeline: D3::Timeline).void } def initialize(title:, timeline:) super(title: title, body: timeline.html) end class Sigils < Timeline extend T::Sig sig { params(snapshots: T::Array[Coverage::Snapshot], title: String).void } def initialize(snapshots:, title: "Sigils Timeline") super(title: title, timeline: D3::Timeline::Sigils.new("timeline_sigils", snapshots)) end end class Calls < Timeline extend T::Sig sig { params(snapshots: T::Array[Coverage::Snapshot], title: String).void } def initialize(snapshots:, title: "Calls Timeline") super(title: title, timeline: D3::Timeline::Calls.new("timeline_calls", snapshots)) end end class Sigs < Timeline extend T::Sig sig { params(snapshots: T::Array[Coverage::Snapshot], title: String).void } def initialize(snapshots:, title: "Signatures Timeline") super(title: title, timeline: D3::Timeline::Sigs.new("timeline_sigs", snapshots)) end end class Versions < Timeline extend T::Sig sig { params(snapshots: T::Array[Coverage::Snapshot], title: String).void } def initialize(snapshots:, title: "Sorbet Versions Timeline") super(title: title, timeline: D3::Timeline::Versions.new("timeline_versions", snapshots)) end end class Runtimes < Timeline extend T::Sig sig { params(snapshots: T::Array[Coverage::Snapshot], title: String).void } def initialize(snapshots:, title: "Sorbet Typechecking Time") super(title: title, timeline: D3::Timeline::Runtimes.new("timeline_runtimes", snapshots)) end end end class SorbetIntro < Erb extend T::Sig sig { params(sorbet_intro_commit: T.nilable(String), sorbet_intro_date: T.nilable(Time)).void } def initialize(sorbet_intro_commit: nil, sorbet_intro_date: nil) @sorbet_intro_commit = sorbet_intro_commit @sorbet_intro_date = sorbet_intro_date end sig { override.returns(String) } def erb <<~ERB
Typchecked by Sorbet since #{@sorbet_intro_date&.strftime('%F')} (commit #{@sorbet_intro_commit}).
ERB end end end class Report < Page extend T::Sig sig { returns(String) } attr_reader :project_name sig { returns(T.nilable(String)) } attr_reader :sorbet_intro_commit sig { returns(T.nilable(Time)) } attr_reader :sorbet_intro_date sig { returns(T::Array[Snapshot]) } attr_reader :snapshots sig { returns(FileTree) } attr_reader :sigils_tree sig do params( project_name: String, palette: D3::ColorPalette, snapshots: T::Array[Snapshot], sigils_tree: FileTree, sorbet_intro_commit: T.nilable(String), sorbet_intro_date: T.nilable(Time), ).void end def initialize( project_name:, palette:, snapshots:, sigils_tree:, sorbet_intro_commit: nil, sorbet_intro_date: nil ) super(title: project_name, palette: palette) @project_name = project_name @snapshots = snapshots @sigils_tree = sigils_tree @sorbet_intro_commit = sorbet_intro_commit @sorbet_intro_date = sorbet_intro_date end sig { override.returns(String) } def header_html last = T.must(snapshots.last) <<~ERB

#{project_name} #{last.commit_sha}

ERB end sig { override.returns(T::Array[Cards::Card]) } def cards last = T.must(snapshots.last) cards = [] cards << Cards::Snapshot.new(snapshot: last) cards << Cards::Map.new(sigils_tree: sigils_tree) cards << Cards::Timeline::Sigils.new(snapshots: snapshots) cards << Cards::Timeline::Calls.new(snapshots: snapshots) cards << Cards::Timeline::Sigs.new(snapshots: snapshots) cards << Cards::Timeline::Versions.new(snapshots: snapshots) cards << Cards::Timeline::Runtimes.new(snapshots: snapshots) cards << Cards::SorbetIntro.new(sorbet_intro_commit: sorbet_intro_commit, sorbet_intro_date: sorbet_intro_date) cards end end end end