Class: Canon::DiffFormatter::ByLine::BaseFormatter

Inherits:
Object
  • Object
show all
Defined in:
lib/canon/diff_formatter/by_line/base_formatter.rb

Overview

Base formatter for line-by-line diffs Provides common LCS diff logic and hunk building

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(use_color: true, context_lines: 3, diff_grouping_lines: nil, visualization_map: nil, show_diffs: :all, differences: [], diff_mode: :separate, legacy_terminal: false, equivalent: nil, theme: nil, character_visualization: true) ⇒ BaseFormatter

rubocop:disable Metrics/ParameterLists



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 49

def initialize(use_color: true, context_lines: 3,
               diff_grouping_lines: nil, visualization_map: nil,
               show_diffs: :all, differences: [],
               diff_mode: :separate, legacy_terminal: false,
               equivalent: nil, theme: nil,
               character_visualization: true)
  @use_color = use_color
  @context_lines = context_lines
  @diff_grouping_lines = diff_grouping_lines
  @visualization_map = visualization_map
  @show_diffs = show_diffs
  @differences = differences
  @line_num_width = 4
  @diff_mode = legacy_terminal ? :separate : diff_mode
  @legacy_terminal = legacy_terminal
  @equivalent = equivalent
  @theme = theme
  @character_visualization = character_visualization
end

Instance Attribute Details

#context_linesObject (readonly)

Returns the value of attribute context_lines.



14
15
16
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 14

def context_lines
  @context_lines
end

#diff_grouping_linesObject (readonly)

Returns the value of attribute diff_grouping_lines.



14
15
16
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 14

def diff_grouping_lines
  @diff_grouping_lines
end

#diff_modeObject (readonly)

Returns the value of attribute diff_mode.



14
15
16
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 14

def diff_mode
  @diff_mode
end

#legacy_terminalObject (readonly)

Returns the value of attribute legacy_terminal.



14
15
16
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 14

def legacy_terminal
  @legacy_terminal
end

#show_diffsObject (readonly)

Returns the value of attribute show_diffs.



14
15
16
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 14

def show_diffs
  @show_diffs
end

#use_colorObject (readonly)

Returns the value of attribute use_color.



14
15
16
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 14

def use_color
  @use_color
end

#visualization_mapObject (readonly)

Returns the value of attribute visualization_map.



14
15
16
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 14

def visualization_map
  @visualization_map
end

Class Method Details

.for_format(format, **options) ⇒ BaseFormatter

Create a format-specific by-line formatter

Parameters:

  • format (Symbol)

    Format type (:xml, :html, :html4, :html5, :json, :yaml, :simple)

  • options (Hash)

    Formatting options

Returns:



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 22

def self.for_format(format, **options)
  case format
  when :xml
    require_relative "xml_formatter"
    XmlFormatter.new(**options)
  when :html, :html4, :html5
    require_relative "html_formatter"
    # Determine HTML version from format
    version = case format
              when :html5 then :html5
              when :html4 then :html4
              else :html4 # default to html4
              end
    HtmlFormatter.new(html_version: version, **options)
  when :json
    require_relative "json_formatter"
    JsonFormatter.new(**options)
  when :yaml
    require_relative "yaml_formatter"
    YamlFormatter.new(**options)
  else
    require_relative "simple_formatter"
    SimpleFormatter.new(**options)
  end
end

Instance Method Details

#apply_bg(presenter, bg_color) ⇒ Rainbow::Presenter

Apply a background color to a Rainbow presenter.

Parameters:

  • presenter (Rainbow::Presenter)

    The presenter to colorize

  • bg_color (Symbol)

    Background color like :red, :light_blue, etc.

Returns:

  • (Rainbow::Presenter)

    Colorized presenter



244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 244

def apply_bg(presenter, bg_color)
  return presenter unless bg_color

  case bg_color.to_s
  when /^light_(.+)$/
    # Rainbow doesn't support light_ backgrounds, use the base color
    base = $1.to_sym
    presenter.background(base)
  when "default", "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white"
    presenter.background(bg_color)
  else
    # Try as-is and let Rainbow handle unknown colors
    presenter.background(bg_color)
  end
end

#apply_color(presenter, color) ⇒ Rainbow::Presenter

Apply a color to a Rainbow presenter, normalizing bright_/light_ colors.

Parameters:

  • presenter (Rainbow::Presenter)

    The presenter to colorize

  • color (Symbol)

    Color like :bright_blue, :red, etc.

Returns:

  • (Rainbow::Presenter)

    Colorized presenter



234
235
236
237
238
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 234

def apply_color(presenter, color)
  valid_colors = normalize_color_for_rainbow(color)
  valid_colors.each { |c| presenter = presenter.send(c) }
  presenter
end

#apply_theme_style(text, style) ⇒ String

Apply full theme styling to text

Parameters:

  • text (String)

    Text to style

  • style (Hash)

    Style properties from theme (color, bg, bold, underline, strikethrough)

Returns:

  • (String)

    Styled text



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 89

def apply_theme_style(text, style)
  return text if style.empty? || !@use_color

  color = style[:color]
  bg = style[:bg]
  bold = style[:bold]
  underline = style[:underline]
  strikethrough = style[:strikethrough]

  # Apply visualization first
  visual = apply_visualization(text)

  return visual unless color || bg || bold || underline || strikethrough

  require "rainbow"
  rainbow = Rainbow.new
  rainbow.enabled = true
  presenter = rainbow.wrap(visual)

  if color && color != :default
    presenter = apply_color(presenter,
                            color)
  end
  presenter = apply_bg(presenter, bg) if bg
  presenter = presenter.bold if bold
  presenter = presenter.underline if underline
  presenter = presenter.cross_out if strikethrough

  presenter.to_s
