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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +0 -0
- data/LICENSE.txt +55 -0
- data/README.md +91 -0
- data/VERSION +1 -0
- data/exe/annotaterb +21 -0
- data/lib/annotate_rb/active_record_patch.rb +9 -0
- data/lib/annotate_rb/commands/annotate_models.rb +22 -0
- data/lib/annotate_rb/commands/annotate_routes.rb +19 -0
- data/lib/annotate_rb/commands/print_help.rb +16 -0
- data/lib/annotate_rb/commands/print_version.rb +12 -0
- data/lib/annotate_rb/commands.rb +10 -0
- data/lib/annotate_rb/config_finder.rb +21 -0
- data/lib/annotate_rb/config_loader.rb +63 -0
- data/lib/annotate_rb/core.rb +23 -0
- data/lib/annotate_rb/eager_loader.rb +23 -0
- data/lib/annotate_rb/env.rb +30 -0
- data/lib/annotate_rb/model_annotator/annotation_pattern_generator.rb +19 -0
- data/lib/annotate_rb/model_annotator/annotator.rb +74 -0
- data/lib/annotate_rb/model_annotator/bad_model_file_error.rb +11 -0
- data/lib/annotate_rb/model_annotator/constants.rb +22 -0
- data/lib/annotate_rb/model_annotator/file_annotation_remover.rb +25 -0
- data/lib/annotate_rb/model_annotator/file_annotator.rb +79 -0
- data/lib/annotate_rb/model_annotator/file_name_resolver.rb +16 -0
- data/lib/annotate_rb/model_annotator/file_patterns.rb +129 -0
- data/lib/annotate_rb/model_annotator/helper.rb +54 -0
- data/lib/annotate_rb/model_annotator/model_class_getter.rb +63 -0
- data/lib/annotate_rb/model_annotator/model_file_annotator.rb +118 -0
- data/lib/annotate_rb/model_annotator/model_files_getter.rb +62 -0
- data/lib/annotate_rb/model_annotator/pattern_getter.rb +27 -0
- data/lib/annotate_rb/model_annotator/schema_info.rb +480 -0
- data/lib/annotate_rb/model_annotator.rb +20 -0
- data/lib/annotate_rb/options.rb +204 -0
- data/lib/annotate_rb/parser.rb +385 -0
- data/lib/annotate_rb/rake_bootstrapper.rb +34 -0
- data/lib/annotate_rb/route_annotator/annotation_processor.rb +56 -0
- data/lib/annotate_rb/route_annotator/annotator.rb +40 -0
- data/lib/annotate_rb/route_annotator/base_processor.rb +104 -0
- data/lib/annotate_rb/route_annotator/header_generator.rb +113 -0
- data/lib/annotate_rb/route_annotator/helper.rb +104 -0
- data/lib/annotate_rb/route_annotator/removal_processor.rb +40 -0
- data/lib/annotate_rb/route_annotator.rb +12 -0
- data/lib/annotate_rb/runner.rb +34 -0
- data/lib/annotate_rb/tasks/annotate_models_migrate.rake +30 -0
- data/lib/annotate_rb.rb +30 -0
- data/lib/generators/annotate_rb/USAGE +4 -0
- data/lib/generators/annotate_rb/install_generator.rb +15 -0
- data/lib/generators/annotate_rb/templates/auto_annotate_models.rake +7 -0
- 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
|