Class Puppet::Parser::Parser
In: lib/puppet/parser/parser_support.rb
lib/puppet/parser/parser.rb
Parent: Object

I pulled this into a separate file, because I got tired of rebuilding the parser.rb file all the time.

Methods

_reduce_none   addcontext   aryfy   ast   classname   clear   error   file   file=   findclass   finddefine   findnode   fqfind   import   initvars   load   namesplit   new   newclass   newdefine   newnode   on_error   parse   reparse?   string=   watch_file  

Constants

ASTSet = Struct.new(:classes, :definitions, :nodes)
AST = Puppet::Parser::AST
Racc_arg = [ racc_action_table, racc_action_check, racc_action_default, racc_action_pointer, racc_goto_table, racc_goto_check, racc_goto_default, racc_goto_pointer, racc_nt_base, racc_reduce_table, racc_token_table, racc_shift_n, racc_reduce_n, racc_use_result_var ]
Racc_token_to_s_table = [ '$end', 'error', 'LBRACK', 'DQTEXT', 'SQTEXT', 'RBRACK', 'LBRACE', 'RBRACE', 'SYMBOL', 'FARROW', 'COMMA', 'TRUE', 'FALSE', 'EQUALS', 'APPENDS', 'LESSEQUAL', 'NOTEQUAL', 'DOT', 'COLON', 'LLCOLLECT', 'RRCOLLECT', 'QMARK', 'LPAREN', 'RPAREN', 'ISEQUAL', 'GREATEREQUAL', 'GREATERTHAN', 'LESSTHAN', 'IF', 'ELSE', 'IMPORT', 'DEFINE', 'ELSIF', 'VARIABLE', 'CLASS', 'INHERITS', 'NODE', 'BOOLEAN', 'NAME', 'SEMIC', 'CASE', 'DEFAULT', 'AT', 'LCOLLECT', 'RCOLLECT', 'CLASSNAME', 'CLASSREF', 'NOT', 'OR', 'AND', 'UNDEF', 'PARROW', '$start', 'program', 'statements', 'nil', 'statement', 'resource', 'virtualresource', 'collection', 'assignment', 'casestatement', 'ifstatement', 'import', 'fstatement', 'definition', 'hostclass', 'nodedef', 'resourceoverride', 'append', 'funcvalues', 'namestrings', 'resourcerefs', 'namestring', 'name', 'variable', 'type', 'boolean', 'funcrvalue', 'selector', 'quotedtext', 'resourceref', 'classname', 'resourceinstances', 'endsemi', 'params', 'endcomma', 'classref', 'anyparams', 'at', 'collectrhand', 'collstatements', 'collstatement', 'colljoin', 'collexpr', 'colllval', 'simplervalue', 'resourceinst', 'resourcename', 'undef', 'array', 'rvalue', 'param', 'addparam', 'anyparam', 'rvalues', 'comma', 'iftest', 'else', 'caseopts', 'caseopt', 'casevalues', 'selectlhand', 'svalues', 'selectval', 'sintvalues', 'qtexts', 'argumentlist', 'classparent', 'hostnames', 'nodeparent', 'hostname', 'nothing', 'arguments', 'argument', 'classnameordefault']
Racc_debug_parser = false

Attributes

environment  [R] 
files  [RW] 
version  [R] 

Public Class methods

[Source]

     # File lib/puppet/parser/parser_support.rb, line 209
209:     def initialize(options = {})
210:         @astset = options[:astset] || ASTSet.new({}, {}, {})
211:         @environment = options[:environment]
212:         initvars()
213:     end

Public Instance methods

[Source]

      # File lib/puppet/parser/parser.rb, line 1818
1818:  def _reduce_none( val, _values, result )
1819:   result
1820:  end

Add context to a message; useful for error messages and such.

[Source]

    # File lib/puppet/parser/parser_support.rb, line 23
23:     def addcontext(message, obj = nil)
24:         obj ||= @lexer
25: 
26:         message += " on line %s" % obj.line
27:         if file = obj.file
28:             message += " in file %s" % file
29:         end
30: 
31:         return message
32:     end

Create an AST array out of all of the args

[Source]

    # File lib/puppet/parser/parser_support.rb, line 35
35:     def aryfy(*args)
36:         if args[0].instance_of?(AST::ASTArray)
37:             result = args.shift
38:             args.each { |arg|
39:                 result.push arg
40:             }
41:         else
42:             result = ast AST::ASTArray, :children => args
43:         end
44: 
45:         return result
46:     end

