annotato 0.1.7 → 0.1.8
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 +1 -1
- data/README.md +29 -14
- data/lib/annotato/column_formatter.rb +70 -50
- data/lib/annotato/model_annotator.rb +3 -2
- data/lib/annotato/version.rb +1 -1
- data/lib/tasks/annotato.rake +18 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c083f5bdc6340dbd160b2392a98776b88540333ed1ab48cb7d9cf10065d5299e
|
4
|
+
data.tar.gz: 415a2f70e589b162364d16b4132b58cb9899f85f1147214756e3f81fdda89cb3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0c5713da93df6b7dfa3bd4cc9524a80e503a2edc5da17268b73e5ee0de762222aa376cd26a7bed31ef9feba87b05e1e0701160987442ea55699033ff8d1341aa
|
7
|
+
data.tar.gz: be1a839e36ef7f8066376087108177472bab0750a912c04c687177a1a3856f6da338c8eb5c9893026fc372992325c8219a3bbbbe1bcb4deae962e59a82fb4462
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -9,15 +9,15 @@
|
|
9
9
|
|
10
10
|
## Features
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
12
|
+
* Annotates Rails models with:
|
13
|
+
|
14
|
+
* Columns and types
|
15
|
+
* Default values and constraints
|
16
|
+
* Enums with values
|
17
|
+
* Indexes (only if present)
|
18
|
+
* Triggers (only if present)
|
19
|
+
* Skips unchanged annotations
|
20
|
+
* Smart formatting with aligned columns
|
21
21
|
|
22
22
|
Example:
|
23
23
|
|
@@ -57,23 +57,38 @@ bundle install
|
|
57
57
|
|
58
58
|
## Usage
|
59
59
|
|
60
|
-
|
60
|
+
### Annotate All Models
|
61
|
+
|
62
|
+
To annotate all models, run:
|
61
63
|
|
62
64
|
```bash
|
63
65
|
bundle exec rake annotato:models
|
64
66
|
```
|
65
67
|
|
68
|
+
### Annotate Specific Models
|
69
|
+
|
70
|
+
You can now pass one or multiple model names to the task.
|
71
|
+
|
72
|
+
```bash
|
73
|
+
# Annotate only the User model
|
74
|
+
rake annotato:models[User]
|
75
|
+
|
76
|
+
# Annotate multiple models (User and Admin::Account)
|
77
|
+
rake annotato:models["User,Admin::Account"]
|
78
|
+
```
|
79
|
+
|
66
80
|
---
|
67
81
|
|
68
82
|
## Rake Task
|
69
83
|
|
70
84
|
```bash
|
71
|
-
rake annotato:models
|
85
|
+
rake annotato:models[MODEL]
|
72
86
|
```
|
73
87
|
|
74
|
-
|
75
|
-
|
76
|
-
|
88
|
+
* Automatically loads all models via `Rails.application.eager_load!`
|
89
|
+
* Modifies model files in place
|
90
|
+
* Replace existing Annotato and legacy annotate blocks
|
91
|
+
* **Pass `MODEL` argument (single or comma-separated) to target specific models**
|
77
92
|
|
78
93
|
---
|
79
94
|
|
@@ -1,77 +1,97 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "json"
|
4
4
|
|
5
5
|
module Annotato
|
6
6
|
class ColumnFormatter
|
7
|
+
# Main entry: returns an array of comment lines per column, flattened.
|
7
8
|
def self.format(model, connection)
|
8
|
-
table_name
|
9
|
-
primary_key
|
10
|
-
enums = model.defined_enums
|
9
|
+
table_name = model.table_name
|
10
|
+
primary_key = model.primary_key
|
11
11
|
unique_indexes = connection.indexes(table_name).select(&:unique)
|
12
|
-
|
12
|
+
enums = model.defined_enums
|
13
|
+
columns = connection.columns(table_name)
|
13
14
|
|
14
|
-
|
15
|
+
# Compute max widths for name and sql_type so that the table lines up.
|
16
|
+
name_width = columns.map(&:name).map(&:length).max
|
17
|
+
type_width = columns.map(&:sql_type).map(&:length).max
|
18
|
+
|
19
|
+
columns.flat_map do |col|
|
15
20
|
name = col.name
|
16
21
|
type = col.sql_type
|
17
|
-
default = col.default
|
18
|
-
opts = []
|
19
|
-
|
20
|
-
opts << "default(#{default.inspect})" unless default.nil?
|
21
|
-
opts << "not null" unless col.null
|
22
|
-
opts << "primary key" if name == primary_key
|
23
|
-
opts << "is an Array" if type.end_with?("[]")
|
24
|
-
opts << "unique" if unique_indexes.any? { |idx| idx.columns == [name] }
|
25
|
-
opts << "enum" if enums.key?(name)
|
26
|
-
|
27
|
-
[name, type, default, opts]
|
28
|
-
end
|
29
|
-
|
30
|
-
name_width = raw_data.map { |name, *_| name.length }.max
|
31
|
-
type_width = raw_data.map { |_, type, *_| type.length }.max
|
32
|
-
|
33
|
-
raw_data.flat_map do |name, type, default, opts|
|
34
|
-
base_line = "# %-#{name_width}s :%-#{type_width}s" % [name, type]
|
35
|
-
indent = ' ' * (base_line.length + 1)
|
36
22
|
|
37
|
-
if
|
38
|
-
|
39
|
-
|
23
|
+
# require "pry" if name == "allowed_statuses"
|
24
|
+
# binding.pry if name == "allowed_statuses"
|
25
|
+
# Build the left-hand side and calculate indent.
|
26
|
+
left = "# %-#{name_width}s :%-#{type_width}s" % [name, type]
|
27
|
+
# " default(" is 9 chars; +2 gives the extra gap.
|
28
|
+
indent_size = left.length + 1
|
29
|
+
indent_str = " " * indent_size
|
30
|
+
closing_indent_str = " " * (indent_size - 2)
|
40
31
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
32
|
+
# Gather all options, pulling out default_lines if multiline.
|
33
|
+
opts = []
|
34
|
+
default_block = build_default_block(col.default)
|
35
|
+
if default_block
|
36
|
+
opts << "__MULTILINE__" # placeholder
|
37
|
+
elsif col.default
|
38
|
+
opts << "default(#{col.default.inspect})"
|
39
|
+
end
|
40
|
+
opts << "not null" unless col.null
|
41
|
+
opts << "primary key" if name == primary_key
|
42
|
+
opts << "is an Array" if type.end_with?("[]")
|
43
|
+
opts << "unique" if unique_indexes.any? { |idx| idx.columns == [name] }
|
44
|
+
opts << "enum" if enums.key?(name)
|
46
45
|
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
# Emit either a multiline block or a single line.
|
47
|
+
if default_block
|
48
|
+
# 1) opening line
|
49
|
+
lines = ["#{left} default(#{col.default[0]}"]
|
50
|
+
# 2) each interior line, prefixed by "# " + indent_str
|
51
|
+
lines += default_block.map { |l| "# #{indent_str}#{l}" }
|
52
|
+
# 3) closing line with trailing options
|
53
|
+
closing = "# #{closing_indent_str}#{col.default[-1]})"
|
54
|
+
trailing = opts.reject { |o| o == "__MULTILINE__" }
|
55
|
+
closing += ", #{trailing.join(', ')}" unless trailing.empty?
|
56
|
+
lines << closing
|
50
57
|
lines
|
51
58
|
else
|
52
|
-
line
|
59
|
+
# single-line comment
|
60
|
+
line = left
|
53
61
|
line += " #{opts.join(', ')}" unless opts.empty?
|
54
|
-
line.rstrip
|
62
|
+
[line.rstrip]
|
55
63
|
end
|
56
64
|
end
|
57
65
|
end
|
58
66
|
|
59
|
-
|
60
|
-
|
61
|
-
|
67
|
+
# Returns nil (no default) or an Array of un-indented lines:
|
68
|
+
# ["[", "\"A\",", "\"B\"", "]"] or ["{", "\"k\":v,", ... , "}"]
|
69
|
+
# If the value is empty array or hash, returns ["[]"] or ["{}"].
|
70
|
+
def self.build_default_block(value)
|
71
|
+
return nil if value.nil?
|
72
|
+
s = value.strip
|
73
|
+
return nil unless s.start_with?("[") || s.start_with?("{")
|
62
74
|
|
63
|
-
|
64
|
-
parsed
|
65
|
-
return [value] unless parsed.is_a?(Array)
|
75
|
+
parsed = JSON.parse(s) rescue nil
|
76
|
+
return nil unless parsed.is_a?(Array) || parsed.is_a?(Hash)
|
66
77
|
|
67
|
-
|
78
|
+
if parsed.is_a?(Array)
|
79
|
+
return if parsed.empty? # empty array → ["[]"]
|
68
80
|
|
69
|
-
|
70
|
-
|
71
|
-
|
81
|
+
# Only a JSON array of strings?
|
82
|
+
parsed.map.with_index do |e, i|
|
83
|
+
comma = i == parsed.size - 1 ? "" : ","
|
84
|
+
%Q{"#{e}"#{comma}}
|
85
|
+
end
|
86
|
+
else
|
87
|
+
return if parsed.empty? # empty hash → ["{}"]
|
88
|
+
|
89
|
+
# JSON hash → key/value pairs
|
90
|
+
parsed.map.with_index do |(k, v), i|
|
91
|
+
comma = i == parsed.size - 1 ? "" : ","
|
92
|
+
%Q{"#{k}": #{v.inspect}#{comma}}
|
93
|
+
end
|
72
94
|
end
|
73
|
-
rescue JSON::ParserError
|
74
|
-
[value]
|
75
95
|
end
|
76
96
|
end
|
77
97
|
end
|
@@ -10,8 +10,9 @@ module Annotato
|
|
10
10
|
@output = output
|
11
11
|
end
|
12
12
|
|
13
|
-
def run
|
14
|
-
|
13
|
+
def run(one_model = nil)
|
14
|
+
mtd = one_model ? [one_model] : models
|
15
|
+
mtd.each do |model|
|
15
16
|
next unless model.table_exists?
|
16
17
|
|
17
18
|
annotation = AnnotationBuilder.build(model)
|
data/lib/annotato/version.rb
CHANGED
data/lib/tasks/annotato.rake
CHANGED
@@ -1,8 +1,23 @@
|
|
1
|
+
# lib/tasks/annotato.rake
|
2
|
+
|
1
3
|
require "annotato/model_annotator"
|
2
4
|
|
3
5
|
namespace :annotato do
|
4
|
-
desc "Annotate models with schema info"
|
5
|
-
task models: :environment do
|
6
|
-
|
6
|
+
desc "Annotate models with schema info. Pass MODEL=name or comma-separated list"
|
7
|
+
task :models, [:MODEL] => :environment do |t, args|
|
8
|
+
# Parse MODEL argument into an array of model names, if given
|
9
|
+
model_list = args[:MODEL]&.split(",")&.map(&:strip)
|
10
|
+
|
11
|
+
annotator = Annotato::ModelAnnotator.new
|
12
|
+
|
13
|
+
if model_list && model_list.any?
|
14
|
+
# Constantize each name and annotate only those classes
|
15
|
+
model_list.map(&:constantize).each do |klass|
|
16
|
+
annotator.run(klass)
|
17
|
+
end
|
18
|
+
else
|
19
|
+
# No MODEL passed → annotate all models
|
20
|
+
annotator.run
|
21
|
+
end
|
7
22
|
end
|
8
23
|
end
|
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.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Serhii Bodnaruk
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-05-
|
10
|
+
date: 2025-05-17 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: rails
|