428 lines · python
1#!/usr/bin/env python32 3#4# //===----------------------------------------------------------------------===//5# //6# // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.7# // See https://llvm.org/LICENSE.txt for license information.8# // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception9# //10# //===----------------------------------------------------------------------===//11#12 13import argparse14import os15import platform16import re17import sys18from libomputils import ScriptError, error19 20 21class TargetPlatform:22 """Convenience class for handling the target platform for configuration/compilation"""23 24 system_override = None25 """26 Target system name override by the user.27 It follows the conventions from https://docs.python.org/3/library/platform.html#platform.system28 """29 30 def set_system_override(override_system):31 """32 Set a system override for the target.33 Please follow the style from https://docs.python.org/3/library/platform.html#platform.system34 """35 TargetPlatform.system_override = override_system36 37 def system():38 """39 Target System name.40 It follows the conventions from https://docs.python.org/3/library/platform.html#platform.system41 """42 if TargetPlatform.system_override is None:43 return platform.system()44 return TargetPlatform.system_override45 46 47class ParseMessageDataError(ScriptError):48 """Convenience class for parsing message data file errors"""49 50 def __init__(self, filename, line, msg):51 super(ParseMessageDataError, self).__init__(msg)52 self.filename = filename53 self.line = line54 55 56def parse_error(filename, line, msg):57 raise ParseMessageDataError(filename, line, msg)58 59 60def display_language_id(inputFile):61 """Quickly parse file for LangId and print it"""62 regex = re.compile(r'^LangId\s+"([0-9]+)"')63 with open(inputFile, encoding="utf-8") as f:64 for line in f:65 m = regex.search(line)66 if not m:67 continue68 print(m.group(1))69 70 71class Message(object):72 special = {73 "n": "\n",74 "t": "\t",75 }76 77 def __init__(self, lineNumber, name, text):78 self.lineNumber = lineNumber79 self.name = name80 self.text = text81 82 def toSrc(self):83 if TargetPlatform.system().casefold() == "Windows".casefold():84 return re.sub(r"%([0-9])\$(s|l?[du])", r"%\1!\2!", self.text)85 return str(self.text)86 87 def toMC(self):88 retval = self.toSrc()89 for special, substitute in Message.special.items():90 retval = re.sub(r"\\{}".format(special), substitute, retval)91 return retval92 93 94class MessageData(object):95 """96 Convenience class representing message data parsed from i18n/* files97 98 Generate these objects using static create() factory method99 """100 101 sectionInfo = {102 "meta": {"short": "prp", "long": "meta", "set": 1, "base": 1 << 16},103 "strings": {"short": "str", "long": "strings", "set": 2, "base": 2 << 16},104 "formats": {"short": "fmt", "long": "formats", "set": 3, "base": 3 << 16},105 "messages": {"short": "msg", "long": "messages", "set": 4, "base": 4 << 16},106 "hints": {"short": "hnt", "long": "hints", "set": 5, "base": 5 << 16},107 }108 orderedSections = ["meta", "strings", "formats", "messages", "hints"]109 110 def __init__(self):111 self.filename = None112 self.sections = {}113 114 def getMeta(self, name):115 metaList = self.sections["meta"]116 for meta in metaList:117 if meta.name == name:118 return meta.text119 error(120 'No "{}" detected in meta data' " for file {}".format(name, self.filename)121 )122 123 @staticmethod124 def create(inputFile):125 """Creates MessageData object from inputFile"""126 data = MessageData()127 data.filename = os.path.abspath(inputFile)128 obsolete = 1129 sectionRegex = re.compile(r"-\*- ([a-zA-Z0-9_]+) -\*-")130 keyValueRegex = re.compile(r'([a-zA-Z_][a-zA-Z0-9_]*)\s+"(.*)"')131 moreValueRegex = re.compile(r'"(.*)"')132 133 with open(inputFile, "r", encoding="utf-8") as f:134 currentSection = None135 currentKey = None136 for lineNumber, line in enumerate(f, 1):137 line = line.strip()138 # Skip empty lines139 if not line:140 continue141 # Skip comment lines142 if line.startswith("#"):143 continue144 # Matched a section header145 match = sectionRegex.search(line)146 if match:147 currentSection = match.group(1).lower()148 if currentSection in data.sections:149 parse_error(150 inputFile,151 lineNumber,152 "section: {} already defined".format(currentSection),153 )154 data.sections[currentSection] = []155 continue156 # Matched a Key "Value" line (most lines)157 match = keyValueRegex.search(line)158 if match:159 if not currentSection:160 parse_error(inputFile, lineNumber, "no section defined yet.")161 key = match.group(1)162 if key == "OBSOLETE":163 key = "OBSOLETE{}".format(obsolete)164 obsolete += 1165 value = match.group(2)166 currentKey = key167 data.sections[currentSection].append(168 Message(lineNumber, key, value)169 )170 continue171 # Matched a Continuation of string line172 match = moreValueRegex.search(line)173 if match:174 value = match.group(1)175 if not currentSection:176 parse_error(inputFile, lineNumber, "no section defined yet.")177 if not currentKey:178 parse_error(inputFile, lineNumber, "no key defined yet.")179 data.sections[currentSection][-1].text += value180 continue181 # Unknown line syntax182 parse_error(inputFile, lineNumber, "bad line:\n{}".format(line))183 return data184 185 186def insert_header(f, data, commentChar="//"):187 f.write(188 "{0} Do not edit this file! {0}\n"189 "{0} The file was generated from"190 " {1} by {2}. {0}\n\n".format(191 commentChar,192 os.path.basename(data.filename),193 os.path.basename(__file__),194 )195 )196 197 198def generate_enum_file(enumFile, prefix, data):199 """Create the include file with message enums"""200 global g_sections201 with open(enumFile, "w") as f:202 insert_header(f, data)203 f.write(204 "enum {0}_id {1}\n"205 "\n"206 " // A special id for absence of message.\n"207 " {0}_null = 0,\n"208 "\n".format(prefix, "{")209 )210 for section in MessageData.orderedSections:211 messages = data.sections[section]212 info = MessageData.sectionInfo[section]213 shortName = info["short"]214 longName = info["long"]215 base = info["base"]216 setIdx = info["set"]217 f.write(218 " // Set #{}, {}.\n"219 " {}_{}_first = {},\n".format(220 setIdx, longName, prefix, shortName, base221 )222 )223 for message in messages:224 f.write(" {}_{}_{},\n".format(prefix, shortName, message.name))225 f.write(" {}_{}_last,\n\n".format(prefix, shortName))226 f.write(227 " {0}_xxx_lastest\n\n"228 "{1}; // enum {0}_id\n\n"229 "typedef enum {0}_id {0}_id_t;\n\n\n"230 "// end of file //\n".format(prefix, "}")231 )232 233 234def generate_signature_file(signatureFile, data):235 """Create the signature file"""236 sigRegex = re.compile(r"(%[0-9]\$(s|l?[du]))")237 with open(signatureFile, "w") as f:238 f.write("// message catalog signature file //\n\n")239 for section in MessageData.orderedSections:240 messages = data.sections[section]241 longName = MessageData.sectionInfo[section]["long"]242 f.write("-*- {}-*-\n\n".format(longName.upper()))243 for message in messages:244 sigs = sorted(list(set([a for a, b in sigRegex.findall(message.text)])))245 i = 0246 # Insert empty placeholders if necessary247 while i != len(sigs):248 num = i + 1249 if not sigs[i].startswith("%{}".format(num)):250 sigs.insert(i, "%{}$-".format(num))251 else:252 i += 1253 f.write("{:<40} {}\n".format(message.name, " ".join(sigs)))254 f.write("\n")255 f.write("// end of file //\n")256 257 258def generate_default_messages_file(defaultFile, prefix, data):259 """Create the include file with message strings organized"""260 with open(defaultFile, "w", encoding="utf-8") as f:261 insert_header(f, data)262 for section in MessageData.orderedSections:263 f.write(264 "static char const *\n"265 "__{}_default_{}[] =\n"266 " {}\n"267 " NULL,\n".format(prefix, section, "{")268 )269 messages = data.sections[section]270 for message in messages:271 f.write(' "{}",\n'.format(message.toSrc()))272 f.write(" NULL\n" " {};\n\n".format("}"))273 f.write(274 "struct kmp_i18n_section {0}\n"275 " int size;\n"276 " char const ** str;\n"277 "{1}; // struct kmp_i18n_section\n"278 "typedef struct kmp_i18n_section kmp_i18n_section_t;\n\n"279 "static kmp_i18n_section_t\n"280 "__{2}_sections[] =\n"281 " {0}\n"282 " {0} 0, NULL {1},\n".format("{", "}", prefix)283 )284 285 for section in MessageData.orderedSections:286 messages = data.sections[section]287 f.write(288 " {} {}, __{}_default_{} {},\n".format(289 "{", len(messages), prefix, section, "}"290 )291 )292 numSections = len(MessageData.orderedSections)293 f.write(294 " {0} 0, NULL {1}\n"295 " {1};\n\n"296 "struct kmp_i18n_table {0}\n"297 " int size;\n"298 " kmp_i18n_section_t * sect;\n"299 "{1}; // struct kmp_i18n_table\n"300 "typedef struct kmp_i18n_table kmp_i18n_table_t;\n\n"301 "static kmp_i18n_table_t __kmp_i18n_default_table =\n"302 " {0}\n"303 " {3},\n"304 " __{2}_sections\n"305 " {1};\n\n"306 "// end of file //\n".format("{", "}", prefix, numSections)307 )308 309 310def generate_message_file_unix(messageFile, data):311 """312 Create the message file for Unix OSes313 314 Encoding is in UTF-8315 """316 with open(messageFile, "w", encoding="utf-8") as f:317 insert_header(f, data, commentChar="$")318 f.write('$quote "\n\n')319 for section in MessageData.orderedSections:320 setIdx = MessageData.sectionInfo[section]["set"]321 f.write(322 "$ ------------------------------------------------------------------------------\n"323 "$ {}\n"324 "$ ------------------------------------------------------------------------------\n\n"325 "$set {}\n\n".format(section, setIdx)326 )327 messages = data.sections[section]328 for num, message in enumerate(messages, 1):329 f.write('{} "{}"\n'.format(num, message.toSrc()))330 f.write("\n")331 f.write("\n$ end of file $")332 333 334def generate_message_file_windows(messageFile, data):335 """336 Create the message file for Windows OS337 338 Encoding is in UTF-16LE339 """340 language = data.getMeta("Language")341 langId = data.getMeta("LangId")342 with open(messageFile, "w", encoding="utf-16-le") as f:343 insert_header(f, data, commentChar=";")344 f.write("\nLanguageNames = ({0}={1}:msg_{1})\n\n".format(language, langId))345 f.write("FacilityNames=(\n")346 for section in MessageData.orderedSections:347 setIdx = MessageData.sectionInfo[section]["set"]348 shortName = MessageData.sectionInfo[section]["short"]349 f.write(" {}={}\n".format(shortName, setIdx))350 f.write(")\n\n")351 352 for section in MessageData.orderedSections:353 shortName = MessageData.sectionInfo[section]["short"]354 n = 0355 messages = data.sections[section]356 for message in messages:357 n += 1358 f.write(359 "MessageId={}\n"360 "Facility={}\n"361 "Language={}\n"362 "{}\n.\n\n".format(n, shortName, language, message.toMC())363 )364 f.write("\n; end of file ;")365 366 367def main():368 parser = argparse.ArgumentParser(description="Generate message data files")369 parser.add_argument(370 "--lang-id",371 action="store_true",372 help="Print language identifier of the message catalog source file",373 )374 parser.add_argument(375 "--prefix",376 default="kmp_i18n",377 help="Prefix to be used for all C identifiers (type and variable names)"378 " in enum and default message files.",379 )380 parser.add_argument("--enum", metavar="FILE", help="Generate enum file named FILE")381 parser.add_argument(382 "--default", metavar="FILE", help="Generate default messages file named FILE"383 )384 parser.add_argument(385 "--signature", metavar="FILE", help="Generate signature file named FILE"386 )387 parser.add_argument(388 "--message", metavar="FILE", help="Generate message file named FILE"389 )390 parser.add_argument(391 "--target-system-override",392 metavar="TARGET_SYSTEM_NAME",393 help="Target System override.\n"394 "By default the target system is the host system\n"395 "See possible values at https://docs.python.org/3/library/platform.html#platform.system",396 )397 parser.add_argument("inputfile")398 commandArgs = parser.parse_args()399 400 if commandArgs.lang_id:401 display_language_id(commandArgs.inputfile)402 return403 data = MessageData.create(commandArgs.inputfile)404 prefix = commandArgs.prefix405 if commandArgs.target_system_override:406 TargetPlatform.set_system_override(commandArgs.target_system_override)407 if commandArgs.enum:408 generate_enum_file(commandArgs.enum, prefix, data)409 if commandArgs.default:410 generate_default_messages_file(commandArgs.default, prefix, data)411 if commandArgs.signature:412 generate_signature_file(commandArgs.signature, data)413 if commandArgs.message:414 if TargetPlatform.system().casefold() == "Windows".casefold():415 generate_message_file_windows(commandArgs.message, data)416 else:417 generate_message_file_unix(commandArgs.message, data)418 419 420if __name__ == "__main__":421 try:422 main()423 except ScriptError as e:424 print("error: {}".format(e))425 sys.exit(1)426 427# end of file428