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

442 lines
12 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 'pipeline/config/projects'
require 'pipeline/os/getopt'
require 'pipeline/util/rage'
require 'systemu'
include Pipeline
#----------------------------------------------------------------------------
# Constants
#----------------------------------------------------------------------------
OPTIONS = [
[ '--help', '-h', Getopt::BOOLEAN, 'display usage information.' ],
[ '--algorithm', '-a', Getopt::REQUIRED, 'set hashing algorithm (u32, u16, s16).' ],
[ '--scm', '-s', Getopt::BOOLEAN, 'build result file in the SCM' ],
[ '--submit', '-s', Getopt::BOOLEAN, '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 initialize( algorithm, scm = false, submit = false )
@g = Globals::instance( )
@config = Pipeline::Config.instance
@project = @config.projects[ENV['RS_PROJECT']]
@r = RageUtils::new( @project )
@algorithm = algorithm
@scm = scm
@submit = submit
env_init()
scm_init() if scm
end
#
# High level control
def process( out_filename, input_files)
begin
if (@scm)
@change_id = @p4.create_changelist( "Automatically Generated EnumMaker File" )
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( out_filename )
end
if (not File::exists?( out_filename ) )
dir = OS::Path::get_directory( 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.run_edit_or_add( out_filename )
@p4.run_reopen( '-c', @change_id, out_filename )
@p4.run_reopen( '-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.run_revert( '-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
def self.log( )
@@log = Log.new( 'enum_maker' ) if ( @@log.nil? )
@@log
end
@@log = nil
#
# 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 = OS::Path.get_extension(filename).downcase
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
#
# 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.starts_with( COMMENT ))
result += read_file(@env.subst( OS::Path::normalise($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)
command = "#{@env.subst( OS::Path::normalise(SCENE_XML_PARSER))} --nopopups #{filename}"
cc_info ">>> Running Command : #{command}"
status, stdout, stderr = systemu(command)
cc_info "...Parsing scenexml file (#{num_just(stdout.length)} names) \t: #{filename}"
cc_info "#{command} returned #{status}"
stdout.each do |name|
name.upcase! # 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
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 = []
xml = File.read(filename)
doc = REXML::Document.new(xml)
names = doc.elements.to_a( XPATH_META )
cc_info "...Parsing meta file (#{num_just(names.length)} nodes) \t: #{filename}"
names.each do |name|
#info name.text.upcase if name and name.text
result << { "name" => name.text.upcase, "filename" => filename } if name and name.text
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
@r.rage.atHash16U( str ).to_s
when :s16
@r.rage.atHash16( str ).to_s
else
@r.rage.atStringHash( 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 environment
def env_init( )
@env = Environment.new()
@project.fill_env( @env )
#@env.list
end
#
# Initialise SCM
def scm_init()
@p4 = SCM::Perforce.new()
@p4.port = @config.sc_server
@p4.client = @config.sc_workspace
@p4.user = @config.sc_username
@p4.connect()
end
#
# submit changelist - also deletes empty changelist.
def submit_changelist( )
files = @p4.run_opened( '-c', @change_id.to_s )
raise Exception if files.nil?
cc_info "There are #{files.size} files to submit."
files.each do |file|
cc_info "**** #{file['depotFile']} has been updated and will be submitted. ****"
end
if ( files.size > 0 ) then
if @submit
cc_info "**** Submitting File currently in #{@change_id} ****"
submit_result = @p4.run_submit( '-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.size ) then
cc_info "Deleting #{@change_id_dest} no files changed."
@p4.run_change('-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().info(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().warn(msg)
end
end # class EnumMaker
#----------------------------------------------------------------------------
# Implementation
#----------------------------------------------------------------------------
if ( __FILE__ == $0 ) then
begin
options, trailing = OS::Getopt::getopts( OPTIONS )
if ( options['help'] )
puts OS::Getopt::usage( OPTIONS, TRAILING )
exit( 1 )
end
algorithm = options['algorithm'].to_sym if ( options.has_key?( 'algorithm' ) )
algorithm = :u32 if ( algorithm.nil? )
scm = options['scm']
submit = options['submit']
files = trailing
output_filename = files[0]
input_filenames = files[1..-1]
enum_maker = EnumMaker.new( algorithm, scm, submit )
enum_maker.process( output_filename, input_filenames )
rescue Exception => ex
puts "Unhandled exception: #{ex.message}."
puts ex.backtrace.join( "\n" )
end
end
# data_mk_hash.rb