commit 4761cd134c36a40c0240698f7440ba4b693c1b32
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Sat Feb 26 14:35:04 2011 +0100

    [evol] allow splitting the API interface declaration over multiple files/directories, split MapMaker interface, and added cross-nodes storage during the interface life (as we can't use closures across files)

diff --git a/bin/mapmaker b/bin/mapmaker
index fe67e5a..f2b11ce 100755
--- a/bin/mapmaker
+++ b/bin/mapmaker
@@ -53,75 +53,7 @@ module CyborgHood
       def setup
         super
 
-        define_interface "0.1~" do
-          node "DNS" do
-            dns = Services::DNS::System.new
-            on_request do |request|
-              request.reply.results = {
-                :type => dns.type
-              }
-            end
-            node "check_config" do
-              on_request do |request|
-                request.reply.results = dns.check_config
-              end
-            end
-            node "Zones" do
-              zone_list = Proc.new{dns.zones}
-              node zone_list do
-                zone = Services::DNS::Zone.new(node_name)
-                on_request do |request|
-                  request.reply.results = {
-                    :master  => zone.master?,
-                    :signed  => zone.signed?,
-                    :serial  => zone.serial_in_dns
-                  }
-                  if zone.master?
-                    request.reply.results.merge!({
-                      :serial_in_zone_file         => zone.serial_in_zone_file,
-                      :serial_in_signed_zone_file  => zone.serial_in_signed_zone_file
-                    })
-                  end
-                end
-                node "content" do
-                  on_request do |request|
-                    request.reply.results = {:content => zone.content}
-                  end
-                end
-                node "content=" do
-                  on_request do |request|
-                    content = request.args.shift
-                    if content.empty?
-                      request.errors << "Zone content missing"
-                      return
-                    end
-
-                    zone.content = content
-                    if zone.changed?
-                      check_result = zone.check
-                      if check_result[:ok]
-                        request.reply.warnings = check_result[:warnings]
-                        # zone signer automatically handles serial bump
-                        if check_result[:serial] > zone.serial or zone.signed?
-                          zone.save
-                          zone.activate
-                        else
-                          request.reply.errors << _("Zone serial is not superior to current serial.")
-                        end
-                      else
-                        request.reply.errors = check_result[:errors]
-                        zone.cancel_changes
-                      end
-                    else
-                      request.reply.warnings << _("Zone is unmodified (same content)")
-                      zone.cancel_changes
-                    end
-                  end
-                end
-              end
-            end
-          end
-        end # interface
+        define_interface "0.1~"
       end
     end # class MapMaker
   end
diff --git a/lib/cyborghood-mapmaker/interface/0_base.rb b/lib/cyborghood-mapmaker/interface/0_base.rb
new file mode 100644
index 0000000..206ff7c
--- /dev/null
+++ b/lib/cyborghood-mapmaker/interface/0_base.rb
@@ -0,0 +1 @@
+store.dns = Services::DNS::System.new
diff --git a/lib/cyborghood-mapmaker/interface/_zone/0_base.rb b/lib/cyborghood-mapmaker/interface/_zone/0_base.rb
new file mode 100644
index 0000000..e208b73
--- /dev/null
+++ b/lib/cyborghood-mapmaker/interface/_zone/0_base.rb
@@ -0,0 +1,52 @@
+zone = Services::DNS::Zone.new(node_name)
+
+on_request do |request|
+  request.reply.results = {
+    :master  => zone.master?,
+    :signed  => zone.signed?,
+    :serial  => zone.serial_in_dns
+  }
+  if zone.master?
+    request.reply.results.merge!({
+                                   :serial_in_zone_file         => zone.serial_in_zone_file,
+                                   :serial_in_signed_zone_file  => zone.serial_in_signed_zone_file
+                                 })
+  end
+end
+
+node "content" do
+  on_request do |request|
+    request.reply.results = {:content => zone.content}
+  end
+end
+
+node "content=" do
+  on_request do |request|
+    content = request.args.shift
+    if content.empty?
+      request.errors << "Zone content missing"
+      return
+    end
+
+    zone.content = content
+    if zone.changed?
+      check_result = zone.check
+      if check_result[:ok]
+        request.reply.warnings = check_result[:warnings]
+        # zone signer automatically handles serial bump
+        if check_result[:serial] > zone.serial or zone.signed?
+          zone.save
+          zone.activate
+        else
+          request.reply.errors << _("Zone serial is not superior to current serial.")
+        end
+      else
+        request.reply.errors = check_result[:errors]
+        zone.cancel_changes
+      end
+    else
+      request.reply.warnings << _("Zone is unmodified (same content)")
+                      zone.cancel_changes
+    end
+  end
+end
diff --git a/lib/cyborghood-mapmaker/interface/dns.rb b/lib/cyborghood-mapmaker/interface/dns.rb
new file mode 100644
index 0000000..b827bd6
--- /dev/null
+++ b/lib/cyborghood-mapmaker/interface/dns.rb
@@ -0,0 +1,20 @@
+node "DNS" do
+  on_request do |request|
+    request.reply.results = {
+      :type => store.dns.type
+    }
+  end
+
+  node "check_config" do
+    on_request do |request|
+      request.reply.results = dns.check_config
+    end
+  end
+
+  node "Zones" do
+    zone_list = Proc.new{ store.dns.zones }
+
+    node zone_list, '_zone' do
+    end
+  end
+end
diff --git a/lib/cyborghood/cyborg/botnet/interface.rb b/lib/cyborghood/cyborg/botnet/interface.rb
index c8609e3..124ba8e 100644
--- a/lib/cyborghood/cyborg/botnet/interface.rb
+++ b/lib/cyborghood/cyborg/botnet/interface.rb
@@ -23,28 +23,46 @@ require 'ostruct'
 module CyborgHood
   module DSL
     class ServerApiNode < BaseDSL
-      attr_reader :bot, :node_name
+      attr_reader :bot, :node_name, :parent_node, :store
 
       # needed for testing node existence
       reveal :nil?
+      reveal :respond_to?
 
-      def initialize(bot, parent_node = nil, &block)
+      def initialize(bot, parent_node = nil, ldir = nil, &block)
         @bot = bot
         @parent_node = parent_node
+        @ldir = ldir
+
         # don't call super because we need defered loading
-        @blocks = [block]
+
+        @blocks = []
+        add_behavior(&block)
+
+        @ldir_loaded = false
 
         cleanup
       end
 
       def add_behavior(&block)
-        @blocks << block
+        if block_given?
+          @blocks << block
+        else
+          return if @ldir_loaded
+
+          Dir.glob(File.join(self.lpath, '*.rb')) do |file|
+            logger.debug "Interface: loading file '#{file}'"
+            @blocks << File.read(file)
+          end
+
+          @ldir_loaded = true
+        end
       end
 
       # string, array (useful for aliases), or regex
       # TODO: name validation
-      def node(match, &block)
-        child_node = self.class.new(@bot, self, &block)
+      def node(match, ldir = nil, &block)
+        child_node = self.class.new(@bot, self, ldir,  &block)
         if match.is_a? Array
           match.each{|n| @nodes[n] = child_node}
         else
@@ -78,16 +96,29 @@ module CyborgHood
         @parent_node.nil?
       end
 
+      def base_lpath
+        File.join(Config::LIB_DIR, 'cyborghood-' + @bot.name.downcase, 'interface')
+      end
+
+      def lpath
+        parent_lpath = @parent_node.nil? ? self.base_lpath : @parent_node.lpath
+        @ldir.nil? ? parent_lpath : File.join(parent_lpath, @ldir)
+      end
+
       def load(node_element = '')
         cleanup
         @node_name = node_element
         @blocks.each do |bl|
-          instance_eval &bl
+          if bl.is_a? String
+            instance_eval bl
+          else
+            instance_eval &bl
+          end
         end
       end
 
       def find_node(session, node_path)
-        # it is a string argument when interface root is called, but a list of node elemnts later
+        # node_path is a string argument when interface root node is called, but is a list of node elements later on
         if root?
           logger.debug "[Server API] Looking for node '#{node_path}'"
           node_path = node_path.split("/")
@@ -173,6 +204,13 @@ module CyborgHood
       def cleanup
         @nodes = {}
         @request_cb = nil
+
+        # data memorized during walk in the node tree
+        if root?
+          @store = OpenStruct.new
+        else
+          @store = @parent_node.store
+        end
       end
     end
   end
