require 'puppet/util/autoload' require 'puppet/parser/scope' require 'monitor' # A module for managing parser functions. Each specified function # is added to a central module that then gets included into the Scope # class. module Puppet::Parser::Functions Environment = Puppet::Node::Environment class << self include Puppet::Util end # This is used by tests def self.reset @functions = Hash.new { |h,k| h[k] = {} }.extend(MonitorMixin) @modules = Hash.new.extend(MonitorMixin) # Runs a newfunction to create a function for each of the log levels Puppet::Util::Log.levels.each do |level| newfunction(level, :doc => "Log a message on the server at level #{level.to_s}.") do |vals| send(level, vals.join(" ")) end end end def self.autoloader @autoloader ||= Puppet::Util::Autoload.new( self, "puppet/parser/functions", :wrap => false ) end def self.environment_module(env = nil) if env and ! env.is_a?(Puppet::Node::Environment) env = Puppet::Node::Environment.new(env) end @modules.synchronize { @modules[ (env || Environment.current || Environment.root).name ] ||= Module.new } end # Create a new function type. def self.newfunction(name, options = {}, &block) name = name.intern Puppet.warning "Overwriting previous definition for function #{name}" if get_function(name) ftype = options[:type] || :statement unless ftype == :statement or ftype == :rvalue raise Puppet::DevError, "Invalid statement type #{ftype.inspect}" end # the block must be installed as a method because it may use "return", # which is not allowed from procs. real_fname = "real_function_#{name}" environment_module.send(:define_method, real_fname, &block) fname = "function_#{name}" environment_module.send(:define_method, fname) do |*args| if args[0].is_a? Array self.send(real_fname, args[0]) else raise ArgumentError, "custom functions must be called with a single array that contains the arguments. For example, function_example([1]) instead of function_example(1)" end end # Someday we'll support specifying an arity, but for now, nope #functions[name] = {:arity => arity, :type => ftype} func = {:type => ftype, :name => fname} func[:doc] = options[:doc] if options[:doc] add_function(name, func) func end # Determine if a given name is a function def self.function(name) name = name.intern func = nil @functions.synchronize do unless func = get_function(name) autoloader.load(name, Environment.current) func = get_function(name) end end if func func[:name] else false end end def self.functiondocs autoloader.loadall ret = "" merged_functions.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, hash| ret += "#{name}\n#{"-" * name.to_s.length}\n" if hash[:doc] ret += Puppet::Util::Docs.scrub(hash[:doc]) else ret += "Undocumented.\n" end ret += "\n\n- *Type*: #{hash[:type]}\n\n" end ret end # Determine if a given function returns a value or not. def self.rvalue?(name) func = get_function(name) func ? func[:type] == :rvalue : false end class << self private def merged_functions @functions.synchronize { @functions[Environment.root].merge(@functions[Environment.current]) } end def get_function(name) name = name.intern merged_functions[name] end def add_function(name, func) name = name.intern @functions.synchronize { @functions[Environment.current][name] = func } end end reset # initialize the class instance variables end