commit e321ca6f2a16f3d882f0c1008a71b3f55cf9e102
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Mon Nov 15 02:20:19 2010 +0100

    [evol] created a DSL for client-side calls (several conditions (calls only at the moment) can be joined together in order to exploit parallel tasks via asynchronous calls)

diff --git a/README b/README
index 234dc53..46ae54f 100644
--- a/README
+++ b/README
@@ -12,6 +12,7 @@ common:
   - libgettext-ruby1.8
   - libdaemons-ruby
   - libeventmachine-ruby
+  - libactivesupport-ruby
   - libxmpp4r-ruby (if using XMPP backend)
 Postman:
   - libtmail-ruby1.8
diff --git a/bin/mapmaker b/bin/mapmaker
index ae263f3..01fdb6c 100755
--- a/bin/mapmaker
+++ b/bin/mapmaker
@@ -59,8 +59,6 @@ module CyborgHood
       include CyborgServerInterface
       include CyborgServerRootInterfaceAddon
 
-export_method :toto
-
       class Taiste
         include CyborgServerInterface
 
diff --git a/bin/mapmaker_client b/bin/mapmaker_client
index 119851a..ce1251f 100755
--- a/bin/mapmaker_client
+++ b/bin/mapmaker_client
@@ -36,33 +36,36 @@ module CyborgHood
       include BotNet
 
       def start_work
-        contact_peer("MapMaker") do |conv|
-          if conv
-            logger.info "Yo ! Conversation ready with MapMaker (#{conv.peer_name}) !"
-            conv.thread("plop") do |conv_thread|
-              conv_thread.call("/api_version", 2) do |reply|
-                case reply[:status]
-                when :ok
-                  puts "Result:"
-                  pp reply[:result]
-                when :decline
-                  puts "Declined: #{reply[:reason]}"
-                when :error
-                  exception = reply[:exception]
-                  puts "EXCEPTION: [#{exception.severity} - #{exception.category}] #{exception.message}"
-pp exception.inspect
-                else
-                  puts "???"
-                end
+        # to test the closure through the inner instance_eval calls
+        toto = true
+
+        conversation_with "MapMaker" do
+          on_error do
+            puts "Halalalala !"
+          end
+          thread "super taiste" do
+            call :ver, "/api_version"
+            call :zones, "/DNS/Zones" if toto
+            on_error do
+              puts "Sniff !"
+              pp reply
+            end
+            on_success do
+              puts "Yahou !"
+              pp reply
+              call :gogogo, "/DNS"
+              on_error do
+                pp "Plouf"
+                pp reply
+              end
+              on_success do
+                pp "Hop!"
+                pp reply
               end
             end
-          else
-            logger.error "Could not connect to MapMaker"
-            stop
           end
-          # in contact loop, in order to wait for conversation registration to happen
-          # or the bot would stop before contact is done
-          try_stop
+          #stop_bot :at_once
+          stop_bot :when_finished
         end
       end
     end
diff --git a/lib/cyborghood/cyborg.rb b/lib/cyborghood/cyborg.rb
index 672be89..b3493c8 100644
--- a/lib/cyborghood/cyborg.rb
+++ b/lib/cyborghood/cyborg.rb
@@ -86,6 +86,14 @@ module CyborgHood
       EventMachine.next_tick { EventMachine.stop_event_loop }
     end
 
+    def try_stop
+      if ready_to_stop?
+        EventMachine.stop_event_loop
+      else
+        EventMachine.next_tick { try_stop }
+      end
+    end
+
     # core capabilities
     def capabilities
       []
@@ -99,14 +107,6 @@ module CyborgHood
 
     def process_system_notification(msg)
     end
-
-    def try_stop
-      if ready_to_stop?
-        EventMachine.stop_event_loop
-      else
-        EventMachine.next_tick { try_stop }
-      end
-    end
   end
 
   autoload :BotNet, 'cyborghood/cyborg/botnet'
diff --git a/lib/cyborghood/cyborg/botnet.rb b/lib/cyborghood/cyborg/botnet.rb
index a140133..0bfd83b 100644
--- a/lib/cyborghood/cyborg/botnet.rb
+++ b/lib/cyborghood/cyborg/botnet.rb
@@ -18,6 +18,7 @@
 
 require 'cyborghood/cyborg/interface'
 require 'cyborghood/cyborg/conversation'