Create an AST object, and automatically add the file and line information if available.

[Source]

    # File lib/puppet/parser/parser_support.rb, line 50
50:     def ast(klass, hash = {})
51:         hash[:line] = @lexer.line unless hash.include?(:line)
52: 
53:         unless hash.include?(:file)
54:             if file = @lexer.file
55:                 hash[:file] = file
56:             end
57:         end
58: 
59:         return klass.new(hash)
60:     end

The fully qualifed name, with the full namespace.

[Source]

    # File lib/puppet/parser/parser_support.rb, line 63
63:     def classname(name)
64:         [@lexer.namespace, name].join("::").sub(/^::/, '')
65:     end

[Source]

    # File lib/puppet/parser/parser_support.rb, line 67
67:     def clear
68:         initvars
69:     end

Raise a Parse error.

[Source]

    # File lib/puppet/parser/parser_support.rb, line 72
72:     def error(message)
73:         if brace = @lexer.expected
74:             message += "; expected '%s'"
75:         end
76:         except = Puppet::ParseError.new(message)
77:         except.line = @lexer.line
78:         if @lexer.file
79:             except.file = @lexer.file
80:         end
81: 
82:         raise except
83:     end

[Source]

    # File lib/puppet/parser/parser_support.rb, line 85
85:     def file
86:         @lexer.file
87:     end

[Source]

     # File lib/puppet/parser/parser_support.rb, line 89
 89:     def file=(file)
 90:         unless FileTest.exists?(file)
 91:             unless file =~ /\.pp$/
 92:                 file = file + ".pp"
 93:             end
 94:             unless FileTest.exists?(file)
 95:                 raise Puppet::Error, "Could not find file %s" % file
 96:             end
 97:         end
 98:         if check_and_add_to_watched_files(file)
 99:             @lexer.file = file
100:         else
101:             raise Puppet::AlreadyImportedError.new("Import loop detected")
102:         end
103:     end

Find a class definition, relative to the current namespace.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 106
106:     def findclass(namespace, name)
107:         fqfind namespace, name, classes
108:     end

Find a component definition, relative to the current namespace.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 111
111:     def finddefine(namespace, name)
112:         fqfind namespace, name, definitions
113:     end

This is only used when nodes are looking up the code for their parent nodes.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 117
117:     def findnode(name)
118:         fqfind "", name, nodes
119:     end

The recursive method used to actually look these objects up.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 122
122:     def fqfind(namespace, name, table)
123:         namespace = namespace.downcase
124:         name = name.to_s.downcase
125: 
126:         # If our classname is fully qualified or we have no namespace,
127:         # just try directly for the class, and return either way.
128:         if name =~ /^::/ or namespace == ""
129:             classname = name.sub(/^::/, '')
130:             self.load(classname) unless table[classname]
131:             return table[classname]
132:         end
133: 
134:         # Else, build our namespace up piece by piece, checking
135:         # for the class in each namespace.
136:         ary = namespace.split("::")
137: 
138:         while ary.length > 0
139:             newname = (ary + [name]).join("::").sub(/^::/, '')
140:             if obj = table[newname] or (self.load(newname) and obj = table[newname])
141:                 return obj
142:             end
143: 
144:             # Delete the second to last object, which reduces our namespace by one.
145:             ary.pop
146:         end
147: 
148:         # If we've gotten to this point without finding it, see if the name
149:         # exists at the top namespace
150:         if obj = table[name] or (self.load(name) and obj = table[name])
151:             return obj
152:         end
153: 
154:         return nil
155:     end

