annotaterb 4.1.0 → 4.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +68 -0
- data/README.md +4 -5
- data/VERSION +1 -1
- data/exe/annotaterb +7 -7
- data/lib/annotate_rb/active_record_patch.rb +2 -0
- data/lib/annotate_rb/commands/annotate_models.rb +0 -1
- data/lib/annotate_rb/commands/annotate_routes.rb +0 -1
- data/lib/annotate_rb/commands/print_help.rb +0 -1
- data/lib/annotate_rb/commands/print_version.rb +0 -1
- data/lib/annotate_rb/commands.rb +4 -4
- data/lib/annotate_rb/config_finder.rb +1 -1
- data/lib/annotate_rb/config_loader.rb +2 -2
- data/lib/annotate_rb/core.rb +3 -3
- data/lib/annotate_rb/helper.rb +16 -0
- data/lib/annotate_rb/model_annotator/{annotation_generator.rb → annotation_builder.rb} +18 -14
- data/lib/annotate_rb/model_annotator/annotation_decider.rb +10 -12
- data/lib/annotate_rb/model_annotator/annotation_diff.rb +19 -0
- data/lib/annotate_rb/model_annotator/annotation_diff_generator.rb +44 -0
- data/lib/annotate_rb/model_annotator/annotation_pattern_generator.rb +3 -3
- data/lib/annotate_rb/model_annotator/annotator.rb +12 -53
- data/lib/annotate_rb/model_annotator/column_annotation/annotation_builder.rb +135 -0
- data/lib/annotate_rb/model_annotator/column_annotation/attributes_builder.rb +104 -0
- data/lib/annotate_rb/model_annotator/column_annotation/column_wrapper.rb +103 -0
- data/lib/annotate_rb/model_annotator/column_annotation/type_builder.rb +54 -0
- data/lib/annotate_rb/model_annotator/column_annotation.rb +12 -0
- data/lib/annotate_rb/model_annotator/file_builder.rb +58 -0
- data/lib/annotate_rb/model_annotator/file_components.rb +78 -0
- data/lib/annotate_rb/model_annotator/file_name_resolver.rb +3 -3
- data/lib/annotate_rb/model_annotator/foreign_key_annotation/annotation_builder.rb +57 -0
- data/lib/annotate_rb/model_annotator/foreign_key_annotation.rb +9 -0
- data/lib/annotate_rb/model_annotator/index_annotation/annotation_builder.rb +113 -0
- data/lib/annotate_rb/model_annotator/index_annotation.rb +9 -0
- data/lib/annotate_rb/model_annotator/magic_comment_parser.rb +32 -0
- data/lib/annotate_rb/model_annotator/model_class_getter.rb +6 -6
- data/lib/annotate_rb/model_annotator/model_files_getter.rb +12 -10
- data/lib/annotate_rb/model_annotator/model_wrapper.rb +44 -37
- data/lib/annotate_rb/model_annotator/pattern_getter.rb +142 -10
- data/lib/annotate_rb/model_annotator/project_annotation_remover.rb +65 -0
- data/lib/annotate_rb/model_annotator/project_annotator.rb +63 -0
- data/lib/annotate_rb/model_annotator/related_files_list_builder.rb +16 -18
- data/lib/annotate_rb/model_annotator/single_file_annotation_remover.rb +37 -0
- data/lib/annotate_rb/model_annotator/single_file_annotator.rb +49 -0
- data/lib/annotate_rb/model_annotator/{file_annotator_instruction.rb → single_file_annotator_instruction.rb} +2 -2
- data/lib/annotate_rb/model_annotator/single_file_remove_annotation_instruction.rb +15 -0
- data/lib/annotate_rb/model_annotator.rb +25 -24
- data/lib/annotate_rb/options.rb +20 -25
- data/lib/annotate_rb/parser.rb +150 -142
- data/lib/annotate_rb/rake_bootstrapper.rb +8 -8
- data/lib/annotate_rb/route_annotator/annotation_processor.rb +7 -8
- data/lib/annotate_rb/route_annotator/annotator.rb +2 -2
- data/lib/annotate_rb/route_annotator/base_processor.rb +3 -3
- data/lib/annotate_rb/route_annotator/header_generator.rb +15 -15
- data/lib/annotate_rb/route_annotator/helper.rb +9 -9
- data/lib/annotate_rb/route_annotator/removal_processor.rb +4 -4
- data/lib/annotate_rb/route_annotator.rb +6 -6
- data/lib/annotate_rb/runner.rb +0 -4
- data/lib/annotate_rb/tasks/annotate_models_migrate.rake +5 -5
- data/lib/annotate_rb.rb +19 -19
- data/lib/generators/annotate_rb/install/install_generator.rb +3 -2
- data/lib/generators/annotate_rb/install/templates/annotate_rb.rake +1 -1
- metadata +24 -16
- data/lib/annotate_rb/model_annotator/column_annotation_builder.rb +0 -92
- data/lib/annotate_rb/model_annotator/column_attributes_builder.rb +0 -102
- data/lib/annotate_rb/model_annotator/column_type_builder.rb +0 -51
- data/lib/annotate_rb/model_annotator/column_wrapper.rb +0 -84
- data/lib/annotate_rb/model_annotator/constants.rb +0 -22
- data/lib/annotate_rb/model_annotator/file_annotation_remover.rb +0 -25
- data/lib/annotate_rb/model_annotator/file_annotator.rb +0 -83
- data/lib/annotate_rb/model_annotator/file_patterns.rb +0 -129
- data/lib/annotate_rb/model_annotator/foreign_key_annotation_builder.rb +0 -55
- data/lib/annotate_rb/model_annotator/helper.rb +0 -107
- data/lib/annotate_rb/model_annotator/index_annotation_builder.rb +0 -74
- data/lib/annotate_rb/model_annotator/model_file_annotator.rb +0 -55
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
module ColumnAnnotation
|
6
|
+
class AnnotationBuilder
|
7
|
+
BARE_TYPE_ALLOWANCE = 16
|
8
|
+
MD_TYPE_ALLOWANCE = 18
|
9
|
+
|
10
|
+
def initialize(column, model, max_size, options)
|
11
|
+
@column = column
|
12
|
+
@model = model
|
13
|
+
@max_size = max_size
|
14
|
+
@options = options
|
15
|
+
end
|
16
|
+
|
17
|
+
def build
|
18
|
+
result = ""
|
19
|
+
|
20
|
+
is_primary_key = is_column_primary_key?(@model, @column.name)
|
21
|
+
|
22
|
+
table_indices = @model.retrieve_indexes_from_table
|
23
|
+
column_indices = table_indices.select { |ind| ind.columns.include?(@column.name) }
|
24
|
+
|
25
|
+
column_attributes = AttributesBuilder.new(@column, @options, is_primary_key, column_indices).build
|
26
|
+
formatted_column_type = TypeBuilder.new(@column, @options).build
|
27
|
+
|
28
|
+
col_name = if @model.with_comments? && @column.comment
|
29
|
+
"#{@column.name}(#{@column.comment.gsub(/\n/, '\\n')})"
|
30
|
+
else
|
31
|
+
@column.name
|
32
|
+
end
|
33
|
+
|
34
|
+
result += if @options[:format_rdoc]
|
35
|
+
format_rdoc(col_name, @max_size, formatted_column_type, column_attributes)
|
36
|
+
elsif @options[:format_yard]
|
37
|
+
format_yard(col_name, @max_size, formatted_column_type, column_attributes)
|
38
|
+
elsif @options[:format_markdown]
|
39
|
+
format_markdown(col_name, @max_size, formatted_column_type, column_attributes)
|
40
|
+
else
|
41
|
+
format_default(col_name, @max_size, formatted_column_type, column_attributes)
|
42
|
+
end
|
43
|
+
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def non_ascii_length(string)
|
50
|
+
string.to_s.chars.count { |element| !element.ascii_only? }
|
51
|
+
end
|
52
|
+
|
53
|
+
def mb_chars_ljust(string, length)
|
54
|
+
string = string.to_s
|
55
|
+
padding = length - Helper.width(string)
|
56
|
+
if padding.positive?
|
57
|
+
string + (" " * padding)
|
58
|
+
else
|
59
|
+
string[0..(length - 1)]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def map_col_type_to_ruby_classes(col_type)
|
64
|
+
case col_type
|
65
|
+
when "integer" then Integer.to_s
|
66
|
+
when "float" then Float.to_s
|
67
|
+
when "decimal" then BigDecimal.to_s
|
68
|
+
when "datetime", "timestamp", "time" then Time.to_s
|
69
|
+
when "date" then Date.to_s
|
70
|
+
when "text", "string", "binary", "inet", "uuid" then String.to_s
|
71
|
+
when "json", "jsonb" then Hash.to_s
|
72
|
+
when "boolean" then "Boolean"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def format_rdoc(col_name, max_size, formatted_column_type, column_attributes)
|
77
|
+
format("# %-#{max_size}.#{max_size}s<tt>%s</tt>",
|
78
|
+
"*#{col_name}*::",
|
79
|
+
column_attributes.unshift(formatted_column_type).join(", ")).rstrip + "\n"
|
80
|
+
end
|
81
|
+
|
82
|
+
def format_yard(col_name, _max_size, formatted_column_type, _column_attributes)
|
83
|
+
res = ""
|
84
|
+
res += sprintf("# @!attribute #{col_name}") + "\n"
|
85
|
+
|
86
|
+
ruby_class = if @column.respond_to?(:array) && @column.array
|
87
|
+
"Array<#{map_col_type_to_ruby_classes(formatted_column_type)}>"
|
88
|
+
else
|
89
|
+
map_col_type_to_ruby_classes(formatted_column_type)
|
90
|
+
end
|
91
|
+
|
92
|
+
res += sprintf("# @return [#{ruby_class}]") + "\n"
|
93
|
+
|
94
|
+
res
|
95
|
+
end
|
96
|
+
|
97
|
+
def format_markdown(col_name, max_size, formatted_column_type, column_attributes)
|
98
|
+
name_remainder = max_size - col_name.length - non_ascii_length(col_name)
|
99
|
+
type_remainder = (MD_TYPE_ALLOWANCE - 2) - formatted_column_type.length
|
100
|
+
|
101
|
+
format("# **`%s`**%#{name_remainder}s | `%s`%#{type_remainder}s | `%s`",
|
102
|
+
col_name,
|
103
|
+
" ",
|
104
|
+
formatted_column_type,
|
105
|
+
" ",
|
106
|
+
column_attributes.join(", ").rstrip).gsub("``", " ").rstrip + "\n"
|
107
|
+
end
|
108
|
+
|
109
|
+
def format_default(col_name, max_size, formatted_column_type, column_attributes)
|
110
|
+
format("# %s:%s %s",
|
111
|
+
mb_chars_ljust(col_name, max_size),
|
112
|
+
mb_chars_ljust(formatted_column_type, BARE_TYPE_ALLOWANCE),
|
113
|
+
column_attributes.join(", ")).rstrip + "\n"
|
114
|
+
end
|
115
|
+
|
116
|
+
# TODO: Simplify this conditional
|
117
|
+
def is_column_primary_key?(model, column_name)
|
118
|
+
if model.primary_key
|
119
|
+
if model.primary_key.is_a?(Array)
|
120
|
+
# If the model has multiple primary keys, check if this column is one of them
|
121
|
+
if model.primary_key.collect(&:to_sym).include?(column_name.to_sym)
|
122
|
+
return true
|
123
|
+
end
|
124
|
+
elsif column_name.to_sym == model.primary_key.to_sym
|
125
|
+
# If model has 1 primary key, check if this column is it
|
126
|
+
return true
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
false
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
module ColumnAnnotation
|
6
|
+
class AttributesBuilder
|
7
|
+
# Don't show default value for these column types
|
8
|
+
NO_DEFAULT_COL_TYPES = %w[json jsonb hstore].freeze
|
9
|
+
|
10
|
+
def initialize(column, options, is_primary_key, column_indices)
|
11
|
+
@column = ColumnWrapper.new(column)
|
12
|
+
@options = options
|
13
|
+
@is_primary_key = is_primary_key
|
14
|
+
@column_indices = column_indices
|
15
|
+
end
|
16
|
+
|
17
|
+
# Get the list of attributes that should be included in the annotation for
|
18
|
+
# a given column.
|
19
|
+
def build
|
20
|
+
column_type = @column.column_type_string
|
21
|
+
attrs = []
|
22
|
+
|
23
|
+
unless @column.default.nil? || hide_default?
|
24
|
+
schema_default = "default(#{@column.default_string})"
|
25
|
+
|
26
|
+
attrs << schema_default
|
27
|
+
end
|
28
|
+
|
29
|
+
if @column.unsigned?
|
30
|
+
attrs << "unsigned"
|
31
|
+
end
|
32
|
+
|
33
|
+
if !@column.null
|
34
|
+
attrs << "not null"
|
35
|
+
end
|
36
|
+
|
37
|
+
if @is_primary_key
|
38
|
+
attrs << "primary key"
|
39
|
+
end
|
40
|
+
|
41
|
+
is_special_type = %w[spatial geometry geography].include?(column_type)
|
42
|
+
is_decimal_type = column_type == "decimal"
|
43
|
+
|
44
|
+
if !is_decimal_type && !is_special_type
|
45
|
+
if @column.limit && !@options[:format_yard]
|
46
|
+
if @column.limit.is_a?(Array)
|
47
|
+
attrs << "(#{@column.limit.join(", ")})"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Check out if we got an array column
|
53
|
+
if @column.array?
|
54
|
+
attrs << "is an Array"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Check out if we got a geometric column
|
58
|
+
# and print the type and SRID
|
59
|
+
if @column.geometry_type?
|
60
|
+
attrs << "#{@column.geometry_type}, #{@column.srid}"
|
61
|
+
elsif @column.geometric_type? && @column.geometric_type.present?
|
62
|
+
attrs << "#{@column.geometric_type.to_s.downcase}, #{@column.srid}"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Check if the column has indices and print "indexed" if true
|
66
|
+
# If the index includes another column, print it too.
|
67
|
+
if @options[:simple_indexes]
|
68
|
+
# Note: there used to be a klass.table_exists? call here, but removed it as it seemed unnecessary.
|
69
|
+
|
70
|
+
sorted_column_indices&.each do |index|
|
71
|
+
indexed_columns = index.columns.reject { |i| i == @column.name }
|
72
|
+
|
73
|
+
attrs << if indexed_columns.empty?
|
74
|
+
"indexed"
|
75
|
+
else
|
76
|
+
"indexed => [#{indexed_columns.join(", ")}]"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
attrs
|
82
|
+
end
|
83
|
+
|
84
|
+
def sorted_column_indices
|
85
|
+
# Not sure why there were & safe accessors here, but keeping in for time being.
|
86
|
+
sorted_indices = @column_indices&.sort_by(&:name)
|
87
|
+
|
88
|
+
_sorted_indices = sorted_indices.reject { |ind| ind.columns.is_a?(String) }
|
89
|
+
end
|
90
|
+
|
91
|
+
def hide_default?
|
92
|
+
excludes =
|
93
|
+
if @options[:hide_default_column_types].blank?
|
94
|
+
NO_DEFAULT_COL_TYPES
|
95
|
+
else
|
96
|
+
@options[:hide_default_column_types].split(",")
|
97
|
+
end
|
98
|
+
|
99
|
+
excludes.include?(@column.column_type_string)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
module ColumnAnnotation
|
6
|
+
class ColumnWrapper
|
7
|
+
def initialize(column)
|
8
|
+
@column = column
|
9
|
+
end
|
10
|
+
|
11
|
+
def default
|
12
|
+
# Note: Used to be klass.column_defaults[name], where name is the column name.
|
13
|
+
# Looks to be identical, but keeping note here in case there are differences.
|
14
|
+
_column_default = @column.default
|
15
|
+
end
|
16
|
+
|
17
|
+
def default_string
|
18
|
+
quote(@column.default)
|
19
|
+
end
|
20
|
+
|
21
|
+
def type
|
22
|
+
@column.type
|
23
|
+
end
|
24
|
+
|
25
|
+
def column_type_string
|
26
|
+
if (@column.respond_to?(:bigint?) && @column.bigint?) || /\Abigint\b/ =~ @column.sql_type
|
27
|
+
"bigint"
|
28
|
+
else
|
29
|
+
(@column.type || @column.sql_type).to_s
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def unsigned?
|
34
|
+
@column.respond_to?(:unsigned?) && @column.unsigned?
|
35
|
+
end
|
36
|
+
|
37
|
+
def null
|
38
|
+
@column.null
|
39
|
+
end
|
40
|
+
|
41
|
+
def precision
|
42
|
+
@column.precision
|
43
|
+
end
|
44
|
+
|
45
|
+
def scale
|
46
|
+
@column.scale
|
47
|
+
end
|
48
|
+
|
49
|
+
def limit
|
50
|
+
@column.limit
|
51
|
+
end
|
52
|
+
|
53
|
+
def geometry_type?
|
54
|
+
@column.respond_to?(:geometry_type)
|
55
|
+
end
|
56
|
+
|
57
|
+
def geometry_type
|
58
|
+
# TODO: Check if we need to check if it responds before accessing the geometry type
|
59
|
+
@column.geometry_type
|
60
|
+
end
|
61
|
+
|
62
|
+
def geometric_type?
|
63
|
+
@column.respond_to?(:geometric_type)
|
64
|
+
end
|
65
|
+
|
66
|
+
def geometric_type
|
67
|
+
# TODO: Check if we need to check if it responds before accessing the geometric type
|
68
|
+
@column.geometric_type
|
69
|
+
end
|
70
|
+
|
71
|
+
def srid
|
72
|
+
# TODO: Check if we need to check if it responds before accessing the srid
|
73
|
+
@column.srid
|
74
|
+
end
|
75
|
+
|
76
|
+
def array?
|
77
|
+
@column.respond_to?(:array) && @column.array
|
78
|
+
end
|
79
|
+
|
80
|
+
def name
|
81
|
+
@column.name
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
# Simple quoting for the default column value
|
87
|
+
def quote(value)
|
88
|
+
case value
|
89
|
+
when NilClass then "NULL"
|
90
|
+
when TrueClass then "TRUE"
|
91
|
+
when FalseClass then "FALSE"
|
92
|
+
when Float, Integer then value.to_s
|
93
|
+
# BigDecimals need to be output in a non-normalized form and quoted.
|
94
|
+
when BigDecimal then value.to_s("F")
|
95
|
+
when Array then value.map { |v| quote(v) }
|
96
|
+
else
|
97
|
+
value.inspect
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
module ColumnAnnotation
|
6
|
+
class TypeBuilder
|
7
|
+
# Don't show limit (#) on these column types
|
8
|
+
# Example: show "integer" instead of "integer(4)"
|
9
|
+
NO_LIMIT_COL_TYPES = %w[integer bigint boolean].freeze
|
10
|
+
|
11
|
+
def initialize(column, options)
|
12
|
+
@column = ColumnWrapper.new(column)
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the formatted column type as a string.
|
17
|
+
def build
|
18
|
+
column_type = @column.column_type_string
|
19
|
+
|
20
|
+
formatted_column_type = column_type
|
21
|
+
|
22
|
+
is_special_type = %w[spatial geometry geography].include?(column_type)
|
23
|
+
is_decimal_type = column_type == "decimal"
|
24
|
+
|
25
|
+
if is_decimal_type
|
26
|
+
formatted_column_type = "decimal(#{@column.precision}, #{@column.scale})"
|
27
|
+
elsif is_special_type
|
28
|
+
# Do nothing. Kept as a code fragment in case we need to do something here.
|
29
|
+
elsif @column.limit && !@options[:format_yard]
|
30
|
+
# Unsure if Column#limit will ever be an array. May be safe to remove.
|
31
|
+
if !@column.limit.is_a?(Array) && !hide_limit?
|
32
|
+
formatted_column_type = column_type + "(#{@column.limit})"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
formatted_column_type
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def hide_limit?
|
42
|
+
excludes =
|
43
|
+
if @options[:hide_limit_column_types].blank?
|
44
|
+
NO_LIMIT_COL_TYPES
|
45
|
+
else
|
46
|
+
@options[:hide_limit_column_types].split(",")
|
47
|
+
end
|
48
|
+
|
49
|
+
excludes.include?(@column.column_type_string)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
module ColumnAnnotation
|
6
|
+
autoload :AttributesBuilder, "annotate_rb/model_annotator/column_annotation/attributes_builder"
|
7
|
+
autoload :TypeBuilder, "annotate_rb/model_annotator/column_annotation/type_builder"
|
8
|
+
autoload :ColumnWrapper, "annotate_rb/model_annotator/column_annotation/column_wrapper"
|
9
|
+
autoload :AnnotationBuilder, "annotate_rb/model_annotator/column_annotation/annotation_builder"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
# Generates the text file content with annotations, these are then to be written to filesystem.
|
6
|
+
class FileBuilder
|
7
|
+
def initialize(file_components, new_annotations, annotation_position, options)
|
8
|
+
@file_components = file_components
|
9
|
+
@new_annotations = new_annotations
|
10
|
+
@annotation_position = annotation_position
|
11
|
+
@options = options
|
12
|
+
|
13
|
+
@new_wrapped_annotations = wrapped_content(new_annotations)
|
14
|
+
end
|
15
|
+
|
16
|
+
def generate_content_with_new_annotations
|
17
|
+
# Need to keep `.to_s` for now since the it can be either a String or Symbol
|
18
|
+
annotation_write_position = @options[@annotation_position].to_s
|
19
|
+
|
20
|
+
_content = if %w[after bottom].include?(annotation_write_position)
|
21
|
+
@file_components.magic_comments + (@file_components.pure_file_content.rstrip + "\n\n" + @new_wrapped_annotations)
|
22
|
+
elsif @file_components.magic_comments.empty?
|
23
|
+
@file_components.magic_comments + @new_wrapped_annotations + @file_components.pure_file_content.lstrip
|
24
|
+
else
|
25
|
+
@file_components.magic_comments + "\n" + @new_wrapped_annotations + @file_components.pure_file_content.lstrip
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def update_existing_annotations
|
30
|
+
return "" if !@file_components.has_annotations?
|
31
|
+
|
32
|
+
annotation_pattern = AnnotationPatternGenerator.call(@options)
|
33
|
+
|
34
|
+
new_annotation = @file_components.space_before_annotation + @new_wrapped_annotations + @file_components.space_after_annotation
|
35
|
+
|
36
|
+
_content = @file_components.current_file_content.sub(annotation_pattern, new_annotation)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def wrapped_content(content)
|
42
|
+
wrapper_open = if @options[:wrapper_open]
|
43
|
+
"# #{@options[:wrapper_open]}\n"
|
44
|
+
else
|
45
|
+
""
|
46
|
+
end
|
47
|
+
|
48
|
+
wrapper_close = if @options[:wrapper_close]
|
49
|
+
"# #{@options[:wrapper_close]}\n"
|
50
|
+
else
|
51
|
+
""
|
52
|
+
end
|
53
|
+
|
54
|
+
_wrapped_info_block = "#{wrapper_open}#{content}#{wrapper_close}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
class FileComponents
|
6
|
+
SKIP_ANNOTATION_STRING = "# -*- SkipSchemaAnnotations"
|
7
|
+
SOME_PATTERN = /\A(?<start>\s*).*?\n(?<end>\s*)\z/m # Unsure what this pattern is
|
8
|
+
|
9
|
+
def initialize(file_content, new_annotations, options)
|
10
|
+
@file_content = file_content
|
11
|
+
@diff = AnnotationDiffGenerator.new(file_content, new_annotations).generate
|
12
|
+
@options = options
|
13
|
+
@annotation_pattern = AnnotationPatternGenerator.call(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def current_file_content
|
17
|
+
@file_content
|
18
|
+
end
|
19
|
+
|
20
|
+
# TODO: Rename method once it's clear what this actually does
|
21
|
+
def space_before_annotation
|
22
|
+
return @space_before_annotation if defined?(@space_before_annotation)
|
23
|
+
|
24
|
+
match = current_annotations.match(SOME_PATTERN)
|
25
|
+
@space_before_annotation = if match
|
26
|
+
match[:start]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# TODO: Rename method once it's clear what this actually does
|
31
|
+
def space_after_annotation
|
32
|
+
return @space_after_annotation if defined?(@space_after_annotation)
|
33
|
+
|
34
|
+
match = current_annotations.match(SOME_PATTERN)
|
35
|
+
@space_after_annotation = if match
|
36
|
+
match[:end]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def pure_file_content
|
41
|
+
@pure_file_content ||=
|
42
|
+
begin
|
43
|
+
content_without_magic_comments = @file_content.gsub(MagicCommentParser::MAGIC_COMMENTS_REGEX, "")
|
44
|
+
content_without_annotations = content_without_magic_comments.sub(@annotation_pattern, "")
|
45
|
+
|
46
|
+
content_without_annotations
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def magic_comments
|
51
|
+
@magic_comments ||= MagicCommentParser.call(@file_content)
|
52
|
+
end
|
53
|
+
|
54
|
+
def has_skip_string?
|
55
|
+
@has_skip_string ||= @file_content.include?(SKIP_ANNOTATION_STRING)
|
56
|
+
end
|
57
|
+
|
58
|
+
def has_annotations?
|
59
|
+
@has_annotations ||= @diff.current_columns.present?
|
60
|
+
end
|
61
|
+
|
62
|
+
def annotations_changed?
|
63
|
+
@has_annotations_changed ||= @diff.changed?
|
64
|
+
end
|
65
|
+
|
66
|
+
def current_annotations
|
67
|
+
@current_annotations ||=
|
68
|
+
if has_annotations?
|
69
|
+
# `#has_annotations?` uses a different regex pattern than the one in `@annotation_pattern`,
|
70
|
+
# this could lead to unexpected behavior
|
71
|
+
@file_content.match(@annotation_pattern).to_s
|
72
|
+
else
|
73
|
+
""
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -6,9 +6,9 @@ module AnnotateRb
|
|
6
6
|
class << self
|
7
7
|
def call(filename_template, model_name, table_name)
|
8
8
|
filename_template
|
9
|
-
.gsub(
|
10
|
-
.gsub(
|
11
|
-
.gsub(
|
9
|
+
.gsub("%MODEL_NAME%", model_name)
|
10
|
+
.gsub("%PLURALIZED_MODEL_NAME%", model_name.pluralize)
|
11
|
+
.gsub("%TABLE_NAME%", table_name || model_name.pluralize)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
module ForeignKeyAnnotation
|
6
|
+
class AnnotationBuilder
|
7
|
+
def initialize(model, options)
|
8
|
+
@model = model
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def build
|
13
|
+
fk_info = if @options[:format_markdown]
|
14
|
+
"#\n# ### Foreign Keys\n#\n"
|
15
|
+
else
|
16
|
+
"#\n# Foreign Keys\n#\n"
|
17
|
+
end
|
18
|
+
|
19
|
+
return "" unless @model.connection.respond_to?(:supports_foreign_keys?) &&
|
20
|
+
@model.connection.supports_foreign_keys? && @model.connection.respond_to?(:foreign_keys)
|
21
|
+
|
22
|
+
foreign_keys = @model.connection.foreign_keys(@model.table_name)
|
23
|
+
return "" if foreign_keys.empty?
|
24
|
+
|
25
|
+
format_name = lambda do |fk|
|
26
|
+
return fk.options[:column] if fk.name.blank?
|
27
|
+
|
28
|
+
@options[:show_complete_foreign_keys] ? fk.name : fk.name.gsub(/(?<=^fk_rails_)[0-9a-f]{10}$/, "...")
|
29
|
+
end
|
30
|
+
|
31
|
+
max_size = foreign_keys.map(&format_name).map(&:size).max + 1
|
32
|
+
foreign_keys.sort_by { |fk| [format_name.call(fk), fk.column] }.each do |fk|
|
33
|
+
ref_info = "#{fk.column} => #{fk.to_table}.#{fk.primary_key}"
|
34
|
+
constraints_info = ""
|
35
|
+
constraints_info += "ON DELETE => #{fk.on_delete} " if fk.on_delete
|
36
|
+
constraints_info += "ON UPDATE => #{fk.on_update} " if fk.on_update
|
37
|
+
constraints_info = constraints_info.strip
|
38
|
+
|
39
|
+
fk_info += if @options[:format_markdown]
|
40
|
+
format("# * `%s`%s:\n# * **`%s`**\n",
|
41
|
+
format_name.call(fk),
|
42
|
+
constraints_info.blank? ? "" : " (_#{constraints_info}_)",
|
43
|
+
ref_info)
|
44
|
+
else
|
45
|
+
format("# %-#{max_size}.#{max_size}s %s %s",
|
46
|
+
format_name.call(fk),
|
47
|
+
"(#{ref_info})",
|
48
|
+
constraints_info).rstrip + "\n"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
fk_info
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|