Class: Scaffolding::BlockManipulator

Inherits:
Object
  • Object
show all
Defined in:
lib/scaffolding/block_manipulator.rb

Class Method Summary collapse

Class Method Details

.find_block_end(starting_from:, lines:) ⇒ Object



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/scaffolding/block_manipulator.rb', line 174

def find_block_end(starting_from:, lines:)
  # This loop was previously in the RoutesFileManipulator.
  lines.each_with_index do |line, line_number|
    next unless line_number > starting_from
    if /^#{indentation_of(starting_from, lines)}end\s*/.match?(line)
      return line_number
    end
  end

  depth = 0
  current_line = starting_from
  lines[starting_from..lines.count].each_with_index do |line, index|
    current_line = starting_from + index
    depth += 1 if line.match?(/\s*<%.+ do .*%>/)
    depth += 1 if line.match?(/\s*<% if .*%>/)
    depth += 1 if line.match?(/\s*<% unless .*%>/)
    depth -= 1 if line.match?(/\s*<%.* end .*%>/)
    break current_line if depth == 0
  end
  current_line
end

.find_block_parent(starting_line_number, lines) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
# File 'lib/scaffolding/block_manipulator.rb', line 149

def find_block_parent(starting_line_number, lines)
  return nil unless indentation_of(starting_line_number, lines)
  cursor = starting_line_number
  while cursor >= 0
    unless lines[cursor].match?(/^#{indentation_of(starting_line_number, lines)}/) || !lines[cursor].present?
      return cursor
    end
    cursor -= 1
  end
  nil
end

.find_block_start(starting_from:, lines:) ⇒ Object



161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/scaffolding/block_manipulator.rb', line 161

def find_block_start(starting_from:, lines:)
  matcher = Regexp.escape(starting_from)
  starting_line = 0

  lines.each_with_index do |line, index|
    if line.match?(matcher)
      starting_line = index
      break
    end
  end
  starting_line
end

.indentation_of(line_number, lines) ⇒ Object

TODO: We shouldn’t need this second argument, but since we have ‘lines` here and in the RoutesFileManipulator, the lines diverge from one another when we edit them individually.



199
200
201
202
203
# File 'lib/scaffolding/block_manipulator.rb', line 199

def indentation_of(line_number, lines)
  lines[line_number].match(/^( +)/)[1]
rescue
  nil
end

.insert(content, lines:, within: nil, after: nil, before: nil, after_block: nil, append: false) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/scaffolding/block_manipulator.rb', line 70

def insert(content, lines:, within: nil, after: nil, before: nil, after_block: nil, append: false)
  content = prepare_content_array(content)

  # We initialize the search with the entire file's lines and look for the block below.
  start_line = 0
  end_line = lines.count - 1

  # Search for before like we do after, we'll just inject before it.
  after ||= before

  # If within is given, find the start and end lines of the block
  if within.present?
    start_line = find_block_start(starting_from: within, lines: lines)
    end_line = find_block_end(starting_from: start_line, lines: lines)
    # start_line += 1 # ensure we actually insert the content _within_ the given block
    # end_line += 1 if end_line == start_line
  end

  if after_block.present?
    block_start = find_block_start(starting_from: after_block, lines: lines)
    block_end = find_block_end(starting_from: block_start, lines: lines)
    start_line = block_end
    end_line = lines.count - 1
  end

  index = start_line
  match = false
  while index < end_line && !match
    line = lines[index]
    if after.nil? || line.match?(after)
      unless append
        match = true
        indent = !(before.present? || after.present? || after_block.present?)

        # We adjust the injection point if we really wanted to insert before.
        lines = insert_lines(content, index - (before ? 1 : 0), lines, indent)
      end
    end
    index += 1
  end

  return lines if match

  # Match should always be false here.
  if append && !match
    lines = insert_lines(content, index - 1, lines)
  end
  lines
end

.insert_block(block_content, after_block:, lines:) ⇒ Object



138
139
140
141
142
143
144
145
146
147
# File 'lib/scaffolding/block_manipulator.rb', line 138

def insert_block(block_content, after_block:, lines:)
  # Since `after_block` must be present for this method to work,
  # the assumption is we never inseart a block inside an empty block, but
  # always after the end of one. For that reason, ident defaults to false.
  indent = false
  block_start = find_block_start(starting_from: after_block, lines: lines)
  block_end = find_block_end(starting_from: block_start, lines: lines)
  lines = insert_line(block_content[0], block_end, lines, indent)
  insert_line(block_content[1], block_end + 1, lines, indent)
end

.insert_line(content, insert_at_index, lines, indent = true) ⇒ Object

TODO: We should eventually replace this with ‘insert_lines“, I just want to make sure everything doesn’t break first.



134
135
136
# File 'lib/scaffolding/block_manipulator.rb', line 134

def insert_line(content, insert_at_index, lines, indent = true)
  insert_lines(prepare_content_array(content), insert_at_index, lines, indent)
end

.insert_lines(content, insert_at_index, lines, indent) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
# File 'lib/scaffolding/block_manipulator.rb', line 120

def insert_lines(content, insert_at_index, lines, indent)
  final = []
  lines.each_with_index do |line, index|
    indentation = line.match(/^\s*/).to_s
    indentation += "\s" * 2 if indent

    final << line
    content.each { |new_line| final << indentation + new_line } if index == insert_at_index
  end
  final
end

.shift_block(lines:, block_start:, direction: :left, amount: 2, shift_contents_only: false) ⇒ Object

Shifts the block either to the left or right.



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/scaffolding/block_manipulator.rb', line 206

def shift_block(lines:, block_start:, direction: :left, amount: 2, shift_contents_only: false)
  block_start = lines.index(block_start) if block_start.is_a? String
  block_range = (block_start..(find_block_end(starting_from: block_start, lines: lines)))
  block_range = (block_range.first + 1)..(block_range.last - 1) if shift_contents_only
  new_lines = []

  lines.each_with_index do |line, line_number|
    if block_range.cover?(line_number)
      # If we're shifting a block to the left, we want to safeguard
      # the String so it doesn't delete any excess characters.
      if direction == :left
        amount.times { line = line.gsub(/^ /, "") }
      elsif direction == :right
        line = "\s" * amount + line
      end
    end
    new_lines << line
  end

  new_lines
end

.unwrap_block(lines:, block_start:) ⇒ Object

This method unwraps the block from the perspective of the child.

2.times do

3.times do
  puts "foo"
end

end

Here we would pass the index of ‘“3.times don”` to `block_start` which would result in removing the outer block.



56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/scaffolding/block_manipulator.rb', line 56

def unwrap_block(lines:, block_start:)
  block_start = if block_start.is_a? String
    block_start_line = lines.find { |line| line.match?(block_start) }
    lines.index(block_start_line)
  end

  # Find the proper indices for both child and parent blocks.
  block_parent_start = find_block_parent(block_start, lines)
  block_parent_end = find_block_end(starting_from: block_parent_start, lines: lines)

  new_lines = shift_block(lines: lines, block_start: block_start)
  new_lines.reject.with_index { |lines, idx| idx == block_parent_start || idx == block_parent_end }
end

.wrap_block(starting:, with:, lines:) ⇒ Object

Wrap a block of ruby code with another block on the outside.

Parameters:

  • `starting` (String)

    A string to search for at the start of the block. Eg “<%= cable_ready_updates_for context, collection do”

  • `with` (Array)

    An array with two String elements. The text that should wrap the block. Eg [“<%= action_model_select_controller do %>”, “<% end %>”]



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/scaffolding/block_manipulator.rb', line 12

def wrap_block(starting:, with:, lines:)
  with[0] += "\n" unless with[0].match?(/\n$/)
  with[1] += "\n" unless with[1].match?(/\n$/)
  starting_line = find_block_start(starting_from: starting, lines: lines)
  end_line = find_block_end(starting_from: starting_line, lines: lines)

  final = []
  block_indent = ""
  spacer = "  "
  lines.each_with_index do |line, index|
    line += "\n" unless line.match?(/\n$/)
    if index < starting_line
      final << line
    elsif index == starting_line
      block_indent = line.match(/^\s*/).to_s
      final << block_indent + with[0]
      final << (line.blank? ? "\n" : "#{spacer}#{line}")
    elsif index > starting_line && index < end_line
      final << (line.blank? ? "\n" : "#{spacer}#{line}")
    elsif index == end_line
      final << (line.blank? ? "\n" : "#{spacer}#{line}")
      final << block_indent + with[1]
    else
      final << line
    end
  end

  lines = final
  unless lines.last.match?(/\n$/)
    lines[-1] += "\n"
  end
  lines
end