Class: Annotato::ColumnFormatter

Inherits:
Object
  • Object
show all
Extended by:
WrapHelper
Defined in:
lib/annotato/column_formatter.rb

Constant Summary

Constants included from WrapHelper

WrapHelper::MAX_LINE

Class Method Summary collapse

Methods included from WrapHelper

wrap_sql

Class Method Details

.build_default_block(value) ⇒ Object

Returns nil for non-JSON/empty values, or an Array of un-indented inner lines for multiline formatting: e.g. [""A",", ""B""] for a JSON array.



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/annotato/column_formatter.rb', line 78

def self.build_default_block(value)
  return nil if value.nil? || !value.is_a?(String)
  s = value.strip
  return nil unless s.start_with?("[") || s.start_with?("{")

  parsed = JSON.parse(s) rescue nil
  return nil unless parsed.is_a?(Array) || parsed.is_a?(Hash)

  if parsed.is_a?(Array)
    return nil if parsed.empty?

    # JSON array of strings
    parsed.map.with_index do |e, i|
      comma = i == parsed.size - 1 ? "" : ","
      %Q{"#{e}"#{comma}}
    end
  else
    return nil if parsed.empty?

    # JSON hash → key/value pairs
    parsed.map.with_index do |(k, v), i|
      comma = i == parsed.size - 1 ? "" : ","
      %Q{"#{k}": #{v.inspect}#{comma}}
    end
  end
end

.format(model, connection) ⇒ Object

Main entry: returns an array of comment lines per column, flattened.



11
12
13
14
15
16
17
18
19
20
21
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/annotato/column_formatter.rb', line 11

def self.format(model, connection)
  table_name     = model.table_name
  primary_key    = model.primary_key
  unique_indexes = connection.indexes(table_name).select(&:unique)
  columns        = connection.columns(table_name)

  # Collect native PG enum type names once up front (avoids N+1 queries).
  pg_enum_types = pg_enum_type_names(connection)

  # Compute max widths for name and sql_type so that the table lines up.
  name_width = columns.map(&:name).map(&:length).max
  type_width = columns.map(&:sql_type).map(&:length).max

  columns.flat_map do |col|
    name = col.name
    type = col.sql_type

    # Build the left-hand side and calculate indent.
    # Strip trailing padding spaces immediately — output must never have trailing whitespace.
    left = ("#  %-#{name_width}s :%-#{type_width}s" % [name, type]).rstrip
    # indent_size aligns multiline default content under the opening "default(" token.
    indent_size = left.length + 1
    indent_str  = " " * indent_size
    closing_indent_str = " " * (indent_size - 2)

    # Gather all options, pulling out default_lines if multiline.
    opts         = []
    default_block = build_default_block(col.default)
    if default_block
      opts << "__MULTILINE__"  # placeholder
    elsif !col.default.nil?
      opts << "default(#{col.default.inspect})"
    end
    opts << "not null"       unless col.null
    opts << "primary key"    if name == primary_key
    opts << "is an Array"    if type.end_with?("[]")
    opts << "unique"         if unique_indexes.any? { |idx| idx.columns == [name] }
    opts << "enum"           if pg_enum_types.include?(type.delete_suffix("[]"))
    opts << "comment: #{col.comment.inspect}" if col.respond_to?(:comment) && col.comment && !col.comment.empty?

    # Emit either a multiline block or a single line.
    if default_block
      # 1) opening line
      lines = ["#{left} default(#{col.default[0]}"]
      # 2) each interior line, prefixed by "# " + indent_str
      lines += default_block.map { |l| "# #{indent_str}#{l}" }
      # 3) closing line with trailing options
      closing = "# #{closing_indent_str}#{col.default[-1]})"
      trailing = opts.reject { |o| o == "__MULTILINE__" }
      closing += ", #{trailing.join(', ')}" unless trailing.empty?
      lines << closing
      lines
    else
      # single-line comment; wrap options onto continuation line if too long
      opts_str = opts.join(", ")
      line = opts_str.empty? ? left : "#{left} #{opts_str}"
      if line.length > WrapHelper::MAX_LINE && !opts_str.empty?
        [left, "#    #{opts_str}"]
      else
        [line]
      end
    end
  end
end