+require 'cyborghood/cyborg/botnet_dsl'
 require 'set'
 
 module CyborgHood
@@ -57,6 +58,11 @@ module CyborgHood
         end
       end
     end
+
+    # start of DSL
+    def conversation_with(peer, &block)
+      BotnetDSL::Conversation.run_dsl(self, peer, &block)
+    end
   end
 
   module BotNet
diff --git a/lib/cyborghood/cyborg/botnet_dsl.rb b/lib/cyborghood/cyborg/botnet_dsl.rb
new file mode 100644
index 0000000..32a819e
--- /dev/null
+++ b/lib/cyborghood/cyborg/botnet_dsl.rb
@@ -0,0 +1,151 @@
+#--
+# CyborgHood, a distributed system management software.
+# Copyright (c) 2009-2010 Marc Dequènes (Duck) <Duck@DuckCorp.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#++
+
+require 'active_support/basic_object'
+
+
+module CyborgHood
+  module BotnetDSL
+    class BaseDSL < ActiveSupport::BasicObject
+
+      def self.run_dsl(*args, &block)
+        dsl = new(*args)
+        dsl.instance_eval(&block)
+        dsl.instance_eval do
+          _start_dsl
+        end
+      end
+    end
+
+    class Thread < BaseDSL
+      attr_reader :name, :conversation, :reply
+
+      def initialize(conversation_dsl, conversation, name, reply = nil)
+        @conversation_dsl = conversation_dsl
+        @conversation = conversation
+        @name = name
+        @reply = reply
+
+        @error_cb = nil
+        @success_cb = nil
+        @calls = {}
+        @calls_reply = {}
+
+        @final_reply = {
+          :results        => {},
+          :errors         => {}
+        }
+      end
+
+      def on_error(&callback)
+        @error_cb = callback
+      end
+
+      def on_success(&callback)
+        @success_cb = callback
+      end
+
+      def call(key, cmd, *args)
+        @calls[key] = {
+          :cmd   => cmd,
+          :args  => args
+        }
+      end
+
+      protected
+
+      def _start_dsl
+        @conversation.thread(@name) do |conv_thread|
+          @calls.each_pair do |key, data|
+            conv_thread.call(data[:cmd], *data[:args]) do |reply|
+              @calls_reply[key] = reply[:status]
+
+              case reply[:status]
+              when :ok
+                @final_reply[:results][key] = reply[:result]
+              when :decline
+                # TODO: remove this case ???
+              when :error
+                @final_reply[:error][key] = reply[:exception]
+              end
+
+              check_finished
+            end
+          end
+        end
+      end
+
+      def check_finished
+        return unless @calls.size == @calls_reply.size
+
+        # process reply in the same thread, but with a new DSL Thread context
+        cb = @calls_reply.values.index(:error) ? @error_cb : @success_cb
+        Thread.run_dsl(@conversation_dsl, @conversation, @name, @final_reply, &cb)
+      end
+    end
+
+    class Conversation < BaseDSL
+      attr_reader :bot, :peer
+
+      def initialize(bot, peer)
+        @bot = bot
+        @peer = peer
+
+        @threads = {}
+        @error_cb = nil
+        @stop_bot = nil
+      end
+
+      def on_error(&callback)
+        @error_cb = callback
+      end
+
+      def thread(name, &block)
+        @threads[name] = block
+      end
+
+      def stop_bot(condition)
+        @stop_bot = condition
+      end
+
+      protected
+
+      def _start_dsl
+        @bot.contact_peer(peer) do |conv|
+          if conv
+            @threads.each_pair do |name, block|
+              Thread.run_dsl(self, conv, name, &block)
+            end
+          else
+            @error_cb.call unless @error_cb.nil?
+          end
+
+          case @stop_bot
+          when :when_finished
+            # in contact loop, in order to wait for conversation registration to happen
+            # or the bot would stop before contact is done
+            @bot.try_stop
+          when :at_once
+            # it won't wait for conversation to finish...
+            @bot.stop
+          end
+        end
+      end
+    end
+  end
+end
