Class: Tailor::Rulers::IndentationSpacesRuler::IndentationManager

Inherits:
Object
  • Object
show all
Includes:
LexerConstants, Logger::Mixin
Defined in:
lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb

Overview

Used for managing the state of indentation for some file/text. An object of this type has no knowledge of the file/text itself, but rather just manages indentation expectations based on the object’s user’s input, somewhat like a state machine.

For the sake of talking about indentation expectations, the docs here make mention of ‘levels’ of indentation. A level here is simply 1 * (number of spaces to indent); so if you’ve set (number of spaces to indent) to 2, saying something should be indented 1 level, is simply saying that it should be indented 2 spaces.

Constant Summary collapse

ENCLOSERS =

These are event names generated by the Lexer that signify indentation level should/could increase by 1.

Set.new [:on_lbrace, :on_lbracket, :on_lparen]
OPEN_EVENT_FOR =

Look-up table that allows for OPEN_EVENT_FOR[:on_rbrace].

{
  on_kw: :on_kw,
  on_rbrace: :on_lbrace,
  on_rbracket: :on_lbracket,
  on_rparen: :on_lparen
}

Constants included from LexerConstants

LexerConstants::CONTINUATION_KEYWORDS, LexerConstants::KEYWORDS_AND_MODIFIERS, LexerConstants::KEYWORDS_TO_INDENT, LexerConstants::LOOP_KEYWORDS, LexerConstants::MODIFIERS, LexerConstants::MULTILINE_OPERATORS

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Logger::Mixin

included

Constructor Details

#initialize(spaces) ⇒ IndentationManager

Returns a new instance of IndentationManager.

Parameters:

  • spaces (Fixnum)

    The number of spaces each level of indentation should move in & out.



47
48
49
50
51
52
53
54
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 47

def initialize(spaces)
  @spaces = spaces
  @proper = { this_line: 0, next_line: 0 }
  @actual_indentation = 0
  @indent_reasons = []

  start
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *args, &blk) ⇒ Boolean

Overriding to be able to call #multi_line_brackets?, #multi_line_braces?, and #multi_line_parens?, where each takes a single parameter, which is the lineno.

Returns:

  • (Boolean)


363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 363

def method_missing(meth, *args, &blk)
  if meth.to_s =~ /^multi_line_(.+)\?$/
    token = case $1
    when 'brackets' then '['
    when 'braces' then '{'
    when 'parens' then '('
    else
      super(meth, *args, &blk)
    end

    lineno = args.first

    tokens = @indent_reasons.find_all do |t|
      t[:token] == token
    end

    log "#{meth} called, but no #{$1} were found." if tokens.empty?
    return false if tokens.empty?

    token_on_this_line = tokens.find { |t| t[:lineno] == lineno }
    return true if token_on_this_line.nil?

    false
  else
    super(meth, *args, &blk)
  end
end

Instance Attribute Details

#actual_indentationFixnum (readonly)

Returns The actual number of characters the current line is indented.

Returns:

  • (Fixnum)

    The actual number of characters the current line is indented.



38
39
40
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 38

def actual_indentation
  @actual_indentation
end

#indent_reasonsArray<Hash> (readonly)

Returns Each element represents a reason why code should be indented. Indent levels are not necessarily 1:1 relationship to these reasons (hence the need for this class).

Returns:

  • (Array<Hash>)

    Each element represents a reason why code should be indented. Indent levels are not necessarily 1:1 relationship to these reasons (hence the need for this class).



43
44
45
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 43

def indent_reasons
  @indent_reasons
end

Instance Method Details

#add_indent_reason(event_type, token, lineno) ⇒ Object

Adds to the list of reasons to indent the next line, then increases the expectation for the next line by @spaces.

Parameters:

  • event_type (Symbol)

    The event type that caused the reason for indenting.

  • token (Tailor::Token, String)

    The token that caused the reason for indenting.

  • lineno (Fixnum)

    The line number the reason for indenting was discovered on.



188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 188

def add_indent_reason(event_type, token, lineno)
  @indent_reasons << {
    event_type: event_type,
    token: token,
    lineno: lineno,
    should_be_at: @proper[:this_line]
  }

  @proper[:next_line] = @indent_reasons.last[:should_be_at] + @spaces
  log "Added indent reason; it's now:"
  @indent_reasons.each { |r| log r.to_s }
end

#decrease_this_lineObject

Decreases the indentation expectation for the current line by 1 level.



63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 63

def decrease_this_line
  if started?
    @proper[:this_line] -= @spaces

    if @proper[:this_line] < 0
      @proper[:this_line] = 0
    end

    log "@proper[:this_line] = #{@proper[:this_line]}"
    log "@proper[:next_line] = #{@proper[:next_line]}"
  else
    log '#decrease_this_line called, but checking is stopped.'
  end
