bullet_train-super_scaffolding 1.0.0
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/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +8 -0
- data/app/assets/config/bullet_train_super_scaffolding_manifest.js +0 -0
- data/config/routes.rb +2 -0
- data/lib/bullet_train/super_scaffolding/engine.rb +6 -0
- data/lib/bullet_train/super_scaffolding/version.rb +5 -0
- data/lib/bullet_train/super_scaffolding.rb +8 -0
- data/lib/scaffolding/class_names_transformer.rb +190 -0
- data/lib/scaffolding/oauth_providers.rb +107 -0
- data/lib/scaffolding/routes_file_manipulator.rb +384 -0
- data/lib/scaffolding/script.rb +543 -0
- data/lib/scaffolding/transformer.rb +1365 -0
- data/lib/scaffolding.rb +8 -0
- data/lib/tasks/bullet_train/super_scaffolding_tasks.rake +4 -0
- metadata +74 -0
@@ -0,0 +1,384 @@
|
|
1
|
+
class Scaffolding::RoutesFileManipulator
|
2
|
+
attr_accessor :child, :parent, :lines, :transformer_options
|
3
|
+
|
4
|
+
def initialize(filename, child, parent, transformer_options = {})
|
5
|
+
self.child = child
|
6
|
+
self.parent = parent
|
7
|
+
@filename = filename
|
8
|
+
self.lines = File.readlines(@filename)
|
9
|
+
self.transformer_options = transformer_options
|
10
|
+
end
|
11
|
+
|
12
|
+
def child_parts
|
13
|
+
@child_parts ||= child.underscore.pluralize.split("/")
|
14
|
+
end
|
15
|
+
|
16
|
+
def parent_parts
|
17
|
+
@parent_parts ||= parent.underscore.pluralize.split("/")
|
18
|
+
end
|
19
|
+
|
20
|
+
def common_namespaces
|
21
|
+
unless @common_namespaces
|
22
|
+
@common_namespaces ||= []
|
23
|
+
child_parts_copy = child_parts.dup
|
24
|
+
parent_parts_copy = parent_parts.dup
|
25
|
+
while child_parts_copy.first == parent_parts_copy.first && child_parts_copy.count > 1 && parent_parts_copy.count > 1
|
26
|
+
@common_namespaces << child_parts_copy.shift
|
27
|
+
parent_parts_copy.shift
|
28
|
+
end
|
29
|
+
end
|
30
|
+
@common_namespaces
|
31
|
+
end
|
32
|
+
|
33
|
+
# def divergent_namespaces
|
34
|
+
# unless @divergent_namespaces
|
35
|
+
# @divergent_namespaces ||= []
|
36
|
+
# child_parts_copy = child_parts.dup
|
37
|
+
# parent_parts_copy = parent_parts.dup
|
38
|
+
# while child_parts_copy.first == parent_parts_copy.first
|
39
|
+
# child_parts_copy.shift
|
40
|
+
# parent_parts_copy.shift
|
41
|
+
# end
|
42
|
+
# child_parts_copy.pop
|
43
|
+
# parent_parts_copy.pop
|
44
|
+
# @divergent_namespaces = [child_parts_copy, parent_parts_copy]
|
45
|
+
# end
|
46
|
+
# @divergent_namespaces
|
47
|
+
# end
|
48
|
+
|
49
|
+
def divergent_parts
|
50
|
+
unless @divergent_namespaces
|
51
|
+
@divergent_namespaces ||= []
|
52
|
+
child_parts_copy = child_parts.dup
|
53
|
+
parent_parts_copy = parent_parts.dup
|
54
|
+
while child_parts_copy.first == parent_parts_copy.first && child_parts_copy.count > 1 && parent_parts_copy.count > 1
|
55
|
+
child_parts_copy.shift
|
56
|
+
parent_parts_copy.shift
|
57
|
+
end
|
58
|
+
child_resource = child_parts_copy.pop
|
59
|
+
parent_resource = parent_parts_copy.pop
|
60
|
+
@divergent_namespaces = [child_parts_copy, child_resource, parent_parts_copy, parent_resource]
|
61
|
+
end
|
62
|
+
@divergent_namespaces
|
63
|
+
end
|
64
|
+
|
65
|
+
def find_namespaces(namespaces, within = nil)
|
66
|
+
namespaces = namespaces.dup
|
67
|
+
results = {}
|
68
|
+
block_end = find_block_end(within) if within
|
69
|
+
lines.each_with_index do |line, line_number|
|
70
|
+
if within
|
71
|
+
next unless line_number > within
|
72
|
+
return results if line_number >= block_end
|
73
|
+
end
|
74
|
+
if line.include?("namespace :#{namespaces.first} do")
|
75
|
+
results[namespaces.shift] = line_number
|
76
|
+
end
|
77
|
+
return results unless namespaces.any?
|
78
|
+
end
|
79
|
+
results
|
80
|
+
end
|
81
|
+
|
82
|
+
def indentation_of(line_number)
|
83
|
+
lines[line_number].match(/^( +)/)[1]
|
84
|
+
rescue
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def find_block_parent(starting_line_number)
|
89
|
+
return nil unless indentation_of(starting_line_number)
|
90
|
+
cursor = starting_line_number
|
91
|
+
while cursor >= 0
|
92
|
+
unless lines[cursor].match?(/^#{indentation_of(starting_line_number)}/) || !lines[cursor].present?
|
93
|
+
return cursor
|
94
|
+
end
|
95
|
+
cursor -= 1
|
96
|
+
end
|
97
|
+
nil
|
98
|
+
end
|
99
|
+
|
100
|
+
def find_block_end(starting_line_number)
|
101
|
+
return nil unless indentation_of(starting_line_number)
|
102
|
+
lines.each_with_index do |line, line_number|
|
103
|
+
next unless line_number > starting_line_number
|
104
|
+
if /^#{indentation_of(starting_line_number)}end\s+/.match?(line)
|
105
|
+
return line_number
|
106
|
+
end
|
107
|
+
end
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
|
111
|
+
def insert_before(new_lines, line_number, options = {})
|
112
|
+
options[:indent] ||= false
|
113
|
+
before = lines[0..(line_number - 1)]
|
114
|
+
new_lines = new_lines.map { |line| (indentation_of(line_number) + (options[:indent] ? " " : "") + line).gsub(/\s+$/, "") + "\n" }
|
115
|
+
after = lines[line_number..]
|
116
|
+
self.lines = before + (options[:prepend_newline] ? ["\n"] : []) + new_lines + after
|
117
|
+
end
|
118
|
+
|
119
|
+
def insert_after(new_lines, line_number, options = {})
|
120
|
+
options[:indent] ||= false
|
121
|
+
before = lines[0..line_number]
|
122
|
+
new_lines = new_lines.map { |line| (indentation_of(line_number) + (options[:indent] ? " " : "") + line).gsub(/\s+$/, "") + "\n" }
|
123
|
+
after = lines[(line_number + 1)..]
|
124
|
+
self.lines = before + new_lines + (options[:append_newline] ? ["\n"] : []) + after
|
125
|
+
end
|
126
|
+
|
127
|
+
def insert_in_namespace(namespaces, new_lines, within = nil)
|
128
|
+
namespace_lines = find_namespaces(namespaces, within)
|
129
|
+
if namespace_lines[namespaces.last]
|
130
|
+
block_start = namespace_lines[namespaces.last]
|
131
|
+
insertion_point = find_block_end(block_start)
|
132
|
+
insert_before(new_lines, insertion_point, indent: true, prepend_newline: (insertion_point > block_start + 1))
|
133
|
+
else
|
134
|
+
raise "we weren't able to insert the following lines into the namespace block for #{namespaces.join(" -> ")}:\n\n#{new_lines.join("\n")}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def find_or_create_namespaces(namespaces, within = nil)
|
139
|
+
namespaces = namespaces.dup
|
140
|
+
created_namespaces = []
|
141
|
+
current_namespace = nil
|
142
|
+
while namespaces.any?
|
143
|
+
current_namespace = namespaces.shift
|
144
|
+
namespace_lines = find_namespaces(created_namespaces + [current_namespace], within)
|
145
|
+
unless namespace_lines[current_namespace]
|
146
|
+
lines_to_add = ["namespace :#{current_namespace} do", "end"]
|
147
|
+
if created_namespaces.any?
|
148
|
+
insert_in_namespace(created_namespaces, lines_to_add, within)
|
149
|
+
else
|
150
|
+
insert(lines_to_add, within)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
created_namespaces << current_namespace
|
154
|
+
end
|
155
|
+
namespace_lines = find_namespaces(created_namespaces + [current_namespace], within)
|
156
|
+
namespace_lines ? namespace_lines[current_namespace] : nil
|
157
|
+
end
|
158
|
+
|
159
|
+
def find(needle, within = nil)
|
160
|
+
lines_within(within).each_with_index do |line, line_number|
|
161
|
+
return (within + (within ? 1 : 0) + line_number) if line.match?(needle)
|
162
|
+
end
|
163
|
+
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
|
167
|
+
def find_in_namespace(needle, namespaces, within = nil, ignore = nil)
|
168
|
+
if namespaces.any?
|
169
|
+
namespace_lines = find_namespaces(namespaces, within)
|
170
|
+
within = namespace_lines[namespaces.last]
|
171
|
+
end
|
172
|
+
|
173
|
+
lines_within(within).each_with_index do |line, line_number|
|
174
|
+
# + 2 because line_number starts from 0, and within starts one line after
|
175
|
+
actual_line_number = (within + line_number + 2)
|
176
|
+
|
177
|
+
# The lines we want to ignore may be a a series of blocks, so we check each Range here.
|
178
|
+
ignore_line = false
|
179
|
+
if ignore.present?
|
180
|
+
ignore.each do |lines_to_ignore|
|
181
|
+
ignore_line = true if lines_to_ignore.include?(actual_line_number)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
next if ignore_line
|
186
|
+
return (within + (within ? 1 : 0) + line_number) if line.match?(needle)
|
187
|
+
end
|
188
|
+
|
189
|
+
nil
|
190
|
+
end
|
191
|
+
|
192
|
+
def find_resource_block(parts, options = {})
|
193
|
+
within = options[:within]
|
194
|
+
parts = parts.dup
|
195
|
+
resource = parts.pop
|
196
|
+
# TODO this doesn't take into account any options like we do in `find_resource`.
|
197
|
+
find_in_namespace(/resources :#{resource}#{options[:options] ? ", #{options[:options].gsub(/(\[)(.*)(\])/, '\[\2\]')}" : ""}(,?\s.*)? do(\s.*)?$/, parts, within)
|
198
|
+
end
|
199
|
+
|
200
|
+
def find_resource(parts, options = {})
|
201
|
+
parts = parts.dup
|
202
|
+
resource = parts.pop
|
203
|
+
needle = /resources :#{resource}#{options[:options] ? ", #{options[:options].gsub(/(\[)(.*)(\])/, '\[\2\]')}" : ""}(,?\s.*)?$/
|
204
|
+
find_in_namespace(needle, parts, options[:within], options[:ignore])
|
205
|
+
end
|
206
|
+
|
207
|
+
def find_or_create_resource(parts, options = {})
|
208
|
+
parts = parts.dup
|
209
|
+
resource = parts.pop
|
210
|
+
namespaces = parts
|
211
|
+
namespace_within = find_or_create_namespaces(namespaces, options[:within])
|
212
|
+
|
213
|
+
# The namespaces that the developer has declared are captured above in `namespace_within`,
|
214
|
+
# so all other namespaces nested inside the resource's parent should be ignored.
|
215
|
+
options[:ignore] = top_level_namespace_block_lines(options[:within]) || []
|
216
|
+
|
217
|
+
unless (result = find_resource([resource], options))
|
218
|
+
result = insert(["resources :#{resource}" + (options[:options] ? ", #{options[:options]}" : "")], namespace_within || options[:within])
|
219
|
+
end
|
220
|
+
result
|
221
|
+
end
|
222
|
+
|
223
|
+
def top_level_namespace_block_lines(within)
|
224
|
+
local_namespace_blocks = []
|
225
|
+
lines_within(within).each do |line|
|
226
|
+
# i.e. - Retrieve "foo" from "namespace :foo do"
|
227
|
+
match_data = line.match(/(\s*namespace\s:)(.*)(\sdo$)/)
|
228
|
+
|
229
|
+
# Since we only want top-level namespace blocks, we ensure that
|
230
|
+
# all other namespace blocks INSIDE the top-level namespace blocks are skipped
|
231
|
+
if match_data.present?
|
232
|
+
namespace_name = match_data[2]
|
233
|
+
local_namespace = find_namespaces([namespace_name], within)
|
234
|
+
starting_line_number = local_namespace[namespace_name]
|
235
|
+
local_namespace_block = ((starting_line_number + 1)..(find_block_end(starting_line_number) + 1))
|
236
|
+
|
237
|
+
if local_namespace_blocks.empty?
|
238
|
+
local_namespace_blocks << local_namespace_block
|
239
|
+
else
|
240
|
+
skip_block = false
|
241
|
+
local_namespace_blocks.each do |block_range|
|
242
|
+
if block_range.include?(local_namespace_block.first)
|
243
|
+
skip_block = true
|
244
|
+
else
|
245
|
+
next
|
246
|
+
end
|
247
|
+
end
|
248
|
+
local_namespace_blocks << local_namespace_block unless skip_block
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
local_namespace_blocks
|
254
|
+
end
|
255
|
+
|
256
|
+
def find_or_create_resource_block(parts, options = {})
|
257
|
+
find_or_create_resource(parts, options)
|
258
|
+
find_or_convert_resource_block(parts.last, options)
|
259
|
+
end
|
260
|
+
|
261
|
+
def lines_within(within)
|
262
|
+
return lines unless within
|
263
|
+
lines[(within + 1)..(find_block_end(within) + 1)]
|
264
|
+
end
|
265
|
+
|
266
|
+
def find_or_convert_resource_block(parent_resource, options = {})
|
267
|
+
unless find_resource_block([parent_resource], options)
|
268
|
+
if (resource_line_number = find_resource([parent_resource], options))
|
269
|
+
# convert it.
|
270
|
+
lines[resource_line_number].gsub!("\n", " do\n")
|
271
|
+
insert_after(["end"], resource_line_number)
|
272
|
+
else
|
273
|
+
raise "the parent resource (`#{parent_resource}`) doesn't appear to exist in `#{@filename}`."
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# update the block of code we're working within.
|
278
|
+
unless (within = find_resource_block([parent_resource], options))
|
279
|
+
raise "tried to convert the parent resource to a block, but failed?"
|
280
|
+
end
|
281
|
+
|
282
|
+
within
|
283
|
+
end
|
284
|
+
|
285
|
+
def insert(lines_to_add, within)
|
286
|
+
insertion_line = find_block_end(within)
|
287
|
+
result_line = insertion_line
|
288
|
+
unless insertion_line == within + 1
|
289
|
+
# only put the extra space if we're adding this line after a block
|
290
|
+
if /^\s*end\s*$/.match?(lines[insertion_line - 1])
|
291
|
+
lines_to_add.unshift("")
|
292
|
+
result_line += 1
|
293
|
+
end
|
294
|
+
end
|
295
|
+
insert_before(lines_to_add, insertion_line, indent: true)
|
296
|
+
result_line
|
297
|
+
end
|
298
|
+
|
299
|
+
def apply(base_namespaces)
|
300
|
+
child_namespaces, child_resource, parent_namespaces, parent_resource = divergent_parts
|
301
|
+
|
302
|
+
within = find_or_create_namespaces(base_namespaces)
|
303
|
+
within = find_or_create_namespaces(common_namespaces, within) if common_namespaces.any?
|
304
|
+
|
305
|
+
# e.g. Project and Projects::Deliverable
|
306
|
+
if parent_namespaces.empty? && child_namespaces.one? && parent_resource == child_namespaces.first
|
307
|
+
|
308
|
+
# resources :projects do
|
309
|
+
# scope module: 'projects' do
|
310
|
+
# resources :deliverables, only: collection_actions
|
311
|
+
# end
|
312
|
+
# end
|
313
|
+
|
314
|
+
parent_within = find_or_convert_resource_block(parent_resource, within: within)
|
315
|
+
|
316
|
+
# add the new resource within that namespace.
|
317
|
+
line = "scope module: '#{parent_resource}' do"
|
318
|
+
# TODO you haven't tested this yet.
|
319
|
+
unless (scope_within = find(/#{line}/, parent_within))
|
320
|
+
scope_within = insert([line, "end"], parent_within)
|
321
|
+
end
|
322
|
+
|
323
|
+
find_or_create_resource([child_resource], options: "only: collection_actions", within: scope_within)
|
324
|
+
|
325
|
+
# namespace :projects do
|
326
|
+
# resources :deliverables, except: collection_actions
|
327
|
+
# end
|
328
|
+
|
329
|
+
unless find_namespaces(child_namespaces, within)[child_namespaces.last]
|
330
|
+
insert_after(["", "namespace :#{child_namespaces.last} do", "end"], find_block_end(scope_within))
|
331
|
+
unless find_namespaces(child_namespaces, within)[child_namespaces.last]
|
332
|
+
raise "tried to insert `namespace :#{child_namespaces.last}` but it seems we failed"
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
find_or_create_resource(child_namespaces + [child_resource], options: "except: collection_actions", within: within)
|
337
|
+
|
338
|
+
# e.g. Projects::Deliverable and Objective Under It, Abstract::Concept and Concrete::Thing
|
339
|
+
elsif parent_namespaces.any?
|
340
|
+
|
341
|
+
# namespace :projects do
|
342
|
+
# resources :deliverables
|
343
|
+
# end
|
344
|
+
#
|
345
|
+
# resources :projects_deliverables, path: 'projects/deliverables' do
|
346
|
+
# resources :objectives
|
347
|
+
# end
|
348
|
+
|
349
|
+
find_resource(parent_namespaces + [parent_resource], within: within)
|
350
|
+
top_parent_namespace = find_namespaces(parent_namespaces, within)[parent_namespaces.first]
|
351
|
+
block_parent_within = find_block_parent(top_parent_namespace)
|
352
|
+
parent_namespaces_and_resource = (parent_namespaces + [parent_resource]).join("_")
|
353
|
+
parent_within = find_or_create_resource_block([parent_namespaces_and_resource], options: "path: '#{parent_namespaces_and_resource.tr("_", "/")}'", within: block_parent_within)
|
354
|
+
find_or_create_resource(child_namespaces + [child_resource], within: parent_within)
|
355
|
+
|
356
|
+
else
|
357
|
+
|
358
|
+
begin
|
359
|
+
within = find_or_convert_resource_block(parent_resource, within: within)
|
360
|
+
rescue
|
361
|
+
within = find_or_convert_resource_block(parent_resource, options: "except: collection_actions", within: within)
|
362
|
+
end
|
363
|
+
|
364
|
+
find_or_create_resource(child_namespaces + [child_resource], options: define_concerns, within: within)
|
365
|
+
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
# Pushing custom concerns here will add them to the routes file when Super Scaffolding.
|
370
|
+
def define_concerns
|
371
|
+
concerns = []
|
372
|
+
concerns.push(:sortable) if transformer_options["sortable"]
|
373
|
+
|
374
|
+
return if concerns.empty?
|
375
|
+
"concerns: #{concerns}"
|
376
|
+
end
|
377
|
+
|
378
|
+
def write
|
379
|
+
puts "Updating '#{@filename}'."
|
380
|
+
File.open(@filename, "w+") do |file|
|
381
|
+
file.puts(lines.join.strip + "\n")
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|