annotato 0.1.15 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -1
- data/README.md +1 -0
- data/lib/annotato/annotation_builder.rb +10 -1
- data/lib/annotato/check_constraint_formatter.rb +17 -0
- data/lib/annotato/column_formatter.rb +21 -10
- data/lib/annotato/enum_formatter.rb +40 -16
- data/lib/annotato/index_formatter.rb +2 -66
- data/lib/annotato/model_annotator.rb +8 -9
- data/lib/annotato/trigger_formatter.rb +5 -5
- data/lib/annotato/version.rb +1 -1
- data/lib/annotato/wrap_helper.rb +68 -0
- data/lib/tasks/annotato.rake +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ad60c697f9ee0213951d02981190dee26a8db0a2e48ab36d3118c1aaf95d4d40
|
|
4
|
+
data.tar.gz: 43a5f89b6509a636d579416c92bbbc52c63e15b8029701b46c525bd651a049a1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7753fdc81bfc3568053c99f3f4f98107d0ca3b2ad1b64b127e19902e00f35a8b889b86b2219a431ea2dd673bab86000d0705b59d7a88ef1c68192be2cf012d3e
|
|
7
|
+
data.tar.gz: d05dd1bb4dd969a77555c3685ab129a9f31a479c5d2f69ef80c9ed7971677c044bc918ec2fd7e1212bc90eb49e678acd306f8afe66d7c9b743ea3b9dc3b9e650
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "wrap_helper"
|
|
3
4
|
require_relative "column_formatter"
|
|
4
5
|
require_relative "index_formatter"
|
|
5
6
|
require_relative "trigger_formatter"
|
|
6
7
|
require_relative "line_formatter"
|
|
7
8
|
require_relative "enum_formatter"
|
|
9
|
+
require_relative "check_constraint_formatter"
|
|
8
10
|
|
|
9
11
|
module Annotato
|
|
10
12
|
class AnnotationBuilder
|
|
@@ -20,7 +22,7 @@ module Annotato
|
|
|
20
22
|
lines << "Columns:"
|
|
21
23
|
lines += ColumnFormatter.format(model, conn)
|
|
22
24
|
|
|
23
|
-
enums = EnumFormatter.format(
|
|
25
|
+
enums = EnumFormatter.format(conn, conn.columns(table_name))
|
|
24
26
|
unless enums.empty?
|
|
25
27
|
lines << ""
|
|
26
28
|
lines << "Enums:"
|
|
@@ -41,6 +43,13 @@ module Annotato
|
|
|
41
43
|
lines += triggers
|
|
42
44
|
end
|
|
43
45
|
|
|
46
|
+
check_constraints = CheckConstraintFormatter.format(conn, table_name)
|
|
47
|
+
unless check_constraints.empty?
|
|
48
|
+
lines << ""
|
|
49
|
+
lines << "Check Constraints:"
|
|
50
|
+
lines += check_constraints
|
|
51
|
+
end
|
|
52
|
+
|
|
44
53
|
LineFormatter.format(lines)
|
|
45
54
|
end
|
|
46
55
|
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Annotato
|
|
4
|
+
class CheckConstraintFormatter
|
|
5
|
+
extend WrapHelper
|
|
6
|
+
|
|
7
|
+
def self.format(conn, table_name)
|
|
8
|
+
conn.check_constraints(table_name).map do |chk|
|
|
9
|
+
expr_clause = ""
|
|
10
|
+
if chk.expression
|
|
11
|
+
expr_clause = "\n" + wrap_sql(chk.expression, first_prefix: "# (", cont_prefix: "# ")
|
|
12
|
+
end
|
|
13
|
+
"# #{chk.name}#{expr_clause}"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -9,9 +9,11 @@ module Annotato
|
|
|
9
9
|
table_name = model.table_name
|
|
10
10
|
primary_key = model.primary_key
|
|
11
11
|
unique_indexes = connection.indexes(table_name).select(&:unique)
|
|
12
|
-
enums = model.defined_enums
|
|
13
12
|
columns = connection.columns(table_name)
|
|
14
13
|
|
|
14
|
+
# Collect native PG enum type names once up front (avoids N+1 queries).
|
|
15
|
+
pg_enum_types = pg_enum_type_names(connection)
|
|
16
|
+
|
|
15
17
|
# Compute max widths for name and sql_type so that the table lines up.
|
|
16
18
|
name_width = columns.map(&:name).map(&:length).max
|
|
17
19
|
type_width = columns.map(&:sql_type).map(&:length).max
|
|
@@ -20,8 +22,6 @@ module Annotato
|
|
|
20
22
|
name = col.name
|
|
21
23
|
type = col.sql_type
|
|
22
24
|
|
|
23
|
-
# require "pry" if name == "allowed_statuses"
|
|
24
|
-
# binding.pry if name == "allowed_statuses"
|
|
25
25
|
# Build the left-hand side and calculate indent.
|
|
26
26
|
left = "# %-#{name_width}s :%-#{type_width}s" % [name, type]
|
|
27
27
|
# " default(" is 9 chars; +2 gives the extra gap.
|
|
@@ -41,7 +41,8 @@ module Annotato
|
|
|
41
41
|
opts << "primary key" if name == primary_key
|
|
42
42
|
opts << "is an Array" if type.end_with?("[]")
|
|
43
43
|
opts << "unique" if unique_indexes.any? { |idx| idx.columns == [name] }
|
|
44
|
-
opts << "enum" if
|
|
44
|
+
opts << "enum" if pg_enum_types.include?(type.delete_suffix("[]"))
|
|
45
|
+
opts << "comment: #{col.comment.inspect}" if col.respond_to?(:comment) && col.comment && !col.comment.empty?
|
|
45
46
|
|
|
46
47
|
# Emit either a multiline block or a single line.
|
|
47
48
|
if default_block
|
|
@@ -64,9 +65,8 @@ module Annotato
|
|
|
64
65
|
end
|
|
65
66
|
end
|
|
66
67
|
|
|
67
|
-
# Returns nil
|
|
68
|
-
#
|
|
69
|
-
# If the value is empty array or hash, returns ["[]"] or ["{}"].
|
|
68
|
+
# Returns nil for non-JSON/empty values, or an Array of un-indented inner lines
|
|
69
|
+
# for multiline formatting: e.g. ["\"A\",", "\"B\""] for a JSON array.
|
|
70
70
|
def self.build_default_block(value)
|
|
71
71
|
return nil if value.nil? || !value.is_a?(String)
|
|
72
72
|
s = value.strip
|
|
@@ -76,15 +76,15 @@ module Annotato
|
|
|
76
76
|
return nil unless parsed.is_a?(Array) || parsed.is_a?(Hash)
|
|
77
77
|
|
|
78
78
|
if parsed.is_a?(Array)
|
|
79
|
-
return if parsed.empty?
|
|
79
|
+
return nil if parsed.empty?
|
|
80
80
|
|
|
81
|
-
#
|
|
81
|
+
# JSON array of strings
|
|
82
82
|
parsed.map.with_index do |e, i|
|
|
83
83
|
comma = i == parsed.size - 1 ? "" : ","
|
|
84
84
|
%Q{"#{e}"#{comma}}
|
|
85
85
|
end
|
|
86
86
|
else
|
|
87
|
-
return if parsed.empty?
|
|
87
|
+
return nil if parsed.empty?
|
|
88
88
|
|
|
89
89
|
# JSON hash → key/value pairs
|
|
90
90
|
parsed.map.with_index do |(k, v), i|
|
|
@@ -93,5 +93,16 @@ module Annotato
|
|
|
93
93
|
end
|
|
94
94
|
end
|
|
95
95
|
end
|
|
96
|
+
|
|
97
|
+
# Fetches all native PostgreSQL enum type names in one query.
|
|
98
|
+
# Returns a Set for O(1) membership checks.
|
|
99
|
+
def self.pg_enum_type_names(connection)
|
|
100
|
+
rows = connection.exec_query(
|
|
101
|
+
"SELECT typname FROM pg_type WHERE typtype = 'e'",
|
|
102
|
+
"SQL"
|
|
103
|
+
)
|
|
104
|
+
rows.map { |r| r["typname"] }.to_set
|
|
105
|
+
end
|
|
106
|
+
private_class_method :pg_enum_type_names
|
|
96
107
|
end
|
|
97
108
|
end
|
|
@@ -1,26 +1,50 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Annotato
|
|
4
|
+
# Formats PostgreSQL native enum type definitions for columns whose sql_type
|
|
5
|
+
# refers to a custom DB enum (e.g. `access_link_category`). Queries pg_enum
|
|
6
|
+
# directly so the output reflects the actual database definition, not the
|
|
7
|
+
# ActiveRecord-level enum mapping already visible in the model source.
|
|
4
8
|
class EnumFormatter
|
|
5
|
-
def self.format(
|
|
6
|
-
|
|
9
|
+
def self.format(conn, columns)
|
|
10
|
+
# Collect columns backed by a native PG enum type.
|
|
11
|
+
# A native enum type is one that exists in pg_type with typtype = 'e'.
|
|
12
|
+
enum_columns = columns.select { |col| pg_enum_type?(conn, col.sql_type) }
|
|
13
|
+
return [] if enum_columns.empty?
|
|
7
14
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
lines += formatted_values.map { |v| "# #{v}," }
|
|
20
|
-
lines[-1] = lines[-1].chomp(',') # remove trailing comma from last line
|
|
21
|
-
lines << "# }"
|
|
15
|
+
enum_columns.map do |col|
|
|
16
|
+
labels = pg_enum_labels(conn, col.sql_type)
|
|
17
|
+
lines = ["# #{col.name} (#{col.sql_type}): ["]
|
|
18
|
+
lines += labels.map.with_index { |label, i|
|
|
19
|
+
comma = i == labels.size - 1 ? "" : ","
|
|
20
|
+
"# #{label}#{comma}"
|
|
21
|
+
}
|
|
22
|
+
lines << "# ]"
|
|
22
23
|
lines.join("\n")
|
|
23
24
|
end
|
|
24
25
|
end
|
|
26
|
+
|
|
27
|
+
private_class_method def self.pg_enum_type?(conn, sql_type)
|
|
28
|
+
# Strip array suffix (e.g. "my_enum[]") before checking.
|
|
29
|
+
type_name = sql_type.delete_suffix("[]")
|
|
30
|
+
result = conn.exec_query(
|
|
31
|
+
"SELECT 1 FROM pg_type WHERE typname = $1 AND typtype = 'e' LIMIT 1",
|
|
32
|
+
"SQL",
|
|
33
|
+
[type_name]
|
|
34
|
+
)
|
|
35
|
+
result.any?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private_class_method def self.pg_enum_labels(conn, sql_type)
|
|
39
|
+
type_name = sql_type.delete_suffix("[]")
|
|
40
|
+
conn.exec_query(
|
|
41
|
+
"SELECT e.enumlabel FROM pg_enum e " \
|
|
42
|
+
"JOIN pg_type t ON e.enumtypid = t.oid " \
|
|
43
|
+
"WHERE t.typname = $1 " \
|
|
44
|
+
"ORDER BY e.enumsortorder",
|
|
45
|
+
"SQL",
|
|
46
|
+
[type_name]
|
|
47
|
+
).map { |r| r["enumlabel"] }
|
|
48
|
+
end
|
|
25
49
|
end
|
|
26
50
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Annotato
|
|
4
4
|
class IndexFormatter
|
|
5
|
-
|
|
5
|
+
extend WrapHelper
|
|
6
6
|
|
|
7
7
|
def self.format(conn, table_name)
|
|
8
8
|
conn.indexes(table_name).map do |idx|
|
|
@@ -11,75 +11,11 @@ module Annotato
|
|
|
11
11
|
|
|
12
12
|
where_clause = ""
|
|
13
13
|
if idx.where
|
|
14
|
-
where_clause = "\n" +
|
|
14
|
+
where_clause = "\n" + wrap_sql(idx.where, first_prefix: "# where (", cont_prefix: "# ")
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
"# #{idx.name} (#{cols_list})#{unique_clause}#{where_clause}"
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
|
-
|
|
21
|
-
# Produces one or more lines, each already prefixed with "# "
|
|
22
|
-
# First line: "# where (..."
|
|
23
|
-
# Next lines: "# ..." (aligned under the "(")
|
|
24
|
-
def self.wrap_where(where_sql, max_len:)
|
|
25
|
-
first_prefix = "# where ("
|
|
26
|
-
cont_prefix = "# " # aligns under the "(" after "where "
|
|
27
|
-
|
|
28
|
-
text = where_sql.to_s
|
|
29
|
-
lines = []
|
|
30
|
-
|
|
31
|
-
# If it already fits, keep it one-liner
|
|
32
|
-
if (first_prefix.length + text.length + 1) <= max_len
|
|
33
|
-
return "#{first_prefix}#{text})"
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
remaining = text.dup
|
|
37
|
-
current_prefix = first_prefix
|
|
38
|
-
|
|
39
|
-
while remaining.length > 0
|
|
40
|
-
available = max_len - current_prefix.length - 1 # -1 for closing ")" on last line (or just safety)
|
|
41
|
-
available = 20 if available < 20
|
|
42
|
-
|
|
43
|
-
if remaining.length <= available
|
|
44
|
-
lines << "#{current_prefix}#{remaining}"
|
|
45
|
-
remaining = ""
|
|
46
|
-
break
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Prefer breaking on logical operators within the window
|
|
50
|
-
window = remaining[0, available]
|
|
51
|
-
cut =
|
|
52
|
-
window.rindex(" AND ") ||
|
|
53
|
-
window.rindex(" OR ") ||
|
|
54
|
-
window.rindex(", ") ||
|
|
55
|
-
window.rindex(") ") ||
|
|
56
|
-
window.rindex(" ") # last resort: whitespace
|
|
57
|
-
|
|
58
|
-
# If we found a breakpoint, include it in the line (so operators stay visible)
|
|
59
|
-
if cut
|
|
60
|
-
# If we’re cutting on AND/OR/comma, keep that token at the end of the line when possible
|
|
61
|
-
if window[cut, 5] == " AND " || window[cut, 4] == " OR "
|
|
62
|
-
cut += (window[cut, 5] == " AND " ? 5 : 4)
|
|
63
|
-
elsif window[cut, 2] == ", "
|
|
64
|
-
cut += 2
|
|
65
|
-
elsif window[cut, 2] == ") "
|
|
66
|
-
cut += 2
|
|
67
|
-
else
|
|
68
|
-
cut += 1
|
|
69
|
-
end
|
|
70
|
-
else
|
|
71
|
-
cut = available
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
lines << "#{current_prefix}#{remaining[0, cut].rstrip}"
|
|
75
|
-
remaining = remaining[cut..].to_s.lstrip
|
|
76
|
-
current_prefix = cont_prefix
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Close the paren on the last line
|
|
80
|
-
lines[-1] = "#{lines[-1]})"
|
|
81
|
-
|
|
82
|
-
lines.join("\n")
|
|
83
|
-
end
|
|
84
20
|
end
|
|
85
21
|
end
|
|
@@ -19,7 +19,6 @@ module Annotato
|
|
|
19
19
|
annotation = AnnotationBuilder.build(model)
|
|
20
20
|
write_annotation(model, annotation)
|
|
21
21
|
end
|
|
22
|
-
@output.puts "✅ Annotato completed"
|
|
23
22
|
end
|
|
24
23
|
|
|
25
24
|
private
|
|
@@ -46,7 +45,13 @@ module Annotato
|
|
|
46
45
|
file = model_file(model)
|
|
47
46
|
return unless file && File.exist?(file)
|
|
48
47
|
|
|
49
|
-
content = File.read(file).dup
|
|
48
|
+
content = File.read(file).dup
|
|
49
|
+
|
|
50
|
+
# Verify the class definition exists before doing anything else.
|
|
51
|
+
unless content.match?(/class\s+\w+/)
|
|
52
|
+
@output.puts "⚠️ Skipped #{model.name} — class not found in file"
|
|
53
|
+
return
|
|
54
|
+
end
|
|
50
55
|
|
|
51
56
|
# Extract the old Annotato annotation block if it exists
|
|
52
57
|
old_annotation = content[/^# == Annotato Schema Info.*?(?=^class|\z)/m]
|
|
@@ -57,17 +62,11 @@ module Annotato
|
|
|
57
62
|
return
|
|
58
63
|
end
|
|
59
64
|
|
|
60
|
-
# Remove old Annotato blocks
|
|
65
|
+
# Remove old Annotato blocks and legacy annotate-gem blocks
|
|
61
66
|
content.gsub!(/^# == Annotato Schema Info.*?(?=^class|\z)/m, "")
|
|
62
67
|
content.gsub!(/^# == Schema Information.*?(?=^class|\z)/m, "")
|
|
63
68
|
content.rstrip!
|
|
64
69
|
|
|
65
|
-
# Verify the class definition exists before appending annotation
|
|
66
|
-
unless content.match?(/class\s+\w+/)
|
|
67
|
-
@output.puts "⚠️ Skipped #{model.name} — class not found in file"
|
|
68
|
-
return
|
|
69
|
-
end
|
|
70
|
-
|
|
71
70
|
# Append the new annotation at the end of the file
|
|
72
71
|
content += "\n\n#{annotation}\n"
|
|
73
72
|
File.write(file, content)
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
module Annotato
|
|
4
4
|
class TriggerFormatter
|
|
5
5
|
def self.format(conn, table_name)
|
|
6
|
-
conn.exec_query(
|
|
7
|
-
SELECT tgname FROM pg_trigger
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
conn.exec_query(
|
|
7
|
+
"SELECT tgname FROM pg_trigger WHERE tgrelid = $1::regclass AND NOT tgisinternal",
|
|
8
|
+
"SQL",
|
|
9
|
+
[table_name]
|
|
10
|
+
).map { |r| "# #{r['tgname']}" }
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
end
|
data/lib/annotato/version.rb
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Annotato
|
|
4
|
+
module WrapHelper
|
|
5
|
+
MAX_LINE = 100
|
|
6
|
+
|
|
7
|
+
# Produces one or more lines, each already prefixed with "# "
|
|
8
|
+
# First line: "# where (..."
|
|
9
|
+
# Next lines: "# ..." (aligned under the "(")
|
|
10
|
+
def wrap_sql(sql, first_prefix:, cont_prefix:, max_len: MAX_LINE)
|
|
11
|
+
text = sql.to_s
|
|
12
|
+
lines = []
|
|
13
|
+
|
|
14
|
+
# If it already fits, keep it one-liner
|
|
15
|
+
if (first_prefix.length + text.length + 1) <= max_len
|
|
16
|
+
return "#{first_prefix}#{text})"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
remaining = text.dup
|
|
20
|
+
current_prefix = first_prefix
|
|
21
|
+
|
|
22
|
+
while remaining.length > 0
|
|
23
|
+
available = max_len - current_prefix.length - 1 # -1 for closing ")" on last line (or just safety)
|
|
24
|
+
available = 20 if available < 20
|
|
25
|
+
|
|
26
|
+
if remaining.length <= available
|
|
27
|
+
lines << "#{current_prefix}#{remaining}"
|
|
28
|
+
remaining = ""
|
|
29
|
+
break
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Prefer breaking on logical operators within the window
|
|
33
|
+
window = remaining[0, available]
|
|
34
|
+
cut =
|
|
35
|
+
window.rindex(" AND ") ||
|
|
36
|
+
window.rindex(" OR ") ||
|
|
37
|
+
window.rindex(", ") ||
|
|
38
|
+
window.rindex(") ") ||
|
|
39
|
+
window.rindex(" ") # last resort: whitespace
|
|
40
|
+
|
|
41
|
+
# If we found a breakpoint, include it in the line (so operators stay visible)
|
|
42
|
+
if cut
|
|
43
|
+
# If we’re cutting on AND/OR/comma, keep that token at the end of the line when possible
|
|
44
|
+
if window[cut, 5] == " AND " || window[cut, 4] == " OR "
|
|
45
|
+
cut += (window[cut, 5] == " AND " ? 5 : 4)
|
|
46
|
+
elsif window[cut, 2] == ", "
|
|
47
|
+
cut += 2
|
|
48
|
+
elsif window[cut, 2] == ") "
|
|
49
|
+
cut += 2
|
|
50
|
+
else
|
|
51
|
+
cut += 1
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
cut = available
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
lines << "#{current_prefix}#{remaining[0, cut].rstrip}"
|
|
58
|
+
remaining = remaining[cut..].to_s.lstrip
|
|
59
|
+
current_prefix = cont_prefix
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Close the paren on the last line
|
|
63
|
+
lines[-1] = "#{lines[-1]})"
|
|
64
|
+
|
|
65
|
+
lines.join("\n")
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
data/lib/tasks/annotato.rake
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: annotato
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Serhii Bodnaruk
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-06-30 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: rails
|
|
@@ -45,6 +45,7 @@ files:
|
|
|
45
45
|
- bin/setup
|
|
46
46
|
- lib/annotato.rb
|
|
47
47
|
- lib/annotato/annotation_builder.rb
|
|
48
|
+
- lib/annotato/check_constraint_formatter.rb
|
|
48
49
|
- lib/annotato/column_formatter.rb
|
|
49
50
|
- lib/annotato/enum_formatter.rb
|
|
50
51
|
- lib/annotato/index_formatter.rb
|
|
@@ -53,6 +54,7 @@ files:
|
|
|
53
54
|
- lib/annotato/railtie.rb
|
|
54
55
|
- lib/annotato/trigger_formatter.rb
|
|
55
56
|
- lib/annotato/version.rb
|
|
57
|
+
- lib/annotato/wrap_helper.rb
|
|
56
58
|
- lib/tasks/annotato.rake
|
|
57
59
|
- sig/annotato.rbs
|
|
58
60
|
homepage: https://github.com/boserh/annotato
|