annotaterb 4.1.1 → 4.2.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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +68 -0
  3. data/VERSION +1 -1
  4. data/exe/annotaterb +7 -7
  5. data/lib/annotate_rb/active_record_patch.rb +2 -0
  6. data/lib/annotate_rb/commands/annotate_models.rb +0 -1
  7. data/lib/annotate_rb/commands/annotate_routes.rb +0 -1
  8. data/lib/annotate_rb/commands/print_help.rb +0 -1
  9. data/lib/annotate_rb/commands/print_version.rb +0 -1
  10. data/lib/annotate_rb/commands.rb +4 -4
  11. data/lib/annotate_rb/config_finder.rb +1 -1
  12. data/lib/annotate_rb/config_loader.rb +2 -2
  13. data/lib/annotate_rb/core.rb +3 -3
  14. data/lib/annotate_rb/helper.rb +16 -0
  15. data/lib/annotate_rb/model_annotator/{annotation_generator.rb → annotation_builder.rb} +18 -14
  16. data/lib/annotate_rb/model_annotator/annotation_decider.rb +10 -12
  17. data/lib/annotate_rb/model_annotator/annotation_diff.rb +1 -1
  18. data/lib/annotate_rb/model_annotator/annotation_diff_generator.rb +9 -9
  19. data/lib/annotate_rb/model_annotator/annotation_pattern_generator.rb +3 -3
  20. data/lib/annotate_rb/model_annotator/annotator.rb +12 -53
  21. data/lib/annotate_rb/model_annotator/column_annotation/annotation_builder.rb +135 -0
  22. data/lib/annotate_rb/model_annotator/column_annotation/attributes_builder.rb +104 -0
  23. data/lib/annotate_rb/model_annotator/column_annotation/column_wrapper.rb +103 -0
  24. data/lib/annotate_rb/model_annotator/column_annotation/type_builder.rb +54 -0
  25. data/lib/annotate_rb/model_annotator/column_annotation.rb +12 -0
  26. data/lib/annotate_rb/model_annotator/file_builder.rb +58 -0
  27. data/lib/annotate_rb/model_annotator/file_components.rb +78 -0
  28. data/lib/annotate_rb/model_annotator/file_name_resolver.rb +3 -3
  29. data/lib/annotate_rb/model_annotator/foreign_key_annotation/annotation_builder.rb +57 -0
  30. data/lib/annotate_rb/model_annotator/foreign_key_annotation.rb +9 -0
  31. data/lib/annotate_rb/model_annotator/index_annotation/annotation_builder.rb +113 -0
  32. data/lib/annotate_rb/model_annotator/index_annotation.rb +9 -0
  33. data/lib/annotate_rb/model_annotator/magic_comment_parser.rb +32 -0
  34. data/lib/annotate_rb/model_annotator/model_class_getter.rb +6 -6
  35. data/lib/annotate_rb/model_annotator/model_files_getter.rb +12 -10
  36. data/lib/annotate_rb/model_annotator/model_wrapper.rb +44 -37
  37. data/lib/annotate_rb/model_annotator/pattern_getter.rb +142 -10
  38. data/lib/annotate_rb/model_annotator/project_annotation_remover.rb +65 -0
  39. data/lib/annotate_rb/model_annotator/project_annotator.rb +63 -0
  40. data/lib/annotate_rb/model_annotator/related_files_list_builder.rb +16 -18
  41. data/lib/annotate_rb/model_annotator/single_file_annotation_remover.rb +37 -0
  42. data/lib/annotate_rb/model_annotator/single_file_annotator.rb +49 -0
  43. data/lib/annotate_rb/model_annotator/{file_annotator_instruction.rb → single_file_annotator_instruction.rb} +2 -2
  44. data/lib/annotate_rb/model_annotator/single_file_remove_annotation_instruction.rb +15 -0
  45. data/lib/annotate_rb/model_annotator.rb +25 -26
  46. data/lib/annotate_rb/options.rb +20 -25
  47. data/lib/annotate_rb/parser.rb +150 -142
  48. data/lib/annotate_rb/rake_bootstrapper.rb +8 -8
  49. data/lib/annotate_rb/route_annotator/annotation_processor.rb +7 -8
  50. data/lib/annotate_rb/route_annotator/annotator.rb +2 -2
  51. data/lib/annotate_rb/route_annotator/base_processor.rb +3 -3
  52. data/lib/annotate_rb/route_annotator/header_generator.rb +15 -15
  53. data/lib/annotate_rb/route_annotator/helper.rb +9 -9
  54. data/lib/annotate_rb/route_annotator/removal_processor.rb +4 -4
  55. data/lib/annotate_rb/route_annotator.rb +6 -6
  56. data/lib/annotate_rb/runner.rb +0 -4
  57. data/lib/annotate_rb/tasks/annotate_models_migrate.rake +5 -5
  58. data/lib/annotate_rb.rb +19 -19
  59. data/lib/generators/annotate_rb/install/install_generator.rb +3 -2
  60. data/lib/generators/annotate_rb/install/templates/annotate_rb.rake +1 -1
  61. metadata +22 -16
  62. data/lib/annotate_rb/model_annotator/column_annotation_builder.rb +0 -92
  63. data/lib/annotate_rb/model_annotator/column_attributes_builder.rb +0 -102
  64. data/lib/annotate_rb/model_annotator/column_type_builder.rb +0 -51
  65. data/lib/annotate_rb/model_annotator/column_wrapper.rb +0 -84
  66. data/lib/annotate_rb/model_annotator/constants.rb +0 -22
  67. data/lib/annotate_rb/model_annotator/file_annotation_remover.rb +0 -25
  68. data/lib/annotate_rb/model_annotator/file_annotator.rb +0 -77
  69. data/lib/annotate_rb/model_annotator/file_patterns.rb +0 -129
  70. data/lib/annotate_rb/model_annotator/foreign_key_annotation_builder.rb +0 -55
  71. data/lib/annotate_rb/model_annotator/helper.rb +0 -107
  72. data/lib/annotate_rb/model_annotator/index_annotation_builder.rb +0 -74
  73. data/lib/annotate_rb/model_annotator/model_file_annotator.rb +0 -55
