annotaterb 4.1.1 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +68 -0
  3. data/README.md +6 -0
  4. data/VERSION +1 -1
  5. data/exe/annotaterb +7 -7
  6. data/lib/annotate_rb/active_record_patch.rb +2 -0
  7. data/lib/annotate_rb/commands/annotate_models.rb +0 -1
  8. data/lib/annotate_rb/commands/annotate_routes.rb +0 -1
  9. data/lib/annotate_rb/commands/print_help.rb +0 -1
  10. data/lib/annotate_rb/commands/print_version.rb +0 -1
  11. data/lib/annotate_rb/commands.rb +4 -4
  12. data/lib/annotate_rb/config_finder.rb +1 -1
  13. data/lib/annotate_rb/config_loader.rb +2 -2
  14. data/lib/annotate_rb/core.rb +3 -3
  15. data/lib/annotate_rb/helper.rb +16 -0
  16. data/lib/annotate_rb/model_annotator/{annotation_generator.rb → annotation_builder.rb} +18 -14
  17. data/lib/annotate_rb/model_annotator/annotation_decider.rb +10 -12
  18. data/lib/annotate_rb/model_annotator/annotation_diff.rb +1 -1
  19. data/lib/annotate_rb/model_annotator/annotation_diff_generator.rb +9 -9
  20. data/lib/annotate_rb/model_annotator/annotation_pattern_generator.rb +3 -3
  21. data/lib/annotate_rb/model_annotator/annotator.rb +12 -53
  22. data/lib/annotate_rb/model_annotator/column_annotation/annotation_builder.rb +135 -0
  23. data/lib/annotate_rb/model_annotator/column_annotation/attributes_builder.rb +104 -0
  24. data/lib/annotate_rb/model_annotator/column_annotation/column_wrapper.rb +103 -0
  25. data/lib/annotate_rb/model_annotator/column_annotation/type_builder.rb +54 -0
  26. data/lib/annotate_rb/model_annotator/column_annotation.rb +12 -0
  27. data/lib/annotate_rb/model_annotator/file_builder.rb +58 -0
  28. data/lib/annotate_rb/model_annotator/file_components.rb +78 -0
  29. data/lib/annotate_rb/model_annotator/file_name_resolver.rb +3 -3
  30. data/lib/annotate_rb/model_annotator/foreign_key_annotation/annotation_builder.rb +57 -0
  31. data/lib/annotate_rb/model_annotator/foreign_key_annotation.rb +9 -0
  32. data/lib/annotate_rb/model_annotator/index_annotation/annotation_builder.rb +113 -0
  33. data/lib/annotate_rb/model_annotator/index_annotation.rb +9 -0
  34. data/lib/annotate_rb/model_annotator/magic_comment_parser.rb +32 -0
  35. data/lib/annotate_rb/model_annotator/model_class_getter.rb +6 -6
  36. data/lib/annotate_rb/model_annotator/model_files_getter.rb +12 -10
  37. data/lib/annotate_rb/model_annotator/model_wrapper.rb +44 -37
  38. data/lib/annotate_rb/model_annotator/pattern_getter.rb +142 -10
  39. data/lib/annotate_rb/model_annotator/project_annotation_remover.rb +65 -0
  40. data/lib/annotate_rb/model_annotator/project_annotator.rb +63 -0
  41. data/lib/annotate_rb/model_annotator/related_files_list_builder.rb +16 -18
  42. data/lib/annotate_rb/model_annotator/single_file_annotation_remover.rb +37 -0
  43. data/lib/annotate_rb/model_annotator/single_file_annotator.rb +49 -0
  44. data/lib/annotate_rb/model_annotator/{file_annotator_instruction.rb → single_file_annotator_instruction.rb} +2 -2
  45. data/lib/annotate_rb/model_annotator/single_file_remove_annotation_instruction.rb +15 -0
  46. data/lib/annotate_rb/model_annotator.rb +25 -26
  47. data/lib/annotate_rb/options.rb +20 -25
  48. data/lib/annotate_rb/parser.rb +150 -142
  49. data/lib/annotate_rb/rake_bootstrapper.rb +8 -8
  50. data/lib/annotate_rb/route_annotator/annotation_processor.rb +7 -8
  51. data/lib/annotate_rb/route_annotator/annotator.rb +2 -2
  52. data/lib/annotate_rb/route_annotator/base_processor.rb +3 -3
  53. data/lib/annotate_rb/route_annotator/header_generator.rb +15 -15
  54. data/lib/annotate_rb/route_annotator/helper.rb +9 -9
  55. data/lib/annotate_rb/route_annotator/removal_processor.rb +4 -4
  56. data/lib/annotate_rb/route_annotator.rb +6 -6
  57. data/lib/annotate_rb/runner.rb +0 -4
  58. data/lib/annotate_rb/tasks/annotate_models_migrate.rake +5 -5
  59. data/lib/annotate_rb.rb +19 -19
  60. data/lib/generators/annotate_rb/install/install_generator.rb +3 -2
  61. data/lib/generators/annotate_rb/install/templates/annotate_rb.rake +3 -2
  62. metadata +22 -16
  63. data/lib/annotate_rb/model_annotator/column_annotation_builder.rb +0 -92
  64. data/lib/annotate_rb/model_annotator/column_attributes_builder.rb +0 -102
  65. data/lib/annotate_rb/model_annotator/column_type_builder.rb +0 -51
  66. data/lib/annotate_rb/model_annotator/column_wrapper.rb +0 -84
  67. data/lib/annotate_rb/model_annotator/constants.rb +0 -22
  68. data/lib/annotate_rb/model_annotator/file_annotation_remover.rb +0 -25
  69. data/lib/annotate_rb/model_annotator/file_annotator.rb +0 -77
  70. data/lib/annotate_rb/model_annotator/file_patterns.rb +0 -129
  71. data/lib/annotate_rb/model_annotator/foreign_key_annotation_builder.rb +0 -55
  72. data/lib/annotate_rb/model_annotator/helper.rb +0 -107
  73. data/lib/annotate_rb/model_annotator/index_annotation_builder.rb +0 -74
  74. 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)