eddorre

Extending XMPP and Ruby

After writing up my XMPP Agent written in Ruby, my friend Billy wrote a comment about a similar system written by Michael Still the author of Practical MythTV. His system for controlling MythTV over XMPP is the gtalkbot.

Curious as to how he worked around some issues, I downloaded the Python source code and stumbled my way through it. I was struck by the fact that he used a plugin architecture that would allow other authors to extend the functionality of his original program.

After seeing that, I decided that I would try something similar.

Without worrying about the XMPP Agent, I set out to code a plugin architecture. In a directory I have the following files:

  • init.rb (should probably be called plugin_loader.rb or something more descriptive)
  • test.rb
  • /plugins (this is a directory)
    • network.rb (inside the plugins directory)
    • system.rb (inside the plugins directory)

The code inside the test.rb file is simple enough:


require 'init'

puts “This is the allowed command set”

puts ALLOWED_COMMANDS

The first line of the code, loads the init.rb file which does the heavy lifting. The init.rb file has a plugin class defined. Other plugins will inherit from this class.

The plugin class is defined as:


require 'find'

class Plugin
  
  attr_reader :allowed_commands
  
  def initialize
    @allowed_commands = %w{ iisreset }
  end
  
  def load_plugins(dir, name="/^[a-z]+.rb/")
    plugins = []
    Find.find(dir) do |path|
        Find.prune if [".",".."].include? path
        plugins << path if File.basename(path).include? ".rb"
    end
    
    plugins.each do |item|
      puts "Loading plugin => #{item}"
      #Load the file
      require File.join(File.dirname(__FILE__), item)
      
      #Extract the file name from the directory name (without .rb extension)
      file_name = File.basename(item, ".rb")
      
      #Create a new object and instantiate it and find the allowed_methods attribute
      c = Object.const_get(file_name.capitalize).new
      puts "Loading command set for plugin #{file_name.capitalize} "
      puts c.allowed_commands
      
      #Add it to the array for allowed_commands
      @allowed_commands << c.allowed_commands
    end
  end
  
  def run_command(command)
    #Strip the command part out of the string - we don't need it anymore
    command.slice!("command: ")
    
    #Create an array for the arguments
    arguments = command.split(" ")
    arguments.delete_at(0) #Delete the first index, this is the command itself without arguments
    
    #Loop through the arguments and then delete them from the command string
    arguments.each { |item| command.slice!(item) }
        
    if @allowed_commands.include? command
      puts "#{command} is an allowed command"
      result = `#{command} #{arguments.join(" ")}` #Backticks are a shortcut for system(yourcommand).
    else
      result = "#{command} cannot be run"
    end
    return result
  end
end

At the end of the init.rb file is the code that actually starts loading the plugins:


app_plugins = Plugin.new app_plugins.load_plugins("./plugins") ALLOWED_COMMANDS = app_plugins.allowed_commands

Now it’s easy to extend the test.rb script by instantiating a new System object (one of our plugins) just by this simple code in the test.rb file:

tester = System.new

I can then call methods defined in the System.rb file (shown below):


tester.test

System.rb file:


class System < Plugin
  
  attr_reader :allowed_commands
  
  def initialize
    @allowed_commands = %w{ set shutdown }  
  end
  
  def test
    "This is a test"
  end
end

Now all of this was just really another proof of concept to find out if it could be done. I’m happy with the results even though I’m sure that there is a better way of doing it.


Comments

Comments are closed

Comments are closed on this post. If you have something on your mind regarding this post, don't hesitate to drop me a line.