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

481 lines
14 KiB
Ruby
Executable File

#
# File:: enum_maker.rb
# Description:: Enum Maker - builds .sch file containing an enum of models found in a collection of .meta and .ide files.
#
#
# TODO : in .meta handle the @hideFrom="script"???
#
# Author:: Derek Ward <derek@rockstarnorth.com>
# Date:: 02 June 2011
#
#----------------------------------------------------------------------------
# Uses
#----------------------------------------------------------------------------
require 'RSG.Base.dll'
require 'RSG.Base.Configuration.dll'
require 'RSG.Base.Windows.dll'
require 'RSG.SourceControl.Perforce.dll'
include RSG::Base::Configuration
include RSG::Base::Logging
include RSG::Base::Logging::Universal
include RSG::Base::OS
include RSG::Base::Windows
include RSG::SourceControl::Perforce
require 'mscorlib'
require 'System'
require 'System.Core'
require 'System.Xml'
include System
include System::IO
require 'pipeline/os/options'
include Pipeline
#----------------------------------------------------------------------------
# Constants
#----------------------------------------------------------------------------
OPTIONS = [
LongOption::new( 'help', LongOption::ArgType.None, 'h', 'display usage information.' ),
LongOption::new( 'algorithm', LongOption::ArgType.Required, 'a', 'set hashing algorithm (u32, u16, s16).' ),
LongOption::new( 'scm', LongOption::ArgType.None, 's', 'build result file in the SCM' ),
LongOption::new( 'submit', LongOption::ArgType.None, 'sub', 'submit result file to the SCM' ),
]
TRAILING = {
'filenames' => 'output filename followed by any number of filelist, meta or ide filename(s)'
}
#
# Monkey patch integer class
class Integer
def to_s32
self >= (2**31) ? self - (2**32) : self
end
end
#
# Enum maker class - reads input files (.txt, .ide, .meta, .xml ) and produces a .sch
class EnumMaker
VERSION = "1.1"
VERBOSE = true
XPATH_META = "//modelName | //Name"
REGEX_IDE = /([\S]*),/
REGEX_TXT = /^([\S]*)$/
REGEX_NAME = /^\D[\w]+$/
IDE_FILETYPE = "ide"
META_FILETYPE = "meta"
SCENEXML_FILETYPE = "xml"
TXT_FILETYPE = "txt"
HARDCODED_ENTRY = "DUMMY_MODEL_FOR_SCRIPT=0,"
COMMENT = "#"
INFO_BLUE = "[colourise=blue]INFO_MSG: "
NUM_JUST = 6
L_JUST = 100
SCH_HARDCODED_ENTRIES = "#{HARDCODED_ENTRY.ljust(L_JUST,' ')}// hardcoded in script #{$0}\n"
SCH_HEADER = "ENUM MODEL_NAMES\n"
SCH_FOOTER = "ENDENUM\n"
SCH_COMMENT = "//Built by enum_maker.rb version #{VERSION}\n"
SCENE_XML_PARSER = "$(toolsbin)/MapExport/SceneXmlEnumMaker.exe"
EXCLUSIONS = [ "NULL", "DEFAULT", "VEHICLE", "VEHICLE_TYPE_CAR", "PLAYER", "ROPE", "TOP", "SIGN", "FLAG" ]
def self.log( )
if ( @@log.nil? )
LogFactory.Initialize()
LogFactory.CreateApplicationConsoleLogTarget( )
@@log = LogFactory.ApplicationLog
end
@@log
end
@@log = nil
def initialize( algorithm, scm = false, submit = false )
@config = RSG::Base::Configuration::ConfigFactory::CreateConfig( )
@project = @config.Project
@algorithm = algorithm
@scm = @project.SCMConnect()
@submit = submit
scm_init() if scm
end
#
# High level control
def process( out_filename, input_files)
begin
if (@scm)
change_list = @p4.CreatePendingChangelist( "Automatically Generated EnumMaker File" )
@change_id = change_list.Number
raise Exception if @change_id.nil?
cc_info "Changelist #{@change_id} is created"
cc_info "syncing #{out_filename} on #{@p4.port} #{@p4.client} #{@p4.user}"
@p4.Run('sync', true, out_filename)
end
if (not System::IO::File::Exists(out_filename ) )
dir = System.IO.Path.GetDirectoryName( out_filename )
FileUtils::mkdir_p( dir ) unless ( File::directory?( out_filename ) )
end
if (@scm)
cc_info "edit or add #{out_filename} on #{@p4.port} #{@p4.client} #{@p4.user}"
@p4.ParseAndLogRecordSetForDepotFilename('edit', @p4.Run("edit", true, "-c", @change_id.to_s, out_filename))
@p4.ParseAndLogRecordSetForDepotFilename('reopen', @p4.Run("reopen", true, "-c",@change_id.to_s, out_filename))
@p4.ParseAndLogRecordSetForDepotFilename('reopen', @p4.Run("reopen", true, '-t', '+w', '-c', @change_id.to_s, out_filename))
end
names = read_files( input_files )
cc_info "#{names.length} names have been read from all input files"
write_outfile( out_filename, names)
if (@scm)
cc_info "Reverting unchanged files #{@change_id}"
@p4.ParseAndLogRecordSetForDepotFilename('revert', @p4.Run("revert", true, '-a', '-c', @change_id.to_s, '//...'))
submit_changelist()
end
rescue Exception => ex
error "Error: EnumMaker Process Exception: #{ex.message}"
error "\tStacktrace: #{ex.backtrace.join('\n\r')}"
end
end
#---------------------------------------------------------------------
# Private Methods
#---------------------------------------------------------------------
private
#
# Process all input files
def read_files( filenames )
names = []
filenames.each do |filename|
result = read_file( filename )
cc_info "#{filename} returned #{result.length} names..."
result.each do |res|
names << res if valid_name(res)
end
end
names
end
#
# Write the outfile
def write_outfile( outfile, names)
cc_info "Writing to #{outfile}"
File.open(outfile, "w+") do |o|
o.write(SCH_COMMENT)
o.write(SCH_HEADER)
o.write(SCH_HARDCODED_ENTRIES)
num1 = names.length
names.uniq!
names.sort! { |a,b| a["name"] <=> b["name"] }
names.each_with_index { |n,idx| names[idx-1]["name"] = nil if ( idx-1 >= 0 and ( names[idx-1]["name"] == n["name"]) ) }
names.delete_if { |n| n["name"] == nil }
num2 = names.length
cc_info("#{num1-num2} duplicates removed resulting in #{num2} names")
names.each do |name|
formatted_name = name_format(name["name"], name == names[-1], name["filename"])
debug formatted_name
o.write("#{formatted_name}\n")
end
o.write(SCH_FOOTER)
end
end
#
# Process an input file
def read_file( filename )
error("Error: file #{filename} doesn't exist") unless File.exist?(filename)
filetype = Path::GetExtension(filename).Replace(".", "");
case filetype.downcase
when IDE_FILETYPE
result = read_ide( filename )
cc_info "\t#{filename} returned #{result.length} names"
when SCENEXML_FILETYPE
result = read_scenexml( filename )
cc_info "\t#{filename} returned #{result.length} names"
when META_FILETYPE
result = read_meta( filename )
cc_info "\t#{filename} returned #{result.length} names"
when TXT_FILETYPE
result = read_txt( filename )
cc_info "\t#{filename} returned #{result.length} names"
else
puts "Warning: File #{filename} is not a recognised filetype #{$0}, it must be either .#{IDE_FILETYPE} .#{META_FILETYPE} .#{SCENEXML_FILETYPE} or .#{IDE_FILETYPE}"
end
return result
end
def normalize(file)
return System::IO::Path::GetFullPath(System::Uri.new(file).LocalPath).Replace("\\","/")
.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
.ToLowerInvariant();
end
#
# Process the file list to read.
def read_txt( filename )
error("Error: file #{filename} doesn't exist") unless File.exist?(filename)
result = []
File::open( filename ) do |fp|
lines = fp.readlines()
cc_info "...Parsing txt file (#{num_just(lines.length)} lines) \t: #{filename}"
lines.each do |line|
line.strip!
next if ( 0 == line.size or line.StartsWith( COMMENT ))
result += read_file(normalize(@config.Project.DefaultBranch.Environment.Subst($1))) if line =~ REGEX_TXT
end
end
cc_info "\t#{result.length} names derived from #{filename}"
result
end
#
# Process a .xml scenexml file ( xml )
def read_scenexml( filename )
exists = File.exist?(filename)
error("Error: file #{filename} doesn't exist") unless exists
result = []
if (exists)
sceneXmlParser = normalize(@config.Project.DefaultBranch.Environment.Subst(SCENE_XML_PARSER))
args = "--nopopups #{filename}"
command = "#{sceneXmlParser} #{args}"
cc_info ">>> Running Command : #{command}"
#status, stdout, stderr = systemu(command)
process = System::Diagnostics::Process.new
process.StartInfo.FileName = sceneXmlParser
process.StartInfo.RedirectStandardOutput = true
process.StartInfo.RedirectStandardError = true
process.StartInfo.Arguments = args
process.StartInfo.UseShellExecute = false
process.Start()
stdout = process.StandardOutput.ReadToEnd().Split(Environment.NewLine.ToCharArray())
stderr = process.StandardError.ReadToEnd().Split(Environment.NewLine.ToCharArray())
process.WaitForExit()
cc_info "...Parsing scenexml file (#{num_just(stdout.length)} names) \t: #{filename}"
cc_info "#{command} returned #{process.ExitCode}"
stdout.each do |name|
if not name.nil?
name = name.ToUpper() # DW : mmm I did have this commented out but can't remember why
result << { "name" => name.gsub(/\n/,"").gsub(/\s/,""), "filename" => filename } if name
end
end
stderr.each { |err| error err }
end
result
end
#
# Process a .meta file ( xml )
def read_meta( filename )
error("Error: file #{filename} doesn't exist") unless File.exist?(filename)
result = []
#doc = System::Xml::Linq::XDocument::Load(filename)
#names = doc.root.XPathEvaluate( XPATH_META )
doc = System::Xml::XmlDocument.new
doc.load(filename)
nav = doc.CreateNavigator();
# Compile a standard XPath expression
expr = nav.Compile(XPATH_META);
names = nav.Select(expr);
cc_info "...Parsing meta file (#{num_just(names.Count)} nodes) \t: #{filename}"
names.each do |name|
#info name.text.upcase if name and name.text
result << { "name" => name.Value.upcase, "filename" => filename } if name and name.Value
end
result
end
#
# Process a .ide file ( text )
# eg. V_Club_BarChair, V_Club_TXD, 100, 537001984, 0, -0.340655, -0.334494, -0.001928, 0.340654, 0.334494, 0.879548, -5.066395E-07, 0, 0.43881, 0.6497551, null
def read_ide( filename )
error("Error: file #{filename} doesn't exist") unless File.exist?(filename)
result = []
File::open( filename ) do |fp|
lines = fp.readlines()
cc_info "...Parsing ide file (#{num_just(lines.length)} lines) \t: #{filename}"
lines.each do |line|
line.strip!
next if ( 0 == line.size or line.starts_with( COMMENT ))
result << { "name" => $1.upcase, "filename" => filename } if line =~ REGEX_IDE
end
end
result
end
#
# Present the name in a format for the 'csv' list in the output file.
def name_format( name, last, filename = "" )
sep = "," unless last
str = "#{name.upcase}=#{string_hash(name)}#{sep}"
"#{str.ljust(L_JUST," ")}// #{filename}"
end
#
# Hash the string.
def string_hash( str )
case @algorithm
when :u16
RSG::Base::Security::Cryptography::Hash16U::ComputeHash( str ).to_s
when :s16
RSG::Base::Security::Cryptography::Hash16::ComputeHash( str ).to_s
else
RSG::Base::Security::Cryptography::OneAtATime::ComputeHash( str ).to_s32().to_s
end
end
#
# Is the name valid / desired in the outfile?
def valid_name( name )
puts (">>>>>>>>>>>>>>>>>Excluded #{name}") if name["name"].include?(" ")
puts (">>>>>>>>>>>>>>>>>Excluded #{name}") if EXCLUSIONS.include?(name["name"])
return false if name["name"].include?(" ")
return false if EXCLUSIONS.include?(name["name"])
name["name"] =~ REGEX_NAME
end
#
# Initialise SCM
def scm_init()
@p4 = @project.SCMConnect()
end
#
# submit changelist - also deletes empty changelist.
def submit_changelist( )
files = @p4.Run("opened", true, '-c', @change_id.to_s)
raise Exception if files.nil?
cc_info "There are #{files.Records.size} files to submit."
files.each do |file|
cc_info "**** #{file['depotFile']} has been updated and will be submitted. ****"
end
if ( files.Records.size > 0 ) then
if @submit
cc_info "**** Submitting File currently in #{@change_id} ****"
submit_result = @p4.Run("submit", true, '-c', @change_id.to_s)
puts submit_result.to_s
else
cc_info "**** CL #{@change_id} is waiting for you to submit. ****"
end
elsif ( 0 == files.Records.size ) then
cc_info "Deleting #{@change_id_dest} no files changed."
@p4.Run("change", true, '-d', @change_id.to_s)
end
rescue Exception => ex
error "Error: Unhandled exception in submit_changelist: #{ex.message}"
end
def num_just( num )
num.to_s.rjust(NUM_JUST," ")
end
def info( msg )
EnumMaker::log().Message(msg)
end
def cc_info( msg )
info("#{INFO_BLUE}#{msg}")
end
def debug( msg )
EnumMaker::log().Debug(msg)
end
def error( msg )
$stderr.puts msg
EnumMaker::log().Error(msg)
end
def warning( msg )
puts msg
EnumMaker::log().Warning(msg)
end
end # class EnumMaker
#----------------------------------------------------------------------------
# Implementation
#----------------------------------------------------------------------------
if ( __FILE__ == $0 ) then
begin
g_Options = OS::Options::new( OPTIONS )
if ( g_Options.is_enabled?('help') )
puts g_Options.usage()
exit( 1 )
end
algorithm = g_Options.get('algorithm').to_sym if ( g_Options.has_option?( 'algorithm' ) )
algorithm = :u32 if ( algorithm.nil? )
scm = g_Options.is_enabled?('scm')
submit = g_Options.is_enabled?('submit')
files = g_Options.trailing()
output_filename = files[0]
input_filenames = files[1..-1]
enum_maker = EnumMaker.new( algorithm, scm, submit )
enum_maker.process( output_filename, input_filenames )
LogFactory.FlushApplicationLog();
rescue Exception => ex
puts "Unhandled exception: #{ex.message}."
puts ex.backtrace.join( "\n" )
end
end
# data_mk_hash.rb