# Just quick mess-around to see what a DSL would look like.
# 
# This is what the executable could look like:
##!/usr/bin/ruby
#
#require 'puppet'
#require 'puppet/dsl'
#
#Puppet::DSL.import(ARGV[0])
#
#bucket = Puppet::TransBucket.new
#bucket.type = "top"
#bucket.keyword = "class"
#
#Puppet::DSL.find_all do |name, sub|
#    sub.included
#end.each do |name, sub|
#    bucket.push sub.export
#end
#
#puts bucket.to_manifest
#
# And here's what an example config could look like:
#

##!/usr/bin/ruby
#
#
# require 'puppet'
# require 'puppet/dsl'
# 
# include Puppet::DSL
# init()
# 
# aspect :webserver do
#     file "/tmp/testone", :content => "yaytest"
# 
#     exec "testing", :command => "/bin/echo this is a test"
# end
# 
# aspect :other, :inherits => :webserver do
#     file "/tmp/testone", :mode => "755"
# end
# 
# acquire :other
# 
# apply

module Puppet
    # Provide the actual commands for acting like a language.
    module DSL
        def aspect(name, options = {}, &block)
            Puppet::Aspect.new(name, options, &block)
        end

        def acquire(*names)
            names.each do |name|
                if aspect = Puppet::Aspect[name]
                    unless aspect.evaluated?
                        aspect.evaluate
                    end
                else
                    raise "Could not find aspect %s" % name
                end
            end
        end

        def apply
            bucket = export()
            objects = bucket.to_type
            master = Puppet::Network::Client.master.new :Master => "whatever"
            master.objects = objects

            master.apply
        end

        def export
            objects = Puppet::Aspect.collect do |name, aspect|
                if aspect.evaluated?
                    aspect.export
                end
            end.reject { |a| a.nil? }.flatten.collect do |obj|
                obj.to_trans
            end
            bucket = Puppet::TransBucket.new(objects)
            bucket.name = "top"

            return bucket
        end

        def init
            unless Process.uid == 0
                Puppet[:confdir] = File.expand_path("~/.puppet")
                Puppet[:vardir] = File.expand_path("~/.puppet/var")
            end
            Puppet[:user] = Process.uid
            Puppet[:group] = Process.gid
            Puppet::Util::Log.newdestination(:console)
            Puppet::Util::Log.level = :info
        end

        private
    end

    class Aspect
        Resource = Puppet::Parser::Resource

        include Puppet::Util
        include Puppet::DSL
        extend Puppet::Util
        extend Enumerable
        attr_accessor :parent, :name, :evaluated

        @aspects = {}

        # For now, just do some hackery so resources work
        @@interp = Puppet::Parser::Interpreter.new :Code => ""
        @@scope = Puppet::Parser::Scope.new(:interp => @@interp)

        @@objects = Hash.new do |hash, key|
            hash[key] = {}
        end

        # Create an instance method for every type
        Puppet::Type.loadall
        Puppet::Type.eachtype do |type|
            define_method(type.name) do |*args|
                newresource(type, *args)
            end
        end

        def self.[]=(name, aspect)
            name = symbolize(name)
            @aspects[name] = aspect
        end

        def self.[](name)
            name = symbolize(name)

            # Make sure there's always a main.  This can get deleted in testing.
            if name == :main and ! @aspects[name]
                new(:main) {}
            end
            @aspects[name]
        end

        def self.clear
            @aspects.clear
            @@objects.clear
        end

        def self.delete(name)
            name = symbolize(name)
            if @aspects.has_key?(name)
                @aspects.delete(name)
            end
        end

        def self.each
            @aspects.each do |name, a|
                yield name, a
            end
        end

        def child_of?(aspect)
            unless aspect.is_a?(self.class)
                obj = self.class[aspect]
                unless obj
                    raise "Could not find aspect %s" % aspect
                end
                aspect = obj
            end
            if self.parent
                if self.parent == aspect
                    return true
                elsif self.parent.child_of?(aspect)
                    return true
                else
                    return false
                end
            else
                return false
            end
        end

        def evaluate
            if self.parent and ! self.parent.evaluated?
                self.parent.evaluate
            end

            unless evaluated?
                if defined? @block
                    instance_eval(&@block)
                end
                @evaluated = true
            end
        end

        def evaluated?
            if self.evaluated
                true
            else
                false
            end
        end

        def export
            @resources.dup
        end

        def initialize(name, options = {}, &block)
            name = symbolize(name)
            @name = name
            if block
                @block = block
            end
            if pname = options[:inherits]
                if pname.is_a?(self.class)
                    @parent = pname
                elsif parent = self.class[pname]
                    @parent = parent
                else
                    raise "Could not find parent aspect %s" % pname
                end
            end

            @resources = []

            self.class[name] = self
        end

        def newresource(type, name, params = {})
            if self.is_a?(Puppet::Aspect)
                source = self
            else
                source = Puppet::Aspect[:main]
            end
            unless obj = @@objects[type][name]
                obj = Resource.new :title => name, :type => type.name,
                    :source => source, :scope => @@scope
                @@objects[type][name] = obj

                @resources << obj
            end

            params.each do |name, value|
                param = Resource::Param.new(
                    :name => name,
                    :value => value,
                    :source => source
                )

                obj.set(param)
            end

            obj
        end

        def type
            self.name
        end
    end
end

@aspects = {}

class Puppet::DisabledDSL
    @@subs = {}
    @name = :DSLClass
    class << self
        include Enumerable
        attr_accessor :included, :name, :objects

        def each
            @@subs.each do |name, sub|
                yield name, sub
            end
        end

        def export
            bucket = nil
            if superclass() != Puppet::DSL
                bucket = superclass.export
            else
                bucket = Puppet::TransBucket.new
                bucket.keyword = "class"
                bucket.type = self.name
            end

            @objects.each do |type, ary|
                ary.each do |name, obj|
                    if pobj = bucket.find { |sobj| obj.name == sobj.name &&  obj.type == sobj.type }
                        obj.each do |param, value|
                            pobj[param] = value
                        end
                    else
                        bucket.push obj
                    end
                end
            end

            return bucket
        end

        def include(name)
            if ary = @@subs.find { |n, s| n == name }
                ary[1].included = true
            else
                raise "Could not find class %s" % name
            end
        end

        def inherited(sub)
            name = sub.to_s.downcase.gsub(/.+::/, '').intern
            @@subs[name] = sub
            sub.name = name
            sub.initvars

            sub
        end

        def initvars
            #if superclass() == Puppet::DSL
                @objects = {}
            #else
            #    @objects = superclass.objects
            #end
        end


        def import(file)
            text = File.read(file)
            # If they don't specify a parent class, then specify one
            # for them.
            text.gsub!(/^class \S+\s*$/) do |match|
                "#{match} < Puppet::DSL"
            end
            eval(text, binding)
        end

        def method_missing(method, *args)
            if klass = Puppet::Type.type(method)
                method = method.intern if method.is_a? String
                @objects[method] ||= {}

                names = args.shift
                hash = args.shift
                names = [names] unless names.is_a? Array
                names.each do |name|
                    unless obj = @objects[method][name]
                        obj = Puppet::TransObject.new(name, method)
                        @objects[method][name] = obj
                    end

                    hash.each do |param, value|
                        if obj[param]
                            raise "Cannot override %s in %s[%s]" %
                                [param, method, name]
                        else
                            obj[param] = value
                        end
                    end
                end
            else
                raise "No type %s" % method
            end
        end
    end
end

# $Id: dsl.rb 2259 2007-03-06 19:03:05Z luke $


syntax highlighted by Code2HTML, v. 0.9.1