Import our files.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 158
158:     def import(file)
159:         if Puppet[:ignoreimport]
160:             return AST::ASTArray.new(:children => [])
161:         end
162:         # use a path relative to the file doing the importing
163:         if @lexer.file
164:             dir = @lexer.file.sub(%r{[^/]+$},'').sub(/\/$/, '')
165:         else
166:             dir = "."
167:         end
168:         if dir == ""
169:             dir = "."
170:         end
171:         result = ast AST::ASTArray
172: 
173:         # We can't interpolate at this point since we don't have any
174:         # scopes set up. Warn the user if they use a variable reference
175:         pat = file
176:         if pat.index("$")
177:             Puppet.warning(
178:                "The import of #{pat} contains a variable reference;" +
179:                " variables are not interpolated for imports " +
180:                "in file #{@lexer.file} at line #{@lexer.line}"
181:             )
182:         end
183:         files = Puppet::Module::find_manifests(pat, :cwd => dir, :environment => @environment)
184:         if files.size == 0
185:             raise Puppet::ImportError.new("No file(s) found for import " +
186:                                           "of '#{pat}'")
187:         end
188: 
189:         files.collect { |file|
190:             parser = Puppet::Parser::Parser.new(:astset => @astset, :environment => @environment)
191:             parser.files = self.files
192:             Puppet.debug("importing '%s'" % file)
193: 
194:             unless file =~ /^#{File::SEPARATOR}/
195:                 file = File.join(dir, file)
196:             end
197:             begin
198:                 parser.file = file
199:             rescue Puppet::AlreadyImportedError
200:                 # This file has already been imported to just move on
201:                 next
202:             end
203: 
204:             # This will normally add code to the 'main' class.
205:             parser.parse
206:         }
207:     end

Initialize or reset all of our variables.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 216
216:     def initvars
217:         @lexer = Puppet::Parser::Lexer.new()
218:         @files = {}
219:         @loaded = []
220:     end

Try to load a class, since we could not find it.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 223
223:     def load(classname)
224:         return false if classname == ""
225:         filename = classname.gsub("::", File::SEPARATOR)
226: 
227:         # First try to load the top-level module
228:         mod = filename.scan(/^[\w-]+/).shift
229:         unless @loaded.include?(mod)
230:             @loaded << mod
231:             begin
232:                 import(mod)
233:                 Puppet.info "Autoloaded module %s" % mod
234:             rescue Puppet::ImportError => detail
235:                 # We couldn't load the module
236:             end
237:         end
238: 
239:         # We don't know whether we're looking for a class or definition, so we have
240:         # to test for both.
241:         return true if classes.include?(classname) || definitions.include?(classname)
242: 
243:         unless @loaded.include?(filename)
244:             @loaded << filename
245:             # Then the individual file
246:             begin
247:                 import(filename)
248:                 Puppet.info "Autoloaded file %s from module %s" % [filename, mod]
249:             rescue Puppet::ImportError => detail
250:                 # We couldn't load the file
251:             end
252:         end
253:         # We don't know whether we're looking for a class or definition, so we have
254:         # to test for both.
255:         return classes.include?(classname) || definitions.include?(classname)
256:     end

Split an fq name into a namespace and name

[Source]

     # File lib/puppet/parser/parser_support.rb, line 259
259:     def namesplit(fullname)
260:         ary = fullname.split("::")
261:         n = ary.pop || ""
262:         ns = ary.join("::")
263:         return ns, n
264:     end

Create a new class, or merge with an existing class.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 267
267:     def newclass(name, options = {})
268:         name = name.downcase
269: 
270:         if definitions.include?(name)
271:             raise Puppet::ParseError, "Cannot redefine class %s as a definition" % name
272:         end
273:         code = options[:code]
274:         parent = options[:parent]
275: 
276:         # If the class is already defined, then add code to it.
277:         if other = @astset.classes[name]
278:             # Make sure the parents match
279:             if parent and other.parentclass and (parent != other.parentclass)
280:                 error("Class %s is already defined at %s:%s; cannot redefine" % [name, other.file, other.line])
281:             end
282: 
283:             # This might be dangerous...
284:             if parent and ! other.parentclass
285:                 other.parentclass = parent
286:             end
287: 
288:             # This might just be an empty, stub class.
289:             if code
290:                 tmp = name
291:                 if tmp == ""
292:                     tmp = "main"
293:                 end
294:                 
295:                 Puppet.debug addcontext("Adding code to %s" % tmp)
296:                 # Else, add our code to it.
297:                 if other.code and code
298:                     other.code.children += code.children
299:                 else
300:                     other.code ||= code
301:                 end
302:             end
303:         else
304:             # Define it anew.
305:             # Note we're doing something somewhat weird here -- we're setting
306:             # the class's namespace to its fully qualified name.  This means
307:             # anything inside that class starts looking in that namespace first.
308:             args = {:namespace => name, :classname => name, :parser => self}
309:             args[:code] = code if code
310:             args[:parentclass] = parent if parent
311:             @astset.classes[name] = ast AST::HostClass, args
312:         end
313: 
314:         return @astset.classes[name]
315:     end

