505 lines
14 KiB
Ruby
Executable File
505 lines
14 KiB
Ruby
Executable File
#
|
|
# File:: maxscript.rb
|
|
# Description:: MaxScript Interface
|
|
#
|
|
# Author:: Marissa Warner-Wu <marissa.warner-wu@rockstarnorth.com>
|
|
# 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" )
|
|
# =>
|
|
# <file name ="x:/myscript.ms">
|
|
# <string value = "RsFunction1">
|
|
# <line>230</line>
|
|
# </string>
|
|
# </file>
|
|
#
|
|
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 |