annotaterb 4.0.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|