end

#in_an_enclosure?Boolean

Determines if the current spot in the file is enclosed in braces, brackets, or parens.

Returns:

  • (Boolean)


161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 161

def in_an_enclosure?
  return false if @indent_reasons.empty?

  i_reasons = @indent_reasons.dup
  log "i reasons: #{i_reasons}"

  until ENCLOSERS.include? i_reasons.last[:event_type]
    i_reasons.pop
    break if i_reasons.empty?
  end

  return false if i_reasons.empty?

  i_reasons.last[:event_type] == :on_lbrace ||
    i_reasons.last[:event_type] == :on_lbracket ||
    i_reasons.last[:event_type] == :on_lparen
end

#last_indent_reason_typeObject



342
343
344
345
346
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 342

def last_indent_reason_type
  return if @indent_reasons.empty?

  @indent_reasons.last[:event_type]
end

#last_opening_event(closing_event_type) ⇒ Object

Returns the last matching opening event that corresponds to the closing_event_type.

Parameters:

  • closing_event_type (Symbol)

    The closing event for which to find its associated opening event.



334
335
336
337
338
339
340
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 334

def last_opening_event(closing_event_type)
  return nil if @indent_reasons.empty?

  @indent_reasons.reverse.find do |r|
    r[:event_type] == OPEN_EVENT_FOR[closing_event_type]
  end
end

#last_single_token_eventObject

A “single-token” event is one that that causes indentation expectations to increase. They don’t have have a paired closing reason like opening reasons. Instead, they’re determined to be done with their indenting when an :on_ignored_nl occurs. Single-token events are operators and commas (commas that aren’t used as separators in {, [, ( events).



321
322
323
324
325
326
327
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 321

def last_single_token_event
  return nil if @indent_reasons.empty?

  @indent_reasons.reverse.find do |r|
    !ENCLOSERS.include?(r[:event_type]) && r[:event_type] != :on_kw
  end
end

#line_ends_with_same_as_last(token_event) ⇒ Boolean

Checks to see if the last token in @single_tokens is the same as the one in token_event.

Parameters:

  • token_event (Array)

    A single event (probably extracted from a LexedLine).

Returns:

  • (Boolean)


151
152
153
154
155
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 151

def line_ends_with_same_as_last(token_event)
  return false if @indent_reasons.empty?

  @indent_reasons.last[:event_type] == token_event[1]
end

#line_ends_with_single_token_indenter?(lexed_line) ⇒ Boolean

Checks if the current line ends with an operator, comma, or period.

Parameters:

Returns:

  • (Boolean)


137
138
139
140
141
142
143
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 137

def line_ends_with_single_token_indenter?(lexed_line)
  lexed_line.ends_with_op? ||
    lexed_line.ends_with_comma? ||
    lexed_line.ends_with_period? ||
    lexed_line.ends_with_label? ||
    lexed_line.ends_with_modifier_kw?
end

#remove_appropriate_reason(closing_event_type) ⇒ Object

Removes the last matching opening reason reason of event_type from the list of indent reasons.

Parameters:

  • closing_event_type (Symbol)

    The closing event for which to find the matching opening event to remove from the list of indent reasons.



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 293

def remove_appropriate_reason(closing_event_type)
  if last_opening_event = last_opening_event(closing_event_type)
    r_index = @indent_reasons.reverse.index(last_opening_event)
    index = @indent_reasons.size - r_index - 1
    tmp_reasons = []

    @indent_reasons.each_with_index do |r, i|
      tmp_reasons << r unless i == index
    end

    @indent_reasons.replace(tmp_reasons)
  elsif last_single_token_event
    log "Just popped off reason: #{@indent_reasons.pop}"
  else
    log "Couldn't find a matching opening reason to pop off...'"
    return
  end

  log "Removed indent reason; it's now:"
  @indent_reasons.each { |r| log r.to_s }
end

#remove_continuation_keywordsObject

Removes all continuation keywords from the list of indentation reasons.



350
351
352
353
354
355
356
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 350

def remove_continuation_keywords
  return if @indent_reasons.empty?

  while CONTINUATION_KEYWORDS.include?(@indent_reasons.last[:token])
    log "Just popped off continuation reason: #{@indent_reasons.pop}"
  end
end

#should_be_atFixnum

Returns The indent level the file should currently be at.

Returns:

  • (Fixnum)

    The indent level the file should currently be at.



57
58
59
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 57

def should_be_at
  @proper[:this_line]
end

#startObject

Starts the process of increasing/decreasing line indentation expectations.



94
95
96
97
98
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 94

def start
  log 'Starting indentation ruling.'
  log "Next check should be at #{should_be_at}"
  @do_measurement = true
end

#started?Boolean

Tells if the indentation checking process is on.

Returns:

  • (Boolean)

    true if it’s started; false if not.



103
104
105
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 103

def started?
  @do_measurement
end

#stopObject

Stops the process of increasing/decreasing line indentation expectations.



109
110
111
112
113
114
115
116
117
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 109

def stop
  if started?
    msg = "Stopping indentation ruling.  Should be: #{should_be_at}; "
    msg << "actual: #{@actual_indentation}"
    log msg
  end

  @do_measurement = false
end

#transition_linesObject

Should be called just before moving to the next line. This sets the expectation set in @proper to @proper.



81
82
83
84
85
86
87
88
89
90
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 81

def transition_lines
  if started?
    log 'Resetting change_this to 0.'
    log 'Setting @proper[:this_line] = that of :next_line'
    @proper[:this_line] = @proper[:next_line]
    log "Transitioning @proper[:this_line] to #{@proper[:this_line]}"
  else
    log 'Skipping #transition_lines; checking is stopped.'
  end
end

#update_actual_indentation(lexed_line_output) ⇒ Object

Updates @actual_indentation based on the given lexed_line_output.

Parameters:

  • lexed_line_output (Array)

    The lexed output for the current line.



122
123
124
125
126
127
128
129
130
131
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 122

def update_actual_indentation(lexed_line_output)
  if lexed_line_output.end_of_multi_line_string?
    log 'Found end of multi-line string.'
    return
  end

  first_non_space_element = lexed_line_output.first_non_space_element
  @actual_indentation = first_non_space_element.first.last
  log "Actual indentation: #{@actual_indentation}"
end

#update_for_closing_reason(event_type, lexed_line) ⇒ Object

A “closing reason” is a reason for indenting that also has an “opening reason”, such as a end, }, ], ).

