lib/canon/diff_formatter/by_line_formatter.rb



# frozen_string_literal: true

require "paint" unless RUBY_ENGINE == "opal"

module Canon
  class DiffFormatter
    # Handles the by_line rendering pipeline for line-by-line diffs.
    #
    # Receives preprocessed document strings from the DiffFormatter facade
    # and delegates to format-specific ByLine formatters (XML, HTML, JSON, YAML).
    class ByLineFormatter
      # rubocop:disable Metrics/ParameterLists
      def initialize(use_color:, visualization_map:, context_lines:,
                     diff_grouping_lines:, show_diffs:, character_visualization:,
                     legacy_terminal:, diff_mode:)
        @use_color = use_color
        @visualization_map = visualization_map
        @context_lines = context_lines
        @diff_grouping_lines = diff_grouping_lines
        @show_diffs = show_diffs
        @character_visualization = character_visualization
        @legacy_terminal = legacy_terminal
        @diff_mode = diff_mode
      end
      # rubocop:enable Metrics/ParameterLists

      # Format a line-by-line diff between two documents.
      #
      # @param doc1 [String] First document (already preprocessed)
      # @param doc2 [String] Second document (already preprocessed)
      # @param format [Symbol] Document format (:xml, :html, :json, :yaml, etc.)
      # @param html_version [Symbol, nil] HTML version override (:html4, :html5)
      # @param differences [Array, ComparisonResult] Differences from comparison
      # @return [String] Formatted diff output
      def format(doc1, doc2, format:, html_version: nil, differences: [])
        resolved_format = format == :html && html_version ? html_version : format
        format_name = resolved_format.to_s.upcase

        output = []
        output << colorize("Line-by-line diff (#{format_name} mode):", :cyan,
                           :bold)

        return output.join("\n") if doc1.nil? || doc2.nil?

        diffs_array = extract_differences(differences)

        formatter = ByLine::BaseFormatter.for_format(
          resolved_format,
          use_color: @use_color,
          context_lines: @context_lines,
          diff_grouping_lines: @diff_grouping_lines,
          visualization_map: @visualization_map,
          show_diffs: @show_diffs,
          differences: diffs_array,
          diff_mode: @legacy_terminal ? :separate : @diff_mode,
          legacy_terminal: @legacy_terminal,
          equivalent: @comparison_equivalent,
          character_visualization: @character_visualization,
        )

        output << formatter.format(doc1, doc2)
        output.join("\n")
      end

      private

      def extract_differences(differences)
        if differences.is_a?(Canon::Comparison::ComparisonResult)
          @comparison_equivalent = differences.equivalent?
          differences.differences
        else
          @comparison_equivalent = nil
          differences
        end
      end

      def colorize(text, *colors)
        return text unless @use_color

        "\e[0m#{Paint[text, *colors]}"
      end
    end
  end
end