# # 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 # 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