Class: Effective::CodeWriter

Inherits:
Object
  • Object
show all
Defined in:
app/models/effective/code_writer.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(filename, indent: ' '.freeze, newline: "\n".freeze, &block) ⇒ CodeWriter

Returns a new instance of CodeWriter.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'app/models/effective/code_writer.rb', line 12

def initialize(filename, indent: '  '.freeze, newline: "\n".freeze, &block)
  @filename = filename
  @indent = indent
  @newline = newline

  @from = []
  @to = []

  @changed = false

  @lines = File.open(filename).readlines

  if block_given?
    block.call(self)
    write!
  end
end

Instance Attribute Details

#filename ⇒ Object (readonly)

Returns the value of attribute filename.



10
11
12
# File 'app/models/effective/code_writer.rb', line 10

def filename
  @filename
end

#indent ⇒ Object (readonly)

Returns the value of attribute indent.



10
11
12
# File 'app/models/effective/code_writer.rb', line 10

def indent
  @indent
end

#lines ⇒ Object (readonly)

Returns the value of attribute lines.



9
10
11
# File 'app/models/effective/code_writer.rb', line 9

def lines
  @lines
end

#newline ⇒ Object (readonly)

Returns the value of attribute newline.



10
11
12
# File 'app/models/effective/code_writer.rb', line 10

def newline
  @newline
end

Instance Method Details

#all(from: @from.last, to: @to.last, &block) ⇒ Object Also known as: select

Returns an array of indexes for each line where the passed block returns true



169
170
171
172
173
174
175
176
177
178
179
# File 'app/models/effective/code_writer.rb', line 169

def all(from: @from.last, to: @to.last, &block)
  retval = []

  each_with_depth do |line, depth, index|
    next if index < (from || 0)
    retval << index if block.call(line, depth, index)
    break if to == index
  end

  retval
end

#changed? ⇒ Boolean

Returns:

  • (Boolean)


213
214
215
# File 'app/models/effective/code_writer.rb', line 213

def changed?
  @changed == true
end

#depth_at(line_index) ⇒ Object



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'app/models/effective/code_writer.rb', line 197

def depth_at(line_index)
  if filename.end_with?('.haml')
    return (lines[line_index].length - lines[line_index].lstrip.length) / indent.length
  end

  depth = 0

  Array(lines).each_with_index do |line, index|
    depth -= 1 if close?(line)
    break if line_index == index
    depth += 1 if open?(line)
  end

  depth
end

#each_with_depth(&block) ⇒ Object

Iterate over the lines with a depth, and passed the stripped line to the passed block



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'app/models/effective/code_writer.rb', line 129

def each_with_depth(&block)
  depth = 0
  from_depth = (@from.last ? depth_at(@from.last) : 0)

  Array(lines).each_with_index do |line, index|
    stripped = line.to_s.strip

    depth -= 1 if close?(stripped)

    block.call(stripped, depth - from_depth, index)
    depth += 1 if open?(stripped)
  end

  nil
end

#first(from: @from.last, to: @to.last, &block) ⇒ Object Also known as: find

Returns the index of the first line where the passed block returns true



146
147
148
149
150
151
152
# File 'app/models/effective/code_writer.rb', line 146

def first(from: @from.last, to: @to.last, &block)
  each_with_depth do |line, depth, index|
    next if index < (from || 0)
    return index if block.call(line, depth, index)
    break if to == index
  end
end

#gsub!(source, target) ⇒ Object



217
218
219
# File 'app/models/effective/code_writer.rb', line 217

def gsub!(source, target)
  lines.each { |line| @changed = true if line.gsub!(source, target) }
end

#insert(content, index, depth: nil, content_depth: nil) ⇒ 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
# File 'app/models/effective/code_writer.rb', line 70