Create a new definition.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 318
318:     def newdefine(name, options = {})
319:         name = name.downcase
320:         if @astset.classes.include?(name)
321:             raise Puppet::ParseError, "Cannot redefine class %s as a definition" %
322:                 name
323:         end
324:         # Make sure our definition doesn't already exist
325:         if other = @astset.definitions[name]
326:             error("%s is already defined at %s:%s; cannot redefine" % [name, other.file, other.line])
327:         end
328: 
329:         ns, whatever = namesplit(name)
330:         args = {
331:             :namespace => ns,
332:             :arguments => options[:arguments],
333:             :code => options[:code],
334:             :parser => self,
335:             :classname => name
336:         }
337: 
338:         [:code, :arguments].each do |param|
339:             args[param] = options[param] if options[param]
340:         end
341: 
342:         @astset.definitions[name] = ast AST::Definition, args
343:     end

Create a new node. Nodes are special, because they‘re stored in a global table, not according to namespaces.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 347
347:     def newnode(names, options = {})
348:         names = [names] unless names.instance_of?(Array)
349:         names.collect do |name|
350:             name = name.to_s.downcase
351:             if other = @astset.nodes[name]
352:                 error("Node %s is already defined at %s:%s; cannot redefine" % [other.name, other.file, other.line])
353:             end
354:             name = name.to_s if name.is_a?(Symbol)
355:             args = {
356:                 :name => name,
357:                 :parser => self
358:             }
359:             if options[:code]
360:                 args[:code] = options[:code]
361:             end
362:             if options[:parent]
363:                 args[:parentclass] = options[:parent]
364:             end
365:             @astset.nodes[name] = ast(AST::Node, args)
366:             @astset.nodes[name].classname = name
367:             @astset.nodes[name]
368:         end
369:     end

[Source]

     # File lib/puppet/parser/parser_support.rb, line 371
371:     def on_error(token,value,stack)
372:         if token == 0 # denotes end of file
373:             value = 'end of file'
374:         else
375:             value = "'%s'" % value
376:         end
377:         error = "Syntax error at %s" % [value]
378: 
379:         if brace = @lexer.expected
380:             error += "; expected '%s'" % brace
381:         end
382: 
383:         except = Puppet::ParseError.new(error)
384:         except.line = @lexer.line
385:         if @lexer.file
386:             except.file = @lexer.file
387:         end
388: 
389:         raise except
390:     end

how should I do error handling here?

[Source]

     # File lib/puppet/parser/parser_support.rb, line 393
393:     def parse(string = nil)
394:         if string
395:             self.string = string
396:         end
397:         begin
398:             main = yyparse(@lexer,:scan)
399:         rescue Racc::ParseError => except
400:             error = Puppet::ParseError.new(except)
401:             error.line = @lexer.line
402:             error.file = @lexer.file
403:             error.set_backtrace except.backtrace
404:             raise error
405:         rescue Puppet::ParseError => except
406:             except.line ||= @lexer.line
407:             except.file ||= @lexer.file
408:             raise except
409:         rescue Puppet::Error => except
410:             # and this is a framework error
411:             except.line ||= @lexer.line
412:             except.file ||= @lexer.file
413:             raise except
414:         rescue Puppet::DevError => except
415:             except.line ||= @lexer.line
416:             except.file ||= @lexer.file
417:             raise except
418:         rescue => except
419:             error = Puppet::DevError.new(except.message)
420:             error.line = @lexer.line
421:             error.file = @lexer.file
422:             error.set_backtrace except.backtrace
423:             raise error
424:         end
425:         if main
426:             # Store the results as the top-level class.
427:             newclass("", :code => main)
428:         end
429:         @version = Time.now.to_i
430:         return @astset
431:     ensure
432:         @lexer.clear
433:     end

See if any of the files have changed.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 436
436:     def reparse?
437:         if file = @files.detect { |name, file| file.changed?  }
438:             return file[1].stamp
439:         else
440:             return false
441:         end
442:     end

[Source]

     # File lib/puppet/parser/parser_support.rb, line 444
444:     def string=(string)
445:         @lexer.string = string
446:     end

Add a new file to be checked when we‘re checking to see if we should be reparsed. This is basically only used by the TemplateWrapper to let the parser know about templates that should be parsed.

[Source]

     # File lib/puppet/parser/parser_support.rb, line 451
451:     def watch_file(filename)
452:             check_and_add_to_watched_files(filename)
453:     end

[Validate]