| 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.
| 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 |
| registryIsBuilt | -> | registryIsBuilt? |
| 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. |
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.
# 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
Build the command registry for this factory.
# 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).
# 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).
# 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.
# File lib/mues/filters/commandshell.rb, line 1056 def getCommandsAvailableToUser( user ) self.registry.values.find_all {|c| c.canBeUsedBy?(user)} end
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.
# 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.
# 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.
# 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