def insert(content, index, depth: nil, content_depth: nil)
  contents = (content.kind_of?(Array) ? content : content.split(newline)).map { |str| str.strip }

  depth ||= depth_at(index)

  # If the line we're inserting at is a block, fast-forward the end of the block. And add a newline.
  if open?(index)
    index = first(from: index) { |line| close?(line) } + 1
    lines.insert(index, newline)
  elsif !whitespace?(index) && (open?(contents) || !same?(contents, index))
    index += 1
    lines.insert(index, newline)
  end

  content_depth ||= 0

  index = index + 1 # Insert after the given line

  contents.each do |content|
    content_depth -= 1 if close?(content)

    if content == ''
      lines.insert(index, newline)
    else
      lines.insert(index, (indent * (depth + content_depth)) + content + newline)
    end

    index += 1
    content_depth += 1 if open?(content)
  end

  unless whitespace?(index) || close?(index)
    if block?(contents) || !same?(contents, index)
      lines.insert(index, newline)
    end
  end

  @changed = true
end

#insert_after_first(content, depth: nil, content_depth: nil, &block) ⇒ Object

Returns true if the insert happened, nil if no insert



31
32
33
34
35
36
# File 'app/models/effective/code_writer.rb', line 31

def insert_after_first(content, depth: nil, content_depth: nil, &block)
  index = first(&block)
  return nil unless index

  insert(content, index, depth: depth, content_depth: content_depth)
end

#insert_after_last(content, depth: nil, content_depth: nil, &block) ⇒ Object

Returns true if the insert happened, nil if no insert



39
40
41
42
43
44
# File 'app/models/effective/code_writer.rb', line 39

def insert_after_last(content, depth: nil, content_depth: nil, &block)
  index = last(&block)
  return nil unless index

  insert(content, index, depth: depth, content_depth: content_depth)
end

#insert_before_last(content, depth: nil, content_depth: nil, &block) ⇒ Object

Returns true if the insert happened, nil if no insert



47
48
49
50
51
52
# File 'app/models/effective/code_writer.rb', line 47

def insert_before_last(content, depth: nil, content_depth: nil, &block)
  index = last(&block)
  return nil unless index

  insert(content, index-1, depth: depth, content_depth: content_depth)
end

#insert_raw(content, index, depth = 0) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'app/models/effective/code_writer.rb', line 110

def insert_raw(content, index, depth = 0)
  contents = (content.kind_of?(Array) ? content : content.split(newline))

  index = index + 1 # Insert after the given line

  contents.each do |content|
    if content.strip == ''
      lines.insert(index, newline)
    else
      lines.insert(index, (indent * depth) + content + newline)
    end

    index += 1
  end

  @changed = true
end

#last(from: @from.last, to: @to.last, &block) ⇒ Object

Returns the index of the last line where the passed block returns true



156
157
158
159
160
161
162
163
164
165
166
# File 'app/models/effective/code_writer.rb', line 156

def last(from: @from.last, to: @to.last, &block)
  retval = nil

  each_with_depth do |line, depth, index|
    next if index < (from || 0)
    retval = index if block.call(line, depth, index)
    break if to == index
  end

  retval
end

#map(from: @from.last, to: @to.last, indexes: [], &block) ⇒ Object

Yields each line to a block and returns an array of the results



183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'app/models/effective/code_writer.rb', line 183

def map(from: @from.last, to: @to.last, indexes: [], &block)
  retval = []

  each_with_depth do |line, depth, index|
    next if index < (from || 0)
    next unless indexes.blank? || indexes.include?(index)

    retval << block.call(line, depth, index)
    break if to == index
  end

  retval
end

#replace(index, content) ⇒ Object



221
222
223
224
# File 'app/models/effective/code_writer.rb', line 221

def replace(index, content)
  @changed = true
  lines[index].replace(content.to_s)
end

#within(content, &block) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'app/models/effective/code_writer.rb', line 54

def within(content, &block)
  content ||= 0

  from = content.kind_of?(Integer) ? content : first { |line| line.start_with?(content) && open?(line) }
  return nil unless from

  from_depth = depth_at(from)

  to = first(from: from) { |line, depth| depth == from_depth && close?(line) }
  return nil unless to

  @from.push(from); @to.push(to)
  yield
  @from.pop; @to.pop
end