442 lines
12 KiB
Ruby
Executable File
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
|