end

#changed_content_stylesHash

Get changed content styles (old and new)

Returns:

  • (Hash)

    Keys: :content_old, :content_new



147
148
149
150
151
152
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 147

def changed_content_styles
  {
    content_old: theme_style(:diff, :changed, :content_old),
    content_new: theme_style(:diff, :changed, :content_new),
  }
end

#compute_line_num_width(doc1, doc2) ⇒ Object

Compute line number column width from document line counts



121
122
123
124
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 121

def compute_line_num_width(doc1, doc2)
  max_lines = [doc1.count("\n"), doc2.count("\n")].max
  @line_num_width = [max_lines.to_s.length, 4].max
end

#content_style(diff_type) ⇒ Hash

Get content style for a diff type

Parameters:

  • diff_type (Symbol)

    :removed, :added, :formatting, :informative

Returns:

  • (Hash)

    Style properties



141
142
143
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 141

def content_style(diff_type)
  theme_style(:diff, diff_type, :content)
end

#display_modeSymbol

Get display mode

Returns:

  • (Symbol)

    :separate, :inline, :mixed



174
175
176
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 174

def display_mode
  theme[:display_mode] || :separate
end

#format(doc1, doc2) ⇒ String

Format line-by-line diff Subclasses must implement this method

Parameters:

  • doc1 (String)

    First document

  • doc2 (String)

    Second document

Returns:

  • (String)

    Formatted diff

Raises:

  • (NotImplementedError)


266
267
268
269
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 266

def format(doc1, doc2)
  raise NotImplementedError,
        "Subclasses must implement the format method"
end

#marker_style(diff_type) ⇒ Hash

Get marker style for a diff type

Parameters:

  • diff_type (Symbol)

    :removed, :added, :changed, :formatting, :informative

Returns:

  • (Hash)

    Style properties



134
135
136
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 134

def marker_style(diff_type)
  theme_style(:diff, diff_type, :marker)
end

#normalize_color_for_rainbow(color) ⇒ Array<Symbol>

Normalize a color symbol for Rainbow presenter. Rainbow doesn’t support :bright_blue directly - instead it uses chained methods like .blue.bright or .bright.blue. This returns an array of method symbols to chain.

Parameters:

  • color (Symbol)

    Color like :bright_blue, :light_red, etc.

Returns:

  • (Array<Symbol>)

    Method chain for Rainbow



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 211

def normalize_color_for_rainbow(color)
  return [] if color.nil?

  case color.to_s
  when /^bright_(.+)$/
    # :bright_blue -> [:blue, :bright]
    base = $1.to_sym
    [base, :bright]
  when /^light_(.+)$/
    # :light_red -> Rainbow doesn't support light_, treat as white
    [:white]
  when "default", "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white"
    [color]
  else
    # Unknown color, return as-is and let Rainbow raise
    [color]
  end
end

#structure_color(element) ⇒ Symbol?

Get structure color

Parameters:

  • element (Symbol)

    :line_number, :pipe, :context

Returns:

  • (Symbol, nil)

    Color value



200
201
202
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 200

def structure_color(element)
  theme.dig(:structure, element, :color)
end

#structure_stylesHash

Get structure styles

Returns:

  • (Hash)

    Keys: :line_number, :pipe, :context



162
163
164
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 162

def structure_styles
  theme[:structure] || {}
end

#styled_marker(text, diff_type) ⇒ String

Apply marker styling using theme

Parameters:

  • text (String)

    Marker text (e.g., “-”, “+”, “*”)

  • diff_type (Symbol)

    Type of diff

Returns:

  • (String)

    Styled marker



182
183
184
185
186
187
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 182

def styled_marker(text, diff_type)
  style = marker_style(diff_type)
  return text unless @use_color && style[:color]

  apply_theme_style(text, style)
end

#themeHash

Get the resolved theme hash

Returns:

  • (Hash)

    Theme hash



72
73
74
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 72

def theme
  @theme ||= Theme.resolver(Canon::Config.instance).resolve
end

#theme_color(diff_type, element) ⇒ Symbol?

Get theme color for a specific diff type and element

Parameters:

  • diff_type (Symbol)

    :removed, :added, :changed, :formatting, :informative

  • element (Symbol)

    :marker, :content, :content_old, :content_new

Returns:

  • (Symbol, nil)

    Color value



193
194
195
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 193

def theme_color(diff_type, element)
  theme_style(:diff, diff_type, element)[:color]
end

#theme_style(section, diff_type, element) ⇒ Hash

Get theme by section and type

Parameters:

  • section (Symbol)

    e.g., :diff, :xml, :structure

  • diff_type (Symbol)

    e.g., :removed, :added, :changed

  • element (Symbol)

    e.g., :marker, :content

Returns:

  • (Hash)

    Style properties



81
82
83
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 81

def theme_style(section, diff_type, element)
  theme.dig(section, diff_type, element) || {}
end

#unchanged_content_styleHash

Get style for unchanged content

Returns:

  • (Hash)

    Style properties



156
157
158
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 156

def unchanged_content_style
  theme_style(:diff, :unchanged, :content)
end

#visualization_charsHash

Get visualization characters

Returns:

  • (Hash)

    Keys: :space, :tab, :newline, :nbsp



168
169
170
# File 'lib/canon/diff_formatter/by_line/base_formatter.rb', line 168

def visualization_chars
  theme[:visualization] || {}
end