annotaterb 4.0.0.beta.1

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 (49) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +0 -0
  3. data/LICENSE.txt +55 -0
  4. data/README.md +91 -0
  5. data/VERSION +1 -0
  6. data/exe/annotaterb +21 -0
  7. data/lib/annotate_rb/active_record_patch.rb +9 -0
  8. data/lib/annotate_rb/commands/annotate_models.rb +22 -0
  9. data/lib/annotate_rb/commands/annotate_routes.rb +19 -0
  10. data/lib/annotate_rb/commands/print_help.rb +16 -0
  11. data/lib/annotate_rb/commands/print_version.rb +12 -0
  12. data/lib/annotate_rb/commands.rb +10 -0
  13. data/lib/annotate_rb/config_finder.rb +21 -0
  14. data/lib/annotate_rb/config_loader.rb +63 -0
  15. data/lib/annotate_rb/core.rb +23 -0
  16. data/lib/annotate_rb/eager_loader.rb +23 -0
  17. data/lib/annotate_rb/env.rb +30 -0
  18. data/lib/annotate_rb/model_annotator/annotation_pattern_generator.rb +19 -0
  19. data/lib/annotate_rb/model_annotator/annotator.rb +74 -0
  20. data/lib/annotate_rb/model_annotator/bad_model_file_error.rb +11 -0
  21. data/lib/annotate_rb/model_annotator/constants.rb +22 -0
  22. data/lib/annotate_rb/model_annotator/file_annotation_remover.rb +25 -0
  23. data/lib/annotate_rb/model_annotator/file_annotator.rb +79 -0
  24. data/lib/annotate_rb/model_annotator/file_name_resolver.rb +16 -0
  25. data/lib/annotate_rb/model_annotator/file_patterns.rb +129 -0
  26. data/lib/annotate_rb/model_annotator/helper.rb +54 -0
  27. data/lib/annotate_rb/model_annotator/model_class_getter.rb +63 -0
  28. data/lib/annotate_rb/model_annotator/model_file_annotator.rb +118 -0
  29. data/lib/annotate_rb/model_annotator/model_files_getter.rb +62 -0
  30. data/lib/annotate_rb/model_annotator/pattern_getter.rb +27 -0
  31. data/lib/annotate_rb/model_annotator/schema_info.rb +480 -0
  32. data/lib/annotate_rb/model_annotator.rb +20 -0
  33. data/lib/annotate_rb/options.rb +204 -0
  34. data/lib/annotate_rb/parser.rb +385 -0
  35. data/lib/annotate_rb/rake_bootstrapper.rb +34 -0
  36. data/lib/annotate_rb/route_annotator/annotation_processor.rb +56 -0
  37. data/lib/annotate_rb/route_annotator/annotator.rb +40 -0
  38. data/lib/annotate_rb/route_annotator/base_processor.rb +104 -0
  39. data/lib/annotate_rb/route_annotator/header_generator.rb +113 -0
  40. data/lib/annotate_rb/route_annotator/helper.rb +104 -0
  41. data/lib/annotate_rb/route_annotator/removal_processor.rb +40 -0
  42. data/lib/annotate_rb/route_annotator.rb +12 -0
  43. data/lib/annotate_rb/runner.rb +34 -0
  44. data/lib/annotate_rb/tasks/annotate_models_migrate.rake +30 -0
  45. data/lib/annotate_rb.rb +30 -0
  46. data/lib/generators/annotate_rb/USAGE +4 -0
  47. data/lib/generators/annotate_rb/install_generator.rb +15 -0
  48. data/lib/generators/annotate_rb/templates/auto_annotate_models.rake +7 -0
  49. metadata +96 -0
