#
# people use all sort of hacks to build dsl's in ruby, everything from
# class_evals to module_evals and a billion method_missing hacks.  i wanted to
# present here what i believe to be the simplest and most robust method for
# creating DSLs, it's suprisingly simply and offers all the advantages and
# none of the disadvantages other methods might have.  the dsl we'll use as an
# example is one which allows running commands against the bash shell with
# dsl methods for each shell command - although it's *only* for example our
# dsl will have the interesting property that, unlike using ruby's built-in
# system or backticks, our dsl will run commands against the same shell which
# allows a more natural interaction with shell state persisting between calls.
#

# obviously we'll namespace the whole kit and kaboodle
#
  module Bash

  # our shell class is s pretty naive one which simply talks to /bin/bash via
  # pipes.  we take a little care to make sure we're shutdown nicely when
  # finished and/or at exit, and provide a simple mechanism for reading back
  # the stdout from a command run against the bash instance - note that we
  # have to ask bash to delim the output for us (____start... and ____stop...)
  # in the output stream since we are going to re-use the same shell for each
  # command and, otherwise, we'd have to way to distinquish the output of each
  # successive command.  the only other important thing to note is that we use
  # a dsl to evaluate any given block - see DSL class below for gory details.
  #
    class Shell
      def initialize path = '/bin/bash', &block
        @shell = IO.popen path, 'r+'
        @dsl = DSL.new self
        @commandno = -1
        @at_exit = lambda{ @at_exit = lambda{}; @shell.close }
        at_exit{ close }
        if block
          begin
            evaluate &block
          ensure
            close
          end
        end
      end

      def close
        @at_exit.call
      end

      def evaluate &block
        @dsl.send('__instance_eval__', &block)
      end

      def execute command
        n = ( @commandno += 1 )
        start = "____start_#{ n }___"
        stop = "____stop_#{ n }___"
        @shell.puts "echo #{ start }"
        @shell.puts command
        @shell.puts "echo #{ stop }"
        @shell.flush
        stdout = []
        while(( line = @shell.gets ))
          case line.chomp
            when Regexp.escape(start)
              next
            when Regexp.escape(stop)
              break
            else
              stdout << line
          end
        end
        stdout.join
      end
    end

    class DSL
    # of course this is the interesting bit, we do two things here: first we
    # alias all the instance methods for later use as underscore methods, in
    # otherwords
    #
    #   is_a?    => __is_a__?
    #   class    => __class__
    #   tainted? => __tainted__?
    #
    # etc.
    #
    # then we nuke just about all the methods, leaving behind only __send__,
    # __id__, etc.  the final result is a blankslate object which actually has
    # 100% of the old methods, only as __burried_away_ones__.
    #
    # finally our initialize method wraps up our shell instance and holds as
    # an ivar for later use
    #
      instance_methods.each do |m|
        next if m[%r'^__']
        src = m.to_s
        name, suffix = src.split %r/([?!])/
        dst = "__#{ name }__#{ suffix }"
        alias_method dst, src
      end

      def initialize shell
        @shell = shell
      end

    # this is the meat - our dsl - notice how these, and *only* these methods
    # will be available to the dsl since all other methods were nuked above.
    # also notice that we are free to have methods on the shell with a more
    # complicated interface, less error checking, etc, since the dsl namespace
    # is *not* the same as the shell class namespace.  also note that these
    # methods are all really dumb to implement this way, but it's only for the
    # purpose of the example - any sane person would use FileUtils or
    # similar.
    #
      def echo *strings
        command = [
          "cat <<'hdoc'",
          strings.join("\n"),
          "hdoc",
        ].join("\n")
        puts @shell.execute(command)
      end

      def cd dir = '.'
        command = "cd #{ dir }"
        @shell.execute(command)
      end

      def pwd
        puts @shell.execute("pwd")
      end

      def set kvs = {}
        kvs.each{|k,v| @shell.execute "#{ k }=#{ v.to_s.inspect }"}
      end

      def get k
        puts @shell.execute("echo ${#{ k }}")
      end
    end
  end

# we setup one top level method to bootstrap us into shell space, it just
# delegates to the contructor
#
  def bash(*a, &b) Bash::Shell.new(*a, &b) end


# finally we can use the shell object, passing a block to force evaluation
# inside the dsl
#
  bash {
    echo 'and the bunnymen.' # prints 'and the bunnymen'

    cd '/'

    pwd                      # prints '/' 

    set :x => 42

    get :x                   # prints '42'
  }