Parameters:

  • event_type (Symbol)

    The event type that is the closing reason.

  • lexed_line (Tailor::LexedLine)

    The line that contains the closing reason.



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 265

def update_for_closing_reason(event_type, lexed_line)
  remove_continuation_keywords
  remove_appropriate_reason(event_type)

  @proper[:next_line] = if @indent_reasons.empty?
    0
  else
    @indent_reasons.last[:should_be_at] + @spaces
  end

  log "Updated :next after closing; it's now #{@proper[:next_line]}"

  meth = "only_#{event_type.to_s.sub(/^on_/, '')}?"

  if lexed_line.send(meth.to_sym) || lexed_line.to_s =~ /^\s*end\n?$/
    @proper[:this_line] = [0, @proper[:this_line] - @spaces].max
    msg = 'End multi-line statement. '
    msg < "change_this -= 1 -> #{@proper[:this_line]}."
    log msg
  end
end

#update_for_continuation_reason(token, lexed_line, lineno) ⇒ Object

A “continuation reason” is a reason for indenting & outdenting that’s not an opening or closing reason, such as elsif, rescue, when (in a case statement), etc.

Parameters:

  • token (Symbol)
  • lexed_line (Tailor::LexedLine)
  • lineno (Fixnum)

    The line number the opening reason was found on.



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 233

def update_for_continuation_reason(token, lexed_line, lineno)
  d_tokens = @indent_reasons.dup
  d_tokens.pop
  on_line_token = d_tokens.find { |t| t[:lineno] == lineno }
  log "online token: #{on_line_token}"

  if on_line_token.nil? && lexed_line.to_s =~ /^\s*#{token}/
    @proper[:this_line] -= @spaces unless @proper[:this_line].zero?
    msg = "Continuation keyword: '#{token}'.  "
    msg << "change_this -= 1 -> #{@proper[:this_line]}"
    log msg
  end

  last_reason_line = @indent_reasons.find { |r| r[:lineno] == lineno }

  @proper[:next_line] = if last_reason_line.nil?
    if @indent_reasons.empty?
      @spaces
    else
      @indent_reasons.last[:should_be_at] + @spaces
    end
  else
    @indent_reasons.last[:should_be_at] - @spaces
  end
end

#update_for_opening_reason(event_type, token, lineno) ⇒ Object

An “opening reason” is a reason for indenting that also has a “closing reason”, such as a def, {, [, (.

Parameters:

  • event_type (Symbol)

    The event type that is the opening reason.

  • token (Tailor::Token, String)

    The token that is the opening reasons.

  • lineno (Fixnum)

    The line number the opening reason was found on.



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/tailor/rulers/indentation_spaces_ruler/indentation_manager.rb', line 209

def update_for_opening_reason(event_type, token, lineno)
  if token.modifier_keyword?
    log "Found modifier in line: '#{token}'"
    return
  end

  log "Token '#{token}' not used as a modifier."

  if token.do_is_for_a_loop?
    log "Found keyword loop using optional 'do'"
    return
  end

  add_indent_reason(event_type, token, lineno)
end