|  | #!/usr/bin/env python3 | 
|  |  | 
|  | # | 
|  | # //===----------------------------------------------------------------------===// | 
|  | # // | 
|  | # // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | # // See https://llvm.org/LICENSE.txt for license information. | 
|  | # // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | # // | 
|  | # //===----------------------------------------------------------------------===// | 
|  | # | 
|  |  | 
|  | import argparse | 
|  | import os | 
|  | import platform | 
|  | import re | 
|  | import sys | 
|  | from libomputils import ScriptError, error | 
|  |  | 
|  |  | 
|  | class TargetPlatform: | 
|  | """Convenience class for handling the target platform for configuration/compilation""" | 
|  |  | 
|  | system_override = None | 
|  | """ | 
|  | Target system name override by the user. | 
|  | It follows the conventions from https://docs.python.org/3/library/platform.html#platform.system | 
|  | """ | 
|  |  | 
|  | def set_system_override(override_system): | 
|  | """ | 
|  | Set a system override for the target. | 
|  | Please follow the style from https://docs.python.org/3/library/platform.html#platform.system | 
|  | """ | 
|  | TargetPlatform.system_override = override_system | 
|  |  | 
|  | def system(): | 
|  | """ | 
|  | Target System name. | 
|  | It follows the conventions from https://docs.python.org/3/library/platform.html#platform.system | 
|  | """ | 
|  | if TargetPlatform.system_override is None: | 
|  | return platform.system() | 
|  | return TargetPlatform.system_override | 
|  |  | 
|  |  | 
|  | class ParseMessageDataError(ScriptError): | 
|  | """Convenience class for parsing message data file errors""" | 
|  |  | 
|  | def __init__(self, filename, line, msg): | 
|  | super(ParseMessageDataError, self).__init__(msg) | 
|  | self.filename = filename | 
|  | self.line = line | 
|  |  | 
|  |  | 
|  | def parse_error(filename, line, msg): | 
|  | raise ParseMessageDataError(filename, line, msg) | 
|  |  | 
|  |  | 
|  | def display_language_id(inputFile): | 
|  | """Quickly parse file for LangId and print it""" | 
|  | regex = re.compile(r'^LangId\s+"([0-9]+)"') | 
|  | with open(inputFile, encoding="utf-8") as f: | 
|  | for line in f: | 
|  | m = regex.search(line) | 
|  | if not m: | 
|  | continue | 
|  | print(m.group(1)) | 
|  |  | 
|  |  | 
|  | class Message(object): | 
|  | special = { | 
|  | "n": "\n", | 
|  | "t": "\t", | 
|  | } | 
|  |  | 
|  | def __init__(self, lineNumber, name, text): | 
|  | self.lineNumber = lineNumber | 
|  | self.name = name | 
|  | self.text = text | 
|  |  | 
|  | def toSrc(self): | 
|  | if TargetPlatform.system().casefold() == "Windows".casefold(): | 
|  | return re.sub(r"%([0-9])\$(s|l?[du])", r"%\1!\2!", self.text) | 
|  | return str(self.text) | 
|  |  | 
|  | def toMC(self): | 
|  | retval = self.toSrc() | 
|  | for special, substitute in Message.special.items(): | 
|  | retval = re.sub(r"\\{}".format(special), substitute, retval) | 
|  | return retval | 
|  |  | 
|  |  | 
|  | class MessageData(object): | 
|  | """ | 
|  | Convenience class representing message data parsed from i18n/* files | 
|  |  | 
|  | Generate these objects using static create() factory method | 
|  | """ | 
|  |  | 
|  | sectionInfo = { | 
|  | "meta": {"short": "prp", "long": "meta", "set": 1, "base": 1 << 16}, | 
|  | "strings": {"short": "str", "long": "strings", "set": 2, "base": 2 << 16}, | 
|  | "formats": {"short": "fmt", "long": "formats", "set": 3, "base": 3 << 16}, | 
|  | "messages": {"short": "msg", "long": "messages", "set": 4, "base": 4 << 16}, | 
|  | "hints": {"short": "hnt", "long": "hints", "set": 5, "base": 5 << 16}, | 
|  | } | 
|  | orderedSections = ["meta", "strings", "formats", "messages", "hints"] | 
|  |  | 
|  | def __init__(self): | 
|  | self.filename = None | 
|  | self.sections = {} | 
|  |  | 
|  | def getMeta(self, name): | 
|  | metaList = self.sections["meta"] | 
|  | for meta in metaList: | 
|  | if meta.name == name: | 
|  | return meta.text | 
|  | error( | 
|  | 'No "{}" detected in meta data' " for file {}".format(name, self.filename) | 
|  | ) | 
|  |  | 
|  | @staticmethod | 
|  | def create(inputFile): | 
|  | """Creates MessageData object from inputFile""" | 
|  | data = MessageData() | 
|  | data.filename = os.path.abspath(inputFile) | 
|  | obsolete = 1 | 
|  | sectionRegex = re.compile(r"-\*- ([a-zA-Z0-9_]+) -\*-") | 
|  | keyValueRegex = re.compile(r'([a-zA-Z_][a-zA-Z0-9_]*)\s+"(.*)"') | 
|  | moreValueRegex = re.compile(r'"(.*)"') | 
|  |  | 
|  | with open(inputFile, "r", encoding="utf-8") as f: | 
|  | currentSection = None | 
|  | currentKey = None | 
|  | for lineNumber, line in enumerate(f, 1): | 
|  | line = line.strip() | 
|  | # Skip empty lines | 
|  | if not line: | 
|  | continue | 
|  | # Skip comment lines | 
|  | if line.startswith("#"): | 
|  | continue | 
|  | # Matched a section header | 
|  | match = sectionRegex.search(line) | 
|  | if match: | 
|  | currentSection = match.group(1).lower() | 
|  | if currentSection in data.sections: | 
|  | parse_error( | 
|  | inputFile, | 
|  | lineNumber, | 
|  | "section: {} already defined".format(currentSection), | 
|  | ) | 
|  | data.sections[currentSection] = [] | 
|  | continue | 
|  | # Matched a Key "Value" line (most lines) | 
|  | match = keyValueRegex.search(line) | 
|  | if match: | 
|  | if not currentSection: | 
|  | parse_error(inputFile, lineNumber, "no section defined yet.") | 
|  | key = match.group(1) | 
|  | if key == "OBSOLETE": | 
|  | key = "OBSOLETE{}".format(obsolete) | 
|  | obsolete += 1 | 
|  | value = match.group(2) | 
|  | currentKey = key | 
|  | data.sections[currentSection].append( | 
|  | Message(lineNumber, key, value) | 
|  | ) | 
|  | continue | 
|  | # Matched a Continuation of string line | 
|  | match = moreValueRegex.search(line) | 
|  | if match: | 
|  | value = match.group(1) | 
|  | if not currentSection: | 
|  | parse_error(inputFile, lineNumber, "no section defined yet.") | 
|  | if not currentKey: | 
|  | parse_error(inputFile, lineNumber, "no key defined yet.") | 
|  | data.sections[currentSection][-1].text += value | 
|  | continue | 
|  | # Unknown line syntax | 
|  | parse_error(inputFile, lineNumber, "bad line:\n{}".format(line)) | 
|  | return data | 
|  |  | 
|  |  | 
|  | def insert_header(f, data, commentChar="//"): | 
|  | f.write( | 
|  | "{0} Do not edit this file! {0}\n" | 
|  | "{0} The file was generated from" | 
|  | " {1} by {2}. {0}\n\n".format( | 
|  | commentChar, | 
|  | os.path.basename(data.filename), | 
|  | os.path.basename(__file__), | 
|  | ) | 
|  | ) | 
|  |  | 
|  |  | 
|  | def generate_enum_file(enumFile, prefix, data): | 
|  | """Create the include file with message enums""" | 
|  | global g_sections | 
|  | with open(enumFile, "w") as f: | 
|  | insert_header(f, data) | 
|  | f.write( | 
|  | "enum {0}_id {1}\n" | 
|  | "\n" | 
|  | "    // A special id for absence of message.\n" | 
|  | "    {0}_null = 0,\n" | 
|  | "\n".format(prefix, "{") | 
|  | ) | 
|  | for section in MessageData.orderedSections: | 
|  | messages = data.sections[section] | 
|  | info = MessageData.sectionInfo[section] | 
|  | shortName = info["short"] | 
|  | longName = info["long"] | 
|  | base = info["base"] | 
|  | setIdx = info["set"] | 
|  | f.write( | 
|  | "    // Set #{}, {}.\n" | 
|  | "    {}_{}_first = {},\n".format( | 
|  | setIdx, longName, prefix, shortName, base | 
|  | ) | 
|  | ) | 
|  | for message in messages: | 
|  | f.write("    {}_{}_{},\n".format(prefix, shortName, message.name)) | 
|  | f.write("    {}_{}_last,\n\n".format(prefix, shortName)) | 
|  | f.write( | 
|  | "    {0}_xxx_lastest\n\n" | 
|  | "{1}; // enum {0}_id\n\n" | 
|  | "typedef enum {0}_id  {0}_id_t;\n\n\n" | 
|  | "// end of file //\n".format(prefix, "}") | 
|  | ) | 
|  |  | 
|  |  | 
|  | def generate_signature_file(signatureFile, data): | 
|  | """Create the signature file""" | 
|  | sigRegex = re.compile(r"(%[0-9]\$(s|l?[du]))") | 
|  | with open(signatureFile, "w") as f: | 
|  | f.write("// message catalog signature file //\n\n") | 
|  | for section in MessageData.orderedSections: | 
|  | messages = data.sections[section] | 
|  | longName = MessageData.sectionInfo[section]["long"] | 
|  | f.write("-*- {}-*-\n\n".format(longName.upper())) | 
|  | for message in messages: | 
|  | sigs = sorted(list(set([a for a, b in sigRegex.findall(message.text)]))) | 
|  | i = 0 | 
|  | # Insert empty placeholders if necessary | 
|  | while i != len(sigs): | 
|  | num = i + 1 | 
|  | if not sigs[i].startswith("%{}".format(num)): | 
|  | sigs.insert(i, "%{}$-".format(num)) | 
|  | else: | 
|  | i += 1 | 
|  | f.write("{:<40} {}\n".format(message.name, " ".join(sigs))) | 
|  | f.write("\n") | 
|  | f.write("// end of file //\n") | 
|  |  | 
|  |  | 
|  | def generate_default_messages_file(defaultFile, prefix, data): | 
|  | """Create the include file with message strings organized""" | 
|  | with open(defaultFile, "w", encoding="utf-8") as f: | 
|  | insert_header(f, data) | 
|  | for section in MessageData.orderedSections: | 
|  | f.write( | 
|  | "static char const *\n" | 
|  | "__{}_default_{}[] =\n" | 
|  | "    {}\n" | 
|  | "        NULL,\n".format(prefix, section, "{") | 
|  | ) | 
|  | messages = data.sections[section] | 
|  | for message in messages: | 
|  | f.write('        "{}",\n'.format(message.toSrc())) | 
|  | f.write("        NULL\n" "    {};\n\n".format("}")) | 
|  | f.write( | 
|  | "struct kmp_i18n_section {0}\n" | 
|  | "    int           size;\n" | 
|  | "    char const ** str;\n" | 
|  | "{1}; // struct kmp_i18n_section\n" | 
|  | "typedef struct kmp_i18n_section  kmp_i18n_section_t;\n\n" | 
|  | "static kmp_i18n_section_t\n" | 
|  | "__{2}_sections[] =\n" | 
|  | "    {0}\n" | 
|  | "        {0} 0, NULL {1},\n".format("{", "}", prefix) | 
|  | ) | 
|  |  | 
|  | for section in MessageData.orderedSections: | 
|  | messages = data.sections[section] | 
|  | f.write( | 
|  | "        {} {}, __{}_default_{} {},\n".format( | 
|  | "{", len(messages), prefix, section, "}" | 
|  | ) | 
|  | ) | 
|  | numSections = len(MessageData.orderedSections) | 
|  | f.write( | 
|  | "        {0} 0, NULL {1}\n" | 
|  | "    {1};\n\n" | 
|  | "struct kmp_i18n_table {0}\n" | 
|  | "    int                   size;\n" | 
|  | "    kmp_i18n_section_t *  sect;\n" | 
|  | "{1}; // struct kmp_i18n_table\n" | 
|  | "typedef struct kmp_i18n_table  kmp_i18n_table_t;\n\n" | 
|  | "static kmp_i18n_table_t __kmp_i18n_default_table =\n" | 
|  | "    {0}\n" | 
|  | "        {3},\n" | 
|  | "        __{2}_sections\n" | 
|  | "    {1};\n\n" | 
|  | "// end of file //\n".format("{", "}", prefix, numSections) | 
|  | ) | 
|  |  | 
|  |  | 
|  | def generate_message_file_unix(messageFile, data): | 
|  | """ | 
|  | Create the message file for Unix OSes | 
|  |  | 
|  | Encoding is in UTF-8 | 
|  | """ | 
|  | with open(messageFile, "w", encoding="utf-8") as f: | 
|  | insert_header(f, data, commentChar="$") | 
|  | f.write('$quote "\n\n') | 
|  | for section in MessageData.orderedSections: | 
|  | setIdx = MessageData.sectionInfo[section]["set"] | 
|  | f.write( | 
|  | "$ ------------------------------------------------------------------------------\n" | 
|  | "$ {}\n" | 
|  | "$ ------------------------------------------------------------------------------\n\n" | 
|  | "$set {}\n\n".format(section, setIdx) | 
|  | ) | 
|  | messages = data.sections[section] | 
|  | for num, message in enumerate(messages, 1): | 
|  | f.write('{} "{}"\n'.format(num, message.toSrc())) | 
|  | f.write("\n") | 
|  | f.write("\n$ end of file $") | 
|  |  | 
|  |  | 
|  | def generate_message_file_windows(messageFile, data): | 
|  | """ | 
|  | Create the message file for Windows OS | 
|  |  | 
|  | Encoding is in UTF-16LE | 
|  | """ | 
|  | language = data.getMeta("Language") | 
|  | langId = data.getMeta("LangId") | 
|  | with open(messageFile, "w", encoding="utf-16-le") as f: | 
|  | insert_header(f, data, commentChar=";") | 
|  | f.write("\nLanguageNames = ({0}={1}:msg_{1})\n\n".format(language, langId)) | 
|  | f.write("FacilityNames=(\n") | 
|  | for section in MessageData.orderedSections: | 
|  | setIdx = MessageData.sectionInfo[section]["set"] | 
|  | shortName = MessageData.sectionInfo[section]["short"] | 
|  | f.write(" {}={}\n".format(shortName, setIdx)) | 
|  | f.write(")\n\n") | 
|  |  | 
|  | for section in MessageData.orderedSections: | 
|  | shortName = MessageData.sectionInfo[section]["short"] | 
|  | n = 0 | 
|  | messages = data.sections[section] | 
|  | for message in messages: | 
|  | n += 1 | 
|  | f.write( | 
|  | "MessageId={}\n" | 
|  | "Facility={}\n" | 
|  | "Language={}\n" | 
|  | "{}\n.\n\n".format(n, shortName, language, message.toMC()) | 
|  | ) | 
|  | f.write("\n; end of file ;") | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | parser = argparse.ArgumentParser(description="Generate message data files") | 
|  | parser.add_argument( | 
|  | "--lang-id", | 
|  | action="store_true", | 
|  | help="Print language identifier of the message catalog source file", | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--prefix", | 
|  | default="kmp_i18n", | 
|  | help="Prefix to be used for all C identifiers (type and variable names)" | 
|  | " in enum and default message files.", | 
|  | ) | 
|  | parser.add_argument("--enum", metavar="FILE", help="Generate enum file named FILE") | 
|  | parser.add_argument( | 
|  | "--default", metavar="FILE", help="Generate default messages file named FILE" | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--signature", metavar="FILE", help="Generate signature file named FILE" | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--message", metavar="FILE", help="Generate message file named FILE" | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--target-system-override", | 
|  | metavar="TARGET_SYSTEM_NAME", | 
|  | help="Target System override.\n" | 
|  | "By default the target system is the host system\n" | 
|  | "See possible values at https://docs.python.org/3/library/platform.html#platform.system", | 
|  | ) | 
|  | parser.add_argument("inputfile") | 
|  | commandArgs = parser.parse_args() | 
|  |  | 
|  | if commandArgs.lang_id: | 
|  | display_language_id(commandArgs.inputfile) | 
|  | return | 
|  | data = MessageData.create(commandArgs.inputfile) | 
|  | prefix = commandArgs.prefix | 
|  | if commandArgs.target_system_override: | 
|  | TargetPlatform.set_system_override(commandArgs.target_system_override) | 
|  | if commandArgs.enum: | 
|  | generate_enum_file(commandArgs.enum, prefix, data) | 
|  | if commandArgs.default: | 
|  | generate_default_messages_file(commandArgs.default, prefix, data) | 
|  | if commandArgs.signature: | 
|  | generate_signature_file(commandArgs.signature, data) | 
|  | if commandArgs.message: | 
|  | if TargetPlatform.system().casefold() == "Windows".casefold(): | 
|  | generate_message_file_windows(commandArgs.message, data) | 
|  | else: | 
|  | generate_message_file_unix(commandArgs.message, data) | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | try: | 
|  | main() | 
|  | except ScriptError as e: | 
|  | print("error: {}".format(e)) | 
|  | sys.exit(1) | 
|  |  | 
|  | # end of file |