# # File:: maxscript.rb # Description:: MaxScript Interface # # Author:: Marissa Warner-Wu # Date:: 6 August 2009 # require 'pipeline/os/file' require 'pipeline/resourcing/path' require 'pipeline/config/projects' require "rexml/document" include REXML #----------------------------------------------------------------------------- # Implementation #----------------------------------------------------------------------------- module Pipeline # # == Description # Simple interface for working with and parsing MaxScript files. # class Maxscript #--------------------------------------------------------------------- # Public Constants #--------------------------------------------------------------------- @@toolsroot = Pipeline::Config::instance().toolsroot @@dir_max = 'dcc/current/max2011' @@dir_scripts = 'dcc/current/max2011/scripts' #--------------------------------------------------------------------- # Class Methods #--------------------------------------------------------------------- # # == Description # Returns the current max directory. # def Maxscript::dir_max OS::Path::combine( @@toolsroot, @@dir_max ) end # # == Description # Returns the current script directory. # def Maxscript::dir_scripts OS::Path::combine( @@toolsroot, @@dir_scripts ) end # # == Description # Reformats a file path the same way it would appear in a MaxScript file. # The reverse of full_path(). # # == Example Usage # Maxscript.format_path( "x:/pipedev/tools/dcc/current/max2009/scripts/rockstar/toolstest.ms" ) # => "/rockstar/toolstest.ms" # def Maxscript::format_path( filepath ) OS::Path::normalise( filepath.split( @@dir_scripts )[1] ) end #format_path # # == Description # Fills out the Max filepath format used in file includes to create a full # path. If no toolsroot is given it uses the system default. The reverse # of format_path(). # # == Example Usage # Maxscript.full_path( "/rockstar/toolstest.ms" ) # => "x:/pipedev/tools/dcc/current/max2009/scripts/rockstar/toolstest.ms" # def Maxscript::full_path( filepath, toolsroot = nil) # Get the toolsroot if toolsroot.nil? scriptDir = Maxscript.dir_scripts else scriptDir = OS::Path::combine( toolsroot, @@dir_scripts ) end OS::Path::combine( scriptDir, filepath ) end #full_path # # == Description # Given a tools root, returns a list of all the MaxScript files # that it can find in the @@dir_scripts folder. If no tools root is # given it uses the system default. # # == Example Usage # Maxscript.find_files( "x:/" ) # => ["filepath1", "filepath2", etc.] # def Maxscript::find_files( toolsroot=@@toolsroot ) # Get the script directory scriptDir = Maxscript.dir_scripts throw RuntimeError.new( "Unable to find MaxScript directory #{scriptDir}." ) unless ::File::directory?( scriptDir ) # Get all the maxscript files ms_files = OS::FindEx::find_files_recurse( OS::Path::combine( scriptDir, '*.ms' ) ) throw RuntimeError.new( "Unable to find any MaxScript files in #{scriptDir}." ) if ms_files.empty? ms_files end #find_files # # == Description # Determines whether this is a valid MaxScript file. Only # accepts .ms or .mcr files. # # == Example Usage # Suppose "x:/toolstest.ms" is a MaxScript file. # # Maxscript.valid_file?( "x:/toolstest.ms" ) # => true # Maxscript.valid_file?( "x:/toolstest.txt" ) # => false # def Maxscript::valid_file?( filename ) valid = false if (File.exist? filename) case OS::Path::get_extension( filename ) when 'ms' then valid = true when 'mcr' then valid = true else puts "ERROR: #{OS::Path::get_extension( filename )} not a valid MaxScript extension." end #case OS::Path::get_extension( filename ) else puts "ERROR: The file #{filename} is not valid because it does not exist." end #if (File.exist? filename) valid end #valid_file? # # == Description # Determines whether a given line is a comment. # # == Example Usage # Maxscript.line_is_comment?( "-- this is a comment" ) # => true # def Maxscript::line_is_comment?( line ) comment = false l = line.strip # Are the first two characters of this line "-"? if (l[0] and l[1]) == 45 comment = true end comment end #line_is_comment? # # == Description # Determines whether a given line is a function declaration. # # == Example Usage # Maxscript.line_is_function?( "fn RsFunction1 var =(" ) # => true # def Maxscript::line_is_function?( line ) function = false l = line.strip if (line.include? "fn") or (line.include? "function") function = true end function end #line_is_function? # # == Description # Determines whether a given line is a file include. # # == Example Usage # Maxscript.line_is_filein?( "filein 'rockstar/toolstest.ms'" ) # => true # def Maxscript::line_is_filein?( line ) filein = false l = line.strip if (line.include? "filein") or (line.include? "include") filein = true end filein end #line_is_filein? # # == Description # Determines whether a given line defines a rollout. # # == Example Usage # Maxscript.line_is_rollout?( "rollout RsToolsTest 'Tools Test'" ) # => true # def Maxscript::line_is_rollout?( line ) rollout = false l = line.strip if (line.include? "rollout") rollout = true end rollout end #line_is_rollout? # # == Description # Returns the contents of MaxScript file. Also tests to make # sure that the file is valid and escapes all doublequotes. # # == Example Usage # Maxscript.get_lines( "x:/toolstest.ms" ) # => ["line1", "line2", etc.] # def Maxscript::get_lines( filename ) throw ArgumentError.new("The file #{filename} is not a valid MaxScript file.") unless Maxscript.valid_file?( filename ) # Get the text text = [] File.open(filename, 'r') do text = IO.readlines( filename ) end # Escape any double quotes text.each { |line| line.tr!('"', '\"') } text end #get_lines # # == Description # Determines whether file1 is really used in file2 by comparing the # list of functions in the first with the function calls in the second. # Returns true or false. # # == Example Usage # file1 = "x:/toolstest.ms" # file2 = "x:/myscript.ms" # Maxscript.file_is_used?( file1, file2 ) # => true # def Maxscript::file_is_used?( file1, file2 ) # Set up our variables used = false # Compare function usage functions = Maxscript.get_functions( file1 ) usage = Maxscript.string_usage_hash( functions, file2 ) used = true if usage.has_value? true # If we couldn't find a use then check the rollouts as well unless used rollouts = Maxscript.get_rollouts( file1 ) usage = Maxscript.string_usage_hash( rollouts, file2 ) used = true if usage.has_value? true end used end #file_is_used? # # == Description # Returns the name of the file included in a line using "filein" # and "include". Reformats path names by normalising them and making # sure they always begin with a "/" character. If no valid file # include is found it returns a blank string. Takes the line itself and # the name of the file where it appears. # # == Example Usage # Maxscript.get_included_file( "filein 'toolstest.ms'", "x:/pipedev/tools/dcc/current/max2009/scripts/rockstar/myscript.ms" ) # => "/rockstar/toolstest.ms" # def Maxscript::get_included_file( line, ref_file="" ) filename = "" # Is this a line we care about? unless Maxscript.line_is_comment?( line ) if Maxscript.line_is_filein?( line ) filename = OS::Path::normalise( line.split('"')[1] ) filename.strip! # Check to see whether this path is relative relpath = OS::Path::combine( OS::Path::remove_filename( ref_file ), filename ) if File.exist? relpath if ref_file.empty? puts "\n\nERROR: No referencing file provided for a relative path!\n\n" else filename = Maxscript.format_path( relpath ) end else # If the path isn't relative then make sure there's a "/" in front filename.insert(0, "/") unless ( filename[0] == "/" ) end #if OS::Path::get_directories( filename ).empty? end #Maxscript.line_is_filein? end filename end #get_included_file # # == Description # Returns a list of all the files included in a file. # # == Example Usage # Maxscript.get_included_files( "x:/toolstest.ms" ) # => ["filepath1", "filepath2"] # def Maxscript::get_included_files( filename ) # Set up our variables text = Maxscript.get_lines( filename ) includes = [] # Parse each line text.each do |line| unless Maxscript.line_is_comment?( line ) filein = Maxscript.get_included_file( line, filename ) includes << filein unless filein.empty? end end #text.each includes end #get_included_files # # == Description # Returns a list of all the functions defined in a file. # # == Example Usage # Maxscript.get_functions( "x:/toolstest.ms" ) # => ["RsFunction1", "RsFunction2"] # def Maxscript::get_functions( filename ) # Set up our variables text = Maxscript.get_lines( filename ) functions = [] # Parse each line text.each do |line| # Is this a line we care about? unless Maxscript.line_is_comment?( line ) if Maxscript.line_is_function?( line ) fnName = line.split[1] next if fnName.nil? fnName.strip! functions << fnName end end end #text.each functions end #get_functions # # == Description # Returns a list of all the rollouts defined in a file. # # == Example Usage # Maxscript.get_rollouts( "x:/toolstest.ms" ) # => ["RsRollout1", "RsRollout2"] # def Maxscript::get_rollouts( filename ) # Set up our variables text = Maxscript.get_lines( filename ) rollouts = [] # Parse each line text.each do |line| # Is this a line we care about? unless Maxscript.line_is_comment?( line ) if Maxscript.line_is_rollout?( line ) rollName = line.split[1] next if rollName.nil? rollName.strip! rollouts << rollName end end end #text.each rollouts end #get_rollouts # # == Description # Takes a list of strings and tells whether these # are in a file. Returns a hash with the strings # names as keys and whether or not it was found. For # more detailed information, use string_usage_xml(). # # == Example Usage # funcs = ["RsFunction1", "RsFunction2"] # Maxscript.string_usage_hash( funcs, "x:/myscript.ms" ) # => {"RsFunction1" => true, "RsFunction2" => false} # def Maxscript::string_usage_hash( find_strings, filename ) # Set up our variables text = Maxscript.get_lines( filename ) usage = {} find_strings.each { |phrase| usage[phrase] = false } # Parse each line text.each do |line| # Is this a line we care about? unless Maxscript.line_is_comment?( line ) # Check each string against this line find_strings.each do |phrase| if line.include? phrase # If we found a match, set the usage to true usage[phrase] = true end end #find_strings.each end #unless Maxscript.line_is_comment? end #text.each usage end #string_usage_hash # # == Description # Takes a list of strings and tells whether these # are in a file. Returns an REXML element which is the # root node of the hierarchy for easy parsing. Essentially # a more verbose version of string_usage_hash(). # # == Example Usage # Suppose RsFunction1 is used in line 230 of myscript.ms and # RsFunction2 is not used. # # funcs = ["RsFunction1", "RsFunction2"] # Maxscript.string_usage_xml( funcs, "x:/myscript.ms" ) # => # # # 230 # # # def Maxscript::string_usage_xml( find_strings, filename ) # Create the file element file_elem = Element.new( 'file' ) file_elem.add_attribute( 'name', filename ) # Read the file and start parsing it text = Maxscript.get_lines( filename ) text.each_index do |index| # Is this a line we care about? unless Maxscript.line_is_comment?( text[index] ) # Check each phrase against this line find_strings.each do |phrase| if text[index].include? phrase line_number = index + 1 # If we found a match, check to see whether we already have a node for this phrase line_elem = nil file_elem.each_element_with_attribute( 'value', phrase ) do |element| line_elem = element.add_element( 'line' ) line_elem.add_text( line_number.to_s ) end # If we were unable to find a matching element, create one unless line_elem text_elem = file_elem.add_element( 'string' ) text_elem.add_attribute( 'value', phrase ) line_elem = text_elem.add_element( 'line' ) line_elem.add_text( line_number.to_s ) end end #if text[index].include? fn end #find_strings.each end #unless Maxscript.line_is_comment? end #text.each file_elem end #string_usage_xml end #Maxscript class end #Pipeline module