Files
2025-09-29 00:52:08 +02:00

469 lines
15 KiB
Ruby
Executable File

#
# File:: file_get_usage.rb
# Description:: Functions for getting file usage.
#
# Author:: Marissa Warner-Wu <marissa.warner-wu@rockstarnorth.com>
# Date:: 5 August 2009
#
#-----------------------------------------------------------------------------
# Uses
#-----------------------------------------------------------------------------
require 'pp'
require 'pipeline/os/file'
require 'pipeline/resourcing/path'
require 'pipeline/util/maxscript'
require "rexml/document"
include REXML
#-----------------------------------------------------------------------------
# Implementation
#-----------------------------------------------------------------------------
module Pipeline
module ProjectUtil
#-------------------------------------------------------------------------
# Functions
#-------------------------------------------------------------------------
#
# == Description
# A function which determines the usage on MaxScript files in a given directory.
#
# Generates a hash which prints to the screen or to an optional output file, if given.
#
# === Example Usage
# Pretty much what it says on the box.
#
# g_ToolsRoot = "x:/pipedev/"
# g_OutputFile = "x:/usage.txt"
# FileUsageHash::file_get_usage_maxscript( g_ToolsRoot, g_OutputFile )
#
def ProjectUtil::file_get_usage_maxscript( toolsroot, outputFile )
# Generate the usage hash and related warnings
usage_hash = {}
bad_refs = {}
FileUsageHash::create_hash_maxscript( toolsroot ) do |newHash, warnings|
usage_hash = newhash
bad_refs = warnings
end
# Generate output
if outputFile.nil?
FileUsageHash::pp( usage_hash )
else
case OS::Path::get_extension( outputFile )
when 'txt' then FileUsageHash::to_txt( usage_hash, outputFile, bad_refs )
when 'xml' then FileUsageHash::to_xml( usage_hash, outputFile )
else puts "ERROR: This file type is not supported, could not create output file."
end #case OS::Path::get_extension( outputFile )
end #if outputFile.nil?
end #file_get_usage_maxscript
#
# == Description
# Similar to file_get_usage_maxscript(), except that it exhaustively attempts to
# find unused files by checking function and rollout calls.
#
# Generates a hash which prints to the screen or to an optional output file, if given
# (in this case it will only print out the unused items).
#
# === Example Usage
# Pretty much what it says on the box.
#
# g_ToolsRoot = "x:/pipedev/"
# g_OutputFile = "x:/usage.txt"
# FileUsageHash::file_get_unused_maxscript( g_ToolsRoot, g_OutputFile )
#
def ProjectUtil::file_get_unused_maxscript( toolsroot, outputFile )
# Generate the usage hash and related warnings
usage_hash = {}
bad_refs = {}
puts "Creating the usage hash..."
FileUsageHash::create_hash_maxscript( toolsroot ) do |newHash, warnings|
usage_hash = newHash
bad_refs = warnings
end
# Cull out items by seeing what is really used
puts "Checking usage against function calls..."
usage_hash.each do |script, references|
# Don't check the settings
next if OS::Path::get_filename( script ) == 'settings.ms'
# Start with the usage as false
used = false
scriptpath = Maxscript::full_path( script, toolsroot )
puts "CHECKING: #{scriptpath}"
references.each_key do |file|
# Don't check this file if it's the menu
next if OS::Path::get_filename( file ) == 'rsutils.mcr'
# Don't check global references
next if file.eql? "global"
# Get the usage
used = Maxscript::file_is_used?( scriptpath, file )
# If we couldn't find it then do a global check
unless used
partpath = Maxscript::format_path( file )
if usage_hash.has_key? partpath
includes = usage_hash[partpath]
includes.each_key do |included_file|
# Don't check global references
next if included_file.eql? "global"
# Otherwise see if it's used
used = Maxscript::file_is_used?( scriptpath, included_file )
break if used == true
end #includes.each
end #if usage_hash.has_key?
# If the file is still unused, mark it
unless used
# Create warning
warning = "Was included in #{file} (line: "
references[file].each{ |line_number| warning << line_number.to_s << " " }
warning << ") but no functions or rollouts were called"
if bad_refs.has_key? scriptpath
bad_refs[scriptpath] << warning
else
bad_refs[scriptpath] = [warning]
end
# Remove from it from the list of referenced files
references.delete( file )
end #unless used
end #includes.each
end #references.each_key
# If we still can't find a use for the file, check global function calls
unless used
usage_hash.each_key do |file|
# Don't check the file itself
next if script.eql? file
# Otherwise see if it's used
filepath = Maxscript::full_path( file, toolsroot )
used = Maxscript::file_is_used?( scriptpath, filepath )
break if used == true
end #usage_hash.each_key
# Add a global reference
references['global'] = [0] if used
end #unless used
end #usage_hash.each
# Generate output
if outputFile.nil?
FileUsageHash::pp( usage_hash )
else
puts "Creating output file..."
case OS::Path::get_extension( outputFile )
when 'txt' then FileUsageHash::to_txt_unused( usage_hash, outputFile, bad_refs )
when 'xml' then FileUsageHash::to_xml_unused( usage_hash, outputFile )
else puts "ERROR: This file type is not supported, could not create output file."
end #case OS::Path::get_extension( outputFile )
end #if outputFile.nil?
end #file_get_usage_maxscript
end # ProjectUtil module
#
# == Description
# Small class for functions which create and manipulate usage hashes for sets of files.
# The hash comes in the following form:
#
#
class FileUsageHash
#---------------------------------------------------------------------
# Class Methods
#---------------------------------------------------------------------
#
# == Description
# A function which determines the usage on MaxScript files in a given directory.
# Yields the hash and a hash of warnings to an optional given block.
#
# === Example Usage
# Pretty much what it says on the box.
#
# g_ToolsRoot = "x:/pipedev/"
# FileUsageHash::create_hash_maxscript( g_ToolsRoot )
#
def FileUsageHash::create_hash_maxscript( toolsroot, &block )
# Get all the maxscript files
ms_files = Maxscript::find_files( toolsroot )
# Create the hash
file_hash = {}
ms_files.each do |filepath|
# Add to the hash unless this is a startup script, which we can safely skip
part_path = Maxscript::format_path( filepath )
file_hash[part_path] = {} unless OS::Path::get_trailing_directory( part_path ) == "startup"
end
# Add the menus to the file list
rsutils = OS::Path::combine( toolsroot, Maxscript::dir_max, "ui/macroscripts/rsutils.mcr" )
ms_files << rsutils
# Keep track of bad references
bad_refs = {}
# Search through the files
ms_files.each do |scriptfile|
puts "USAGE: #{scriptfile}"
# Get the contents of this file
text = Maxscript::get_lines( scriptfile )
# Parse each line of text
text.each_index do |index|
line = text[index]
line_number = index + 1
# Try to get the filename
filename = Maxscript::get_included_file( line, scriptfile )
unless filename.empty?
# Try to find this file in the hash
if file_hash.has_key?(filename)
# Add this script file to the list of places this file was used
refs = file_hash[filename]
if refs.has_key?(scriptfile)
refs[scriptfile] << line_number
else
refs[scriptfile] = [line_number]
end
else
# Store a warning if the file wasn't found unless this is a comment
unless Maxscript::line_is_comment?( line )
warning = "#{filename} in the following line: \"#{line.chomp}\""
if bad_refs.has_key? scriptfile
bad_refs[scriptfile] << warning
else
bad_refs[scriptfile] = [warning]
end
end #unless Maxscript::line_is_comment?
end #if file_hash.has_key?
end #unless filename.empty?
end #text.each
end #ms_files.each do |scriptfile|
yield( file_hash, bad_refs ) if block_given?
end #create_hash_maxscript
#
# == Description
# A specialised helper function which pretty prints the Get Usage hash information.
#
# === Example Usage
# Fairly self-explanatory, only really useful for debugging. Usually you would want
# to print to a file as in below.
#
def FileUsageHash::pp( usage_hash )
usage_hash.each do |file, references|
puts "---------------- #{file}:"
pp references
end
end #pp
#
# == Description
# A specialised helper function which prints the Get Usage hash information to a .txt
# file. If given a hash of bad references it will also format these and print out
# the information at the end of the file.
#
# === Example Usage
# MWW TODO
#
def FileUsageHash::to_txt( usage_hash, outputFile, bad_refs={} )
::FileUtils::rm( outputFile ) if ( File.exist?( outputFile ) )
log = File.new(outputFile, 'w')
log.write("=== MAXSCRIPT FILE USAGE ===\n\n")
# Print out information for each item in the hash
keys = usage_hash.keys
keys.sort!
keys.each do |script_name|
usage_info = usage_hash[script_name]
log.write("----#{script_name}:\n")
if usage_info.empty?
log.write("[[UNUSED]]\n\n")
else
usage_info.each do |file, lines|
log.write("\t\t#{file} used this at line(s): ")
lines.each { |number| log.write( "#{number.to_s} " ) }
log.write("\n")
end #usage_info.each
log.write("\n")
end
end #file_hash.each
# Print out bad refs warning
unless bad_refs.empty?
log.write("============= Found the following possible bad refs:\n")
bad_refs.each do |file_name, warnings|
log.write("----#{file_name}:\n")
warnings.each { |ref| log.write("\t\t#{ref}\n") }
log.write("\n")
end #bad_refs.each
end #unless bad_refs.empty?
log.close()
puts "Wrote output file to #{outputFile}."
end #to_txt
#
# == Description
# The same as to_txt(), but only prints out information for unused files. If given
# a hash of bad references it will also format these and print out the information
# at the end of the file.
#
# === Example Usage
# MWW TODO
#
def FileUsageHash::to_txt_unused( usage_hash, outputFile, bad_refs={} )
::FileUtils::rm( outputFile ) if ( File.exist?( outputFile ) )
log = File.new(outputFile, 'w')
log.write("=== MAXSCRIPT FILE USAGE -- UNUSED ===\n\n")
# Print out information for each item in the hash
keys = usage_hash.keys
keys.sort!
keys.each do |script_name|
usage_info = usage_hash[script_name]
log.write("#{script_name}\n") if usage_info.empty?
end #usage_hash.each
log.write("\n")
# Print out bad refs warning
unless bad_refs.empty?
log.write("============= Found the following possible bad refs:\n")
bad_refs.each do |file_name, warnings|
log.write("----#{file_name}:\n")
warnings.each { |ref| log.write("\t\t#{ref}\n") }
log.write("\n")
end #bad_refs.each
end #unless bad_refs.empty?
log.close()
puts "Wrote output file to #{outputFile}"
end #to_txt_unused
#
# == Description
# A specialised helper function which prints the Get Usage hash information to a .xml
# file. If no output file is given then it passes back the REXML Document object.
#
# Note that this function does not take or print any of the bad reference warnings.
# To print this information, use to_txt() or to_txt_unused().
#
# === Example Usage
# MWW TODO
#
def FileUsageHash::to_xml( usage_hash, outputFile )
::FileUtils::rm( outputFile ) if ( File.exist?( outputFile ) )
# Create document
log = Document.new
log << XMLDecl.new
log << Comment.new( "MAXSCRIPT FILE USAGE" )
root = Element.new( 'Usage' )
# Create elements for each file
keys = usage_hash.keys
keys.sort!
keys.each do |file|
references = usage_hash[file]
file_elem = root.add_element( 'file' )
file_elem.add_attribute( 'name', file )
references.each do |ref_file, lines|
ref_elem = file_elem.add_element( 'ref_file' )
ref_elem.add_attribute( 'name', ref_file )
lines.each do |number|
line_elem = ref_elem.add_element( 'line' )
line_elem.add_text( number.to_s )
end
end #references.each
end #usage_hash.each
log << root
# Write the XML or pass back the document
if outputFile.nil?
return log
else
File.open( outputFile, 'w' ) do |file|
fmt = REXML::Formatters::Pretty.new()
fmt.write( log, file )
end
end #if outputFile.nil?
end #to_xml
#
# == Description
# The same as to_xml(), but only prints out information for unused items. If no output
# file is given then it passes back the REXML Document object.
#
# Note that this function does not take or print any of the bad reference warnings.
# To print this information, use to_txt() or to_txt_unused().
#
# === Example Usage
# MWW TODO
#
def FileUsageHash::to_xml_unused( usage_hash, outputFile )
::FileUtils::rm( outputFile ) if ( File.exist?( outputFile ) )
# Create document
log = Document.new
log << XMLDecl.new
log << Comment.new( "MAXSCRIPT FILE USAGE -- UNUSED" )
root = Element.new( 'Usage' )
# Create elements for each file
keys = usage_hash.keys
keys.sort!
keys.each do |file|
references = usage_hash[file]
if references.empty?
file_elem = root.add_element( 'file' )
file_elem.add_attribute( 'name', file )
end
end #usage_hash.each
log << root
# Write the XML or pass back the document
if outputFile.nil?
return log
else
File.open( outputFile, 'w' ) do |file|
fmt = REXML::Formatters::Pretty.new()
fmt.write( log, file )
end
end #if outputFile.nil?
end #to_xml_unused
end #FileUsageHash
end # Pipeline module
# file_get_usage.rb