annotaterb 4.0.0 → 4.1.0
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/VERSION +1 -1
- data/lib/annotate_rb/model_annotator/annotation_decider.rb +62 -0
- data/lib/annotate_rb/model_annotator/annotation_generator.rb +94 -0
- data/lib/annotate_rb/model_annotator/annotator.rb +27 -31
- data/lib/annotate_rb/model_annotator/column_annotation_builder.rb +92 -0
- data/lib/annotate_rb/model_annotator/column_attributes_builder.rb +102 -0
- data/lib/annotate_rb/model_annotator/column_type_builder.rb +51 -0
- data/lib/annotate_rb/model_annotator/column_wrapper.rb +84 -0
- data/lib/annotate_rb/model_annotator/constants.rb +2 -2
- data/lib/annotate_rb/model_annotator/file_annotator.rb +6 -2
- data/lib/annotate_rb/model_annotator/file_annotator_instruction.rb +17 -0
- data/lib/annotate_rb/model_annotator/foreign_key_annotation_builder.rb +55 -0
- data/lib/annotate_rb/model_annotator/helper.rb +75 -22
- data/lib/annotate_rb/model_annotator/index_annotation_builder.rb +74 -0
- data/lib/annotate_rb/model_annotator/model_file_annotator.rb +21 -84
- data/lib/annotate_rb/model_annotator/model_files_getter.rb +4 -2
- data/lib/annotate_rb/model_annotator/model_wrapper.rb +155 -0
- data/lib/annotate_rb/model_annotator/related_files_list_builder.rb +137 -0
- data/lib/annotate_rb/model_annotator.rb +11 -1
- data/lib/annotate_rb/options.rb +16 -9
- data/lib/annotate_rb/parser.rb +0 -14
- data/lib/annotate_rb.rb +0 -1
- metadata +13 -4
- data/lib/annotate_rb/env.rb +0 -30
- data/lib/annotate_rb/model_annotator/schema_info.rb +0 -480
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1c77ca5a809b63c94e69b937732b8f8f531807cc332627791b649576e8ed583e
|
4
|
+
data.tar.gz: c2fb3ac84bad2e0cd108c801ca162a6a17b34ba800af8f5e885e03c8338d636d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fb88fa8b511e0ccbbadce0db7d0ae941234967eb7358fea1146e48309939b1b35e51f8db9f15d544a6af4b3827decb65e51f4546d73feb2ab2f404c67f3c0f05
|
7
|
+
data.tar.gz: 562cd430feba785e8f53ab90f79dfa1c3765f46a8e048bbe379955c8f696dd4e01ce167245cf17e31de4704dc0e22da859cba2f6f00c87d10782e637c2faad65
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
4.
|
1
|
+
4.1.0
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
# Class that encapsulates the logic to decide whether to annotate a model file and its related files or not.
|
6
|
+
class AnnotationDecider
|
7
|
+
def initialize(file, options)
|
8
|
+
@file = file
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def annotate?
|
13
|
+
return false if file_contains_skip_annotation
|
14
|
+
|
15
|
+
begin
|
16
|
+
klass = ModelClassGetter.call(@file, @options)
|
17
|
+
|
18
|
+
klass_is_a_class = klass.is_a?(Class)
|
19
|
+
klass_inherits_active_record_base = klass < ActiveRecord::Base
|
20
|
+
klass_is_not_abstract = klass.respond_to?(:abstract_class) && !klass.abstract_class?
|
21
|
+
klass_table_exists = klass.respond_to?(:abstract_class) && klass.table_exists?
|
22
|
+
|
23
|
+
not_sure_this_conditional = (!@options[:exclude_sti_subclasses] || !(klass.superclass < ActiveRecord::Base && klass.table_name == klass.superclass.table_name))
|
24
|
+
|
25
|
+
annotate_conditions = [
|
26
|
+
klass_is_a_class,
|
27
|
+
klass_inherits_active_record_base,
|
28
|
+
not_sure_this_conditional,
|
29
|
+
klass_is_not_abstract,
|
30
|
+
klass_table_exists
|
31
|
+
]
|
32
|
+
|
33
|
+
to_annotate = annotate_conditions.all?
|
34
|
+
|
35
|
+
return to_annotate
|
36
|
+
rescue BadModelFileError => e
|
37
|
+
unless @options[:ignore_unknown_models]
|
38
|
+
$stderr.puts "Unable to annotate #{@file}: #{e.message}"
|
39
|
+
$stderr.puts "\t" + e.backtrace.join("\n\t") if @options[:trace]
|
40
|
+
end
|
41
|
+
rescue StandardError => e
|
42
|
+
$stderr.puts "Unable to annotate #{@file}: #{e.message}"
|
43
|
+
$stderr.puts "\t" + e.backtrace.join("\n\t") if @options[:trace]
|
44
|
+
end
|
45
|
+
|
46
|
+
false
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def file_contains_skip_annotation
|
52
|
+
file_string = File.exist?(@file) ? File.read(@file) : ''
|
53
|
+
|
54
|
+
if /#{Constants::SKIP_ANNOTATION_PREFIX}.*/ =~ file_string
|
55
|
+
true
|
56
|
+
else
|
57
|
+
false
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
class AnnotationGenerator
|
6
|
+
# Annotate Models plugin use this header
|
7
|
+
PREFIX = '== Schema Information'.freeze
|
8
|
+
PREFIX_MD = '## Schema Information'.freeze
|
9
|
+
|
10
|
+
END_MARK = '== Schema Information End'.freeze
|
11
|
+
|
12
|
+
MD_NAMES_OVERHEAD = 6
|
13
|
+
MD_TYPE_ALLOWANCE = 18
|
14
|
+
|
15
|
+
def initialize(klass, options = {})
|
16
|
+
@model = ModelWrapper.new(klass, options)
|
17
|
+
@options = options
|
18
|
+
@info = "" # TODO: Make array and build string that way
|
19
|
+
end
|
20
|
+
|
21
|
+
def generate
|
22
|
+
@info = "# #{header}\n"
|
23
|
+
@info += schema_header_text
|
24
|
+
|
25
|
+
max_size = @model.max_schema_info_width
|
26
|
+
|
27
|
+
if @options[:format_markdown]
|
28
|
+
@info += format("# %-#{max_size + MD_NAMES_OVERHEAD}.#{max_size + MD_NAMES_OVERHEAD}s | %-#{MD_TYPE_ALLOWANCE}.#{MD_TYPE_ALLOWANCE}s | %s\n",
|
29
|
+
'Name',
|
30
|
+
'Type',
|
31
|
+
'Attributes')
|
32
|
+
@info += "# #{'-' * (max_size + MD_NAMES_OVERHEAD)} | #{'-' * MD_TYPE_ALLOWANCE} | #{'-' * 27}\n"
|
33
|
+
end
|
34
|
+
|
35
|
+
@info += @model.columns.map do |col|
|
36
|
+
ColumnAnnotationBuilder.new(col, @model, max_size, @options).build
|
37
|
+
end.join
|
38
|
+
|
39
|
+
if @options[:show_indexes] && @model.table_exists?
|
40
|
+
@info += IndexAnnotationBuilder.new(@model, @options).build
|
41
|
+
end
|
42
|
+
|
43
|
+
if @options[:show_foreign_keys] && @model.table_exists?
|
44
|
+
@info += ForeignKeyAnnotationBuilder.new(@model, @options).build
|
45
|
+
end
|
46
|
+
|
47
|
+
@info += schema_footer_text
|
48
|
+
|
49
|
+
@info
|
50
|
+
end
|
51
|
+
|
52
|
+
def header
|
53
|
+
header = @options[:format_markdown] ? PREFIX_MD.dup : PREFIX.dup
|
54
|
+
version = ActiveRecord::Migrator.current_version rescue 0
|
55
|
+
|
56
|
+
if @options[:include_version] && version > 0
|
57
|
+
header += "\n# Schema version: #{version}"
|
58
|
+
end
|
59
|
+
|
60
|
+
header
|
61
|
+
end
|
62
|
+
|
63
|
+
def schema_header_text
|
64
|
+
info = []
|
65
|
+
info << "#"
|
66
|
+
|
67
|
+
if @options[:format_markdown]
|
68
|
+
info << "# Table name: `#{@model.table_name}`"
|
69
|
+
info << "#"
|
70
|
+
info << "# ### Columns"
|
71
|
+
else
|
72
|
+
info << "# Table name: #{@model.table_name}"
|
73
|
+
end
|
74
|
+
info << "#\n" # We want the last line break
|
75
|
+
|
76
|
+
info.join("\n")
|
77
|
+
end
|
78
|
+
|
79
|
+
def schema_footer_text
|
80
|
+
info = []
|
81
|
+
|
82
|
+
if @options[:format_rdoc]
|
83
|
+
info << "#--"
|
84
|
+
info << "# #{END_MARK}"
|
85
|
+
info << "#++\n"
|
86
|
+
else
|
87
|
+
info << "#\n"
|
88
|
+
end
|
89
|
+
|
90
|
+
info.join("\n")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -1,31 +1,20 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module AnnotateRb
|
4
4
|
module ModelAnnotator
|
5
5
|
class Annotator
|
6
|
-
# Annotate Models plugin use this header
|
7
|
-
PREFIX = '== Schema Information'.freeze
|
8
|
-
PREFIX_MD = '## Schema Information'.freeze
|
9
|
-
|
10
|
-
MAGIC_COMMENT_MATCHER = Regexp.new(/(^#\s*encoding:.*(?:\n|r\n))|(^# coding:.*(?:\n|\r\n))|(^# -\*- coding:.*(?:\n|\r\n))|(^# -\*- encoding\s?:.*(?:\n|\r\n))|(^#\s*frozen_string_literal:.+(?:\n|\r\n))|(^# -\*- frozen_string_literal\s*:.+-\*-(?:\n|\r\n))/).freeze
|
11
|
-
|
12
6
|
class << self
|
13
|
-
# We're passed a name of things that might be
|
14
|
-
# ActiveRecord models. If we can find the class, and
|
15
|
-
# if its a subclass of ActiveRecord::Base,
|
16
|
-
# then pass it to the associated block
|
17
7
|
def do_annotations(options = {})
|
18
|
-
header = options[:format_markdown] ? PREFIX_MD.dup : PREFIX.dup
|
19
|
-
version = ActiveRecord::Migrator.current_version rescue 0
|
20
|
-
if options[:include_version] && version > 0
|
21
|
-
header << "\n# Schema version: #{version}"
|
22
|
-
end
|
23
|
-
|
24
8
|
annotated = []
|
25
|
-
model_files_to_annotate = ModelFilesGetter.call(options)
|
26
9
|
|
27
|
-
|
28
|
-
|
10
|
+
model_files_to_consider = ModelFilesGetter.call(options)
|
11
|
+
|
12
|
+
model_files_to_consider.each do |path, filename|
|
13
|
+
file = File.join(path, filename)
|
14
|
+
|
15
|
+
if AnnotationDecider.new(file, options).annotate?
|
16
|
+
ModelFileAnnotator.call(annotated, file, options)
|
17
|
+
end
|
29
18
|
end
|
30
19
|
|
31
20
|
if annotated.empty?
|
@@ -37,34 +26,41 @@ module AnnotateRb
|
|
37
26
|
|
38
27
|
def remove_annotations(options = {})
|
39
28
|
deannotated = []
|
40
|
-
|
41
|
-
ModelFilesGetter.call(options)
|
42
|
-
|
29
|
+
|
30
|
+
model_files_to_consider = ModelFilesGetter.call(options)
|
31
|
+
|
32
|
+
model_files_to_consider.each do |path, filename|
|
33
|
+
deannotated_klass = false
|
34
|
+
file = File.join(path, filename)
|
35
|
+
|
43
36
|
begin
|
44
37
|
klass = ModelClassGetter.call(file, options)
|
45
38
|
if klass < ActiveRecord::Base && !klass.abstract_class?
|
46
39
|
model_name = klass.name.underscore
|
47
40
|
table_name = klass.table_name
|
48
|
-
model_file_name = file
|
49
|
-
deannotated_klass = true if FileAnnotationRemover.call(model_file_name, options)
|
50
41
|
|
51
|
-
|
42
|
+
if FileAnnotationRemover.call(file, options)
|
43
|
+
deannotated_klass = true
|
44
|
+
end
|
45
|
+
|
46
|
+
related_files = RelatedFilesListBuilder.new(file, model_name, table_name, options).build
|
52
47
|
|
53
|
-
|
54
|
-
.map { |f| FileNameResolver.call(f, model_name, table_name) }
|
55
|
-
.each do |f|
|
48
|
+
related_files.each do |f, _position_key|
|
56
49
|
if File.exist?(f)
|
57
50
|
FileAnnotationRemover.call(f, options)
|
58
|
-
deannotated_klass = true
|
59
51
|
end
|
60
52
|
end
|
61
53
|
end
|
62
|
-
|
54
|
+
|
55
|
+
if deannotated_klass
|
56
|
+
deannotated << klass
|
57
|
+
end
|
63
58
|
rescue StandardError => e
|
64
59
|
$stderr.puts "Unable to deannotate #{File.join(file)}: #{e.message}"
|
65
60
|
$stderr.puts "\t" + e.backtrace.join("\n\t") if options[:trace]
|
66
61
|
end
|
67
62
|
end
|
63
|
+
|
68
64
|
puts "Removed annotations from: #{deannotated.join(', ')}"
|
69
65
|
end
|
70
66
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
class ColumnAnnotationBuilder
|
6
|
+
BARE_TYPE_ALLOWANCE = 16
|
7
|
+
MD_TYPE_ALLOWANCE = 18
|
8
|
+
|
9
|
+
def initialize(column, model, max_size, options)
|
10
|
+
@column = column
|
11
|
+
@model = model
|
12
|
+
@max_size = max_size
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def build
|
17
|
+
result = ''
|
18
|
+
|
19
|
+
is_primary_key = is_column_primary_key?(@model, @column.name)
|
20
|
+
|
21
|
+
table_indices = @model.retrieve_indexes_from_table
|
22
|
+
column_indices = table_indices.select { |ind| ind.columns.include?(@column.name) }
|
23
|
+
|
24
|
+
column_attributes = ColumnAttributesBuilder.new(@column, @options, is_primary_key, column_indices).build
|
25
|
+
formatted_column_type = ColumnTypeBuilder.new(@column, @options).build
|
26
|
+
|
27
|
+
col_name = if @model.with_comments? && @column.comment
|
28
|
+
"#{@column.name}(#{@column.comment.gsub(/\n/, '\\n')})"
|
29
|
+
else
|
30
|
+
@column.name
|
31
|
+
end
|
32
|
+
|
33
|
+
if @options[:format_rdoc]
|
34
|
+
result += format("# %-#{@max_size}.#{@max_size}s<tt>%s</tt>",
|
35
|
+
"*#{col_name}*::",
|
36
|
+
column_attributes.unshift(formatted_column_type).join(', ')).rstrip + "\n"
|
37
|
+
elsif @options[:format_yard]
|
38
|
+
result += sprintf("# @!attribute #{col_name}") + "\n"
|
39
|
+
|
40
|
+
if @column.respond_to?(:array) && @column.array
|
41
|
+
ruby_class = "Array<#{Helper.map_col_type_to_ruby_classes(formatted_column_type)}>"
|
42
|
+
else
|
43
|
+
ruby_class = Helper.map_col_type_to_ruby_classes(formatted_column_type)
|
44
|
+
end
|
45
|
+
|
46
|
+
result += sprintf("# @return [#{ruby_class}]") + "\n"
|
47
|
+
elsif @options[:format_markdown]
|
48
|
+
name_remainder = @max_size - col_name.length - Helper.non_ascii_length(col_name)
|
49
|
+
type_remainder = (MD_TYPE_ALLOWANCE - 2) - formatted_column_type.length
|
50
|
+
result += format("# **`%s`**%#{name_remainder}s | `%s`%#{type_remainder}s | `%s`",
|
51
|
+
col_name,
|
52
|
+
' ',
|
53
|
+
formatted_column_type,
|
54
|
+
' ',
|
55
|
+
column_attributes.join(', ').rstrip).gsub('``', ' ').rstrip + "\n"
|
56
|
+
else
|
57
|
+
result += format_default(col_name, @max_size, formatted_column_type, column_attributes)
|
58
|
+
end
|
59
|
+
|
60
|
+
result
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def format_default(col_name, max_size, col_type, attrs)
|
66
|
+
format('# %s:%s %s',
|
67
|
+
Helper.mb_chars_ljust(col_name, max_size),
|
68
|
+
Helper.mb_chars_ljust(col_type, BARE_TYPE_ALLOWANCE),
|
69
|
+
attrs.join(', ')).rstrip + "\n"
|
70
|
+
end
|
71
|
+
|
72
|
+
# TODO: Simplify this conditional
|
73
|
+
def is_column_primary_key?(model, column_name)
|
74
|
+
if model.primary_key
|
75
|
+
if model.primary_key.is_a?(Array)
|
76
|
+
# If the model has multiple primary keys, check if this column is one of them
|
77
|
+
if model.primary_key.collect(&:to_sym).include?(column_name.to_sym)
|
78
|
+
return true
|
79
|
+
end
|
80
|
+
else
|
81
|
+
# If model has 1 primary key, check if this column is it
|
82
|
+
if column_name.to_sym == model.primary_key.to_sym
|
83
|
+
return true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
false
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
class ColumnAttributesBuilder
|
6
|
+
# Don't show default value for these column types
|
7
|
+
NO_DEFAULT_COL_TYPES = %w[json jsonb hstore].freeze
|
8
|
+
|
9
|
+
def initialize(column, options, is_primary_key, column_indices)
|
10
|
+
@column = ColumnWrapper.new(column)
|
11
|
+
@options = options
|
12
|
+
@is_primary_key = is_primary_key
|
13
|
+
@column_indices = column_indices
|
14
|
+
end
|
15
|
+
|
16
|
+
# Get the list of attributes that should be included in the annotation for
|
17
|
+
# a given column.
|
18
|
+
def build
|
19
|
+
column_type = @column.column_type_string
|
20
|
+
attrs = []
|
21
|
+
|
22
|
+
unless @column.default.nil? || hide_default?
|
23
|
+
schema_default = "default(#{@column.default_string})"
|
24
|
+
|
25
|
+
attrs << schema_default
|
26
|
+
end
|
27
|
+
|
28
|
+
if @column.unsigned?
|
29
|
+
attrs << 'unsigned'
|
30
|
+
end
|
31
|
+
|
32
|
+
if !@column.null
|
33
|
+
attrs << 'not null'
|
34
|
+
end
|
35
|
+
|
36
|
+
if @is_primary_key
|
37
|
+
attrs << 'primary key'
|
38
|
+
end
|
39
|
+
|
40
|
+
is_special_type = %w[spatial geometry geography].include?(column_type)
|
41
|
+
is_decimal_type = column_type == 'decimal'
|
42
|
+
|
43
|
+
if !is_decimal_type && !is_special_type
|
44
|
+
if @column.limit && !@options[:format_yard]
|
45
|
+
if @column.limit.is_a?(Array)
|
46
|
+
attrs << "(#{@column.limit.join(', ')})"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Check out if we got an array column
|
52
|
+
if @column.array?
|
53
|
+
attrs << 'is an Array'
|
54
|
+
end
|
55
|
+
|
56
|
+
# Check out if we got a geometric column
|
57
|
+
# and print the type and SRID
|
58
|
+
if @column.geometry_type?
|
59
|
+
attrs << "#{@column.geometry_type}, #{@column.srid}"
|
60
|
+
elsif @column.geometric_type? && @column.geometric_type.present?
|
61
|
+
attrs << "#{@column.geometric_type.to_s.downcase}, #{@column.srid}"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Check if the column has indices and print "indexed" if true
|
65
|
+
# If the index includes another column, print it too.
|
66
|
+
if @options[:simple_indexes]
|
67
|
+
# Note: there used to be a klass.table_exists? call here, but removed it as it seemed unnecessary.
|
68
|
+
|
69
|
+
sorted_column_indices&.each do |index|
|
70
|
+
indexed_columns = index.columns.reject { |i| i == @column.name }
|
71
|
+
|
72
|
+
if indexed_columns.empty?
|
73
|
+
attrs << 'indexed'
|
74
|
+
else
|
75
|
+
attrs << "indexed => [#{indexed_columns.join(', ')}]"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
attrs
|
81
|
+
end
|
82
|
+
|
83
|
+
def sorted_column_indices
|
84
|
+
# Not sure why there were & safe accessors here, but keeping in for time being.
|
85
|
+
sorted_indices = @column_indices&.sort_by(&:name)
|
86
|
+
|
87
|
+
_sorted_indices = sorted_indices.reject { |ind| ind.columns.is_a?(String) }
|
88
|
+
end
|
89
|
+
|
90
|
+
def hide_default?
|
91
|
+
excludes =
|
92
|
+
if @options[:hide_default_column_types].blank?
|
93
|
+
NO_DEFAULT_COL_TYPES
|
94
|
+
else
|
95
|
+
@options[:hide_default_column_types].split(',')
|
96
|
+
end
|
97
|
+
|
98
|
+
excludes.include?(@column.column_type_string)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
class ColumnTypeBuilder
|
6
|
+
# Don't show limit (#) on these column types
|
7
|
+
# Example: show "integer" instead of "integer(4)"
|
8
|
+
NO_LIMIT_COL_TYPES = %w[integer bigint boolean].freeze
|
9
|
+
|
10
|
+
def initialize(column, options)
|
11
|
+
@column = ColumnWrapper.new(column)
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the formatted column type as a string.
|
16
|
+
def build
|
17
|
+
column_type = @column.column_type_string
|
18
|
+
|
19
|
+
formatted_column_type = column_type
|
20
|
+
|
21
|
+
is_special_type = %w[spatial geometry geography].include?(column_type)
|
22
|
+
is_decimal_type = column_type == 'decimal'
|
23
|
+
|
24
|
+
if is_decimal_type
|
25
|
+
formatted_column_type = "decimal(#{@column.precision}, #{@column.scale})"
|
26
|
+
elsif is_special_type
|
27
|
+
# Do nothing. Kept as a code fragment in case we need to do something here.
|
28
|
+
else
|
29
|
+
if @column.limit && !@options[:format_yard]
|
30
|
+
if !@column.limit.is_a?(Array) && !hide_limit?
|
31
|
+
formatted_column_type = column_type + "(#{@column.limit})"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
formatted_column_type
|
37
|
+
end
|
38
|
+
|
39
|
+
def hide_limit?
|
40
|
+
excludes =
|
41
|
+
if @options[:hide_limit_column_types].blank?
|
42
|
+
NO_LIMIT_COL_TYPES
|
43
|
+
else
|
44
|
+
@options[:hide_limit_column_types].split(',')
|
45
|
+
end
|
46
|
+
|
47
|
+
excludes.include?(@column.column_type_string)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
class ColumnWrapper
|
6
|
+
def initialize(column)
|
7
|
+
@column = column
|
8
|
+
end
|
9
|
+
|
10
|
+
def default
|
11
|
+
# Note: Used to be klass.column_defaults[name], where name is the column name.
|
12
|
+
# Looks to be identical, but keeping note here in case there are differences.
|
13
|
+
_column_default = @column.default
|
14
|
+
end
|
15
|
+
|
16
|
+
def default_string
|
17
|
+
Helper.quote(@column.default)
|
18
|
+
end
|
19
|
+
|
20
|
+
def type
|
21
|
+
@column.type
|
22
|
+
end
|
23
|
+
|
24
|
+
def column_type_string
|
25
|
+
if (@column.respond_to?(:bigint?) && @column.bigint?) || /\Abigint\b/ =~ @column.sql_type
|
26
|
+
'bigint'
|
27
|
+
else
|
28
|
+
(@column.type || @column.sql_type).to_s
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def unsigned?
|
33
|
+
@column.respond_to?(:unsigned?) && @column.unsigned?
|
34
|
+
end
|
35
|
+
|
36
|
+
def null
|
37
|
+
@column.null
|
38
|
+
end
|
39
|
+
|
40
|
+
def precision
|
41
|
+
@column.precision
|
42
|
+
end
|
43
|
+
|
44
|
+
def scale
|
45
|
+
@column.scale
|
46
|
+
end
|
47
|
+
|
48
|
+
def limit
|
49
|
+
@column.limit
|
50
|
+
end
|
51
|
+
|
52
|
+
def geometry_type?
|
53
|
+
@column.respond_to?(:geometry_type)
|
54
|
+
end
|
55
|
+
|
56
|
+
def geometry_type
|
57
|
+
# TODO: Check if we need to check if it responds before accessing the geometry type
|
58
|
+
@column.geometry_type
|
59
|
+
end
|
60
|
+
|
61
|
+
def geometric_type?
|
62
|
+
@column.respond_to?(:geometric_type)
|
63
|
+
end
|
64
|
+
|
65
|
+
def geometric_type
|
66
|
+
# TODO: Check if we need to check if it responds before accessing the geometric type
|
67
|
+
@column.geometric_type
|
68
|
+
end
|
69
|
+
|
70
|
+
def srid
|
71
|
+
# TODO: Check if we need to check if it responds before accessing the srid
|
72
|
+
@column.srid
|
73
|
+
end
|
74
|
+
|
75
|
+
def array?
|
76
|
+
@column.respond_to?(:array) && @column.array
|
77
|
+
end
|
78
|
+
|
79
|
+
def name
|
80
|
+
@column.name
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -1,8 +1,6 @@
|
|
1
1
|
module AnnotateRb
|
2
2
|
module ModelAnnotator
|
3
3
|
module Constants
|
4
|
-
TRUE_RE = /^(true|t|yes|y|1)$/i.freeze
|
5
|
-
|
6
4
|
##
|
7
5
|
# The set of available options to customize the behavior of Annotate.
|
8
6
|
#
|
@@ -17,6 +15,8 @@ module AnnotateRb
|
|
17
15
|
ALL_ANNOTATE_OPTIONS = ::AnnotateRb::Options::ALL_OPTION_KEYS
|
18
16
|
|
19
17
|
SKIP_ANNOTATION_PREFIX = '# -\*- SkipSchemaAnnotations'.freeze
|
18
|
+
|
19
|
+
MAGIC_COMMENT_MATCHER = Regexp.new(/(^#\s*encoding:.*(?:\n|r\n))|(^# coding:.*(?:\n|\r\n))|(^# -\*- coding:.*(?:\n|\r\n))|(^# -\*- encoding\s?:.*(?:\n|\r\n))|(^#\s*frozen_string_literal:.+(?:\n|\r\n))|(^# -\*- frozen_string_literal\s*:.+-\*-(?:\n|\r\n))/).freeze
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
@@ -4,6 +4,10 @@ module AnnotateRb
|
|
4
4
|
module ModelAnnotator
|
5
5
|
class FileAnnotator
|
6
6
|
class << self
|
7
|
+
def call_with_instructions(instruction)
|
8
|
+
call(instruction.file, instruction.annotation, instruction.position, instruction.options)
|
9
|
+
end
|
10
|
+
|
7
11
|
# Add a schema block to a file. If the file already contains
|
8
12
|
# a schema info block (a comment starting with "== Schema Information"),
|
9
13
|
# check if it matches the block that is already there. If so, leave it be.
|
@@ -33,7 +37,7 @@ module AnnotateRb
|
|
33
37
|
|
34
38
|
return false if old_columns == new_columns && !options[:force]
|
35
39
|
|
36
|
-
abort "
|
40
|
+
abort "AnnotateRb error. #{file_name} needs to be updated, but annotaterb was run with `--frozen`." if options[:frozen]
|
37
41
|
|
38
42
|
# Replace inline the old schema info with the new schema info
|
39
43
|
wrapper_open = options[:wrapper_open] ? "# #{options[:wrapper_open]}\n" : ""
|
@@ -47,7 +51,7 @@ module AnnotateRb
|
|
47
51
|
# need to insert it in correct position
|
48
52
|
if old_annotation.empty? || options[:force]
|
49
53
|
magic_comments_block = Helper.magic_comments_as_string(old_content)
|
50
|
-
old_content.gsub!(
|
54
|
+
old_content.gsub!(Constants::MAGIC_COMMENT_MATCHER, '')
|
51
55
|
|
52
56
|
annotation_pattern = AnnotationPatternGenerator.call(options)
|
53
57
|
old_content.sub!(annotation_pattern, '')
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
# A plain old Ruby object (PORO) that contains all necessary information for FileAnnotator
|
6
|
+
class FileAnnotatorInstruction
|
7
|
+
def initialize(file, annotation, position, options = {})
|
8
|
+
@file = file # Path to file
|
9
|
+
@annotation = annotation # Annotation string
|
10
|
+
@position = position # Position in the file where to write the annotation to
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :file, :annotation, :position, :options
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|