Class MUES::Factory
In: lib/mues/filters/commandshell.rb  (CVS)
Parent: MUES::Object

A command-shell creation factory. Instances of this class create and combine instances of MUES::CommandShell and MUES::CommandShell::CommandTable, or derivatives thereof as specified by the configuration, and then maintain a list of commands loaded from a configured list of directories, reloading any that change via a scheduled event in the Engine to which it belongs.

Methods

Included Modules

MUES::Debuggable Observable MUES::TypeCheckFunctions MUES::ServerFunctions

Constants

SVNRev = %q$Rev: 1206 $   SVN Revision
SVNId = %q$Id: commandshell.rb 1206 2004-05-09 21:25:11Z deveiant $   SVN Id
SVNURL = %q$URL: svn+ssh://deveiate.org/usr/local/svn/MUES/trunk/lib/mues/filters/commandshell.rb $   SVN URL
DefaultShellClass = MUES::CommandShell   The shell class to use if no alternate is specified
DefaultTableClass = MUES::CommandShell::CommandTable   The table class to use if no alternate is specified
DefaultParserClass = MUES::CommandShell::CommandParser   The command parser class to use if no alterate is specified

External Aliases

registryIsBuilt -> registryIsBuilt?

Attributes

commandPath  [RW]  The Array of directories to search for command source files
parserClass  [RW]  The class of object that will be used by the factory to parse command definitions.
registry  [R]  The registry of all loaded commands, keyed by command and alias
registryIsBuilt  [R]  Flag that indicates whether or not the factory‘s registry of commands has been built.
reloadInterval  [RW]  The number of seconds to use as the interval for scheduling reloads (in the format understood by MUES::Engine#scheduleEvents).
shellClass  [RW]  The class of objects that will be used by the factory for the shell itself.
tableClass  [RW]  The class of objects that will be used by the factory for the shell‘s command table.

Public Class methods

Create and return a new CommandShell::Factory, configured with the specified commandPath and shellParameters. The classes that will be used in construction can be specified with the shellClass, tableClass, and parserClass arguments, which can be either the class object or a name suitable for passing to the appropriate factory‘s create method. They default to MUES::CommandShell, MUES::CommandShell::CommandTable, and MUES::CommandShell::CommandParser, respectively.

[Source]

# File lib/mues/filters/commandshell.rb, line 964
            def initialize( commandPath=[], shellParameters={},
                            shellClass=DefaultShellClass,
                            tableClass=DefaultTableClass,
                            parserClass=DefaultParserClass )

                # Classes used to build a shell
                @shellClass         = shellClass || DefaultShellClass
                @tableClass         = tableClass || DefaultTableClass
                @parserClass        = parserClass || DefaultParserClass
                @shellParameters    = shellParameters || {}

                # Command objects are kept in a Hash so we can detect collisions
                # early.
                @registry           = {}
                @registryIsBuilt    = false
                @mutex              = Sync.new
                @commandLoadTime    = Time.at(0) # Set initial load time to epoch
                @parser             = CommandParser::create( @parserClass,
                                                             MUES::CommandShell::Command )

                # Set the reload interval to 10 minutes
                @reloadInterval     = -600

                # Fully-qualify all the directories in the command path
                unless commandPath.nil? || commandPath.empty?
                    @commandPath = commandPath.collect {|dir|
                        File.expand_path( dir )
                    }.find_all {|dir|
                        File.exists?( dir ) && File.directory?( dir )
                    }
                end
                
                buildCommandRegistry()
                return self
            end

Public Instance methods

Build the command registry for this factory.

[Source]

# File lib/mues/filters/commandshell.rb, line 1062
            def buildCommandRegistry
                @mutex.synchronize( Sync::EX ) {

                    # If the registry's already been built, unset the
                    # appropriate flag. Only log if it's being built for the
                    # first time to avoid spamming the log if the update cycle
                    # is small.
                    if @registryIsBuilt
                        @registryIsBuilt = false
                    else
                        self.log.notice( "Building command registry" )
                    end
                    loadCommandsIntoRegistry()
                }

                return []
            end

Returns a MUES::CommandShell::CommandTable filled with the commands that are allowed for the specified user (a MUES::User object).

[Source]

# File lib/mues/filters/commandshell.rb, line 1047
            def createCommandTableForUser( user )
                commands = getCommandsAvailableToUser( user )
                return CommandTable::create( @tableClass, *commands )
            end

Returns a instance of MUES::CommandShell or one of its derivatives (as specified by the configuration which created the factory) tailored for the specified user (a MUES::User object).

[Source]

# File lib/mues/filters/commandshell.rb, line 1035
            def createShellForUser( user )
                table = createCommandTableForUser( user )
                shell = CommandShell::create( @shellClass, user, table, @shellParameters )
                add_observer( shell )

                return shell
            end

Returns the MUES::CommandShell::Command objects that are available to the given user (a MUES::User object) based on her user account type.

[Source]

# File lib/mues/filters/commandshell.rb, line 1056
            def getCommandsAvailableToUser( user )
                self.registry.values.find_all {|c| c.canBeUsedBy?(user)}
            end
rebuildCommandRegistry()

Protected Instance methods

Find and return an Array of the fully-qualified paths to any command files under the factory‘s command path that are newer than oldLoadTime.

[Source]

# File lib/mues/filters/commandshell.rb, line 1193
            def findUpdatedCommandFiles( oldLoadTime )
                # Get the target filespec from the parser
                fileSpec = @parser.fileSpec
                fileSpec.untaint
                debugMsg( 2, "File spec is: #{fileSpec.inspect}" )

                # Search each directory in the path, top-down, for command
                # files newer than our last load time, loading any we
                # find.
                newFiles = []
                return newFiles if @commandPath.nil? || @commandPath.empty?

                @commandPath.each {|cmdsdir|
                    cmdsdir.untaint
                    self.log.info( "Looking for updated commands in '#{cmdsdir}'." )
                    Find.find( cmdsdir ) {|f|
                        f.untaint
                        Find.prune if f =~ %r{^\.} # Ignore hidden stuff

                        # Turn the filename into its fully-qualified version
                        fqf = File::expand_path( f, cmdsdir )
                        fqf.untaint

                        if fileSpec.match(fqf) && File.file?(fqf) && File.mtime(fqf) > oldLoadTime
                            debugMsg 3, "Found updated file '#{fqf}'"
                            newFiles.push( fqf )
                        end
                    }
                }

                return newFiles
            end

Find commands newer than the last time the registry was build, load them, and insert the commands into the Factory‘s registry. Returns the number of commands that were successfully (re)loaded.

[Source]

# File lib/mues/filters/commandshell.rb, line 1090
            def loadCommandsIntoRegistry
                commands = nil

                @mutex.synchronize(Sync::EX) {

                    # Get the list of updated commands and derive the list of
                    # their sources
                    commands = loadNewCommands()
                    unless commands.empty?

                        self.log.notice "Loading %d new/reloaded commands into the "\
                        "CommandFactory's registry" % commands.length

                        # Remove old commands loaded from the modified sources (so
                        # deleting a command from sources works)
                        sources = commands.collect {|cmd| cmd.sourceFile}.sort.uniq
                        @registry.delete_if {|k,v| sources.include? v.sourceFile}

                        # Insert new versions of the commands into the registry,
                        # checking for collisions.
                        commands.each {|command|

                            # Iterate over the command name and any associated aliases
                            [ command.name, command.synonyms ].flatten.compact.each {|name|

                                # Test for collision
                                if @registry.has_key?( name )
                                    raise CommandNameConflictError,
                                        "Command '%s' has clashing implementations in %s:%d and %s:%d " % [
                                        name,
                                        @registry[name].sourceFile, @registry[name].sourceLine,
                                        command.sourceFile, command.sourceLine
                                    ]
                                end

                                # Install the command into the command registry
                                @registry[ name ] = command
                            }
                        }

                        self.changed( true )
                    end

                    @registryIsBuilt = true
                }

                self.notify_observers( self )
                return commands.length
            end

Iterate over each file in the shell commands directory specified in the configuration, parsing the ones that have changed since last we loaded, and returning an Array of resulting MUES::CommandShell::Command objects.

[Source]

# File lib/mues/filters/commandshell.rb, line 1145
            def loadNewCommands
                commands = nil

                # Parse all command files in the configured directories newer
                # than our last load time.
                @mutex.synchronize( Sync::EX ) {

                    # Get the old load time for comparison and set it to the
                    # current time
                    oldLoadTime = @commandLoadTime
                    @commandLoadTime = Time.now

                    debugMsg 2, "Loading commands newer than #{oldLoadTime.to_s}"

                    # Load the default commands defined at the end of this file.
                    commands = []
                    if File.mtime(__FILE__) > oldLoadTime
                        self.log.info( "(Re)loading built-in commands from %s" % __FILE__ )
                        commands += @parser.parse( __FILE__ )
                    end
                    
                    # Find any files that have changed
                    newFiles = findUpdatedCommandFiles( oldLoadTime )

                    # Now if any newer files were found, load commands from
                    # them.
                    newFiles.each {|fqf|
                        fqf.untaint
                        self.log.info( "(Re)loading commands from '#{fqf}'" )
                        begin
                            commands += @parser.parse( fqf )
                        rescue SyntaxError => e
                            self.log.error "Syntax error in command file '%s': %s:\n\t%s" %
                                [ fqf, e.message, e.backtrace.join("\t\n") ]
                        rescue => e
                            self.log.error "Unknown error while parsing command file '%s': %s:\n\t%s" %
                                [ fqf, e.message, e.backtrace.join("\t\n") ]
                        end
                    }
                }

                return commands.flatten
            end

[Validate]