summaryrefslogtreecommitdiffstats
path: root/lib/puppet
diff options
context:
space:
mode:
authorluke <luke@980ebf18-57e1-0310-9a29-db15c13687c0>2007-08-04 20:00:19 +0000
committerluke <luke@980ebf18-57e1-0310-9a29-db15c13687c0>2007-08-04 20:00:19 +0000
commita8bf96af7649eedb294b0525c877cd8ba565d6e2 (patch)
tree81b8d5c1e963c3469807e0af9a52830cdbf6c6e3 /lib/puppet
parent40e4d6fa02e801a26c2880840befa32718e55452 (diff)
downloadpuppet-a8bf96af7649eedb294b0525c877cd8ba565d6e2.tar.gz
puppet-a8bf96af7649eedb294b0525c877cd8ba565d6e2.tar.xz
puppet-a8bf96af7649eedb294b0525c877cd8ba565d6e2.zip
Adding a file that should have been in a commit from yesterda
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@2744 980ebf18-57e1-0310-9a29-db15c13687c0
Diffstat (limited to 'lib/puppet')
-rw-r--r--lib/puppet/parser/parser_support.rb447
1 files changed, 447 insertions, 0 deletions
diff --git a/lib/puppet/parser/parser_support.rb b/lib/puppet/parser/parser_support.rb
new file mode 100644
index 000000000..728f75a69
--- /dev/null
+++ b/lib/puppet/parser/parser_support.rb
@@ -0,0 +1,447 @@
+class Puppet::Parser::Parser
+ require 'puppet/parser/functions'
+
+ ASTSet = Struct.new(:classes, :definitions, :nodes)
+
+ # Define an accessor method for each table. We hide the existence of
+ # the struct.
+ [:classes, :definitions, :nodes].each do |name|
+ define_method(name) do
+ @astset.send(name)
+ end
+ end
+
+ AST = Puppet::Parser::AST
+
+ attr_reader :file, :interp
+ attr_accessor :files
+
+
+ # Add context to a message; useful for error messages and such.
+ def addcontext(message, obj = nil)
+ obj ||= @lexer
+
+ message += " on line %s" % obj.line
+ if file = obj.file
+ message += " in file %s" % file
+ end
+
+ return message
+ end
+
+ # Create an AST array out of all of the args
+ def aryfy(*args)
+ if args[0].instance_of?(AST::ASTArray)
+ result = args.shift
+ args.each { |arg|
+ result.push arg
+ }
+ else
+ result = ast AST::ASTArray, :children => args
+ end
+
+ return result
+ end
+
+ # Create an AST object, and automatically add the file and line information if
+ # available.
+ def ast(klass, hash = nil)
+ hash ||= {}
+ unless hash.include?(:line)
+ hash[:line] = @lexer.line
+ end
+
+ unless hash.include?(:file)
+ if file = @lexer.file
+ hash[:file] = file
+ end
+ end
+
+ return klass.new(hash)
+ end
+
+ # The fully qualifed name, with the full namespace.
+ def classname(name)
+ [@lexer.namespace, name].join("::").sub(/^::/, '')
+ end
+
+ def clear
+ initvars
+ end
+
+ # Raise a Parse error.
+ def error(message)
+ if brace = @lexer.expected
+ message += "; expected '%s'"
+ end
+ except = Puppet::ParseError.new(message)
+ except.line = @lexer.line
+ if @lexer.file
+ except.file = @lexer.file
+ end
+
+ raise except
+ end
+
+ def file=(file)
+ unless FileTest.exists?(file)
+ unless file =~ /\.pp$/
+ file = file + ".pp"
+ end
+ unless FileTest.exists?(file)
+ raise Puppet::Error, "Could not find file %s" % file
+ end
+ end
+ if @files.detect { |f| f.file == file }
+ raise Puppet::AlreadyImportedError.new("Import loop detected")
+ else
+ @files << Puppet::Util::LoadedFile.new(file)
+ @lexer.file = file
+ end
+ end
+
+ # Find a class definition, relative to the current namespace.
+ def findclass(namespace, name)
+ fqfind namespace, name, classes
+ end
+
+ # Find a component definition, relative to the current namespace.
+ def finddefine(namespace, name)
+ fqfind namespace, name, definitions
+ end
+
+ # This is only used when nodes are looking up the code for their
+ # parent nodes.
+ def findnode(name)
+ fqfind "", name, nodes
+ end
+
+ # The recursive method used to actually look these objects up.
+ def fqfind(namespace, name, table)
+ namespace = namespace.downcase
+ name = name.downcase
+ if name =~ /^::/ or namespace == ""
+ classname = name.sub(/^::/, '')
+ unless table[classname]
+ self.load(classname)
+ end
+ return table[classname]
+ end
+ ary = namespace.split("::")
+
+ while ary.length > 0
+ newname = (ary + [name]).join("::").sub(/^::/, '')
+ if obj = table[newname] or (self.load(newname) and obj = table[newname])
+ return obj
+ end
+
+ # Delete the second to last object, which reduces our namespace by one.
+ ary.pop
+ end
+
+ # If we've gotten to this point without finding it, see if the name
+ # exists at the top namespace
+ if obj = table[name] or (self.load(name) and obj = table[name])
+ return obj
+ end
+
+ return nil
+ end
+
+ # Import our files.
+ def import(file)
+ if Puppet[:ignoreimport]
+ return AST::ASTArray.new(:children => [])
+ end
+ # use a path relative to the file doing the importing
+ if @lexer.file
+ dir = @lexer.file.sub(%r{[^/]+$},'').sub(/\/$/, '')
+ else
+ dir = "."
+ end
+ if dir == ""
+ dir = "."
+ end
+ result = ast AST::ASTArray
+
+ # We can't interpolate at this point since we don't have any
+ # scopes set up. Warn the user if they use a variable reference
+ pat = file
+ if pat.index("$")
+ Puppet.warning(
+ "The import of #{pat} contains a variable reference;" +
+ " variables are not interpolated for imports " +
+ "in file #{@lexer.file} at line #{@lexer.line}"
+ )
+ end
+ files = Puppet::Module::find_manifests(pat, dir)
+ if files.size == 0
+ raise Puppet::ImportError.new("No file(s) found for import " +
+ "of '#{pat}'")
+ end
+
+ files.collect { |file|
+ parser = Puppet::Parser::Parser.new(@astset)
+ parser.files = self.files
+ Puppet.debug("importing '%s'" % file)
+
+ unless file =~ /^#{File::SEPARATOR}/
+ file = File.join(dir, file)
+ end
+ begin
+ parser.file = file
+ rescue Puppet::AlreadyImportedError
+ # This file has already been imported to just move on
+ next
+ end
+
+ # This will normally add code to the 'main' class.
+ parser.parse
+ }
+ end
+
+ def initialize(astset = nil)
+ initvars()
+ if astset
+ @astset = astset
+ end
+ end
+
+ # Initialize or reset all of our variables.
+ def initvars
+ @lexer = Puppet::Parser::Lexer.new()
+ @files = []
+ @loaded = []
+
+ # This is where we store our classes and definitions and nodes.
+ # Clear each hash, just to help the GC a bit.
+ if defined?(@astset)
+ [:classes, :definitions, :nodes].each do |name|
+ @astset.send(name).clear
+ end
+ end
+ @astset = ASTSet.new({}, {}, {})
+ end
+
+ # Try to load a class, since we could not find it.
+ def load(classname)
+ return false if classname == ""
+ filename = classname.gsub("::", File::SEPARATOR)
+
+ loaded = false
+ # First try to load the top-level module
+ mod = filename.scan(/^[\w-]+/).shift
+ unless @loaded.include?(mod)
+ @loaded << mod
+ begin
+ import(mod)
+ Puppet.info "Autoloaded module %s" % mod
+ loaded = true
+ rescue Puppet::ImportError => detail
+ # We couldn't load the module
+ end
+ end
+
+ unless filename == mod and ! @loaded.include?(mod)
+ @loaded << mod
+ # Then the individual file
+ begin
+ import(filename)
+ Puppet.info "Autoloaded file %s from module %s" % [filename, mod]
+ loaded = true
+ rescue Puppet::ImportError => detail
+ # We couldn't load the file
+ end
+ end
+ return loaded
+ end
+
+ # Split an fq name into a namespace and name
+ def namesplit(fullname)
+ ary = fullname.split("::")
+ n = ary.pop || ""
+ ns = ary.join("::")
+ return ns, n
+ end
+
+ # Create a new class, or merge with an existing class.
+ def newclass(name, options = {})
+ name = name.downcase
+
+ if definitions.include?(name)
+ raise Puppet::ParseError, "Cannot redefine class %s as a definition" % name
+ end
+ code = options[:code]
+ parent = options[:parent]
+
+ # If the class is already defined, then add code to it.
+ if other = @astset.classes[name]
+ # Make sure the parents match
+ if parent and other.parentclass and (parent != other.parentclass)
+ error("Class %s is already defined at %s:%s; cannot redefine" % [name, other.file, other.line])
+ end
+
+ # This might be dangerous...
+ if parent and ! other.parentclass
+ other.parentclass = parent
+ end
+
+ # This might just be an empty, stub class.
+ if code
+ tmp = name
+ if tmp == ""
+ tmp = "main"
+ end
+
+ Puppet.debug addcontext("Adding code to %s" % tmp)
+ # Else, add our code to it.
+ if other.code and code
+ other.code.children += code.children
+ else
+ other.code ||= code
+ end
+ end
+ else
+ # Define it anew.
+ # Note we're doing something somewhat weird here -- we're setting
+ # the class's namespace to its fully qualified name. This means
+ # anything inside that class starts looking in that namespace first.
+ args = {:namespace => name, :classname => name, :parser => self}
+ args[:code] = code if code
+ args[:parentclass] = parent if parent
+ @astset.classes[name] = ast AST::HostClass, args
+ end
+
+ return @astset.classes[name]
+ end
+
+ # Create a new definition.
+ def newdefine(name, options = {})
+ name = name.downcase
+ if @astset.classes.include?(name)
+ raise Puppet::ParseError, "Cannot redefine class %s as a definition" %
+ name
+ end
+ # Make sure our definition doesn't already exist
+ if other = @astset.definitions[name]
+ error("%s is already defined at %s:%s; cannot redefine" % [name, other.file, other.line])
+ end
+
+ ns, whatever = namesplit(name)
+ args = {
+ :namespace => ns,
+ :arguments => options[:arguments],
+ :code => options[:code],
+ :parser => self,
+ :classname => name
+ }
+
+ [:code, :arguments].each do |param|
+ args[param] = options[param] if options[param]
+ end
+
+ @astset.definitions[name] = ast AST::Component, args
+ end
+
+ # Create a new node. Nodes are special, because they're stored in a global
+ # table, not according to namespaces.
+ def newnode(names, options = {})
+ names = [names] unless names.instance_of?(Array)
+ names.collect do |name|
+ name = name.to_s.downcase
+ if other = @astset.nodes[name]
+ error("Node %s is already defined at %s:%s; cannot redefine" % [other.name, other.file, other.line])
+ end
+ name = name.to_s if name.is_a?(Symbol)
+ args = {
+ :name => name,
+ :parser => self
+ }
+ if options[:code]
+ args[:code] = options[:code]
+ end
+ if options[:parent]
+ args[:parentclass] = options[:parent]
+ end
+ @astset.nodes[name] = ast(AST::Node, args)
+ @astset.nodes[name].classname = name
+ @astset.nodes[name]
+ end
+ end
+
+ def on_error(token,value,stack)
+ #on '%s' at '%s' in\n'%s'" % [token,value,stack]
+ #error = "line %s: parse error after '%s'" %
+ # [@lexer.line,@lexer.last]
+ error = "Syntax error at '%s'" % [value]
+
+ if brace = @lexer.expected
+ error += "; expected '%s'" % brace
+ end
+
+ except = Puppet::ParseError.new(error)
+ except.line = @lexer.line
+ if @lexer.file
+ except.file = @lexer.file
+ end
+
+ raise except
+ end
+
+ # how should I do error handling here?
+ def parse(string = nil)
+ if string
+ self.string = string
+ end
+ begin
+ main = yyparse(@lexer,:scan)
+ rescue Racc::ParseError => except
+ error = Puppet::ParseError.new(except)
+ error.line = @lexer.line
+ error.file = @lexer.file
+ error.set_backtrace except.backtrace
+ raise error
+ rescue Puppet::ParseError => except
+ except.line ||= @lexer.line
+ except.file ||= @lexer.file
+ raise except
+ rescue Puppet::Error => except
+ # and this is a framework error
+ except.line ||= @lexer.line
+ except.file ||= @lexer.file
+ raise except
+ rescue Puppet::DevError => except
+ except.line ||= @lexer.line
+ except.file ||= @lexer.file
+ raise except
+ rescue => except
+ error = Puppet::DevError.new(except.message)
+ error.line = @lexer.line
+ error.file = @lexer.file
+ error.set_backtrace except.backtrace
+ raise error
+ end
+ if main
+ # Store the results as the top-level class.
+ newclass("", :code => main)
+ end
+ return @astset
+ ensure
+ @lexer.clear
+ end
+
+ # See if any of the files have changed.
+ def reparse?
+ if file = @files.detect { |file| file.changed? }
+ return file.stamp
+ else
+ return false
+ end
+ end
+
+ def string=(string)
+ @lexer.string = string
+ end
+end
+
+# $Id$