@@ -0,0 +1,385 @@
1
+ require 'optparse'
2
+
3
+ module AnnotateRb
4
+ # Class for handling command line arguments
5
+ class Parser # rubocop:disable Metrics/ClassLength
6
+ def self.parse(args, existing_options = {})
7
+ new(args, existing_options).parse
8
+ end
9
+
10
+ BANNER_STRING = <<~BANNER.freeze
11
+ Usage: annotaterb [command] [options]
12
+
13
+ Commands:
14
+ models [options]
15
+ routes [options]
16
+ help
17
+ version
18
+ BANNER
19
+
20
+ DEFAULT_OPTIONS = {
21
+ target_action: :do_annotations,
22
+ exit: false
23
+ }.freeze
24
+
25
+ ANNOTATION_POSITIONS = %w[before top after bottom].freeze
26
+ FILE_TYPE_POSITIONS = %w[position_in_class position_in_factory position_in_fixture position_in_test position_in_routes position_in_serializer].freeze
27
+ EXCLUSION_LIST = %w[tests fixtures factories serializers].freeze
28
+ FORMAT_TYPES = %w[bare rdoc yard markdown].freeze
29
+
30
+ COMMAND_MAP = {
31
+ 'models' => :models,
32
+ 'routes' => :routes,
33
+ 'version' => :version,
34
+ 'help' => :help
35
+ }.freeze
36
+
37
+ def initialize(args, existing_options)
38
+ @args = args
39
+ base_options = DEFAULT_OPTIONS.dup
40
+ @options = base_options.merge(existing_options)
41
+ @commands = []
42
+ @options[:original_args] = args.dup
43
+ end
44
+
45
+ def parse
46
+ parse_command(@args)
47
+
48
+ parser.parse!(@args)
49
+
50
+ act_on_command
51
+
52
+ @options
53
+ end
54
+
55
+ private
56
+
57
+ def parse_command(args)
58
+ command_arg = args.first
59
+ command = COMMAND_MAP[command_arg]
60
+
61
+ if command
62
+ args.shift
63
+ @commands << command
64
+ end
65
+ end
66
+
67
+ def act_on_command
68
+ map = {
69
+ models: Commands::AnnotateModels.new,
70
+ routes: Commands::AnnotateRoutes.new,
71
+ help: Commands::PrintHelp.new(@parser),
72
+ version: Commands::PrintVersion.new
73
+ }
74
+
75
+ if @commands.any?
76
+ @options[:command] = map[@commands.first]
77
+ elsif @commands.size > 1
78
+ # TODO: Should raise or alert user that multiple commands were selected but only 1 command will be ran
79
+ @options[:command] = map[@commands.first]
80
+ else # None
81
+ @options[:command] = nil
82
+ end
83
+ end
84
+
85
+ def parser
86
+ @parser ||= OptionParser.new do |option_parser|
87
+ option_parser.banner = BANNER_STRING
88
+
89
+ # ------------------------------------------------------------------------------------------------------------=
90
+ option_parser.separator('')
91
+ option_parser.separator('Options:')
92
+
93
+ option_parser.on('-v', '--version', "Display the version..") do
94
+ @commands << :version
95
+ end
96
+
97
+ option_parser.on('-h', '--help', "You're looking at it.") do
98
+ @commands << :help
99
+ end
100
+
101
+ add_model_options_to_parser(option_parser)
102
+ add_route_options_to_parser(option_parser)
103
+ add_wrapper_options_to_parser(option_parser)
104
+ add_options_to_parser(option_parser)
105
+ add_position_options_to_parser(option_parser)
106
+ add_utils_to_parser(option_parser)
107
+ end
108
+ end
109
+
110
+ def add_wrapper_options_to_parser(option_parser)
111
+ option_parser.on('--w',
112
+ '--wrapper STR',
113
+ 'Wrap annotation with the text passed as parameter.',
114
+ 'If --w option is used, the same text will be used as opening and closing') do |wrapper|
115
+ @options[:wrapper] = wrapper
116
+ end
117
+
118
+ option_parser.on('--wo',
119
+ '--wrapper-open STR',
120
+ 'Annotation wrapper opening.') do |wrapper_open|
121
+ @options[:wrapper_open] = wrapper_open
122
+ end
123
+
124
+ option_parser.on('--wc',
125
+ '--wrapper-close STR',
126
+ 'Annotation wrapper closing') do |wrapper_close|
127
+ @options[:wrapper_close] = wrapper_close
128
+ end
129
+ end
130
+
131
+ def add_utils_to_parser(option_parser)
132
+ option_parser.on('--force',
133
+ 'Force new annotations even if there are no changes.') do
134
+ @options[:force] = true
135
+ end
136
+
137
+ option_parser.on('--debug',
138
+ 'Prints the options and outputs messages to make it easier to debug.') do
139
+ @options[:debug] = true
140
+ end
141
+
142
+ option_parser.on('--frozen',
143
+ 'Do not allow to change annotations. Exits non-zero if there are going to be changes to files.') do
144
+ @options[:frozen] = true
145
+ end
146
+
147
+ option_parser.on('--trace',
148
+ 'If unable to annotate a file, print the full stack trace, not just the exception message.') do
149
+ @options[:trace] = true
150
+ end
151
+ end
152
+
153
+ def add_model_options_to_parser(option_parser)
154
+ option_parser.separator('')
155
+ option_parser.separator('Annotate model options:')
156
+ option_parser.separator(' ' * 4 + 'Usage: annotaterb models [options]')
157
+ option_parser.separator('')
158
+
159
+ # option_parser.on('-m',
160
+ # '--models',
161
+ # "Annotate ActiveRecord models") do
162
+ # @options[:models] = true
163
+ # @options[:command] = Commands::AnnotateModels.new
164
+ # end
165
+
166
+ option_parser.on('-a',
167
+ '--active-admin',
168
+ 'Annotate active_admin models') do
169
+ @options[:active_admin] = true
170
+ end
171
+
172
+ option_parser.on('--show-migration',
173
+ 'Include the migration version number in the annotation') do
174
+ @options[:include_version] = true
175
+ end
176
+
177
+ option_parser.on('-k',
178
+ '--show-foreign-keys',
179
+ "List the table's foreign key constraints in the annotation") do
180
+ @options[:show_foreign_keys] = true
181
+ end
182
+
183
+ option_parser.on('--ck',
184
+ '--complete-foreign-keys',
185
+ 'Complete foreign key names in the annotation') do
186
+ @options[:show_foreign_keys] = true
187
+ @options[:show_complete_foreign_keys] = true
188
+ end
189
+
190
+ option_parser.on('-i',
191
+ '--show-indexes',
192
+ "List the table's database indexes in the annotation") do
193
+ @options[:show_indexes] = true
194
+ end
195
+
196
+ option_parser.on('-s',
197
+ '--simple-indexes',
198
+ "Concat the column's related indexes in the annotation") do
199
+ @options[:simple_indexes] = true
200
+ end
201
+
202
+ option_parser.on('--hide-limit-column-types VALUES',
203
+ "don't show limit for given column types, separated by commas (i.e., `integer,boolean,text`)") do |values|
204
+ @options[:hide_limit_column_types] = values.to_s
205
+ end
206
+
207
+ option_parser.on('--hide-default-column-types VALUES',
208
+ "don't show default for given column types, separated by commas (i.e., `json,jsonb,hstore`)") do |values|
209
+ @options[:hide_default_column_types] = values.to_s
210
+ end
211
+
212
+ option_parser.on('--ignore-unknown-models',
213
+ "don't display warnings for bad model files") do
214
+ @options[:ignore_unknown_models] = true
215
+ end
216
+
217
+ option_parser.on('-I',
218
+ '--ignore-columns REGEX',
219
+ "don't annotate columns that match a given REGEX (i.e., `annotate -I '^(id|updated_at|created_at)'`") do |regex|
220
+ @options[:ignore_columns] = regex
221
+ end
222
+
223
+ option_parser.on('--with-comment',
224
+ "include database comments in model annotations") do
225
+ @options[:with_comment] = true
226
+ end
227
+ end
228
+
229
+ def add_route_options_to_parser(option_parser)
230
+ option_parser.separator('')
231
+ option_parser.separator('Annotate routes options:')
232
+ option_parser.separator(' ' * 4 + 'Usage: annotaterb routes [options]')
233
+ option_parser.separator('')
234
+
235
+ # option_parser.on('-r',
236
+ # '--routes',
237
+ # "Annotate routes.rb with the output of 'rails routes'") do
238
+ # @options[:routes] = true
239
+ # @options[:command] = Commands::AnnotateRoutes.new
240
+ # end
241
+
242
+ option_parser.on('--ignore-routes REGEX',
243
+ "don't annotate routes that match a given REGEX (i.e., `annotate -I '(mobile|resque|pghero)'`") do |regex|
244
+ @options[:ignore_routes] = regex
245
+ end
246
+
247
+ option_parser.on('--timestamp',
248
+ 'Include timestamp in (routes) annotation') do
249
+ @options[:timestamp] = true
250
+ end
251
+ end
252
+
253
+ def add_position_options_to_parser(option_parser)
254
+ has_set_position = {}
255
+
256
+ option_parser.on('-p',
257
+ '--position [before|top|after|bottom]',
258
+ ANNOTATION_POSITIONS,
259
+ 'Place the annotations at the top (before) or the bottom (after) of the model/test/fixture/factory/route/serializer file(s)') do |position|
260
+ @options[:position] = position
261
+
262
+ FILE_TYPE_POSITIONS.each do |key|
263
+ @options[key.to_sym] = position unless has_set_position[key]
264
+ end
265
+ end
266
+
267
+ option_parser.on('--pc',
268
+ '--position-in-class [before|top|after|bottom]',
269
+ ANNOTATION_POSITIONS,
270
+ 'Place the annotations at the top (before) or the bottom (after) of the model file') do |position_in_class|
271
+ @options[:position_in_class] = position_in_class
272
+ has_set_position['position_in_class'] = true
273
+ end
274
+
275
+ option_parser.on('--pf',
276
+ '--position-in-factory [before|top|after|bottom]',
277
+ ANNOTATION_POSITIONS,
278
+ 'Place the annotations at the top (before) or the bottom (after) of any factory files') do |position_in_factory|
279
+ @options[:position_in_factory] = position_in_factory
280
+ has_set_position['position_in_factory'] = true
281
+ end
282
+
283
+ option_parser.on('--px',
284
+ '--position-in-fixture [before|top|after|bottom]',
285
+ ANNOTATION_POSITIONS,
286
+ 'Place the annotations at the top (before) or the bottom (after) of any fixture files') do |position_in_fixture|
287
+ @options[:position_in_fixture] = position_in_fixture
288
+ has_set_position['position_in_fixture'] = true
289
+ end
290
+
291
+ option_parser.on('--pt',
292
+ '--position-in-test [before|top|after|bottom]',
293
+ ANNOTATION_POSITIONS,
294
+ 'Place the annotations at the top (before) or the bottom (after) of any test files') do |position_in_test|
295
+ @options[:position_in_test] = position_in_test
296
+ has_set_position['position_in_test'] = true
297
+ end
298
+
299
+ option_parser.on('--pr',
300
+ '--position-in-routes [before|top|after|bottom]',
301
+ ANNOTATION_POSITIONS,
302
+ 'Place the annotations at the top (before) or the bottom (after) of the routes.rb file') do |position_in_routes|
303
+ @options[:position_in_routes] = position_in_routes
304
+ has_set_position['position_in_routes'] = true
305
+ end
306
+
307
+ option_parser.on('--ps',
308
+ '--position-in-serializer [before|top|after|bottom]',
309
+ ANNOTATION_POSITIONS,
310
+ 'Place the annotations at the top (before) or the bottom (after) of the serializer files') do |position_in_serializer|
311
+ @options[:position_in_serializer] = position_in_serializer
312
+ has_set_position['position_in_serializer'] = true
313
+ end
314
+ end
315
+
316
+ def add_options_to_parser(option_parser) # rubocop:disable Metrics/MethodLength
317
+ option_parser.separator('')
318
+ option_parser.separator('Command options:')
319
+ option_parser.separator('Additional options that work for annotating models and routes')
320
+ option_parser.separator('')
321
+
322
+ option_parser.on('--additional-file-patterns path1,path2,path3',
323
+ Array,
324
+ "Additional file paths or globs to annotate, separated by commas (e.g. `/foo/bar/%model_name%/*.rb,/baz/%model_name%.rb`)") do |additional_file_patterns|
325
+ @options[:additional_file_patterns] = additional_file_patterns
326
+ end
327
+
328
+ option_parser.on('-d',
329
+ '--delete',
330
+ 'Remove annotations from all model files or the routes.rb file') do
331
+ @options[:target_action] = :remove_annotations
332
+ end
333
+
334
+ option_parser.on('--model-dir dir',
335
+ "Annotate model files stored in dir rather than app/models, separate multiple dirs with commas") do |dir|
336
+ @options[:model_dir] = dir
337
+ end
338
+
339
+ option_parser.on('--root-dir dir',
340
+ "Annotate files stored within root dir projects, separate multiple dirs with commas") do |dir|
341
+ @options[:root_dir] = dir
342
+ end
343
+
344
+ option_parser.on('--ignore-model-subdirects',
345
+ "Ignore subdirectories of the models directory") do
346
+ @options[:ignore_model_sub_dir] = true
347
+ end
348
+
349
+ option_parser.on('--sort',
350
+ "Sort columns alphabetically, rather than in creation order") do
351
+ @options[:sort] = true
352
+ end
353
+
354
+ option_parser.on('--classified-sort',
355
+ "Sort columns alphabetically, but first goes id, then the rest columns, then the timestamp columns and then the association columns") do
356
+ @options[:classified_sort] = true
357
+ end
358
+
359
+ option_parser.on('-R',
360
+ '--require path',
361
+ "Additional file to require before loading models, may be used multiple times") do |path|
362
+ @options[:require] = if @options[:require].present?
363
+ [@options[:require], path].join(',')
364
+ else
365
+ path
366
+ end
367
+ end
368
+
369
+ option_parser.on('-e',
370
+ '--exclude [tests,fixtures,factories,serializers]',
371
+ Array,
372
+ "Do not annotate fixtures, test files, factories, and/or serializers") do |exclusions|
373
+ exclusions ||= EXCLUSION_LIST
374
+ exclusions.each { |exclusion| @options["exclude_#{exclusion}".to_sym] = true }
375
+ end
376
+
377
+ option_parser.on('-f',
378
+ '--format [bare|rdoc|yard|markdown]',
379
+ FORMAT_TYPES,
380
+ 'Render Schema Information as plain/RDoc/Yard/Markdown') do |format_type|
381
+ @options["format_#{format_type}".to_sym] = true
382
+ end
383
+ end
384
+ end
385
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnnotateRb
4
+ class RakeBootstrapper
5
+ class << self
6
+ def call(options)
7
+ begin
8
+ require 'rake/dsl_definition'
9
+ rescue StandardError => e
10
+ # We might just be on an old version of Rake...
11
+ $stderr.puts e.message
12
+ exit e.status_code
13
+ end
14
+
15
+ require 'rake'
16
+ load './Rakefile' if File.exist?('./Rakefile')
17
+
18
+ begin
19
+ Rake::Task[:environment].invoke
20
+ rescue
21
+ nil
22
+ end
23
+
24
+ unless defined?(Rails)
25
+ # Not in a Rails project, so time to load up the parts of
26
+ # ActiveSupport we need.
27
+ require 'active_support'
28
+ require 'active_support/core_ext/class/subclasses'
29
+ require 'active_support/core_ext/string/inflections'
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This module provides methods for annotating config/routes.rb.
4
+ module AnnotateRb
5
+ module RouteAnnotator
6
+ # This class is abstract class of classes adding and removing annotation to config/routes.rb.
7
+ class AnnotationProcessor < BaseProcessor
8
+
9
+ # @return [String]
10
+ def execute
11
+ if routes_file_exist?
12
+ if update
13
+ "#{routes_file} was annotated."
14
+ else
15
+ "#{routes_file} was not changed."
16
+ end
17
+ else
18
+ "#{routes_file} could not be found."
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def header
25
+ @header ||= HeaderGenerator.generate(options)
26
+ end
27
+
28
+ def generate_new_content_array(content, header_position)
29
+ magic_comments_map, content = Helper.extract_magic_comments_from_array(content)
30
+ if %w(before top).include?(options[:position_in_routes])
31
+ new_content_array = []
32
+ new_content_array += magic_comments_map
33
+ new_content_array << '' if magic_comments_map.any?
34
+ new_content_array += header
35
+ new_content_array << '' if content.first != ''
36
+ new_content_array += content
37
+ else
38
+ # Ensure we have adequate trailing newlines at the end of the file to
39
+ # ensure a blank line separating the content from the annotation.
40
+ content << '' unless content.last == ''
41
+
42
+ # We're moving something from the top of the file to the bottom, so ditch
43
+ # the spacer we put in the first time around.
44
+ content.shift if header_position == :before && content.first == ''
45
+
46
+ new_content_array = magic_comments_map + content + header
47
+ end
48
+
49
+ # Make sure we end on a trailing newline.
50
+ new_content_array << '' unless new_content_array.last == ''
51
+
52
+ new_content_array
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnnotateRb
4
+ module RouteAnnotator
5
+ class Annotator
6
+ class << self
7
+ # TODO: Deprecate
8
+ def do_annotations(options = {})
9
+ add_annotations(options)
10
+ end
11
+
12
+ def add_annotations(options = {})
13
+ new(options).add_annotations
14
+ end
15
+
16
+ def remove_annotations(options = {})
17
+ new(options).remove_annotations
18
+ end
19
+ end
20
+
21
+ def initialize(options = {})
22
+ @options = options
23
+ end
24
+
25
+ def add_annotations
26
+ routes_file = File.join('config', 'routes.rb')
27
+ AnnotationProcessor.execute(@options, routes_file).tap do |result|
28
+ puts result
29
+ end
30
+ end
31
+
32
+ def remove_annotations
33
+ routes_file = File.join('config', 'routes.rb')
34
+ RemovalProcessor.execute(@options, routes_file).tap do |result|
35
+ puts result
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This module provides methods for annotating config/routes.rb.
4
+ module AnnotateRb
5
+ module RouteAnnotator
6
+ # This class is abstract class of classes adding and removing annotation to config/routes.rb.
7
+ class BaseProcessor
8
+ class << self
9
+ # @param options [Hash]
10
+ # @param routes_file [String]
11
+ # @return [String]
12
+ def execute(options, routes_file)
13
+ new(options, routes_file).execute
14
+ end
15
+
16
+ private :new
17
+ end
18
+
19
+ def initialize(options, routes_file)
20
+ @options = options
21
+ @routes_file = routes_file
22
+ end
23
+
24
+ # @return [Boolean]
25
+ def update
26
+ if existing_text == new_text
27
+ false
28
+ else
29
+ write(new_text)
30
+ true
31
+ end
32
+ end
33
+
34
+ def routes_file_exist?
35
+ File.exist?(routes_file)
36
+ end
37
+
38
+ private
39
+
40
+ attr_reader :options, :routes_file
41
+
42
+ def generate_new_content_array(_content, _header_position)
43
+ raise NoMethodError
44
+ end
45
+
46
+ def existing_text
47
+ @existing_text ||= File.read(routes_file)
48
+ end
49
+
50
+ # @return [String]
51
+ def new_text
52
+ content, header_position = strip_annotations(existing_text)
53
+ new_content = generate_new_content_array(content, header_position)
54
+ new_content.join("\n")
55
+ end
56
+
57
+ def write(text)
58
+ File.open(routes_file, 'wb') { |f| f.puts(text) }
59
+ end
60
+
61
+ # TODO: write the method doc using ruby rdoc formats
62
+ # This method returns an array of 'real_content' and 'header_position'.
63
+ # 'header_position' will either be :before, :after, or
64
+ # a number. If the number is > 0, the
65
+ # annotation was found somewhere in the
66
+ # middle of the file. If the number is
67
+ # zero, no annotation was found.
68
+ def strip_annotations(content)
69
+ real_content = []
70
+ mode = :content
71
+ header_position = 0
72
+
73
+ content.split(/\n/, -1).each_with_index do |line, line_number|
74
+ if mode == :header && line !~ /\s*#/
75
+ mode = :content
76
+ real_content << line unless line.blank?
77
+ elsif mode == :content
78
+ if line =~ /^\s*#\s*== Route.*$/
79
+ header_position = line_number + 1 # index start's at 0
80
+ mode = :header
81
+ else
82
+ real_content << line
83
+ end
84
+ end
85
+ end
86
+
87
+ real_content_and_header_position(real_content, header_position)
88
+ end
89
+
90
+ def real_content_and_header_position(real_content, header_position)
91
+ # By default assume the annotation was found in the middle of the file
92
+
93
+ # ... unless we have evidence it was at the beginning ...
94
+ return real_content, :before if header_position == 1
95
+
96
+ # ... or that it was at the end.
97
+ return real_content, :after if header_position >= real_content.count
98
+
99
+ # and the default
100
+ return real_content, header_position
101
+ end
102
+ end
103
+ end
104
+ end