Class: PuppetLint::Lexer::StringSlurper

Inherits:
Object
  • Object
show all
Defined in:
lib/puppet-lint/lexer/string_slurper.rb

Overview

Internal: A class for slurping strings from a Puppet manifest.

Defined Under Namespace

Classes: UnterminatedStringError

Constant Summary collapse

START_INTERP_PATTERN =
%r{\$\{}.freeze
END_INTERP_PATTERN =
%r{\}}.freeze
END_STRING_PATTERN =
%r{(\A|[^\\])(\\\\)*"}.freeze
UNENC_VAR_PATTERN =
%r{(\A|[^\\])\$(::)?(\w+(-\w+)*::)*\w+(-\w+)*}.freeze
ESC_DQUOTE_PATTERN =
%r{\\+"}.freeze
LBRACE_PATTERN =
%r{\{}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(string) ⇒ StringSlurper

Returns a new instance of StringSlurper.



16
17
18
19
20
21
# File 'lib/puppet-lint/lexer/string_slurper.rb', line 16

def initialize(string)
  @scanner = StringScanner.new(string)
  @results = []
  @interp_stack = []
  @segment = []
end

Instance Attribute Details

#interp_stackObject

Returns the value of attribute interp_stack.



7
8
9
# File 'lib/puppet-lint/lexer/string_slurper.rb', line 7

def interp_stack
  @interp_stack
end

#resultsObject

Returns the value of attribute results.



7
8
9
# File 'lib/puppet-lint/lexer/string_slurper.rb', line 7

def results
  @results
end

#scannerObject

Returns the value of attribute scanner.



7
8
9
# File 'lib/puppet-lint/lexer/string_slurper.rb', line 7

def scanner
  @scanner
end

Instance Method Details

#consumed_charsObject

Get the number of characters consumed by the StringSlurper.

StringScanner from Ruby 2.0 onwards supports #charpos which returns the number of characters and is multibyte character aware.

Prior to this, Ruby’s multibyte character support in Strings was a bit unusual and neither String#length nor String#split behave as expected, so we use String#scan to split all the consumed text using a UTF-8 aware regex and use the length of the result



105
106
107
108
109
# File 'lib/puppet-lint/lexer/string_slurper.rb', line 105

def consumed_chars
  return scanner.charpos if scanner.respond_to?(:charpos)

  (scanner.pre_match + scanner.matched).scan(%r{.}mu).length
end

#end_heredoc(pattern) ⇒ Object



153
154
155
156
# File 'lib/puppet-lint/lexer/string_slurper.rb', line 153

def end_heredoc(pattern)
  results << [:HEREDOC, @segment.join]
  results << [:HEREDOC_TERM, scanner.scan(pattern)]
end

#end_interpObject



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/puppet-lint/lexer/string_slurper.rb', line 128

def end_interp
  if interp_stack.empty?
    @segment << scanner.scan(END_INTERP_PATTERN)
    return
  else
    interp_stack.pop
  end

  if interp_stack.empty?
    results << [:INTERP, @segment.join]
    @segment = []
    scanner.skip(END_INTERP_PATTERN)
  else
    @segment << scanner.scan(END_INTERP_PATTERN)
  end
end

#end_stringObject



158
159
160
161
162
163
164
165
# File 'lib/puppet-lint/lexer/string_slurper.rb', line 158

def end_string
  if interp_stack.empty?
    @segment << scanner.scan(END_STRING_PATTERN).gsub!(%r{"\Z}, '')
    results << [@segment_type, @segment.join]
  else
    @segment << scanner.scan(END_STRING_PATTERN)
  end
end

#parseObject



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/puppet-lint/lexer/string_slurper.rb', line 23

def parse
  @segment_type = :STRING

  until scanner.eos?
    if scanner.match?(START_INTERP_PATTERN)
      start_interp
    elsif !interp_stack.empty? && scanner.match?(LBRACE_PATTERN)
      read_char
    elsif scanner.match?(END_INTERP_PATTERN)
      end_interp
    elsif unenclosed_variable?
      unenclosed_variable
    elsif scanner.match?(END_STRING_PATTERN)
      end_string
      break if interp_stack.empty?
    elsif scanner.match?(ESC_DQUOTE_PATTERN)
      @segment << scanner.scan(ESC_DQUOTE_PATTERN)
    else
      read_char
    end
  end

  raise UnterminatedStringError if results.empty? && scanner.matched?

  results
end

#parse_heredoc(heredoc_tag) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/puppet-lint/lexer/string_slurper.rb', line 56

def parse_heredoc(heredoc_tag)
  heredoc_name = heredoc_tag[%r{\A"?(.+?)"?(:.+?)?#{PuppetLint::Lexer::WHITESPACE_RE}*(/.*)?\Z}o, 1]
  end_heredoc_pattern = %r{^\|?\s*-?\s*#{Regexp.escape(heredoc_name)}$}
  interpolation = heredoc_tag.start_with?('"')

  @segment_type = :HEREDOC

  until scanner.eos?
    if scanner.match?(end_heredoc_pattern)
      end_heredoc(end_heredoc_pattern)
      break if interp_stack.empty?
    elsif interpolation && scanner.match?(START_INTERP_PATTERN)
      start_interp
    elsif interpolation && !interp_stack.empty? && scanner.match?(LBRACE_PATTERN)
      read_char
    elsif interpolation && unenclosed_variable?
      unenclosed_variable
    elsif interpolation && scanner.match?(END_INTERP_PATTERN)
      end_interp
    else
      read_char
    end
  end

  results
end

#read_charObject



83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/puppet-lint/lexer/string_slurper.rb', line 83

def read_char
  @segment << scanner.getch

  return if interp_stack.empty?

  case @segment.last
  when '{'
    interp_stack.push(true)
  when '}'
    interp_stack.pop
  end
end

#start_interpObject



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/puppet-lint/lexer/string_slurper.rb', line 111

def start_interp
  if @segment.last && @segment.last == '\\'
    read_char
    return
  end

  if interp_stack.empty?
    scanner.skip(START_INTERP_PATTERN)
    results << [@segment_type, @segment.join]
    @segment = []
  else
    @segment << scanner.scan(START_INTERP_PATTERN)
  end

  interp_stack.push(true)
end

#unenclosed_variableObject



145
146
147
148
149
150
151
# File 'lib/puppet-lint/lexer/string_slurper.rb', line 145

def unenclosed_variable
  read_char if scanner.match?(%r{.\$})

  results << [@segment_type, @segment.join]
  results << [:UNENC_VAR, scanner.scan(UNENC_VAR_PATTERN)]
  @segment = []
end

#unenclosed_variable?Boolean

Returns:

  • (Boolean)


50
51
52
53
54
# File 'lib/puppet-lint/lexer/string_slurper.rb', line 50

def unenclosed_variable?
  interp_stack.empty? &&
    scanner.match?(UNENC_VAR_PATTERN) &&
    (@segment.last.nil? ? true : !@segment.last.end_with?('\\'))
end