@@ -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('%MODEL_NAME%', model_name)
10
- .gsub('%PLURALIZED_MODEL_NAME%', model_name.pluralize)
11
- .gsub('%TABLE_NAME%', table_name || model_name.pluralize)
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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnnotateRb
4
+ module ModelAnnotator
5
+ module ForeignKeyAnnotation
6
+ autoload :AnnotationBuilder, "annotate_rb/model_annotator/foreign_key_annotation/annotation_builder"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnnotateRb
4
+ module ModelAnnotator
5
+ module IndexAnnotation
6
+ class AnnotationBuilder
7
+ INDEX_CLAUSES = {
8
+ unique: {
9
+ default: "UNIQUE",
10
+ markdown: "_unique_"
11
+ },
12
+ where: {
13
+ default: "WHERE",
14
+ markdown: "_where_"
15
+ },
16
+ using: {
17
+ default: "USING",
18
+ markdown: "_using_"
19
+ }
20
+ }.freeze
21
+
22
+ def initialize(model, options)
23
+ @model = model
24
+ @options = options
25
+ end
26
+
27
+ def build
28
+ index_info = if @options[:format_markdown]
29
+ "#\n# ### Indexes\n#\n"
30
+ else
31
+ "#\n# Indexes\n#\n"
32
+ end
33
+
34
+ indexes = @model.retrieve_indexes_from_table
35
+ return "" if indexes.empty?
36
+
37
+ max_size = indexes.collect { |index| index.name.size }.max + 1
38
+ indexes.sort_by(&:name).each do |index|
39
+ index_info += if @options[:format_markdown]
40
+ final_index_string_in_markdown(index)
41
+ else
42
+ final_index_string(index, max_size)
43
+ end
44
+ end
45
+
46
+ index_info
47
+ end
48
+
49
+ private
50
+
51
+ def index_using_info(index, format = :default)
52
+ value = index.try(:using) && index.using.try(:to_sym)
53
+ if !value.blank? && value != :btree
54
+ " #{INDEX_CLAUSES[:using][format]} #{value}"
55
+ else
56
+ ""
57
+ end
58
+ end
59
+
60
+ def index_where_info(index, format = :default)
61
+ value = index.try(:where).try(:to_s)
62
+ if value.blank?
63
+ ""
64
+ else
65
+ " #{INDEX_CLAUSES[:where][format]} #{value}"
66
+ end
67
+ end
68
+
69
+ def index_unique_info(index, format = :default)
70
+ index.unique ? " #{INDEX_CLAUSES[:unique][format]}" : ""
71
+ end
72
+
73
+ def final_index_string_in_markdown(index)
74
+ details = format(
75
+ "%s%s%s",
76
+ index_unique_info(index, :markdown),
77
+ index_where_info(index, :markdown),
78
+ index_using_info(index, :markdown)
79
+ ).strip
80
+ details = " (#{details})" unless details.blank?
81
+
82
+ format(
83
+ "# * `%s`%s:\n# * **`%s`**\n",
84
+ index.name,
85
+ details,
86
+ index_columns_info(index).join("`**\n# * **`")
87
+ )
88
+ end
89
+
90
+ def final_index_string(index, max_size)
91
+ format(
92
+ "# %-#{max_size}.#{max_size}s %s%s%s%s",
93
+ index.name,
94
+ "(#{index_columns_info(index).join(",")})",
95
+ index_unique_info(index),
96
+ index_where_info(index),
97
+ index_using_info(index)
98
+ ).rstrip + "\n"
99
+ end
100
+
101
+ def index_columns_info(index)
102
+ Array(index.columns).map do |col|
103
+ if index.try(:orders) && index.orders[col.to_s]
104
+ "#{col} #{index.orders[col.to_s].upcase}"
105
+ else
106
+ col.to_s.gsub("\r", '\r').gsub("\n", '\n')
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnnotateRb
4
+ module ModelAnnotator
5
+ module IndexAnnotation
6
+ autoload :AnnotationBuilder, "annotate_rb/model_annotator/index_annotation/annotation_builder"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnnotateRb
4
+ module ModelAnnotator
5
+ # Extracts magic comments strings and returns them
6
+ class MagicCommentParser
7
+ MAGIC_COMMENTS = [
8
+ HASH_ENCODING = /(^#\s*encoding:.*(?:\n|r\n))/,
9
+ HASH_CODING = /(^# coding:.*(?:\n|\r\n))/,
10
+ HASH_FROZEN_STRING = /(^#\s*frozen_string_literal:.+(?:\n|\r\n))/,
11
+ STAR_ENCODING = /(^# -\*- encoding\s?:.*(?:\n|\r\n))/,
12
+ STAR_CODING = /(^# -\*- coding:.*(?:\n|\r\n))/,
13
+ STAR_FROZEN_STRING = /(^# -\*- frozen_string_literal\s*:.+-\*-(?:\n|\r\n))/,
14
+ SORBET_TYPED_STRING = /(^#\s*typed:.*(?:\n|r\n))/.freeze
15
+ ].freeze
16
+
17
+ MAGIC_COMMENTS_REGEX = Regexp.union(*MAGIC_COMMENTS).freeze
18
+
19
+ class << self
20
+ def call(content)
21
+ magic_comments = content.scan(MAGIC_COMMENTS_REGEX).flatten.compact
22
+
23
+ if magic_comments.any?
24
+ magic_comments.join
25
+ else
26
+ ""
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -8,8 +8,8 @@ module AnnotateRb
8
8
  # Check for namespaced models in subdirectories as well as models
9
9
  # in subdirectories without namespacing.
10
10
  def call(file, options)
11
- model_path = file.gsub(/\.rb$/, '')
12
- options[:model_dir].each { |dir| model_path = model_path.gsub(/^#{dir}/, '').gsub(/^\//, '') }
11
+ model_path = file.gsub(/\.rb$/, "")
12
+ options[:model_dir].each { |dir| model_path = model_path.gsub(/^#{dir}/, "").gsub(/^\//, "") }
13
13
 
14
14
  begin
15
15
  get_loaded_model(model_path, file) || raise(BadModelFileError.new)
@@ -18,8 +18,8 @@ module AnnotateRb
18
18
  file_path = File.expand_path(file)
19
19
  if File.file?(file_path) && Kernel.require(file_path)
20
20
  retry
21
- elsif model_path =~ /\//
22
- model_path = model_path.split('/')[1..-1].join('/').to_s
21
+ elsif /\//.match?(model_path)
22
+ model_path = model_path.split("/")[1..-1].join("/").to_s
23
23
  retry
24
24
  else
25
25
  raise
@@ -39,7 +39,7 @@ module AnnotateRb
39
39
  absolute_file = File.expand_path(file)
40
40
  model_paths =
41
41
  $LOAD_PATH.select { |path| absolute_file.include?(path) }
42
- .map { |path| absolute_file.sub(path, '').sub(/\.rb$/, '').sub(/^\//, '') }
42
+ .map { |path| absolute_file.sub(path, "").sub(/\.rb$/, "").sub(/^\//, "") }
43
43
  model_paths
44
44
  .map { |path| get_loaded_model_by_path(path) }
45
45
  .find { |loaded_model| !loaded_model.nil? }
@@ -51,7 +51,7 @@ module AnnotateRb
51
51
  rescue StandardError, LoadError
52
52
  # Revert to the old way but it is not really robust
53
53
  ObjectSpace.each_object(::Class)
54
- .select do |c|
54
+ .select do |c|
55
55
  Class === c && # note: we use === to avoid a bug in activesupport 2.3.14 OptionMerger vs. is_a?
56
56
  c.ancestors.respond_to?(:include?) && # to fix FactoryGirl bug, see https://github.com/ctran/annotate_models/pull/82
57
57
  c.ancestors.include?(::ActiveRecord::Base)