4 # //===----------------------------------------------------------------------===//
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-exception
10 # //===----------------------------------------------------------------------===//
19 from libomputils
import ScriptError
, error
23 """Convenience class for handling the target platform for configuration/compilation"""
25 system_override
= None
27 Target system name override by the user.
28 It follows the conventions from https://docs.python.org/3/library/platform.html#platform.system
31 def set_system_override(override_system
):
33 Set a system override for the target.
34 Please follow the style from https://docs.python.org/3/library/platform.html#platform.system
36 TargetPlatform
.system_override
= override_system
41 It follows the conventions from https://docs.python.org/3/library/platform.html#platform.system
43 if TargetPlatform
.system_override
is None:
44 return platform
.system()
45 return TargetPlatform
.system_override
48 class ParseMessageDataError(ScriptError
):
49 """Convenience class for parsing message data file errors"""
51 def __init__(self
, filename
, line
, msg
):
52 super(ParseMessageDataError
, self
).__init
__(msg
)
53 self
.filename
= filename
57 def parse_error(filename
, line
, msg
):
58 raise ParseMessageDataError(filename
, line
, msg
)
61 def display_language_id(inputFile
):
62 """Quickly parse file for LangId and print it"""
63 regex
= re
.compile(r
'^LangId\s+"([0-9]+)"')
64 with
open(inputFile
, encoding
="utf-8") as f
:
66 m
= regex
.search(line
)
72 class Message(object):
78 def __init__(self
, lineNumber
, name
, text
):
79 self
.lineNumber
= lineNumber
84 if TargetPlatform
.system().casefold() == "Windows".casefold():
85 return re
.sub(r
"%([0-9])\$(s|l?[du])", r
"%\1!\2!", self
.text
)
90 for special
, substitute
in Message
.special
.items():
91 retval
= re
.sub(r
"\\{}".format(special
), substitute
, retval
)
95 class MessageData(object):
97 Convenience class representing message data parsed from i18n/* files
99 Generate these objects using static create() factory method
103 "meta": {"short": "prp", "long": "meta", "set": 1, "base": 1 << 16},
104 "strings": {"short": "str", "long": "strings", "set": 2, "base": 2 << 16},
105 "formats": {"short": "fmt", "long": "formats", "set": 3, "base": 3 << 16},
106 "messages": {"short": "msg", "long": "messages", "set": 4, "base": 4 << 16},
107 "hints": {"short": "hnt", "long": "hints", "set": 5, "base": 5 << 16},
109 orderedSections
= ["meta", "strings", "formats", "messages", "hints"]
115 def getMeta(self
, name
):
116 metaList
= self
.sections
["meta"]
117 for meta
in metaList
:
118 if meta
.name
== name
:
121 'No "{}" detected in meta data' " for file {}".format(name
, self
.filename
)
125 def create(inputFile
):
126 """Creates MessageData object from inputFile"""
128 data
.filename
= os
.path
.abspath(inputFile
)
130 sectionRegex
= re
.compile(r
"-\*- ([a-zA-Z0-9_]+) -\*-")
131 keyValueRegex
= re
.compile(r
'([a-zA-Z_][a-zA-Z0-9_]*)\s+"(.*)"')
132 moreValueRegex
= re
.compile(r
'"(.*)"')
134 with
open(inputFile
, "r", encoding
="utf-8") as f
:
135 currentSection
= None
137 for lineNumber
, line
in enumerate(f
, 1):
143 if line
.startswith("#"):
145 # Matched a section header
146 match
= sectionRegex
.search(line
)
148 currentSection
= match
.group(1).lower()
149 if currentSection
in data
.sections
:
153 "section: {} already defined".format(currentSection
),
155 data
.sections
[currentSection
] = []
157 # Matched a Key "Value" line (most lines)
158 match
= keyValueRegex
.search(line
)
160 if not currentSection
:
161 parse_error(inputFile
, lineNumber
, "no section defined yet.")
163 if key
== "OBSOLETE":
164 key
= "OBSOLETE{}".format(obsolete
)
166 value
= match
.group(2)
168 data
.sections
[currentSection
].append(
169 Message(lineNumber
, key
, value
)
172 # Matched a Continuation of string line
173 match
= moreValueRegex
.search(line
)
175 value
= match
.group(1)
176 if not currentSection
:
177 parse_error(inputFile
, lineNumber
, "no section defined yet.")
179 parse_error(inputFile
, lineNumber
, "no key defined yet.")
180 data
.sections
[currentSection
][-1].text
+= value
182 # Unknown line syntax
183 parse_error(inputFile
, lineNumber
, "bad line:\n{}".format(line
))
187 def insert_header(f
, data
, commentChar
="//"):
189 "{0} Do not edit this file! {0}\n"
190 "{0} The file was generated from"
191 " {1} by {2} on {3}. {0}\n\n".format(
193 os
.path
.basename(data
.filename
),
194 os
.path
.basename(__file__
),
195 datetime
.datetime
.now().ctime(),
200 def generate_enum_file(enumFile
, prefix
, data
):
201 """Create the include file with message enums"""
203 with
open(enumFile
, "w") as f
:
204 insert_header(f
, data
)
208 " // A special id for absence of message.\n"
210 "\n".format(prefix
, "{")
212 for section
in MessageData
.orderedSections
:
213 messages
= data
.sections
[section
]
214 info
= MessageData
.sectionInfo
[section
]
215 shortName
= info
["short"]
216 longName
= info
["long"]
221 " {}_{}_first = {},\n".format(
222 setIdx
, longName
, prefix
, shortName
, base
225 for message
in messages
:
226 f
.write(" {}_{}_{},\n".format(prefix
, shortName
, message
.name
))
227 f
.write(" {}_{}_last,\n\n".format(prefix
, shortName
))
229 " {0}_xxx_lastest\n\n"
230 "{1}; // enum {0}_id\n\n"
231 "typedef enum {0}_id {0}_id_t;\n\n\n"
232 "// end of file //\n".format(prefix
, "}")
236 def generate_signature_file(signatureFile
, data
):
237 """Create the signature file"""
238 sigRegex
= re
.compile(r
"(%[0-9]\$(s|l?[du]))")
239 with
open(signatureFile
, "w") as f
:
240 f
.write("// message catalog signature file //\n\n")
241 for section
in MessageData
.orderedSections
:
242 messages
= data
.sections
[section
]
243 longName
= MessageData
.sectionInfo
[section
]["long"]
244 f
.write("-*- {}-*-\n\n".format(longName
.upper()))
245 for message
in messages
:
246 sigs
= sorted(list(set([a
for a
, b
in sigRegex
.findall(message
.text
)])))
248 # Insert empty placeholders if necessary
249 while i
!= len(sigs
):
251 if not sigs
[i
].startswith("%{}".format(num
)):
252 sigs
.insert(i
, "%{}$-".format(num
))
255 f
.write("{:<40} {}\n".format(message
.name
, " ".join(sigs
)))
257 f
.write("// end of file //\n")
260 def generate_default_messages_file(defaultFile
, prefix
, data
):
261 """Create the include file with message strings organized"""
262 with
open(defaultFile
, "w", encoding
="utf-8") as f
:
263 insert_header(f
, data
)
264 for section
in MessageData
.orderedSections
:
266 "static char const *\n"
267 "__{}_default_{}[] =\n"
269 " NULL,\n".format(prefix
, section
, "{")
271 messages
= data
.sections
[section
]
272 for message
in messages
:
273 f
.write(' "{}",\n'.format(message
.toSrc()))
274 f
.write(" NULL\n" " {};\n\n".format("}"))
276 "struct kmp_i18n_section {0}\n"
278 " char const ** str;\n"
279 "{1}; // struct kmp_i18n_section\n"
280 "typedef struct kmp_i18n_section kmp_i18n_section_t;\n\n"
281 "static kmp_i18n_section_t\n"
282 "__{2}_sections[] =\n"
284 " {0} 0, NULL {1},\n".format("{", "}", prefix
)
287 for section
in MessageData
.orderedSections
:
288 messages
= data
.sections
[section
]
290 " {} {}, __{}_default_{} {},\n".format(
291 "{", len(messages
), prefix
, section
, "}"
294 numSections
= len(MessageData
.orderedSections
)
298 "struct kmp_i18n_table {0}\n"
300 " kmp_i18n_section_t * sect;\n"
301 "{1}; // struct kmp_i18n_table\n"
302 "typedef struct kmp_i18n_table kmp_i18n_table_t;\n\n"
303 "static kmp_i18n_table_t __kmp_i18n_default_table =\n"
308 "// end of file //\n".format("{", "}", prefix
, numSections
)
312 def generate_message_file_unix(messageFile
, data
):
314 Create the message file for Unix OSes
318 with
open(messageFile
, "w", encoding
="utf-8") as f
:
319 insert_header(f
, data
, commentChar
="$")
320 f
.write('$quote "\n\n')
321 for section
in MessageData
.orderedSections
:
322 setIdx
= MessageData
.sectionInfo
[section
]["set"]
324 "$ ------------------------------------------------------------------------------\n"
326 "$ ------------------------------------------------------------------------------\n\n"
327 "$set {}\n\n".format(section
, setIdx
)
329 messages
= data
.sections
[section
]
330 for num
, message
in enumerate(messages
, 1):
331 f
.write('{} "{}"\n'.format(num
, message
.toSrc()))
333 f
.write("\n$ end of file $")
336 def generate_message_file_windows(messageFile
, data
):
338 Create the message file for Windows OS
340 Encoding is in UTF-16LE
342 language
= data
.getMeta("Language")
343 langId
= data
.getMeta("LangId")
344 with
open(messageFile
, "w", encoding
="utf-16-le") as f
:
345 insert_header(f
, data
, commentChar
=";")
346 f
.write("\nLanguageNames = ({0}={1}:msg_{1})\n\n".format(language
, langId
))
347 f
.write("FacilityNames=(\n")
348 for section
in MessageData
.orderedSections
:
349 setIdx
= MessageData
.sectionInfo
[section
]["set"]
350 shortName
= MessageData
.sectionInfo
[section
]["short"]
351 f
.write(" {}={}\n".format(shortName
, setIdx
))
354 for section
in MessageData
.orderedSections
:
355 shortName
= MessageData
.sectionInfo
[section
]["short"]
357 messages
= data
.sections
[section
]
358 for message
in messages
:
364 "{}\n.\n\n".format(n
, shortName
, language
, message
.toMC())
366 f
.write("\n; end of file ;")
370 parser
= argparse
.ArgumentParser(description
="Generate message data files")
374 help="Print language identifier of the message catalog source file",
379 help="Prefix to be used for all C identifiers (type and variable names)"
380 " in enum and default message files.",
382 parser
.add_argument("--enum", metavar
="FILE", help="Generate enum file named FILE")
384 "--default", metavar
="FILE", help="Generate default messages file named FILE"
387 "--signature", metavar
="FILE", help="Generate signature file named FILE"
390 "--message", metavar
="FILE", help="Generate message file named FILE"
393 "--target-system-override",
394 metavar
="TARGET_SYSTEM_NAME",
395 help="Target System override.\n"
396 "By default the target system is the host system\n"
397 "See possible values at https://docs.python.org/3/library/platform.html#platform.system",
399 parser
.add_argument("inputfile")
400 commandArgs
= parser
.parse_args()
402 if commandArgs
.lang_id
:
403 display_language_id(commandArgs
.inputfile
)
405 data
= MessageData
.create(commandArgs
.inputfile
)
406 prefix
= commandArgs
.prefix
407 if commandArgs
.target_system_override
:
408 TargetPlatform
.set_system_override(commandArgs
.target_system_override
)
410 generate_enum_file(commandArgs
.enum
, prefix
, data
)
411 if commandArgs
.default
:
412 generate_default_messages_file(commandArgs
.default
, prefix
, data
)
413 if commandArgs
.signature
:
414 generate_signature_file(commandArgs
.signature
, data
)
415 if commandArgs
.message
:
416 if TargetPlatform
.system().casefold() == "Windows".casefold():
417 generate_message_file_windows(commandArgs
.message
, data
)
419 generate_message_file_unix(commandArgs
.message
, data
)
422 if __name__
== "__main__":
425 except ScriptError
as e
:
426 print("error: {}".format(e
))