annotaterb 4.0.0 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: af9f792c5cb7386482abff5b7fb151dd000785316e0849139795ce473d48d076
4
- data.tar.gz: 3d96f0869c4fa6616570bd1e9d077c962e56f59be4437496fc0cd0e428b5f3e3
3
+ metadata.gz: 1c77ca5a809b63c94e69b937732b8f8f531807cc332627791b649576e8ed583e
4
+ data.tar.gz: c2fb3ac84bad2e0cd108c801ca162a6a17b34ba800af8f5e885e03c8338d636d
5
5
  SHA512:
6
- metadata.gz: 8cf8cb1fe5183a9f814272173f8af849499789790a87c63fa15de25a561c249490c7fefc8605c69464298923c14d6349d29ece493ab35c22e37466a0707e27b1
7
- data.tar.gz: 0e43bbc446a18dd28c08c68bda96e3acfe58b427e3d5d16f0836a713456629280dcc9eb67781013e1bf5a8c621cb3068b8e2058fb82267cc3a6e115805f64fad
6
+ metadata.gz: fb88fa8b511e0ccbbadce0db7d0ae941234967eb7358fea1146e48309939b1b35e51f8db9f15d544a6af4b3827decb65e51f4546d73feb2ab2f404c67f3c0f05
7
+ data.tar.gz: 562cd430feba785e8f53ab90f79dfa1c3765f46a8e048bbe379955c8f696dd4e01ce167245cf17e31de4704dc0e22da859cba2f6f00c87d10782e637c2faad65
data/VERSION CHANGED
@@ -1 +1 @@
1
- 4.0.0
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
- # require 'bigdecimal'
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
- model_files_to_annotate.each do |path, filename|
28
- ModelFileAnnotator.call(annotated, File.join(path, filename), header, options)
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
- deannotated_klass = false
41
- ModelFilesGetter.call(options).each do |file|
42
- file = File.join(file)
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
- patterns = PatternGetter.call(options)
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
- patterns
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
- deannotated << klass if deannotated_klass
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 "annotate error. #{file_name} needs to be updated, but annotate was run with `--frozen`." if options[:frozen]
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!(Annotator::MAGIC_COMMENT_MATCHER, '')
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