Class: Puppet::Pops::Parser::EppSupport::EppScanner

Inherits:
Object
  • Object
show all
Defined in:
lib/puppet/pops/parser/epp_support.rb

Overview

A scanner specialized in processing text with embedded EPP (Embedded Puppet) tags. The scanner is initialized with a StringScanner which it mutates as scanning takes place. The intent is to use one instance of EppScanner per wanted scan, and this instance represents the state after the scan.

The scanner supports

  • scanning text until <%, <%-, <%=

  • while scanning text:

    • tokens <%% and %%> are translated to <% and %>, respectively, and is returned as text.

    • tokens <%# and %> (or ending with -%>) and the enclosed text is a comment and is not included in the returned text

    • text following a comment that ends with -%> gets trailing whitespace (up to and including a line break) trimmed and this whitespace is not included in the returned text.

  • The continuation #mode is set to one of:

    • ‘:epp` - for a <% token

    • ‘:expr` - for a <%= token

    • ‘:text` - when there was no continuation mode (e.g. when input ends with text)

    • ‘:error` - if the tokens are unbalanced (reaching the end without a closing matching token). An error message is then also available via the method #message.

Note that the intent is to use this specialized scanner to scan the text parts, when continuation mode is ‘:epp` or `:expr` the pp lexer should advance scanning (using the string scanner) until it reaches and consumes a `-%>` or ’%>´ token. If it finds a ‘-%> token it should pass this on as a `skip_leading` parameter when it performs the next #scan.

Examples:

Sample usage

a = "some text <% pp code %> some more text"
scan = StringScanner.new(a)
eppscan = EppScanner.new(scan)
str = eppscan.scan
eppscan.mode # => :epp
eppscan.lines # => 0
eppscan

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(scanner) ⇒ EppScanner

Creates an EppScanner based on a StringScanner that represents the state where EppScanner should start scanning. The given scanner will be mutated (i.e. position moved) to reflect the EppScanner’s end state after a scan.



164
165
166
# File 'lib/puppet/pops/parser/epp_support.rb', line 164

def initialize(scanner)
  @scanner = scanner
end

Instance Attribute Details

#issueObject (readonly)

An error issue if ‘mode == :error`, `nil` otherwise.



154
155
156
# File 'lib/puppet/pops/parser/epp_support.rb', line 154

def issue
  @issue
end

#modeObject (readonly)

The resulting mode after the scan. The mode is one of ‘:text` (the initial mode), `:epp` embedded code (no output), `:expr` (embedded expression), or `:error`



151
152
153
# File 'lib/puppet/pops/parser/epp_support.rb', line 151

def mode
  @mode
end

#scannerObject (readonly)

The original scanner used by the lexer/container using EppScanner



145
146
147
# File 'lib/puppet/pops/parser/epp_support.rb', line 145

def scanner
  @scanner
end

#skip_leadingObject (readonly)

If the first scan should skip leading whitespace (typically detected by the pp lexer when the pp mode end-token is found (i.e. ‘-%>`) and then passed on to the scanner.



159
160
161
# File 'lib/puppet/pops/parser/epp_support.rb', line 159

def skip_leading
  @skip_leading
end

Instance Method Details

#messageString

Deprecated.

Use issue instead

Here for backwards compatibility.

Returns:

  • (String)

    the issue message



171
172
173
# File 'lib/puppet/pops/parser/epp_support.rb', line 171

def message
  @issue.nil? ? nil : @issue.format
end

#scan(skip_leading = false) ⇒ String?

Scans from the current position in the configured scanner, advances this scanner’s position until the end of the input, or to the first position after a mode switching token (‘<%`, `<%-` or `<%=`). Number of processed lines and continuation mode can be obtained via #lines, and #mode.

Returns:

  • (String, nil)

    the scanned and processed text, or nil if at the end of the input.



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
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
258
259
260
261
262
# File 'lib/puppet/pops/parser/epp_support.rb', line 181

def scan(skip_leading = false)
  @mode = :text
  @skip_leading = skip_leading

  return nil if scanner.eos?

  s = ''.dup
  until scanner.eos?
    part = @scanner.scan_until(/(<%)|\z/)
    if @skip_leading
      part.sub!(/^[ \t]*\r?(?:\n|\z)?/, '')
      @skip_leading = false
    end
    # The spec for %%> is to transform it into a literal %>. This is done here, as %%> otherwise would go
    # undetected in text mode. (i.e. it is not really necessary to escape %> with %%> in text mode unless
    # adding checks stating that a literal %> is illegal in text (unbalanced).
    #
    part.gsub!(/%%>/, '%>')
    s += part
    case @scanner.peek(1)
    when ""
      # at the end
      # if s ends with <% then this is an error (unbalanced <% %>)
      if s.end_with? "<%"
        @mode = :error
        @issue = Issues::EPP_UNBALANCED_EXPRESSION
      end
      return s

    when "-"
      # trim trailing whitespace on same line from accumulated s
      # return text and signal switch to pp mode
      @scanner.getch # drop the -
      s.sub!(/[ \t]*<%\z/, '')
      @mode = :epp
      return s

    when "%"
      # verbatim text
      # keep the scanned <%, and continue scanning after skipping one %
      # (i.e. do nothing here)
      @scanner.getch # drop the % to get a literal <% in the output

    when "="
      # expression
      # return text and signal switch to expression mode
      # drop the scanned <%, and skip past -%>, or %>, but also skip %%>
      @scanner.getch # drop the =
      s.slice!(-2..-1)
      @mode = :expr
      return s

    when "#"
      # template comment

      # drop the scanned <%, and skip past -%>, or %>, but also skip %%>
      s.slice!(-2..-1)

      # unless there is an immediate termination i.e. <%#%> scan for the next %> that is not
      # preceded by a % (i.e. skip %%>)
      part = scanner.scan_until(/[^%]%>/)
      unless part
        @issue = Issues::EPP_UNBALANCED_COMMENT
        @mode = :error
        return s
      end
      # Trim leading whitespace on the same line when start was <%#-
      if part[1] == '-'
        s.sub!(/[ \t]*\z/, '')
      end

      @skip_leading = true if part.end_with?("-%>")
      # Continue scanning for more text

    else
      # Switch to pp after having removed the <%
      s.slice!(-2..-1)
      @mode = :epp
      return s
    end
  end
end