#!/usr/bin/env python3 # encoding: utf-8 # # wmlxgettext -- generate a blank .pot file for official campaigns translations # (build tool for wesnoth core) # # # By Nobun, october 2015 # Thanks to Elvish Hunter for writing code for coloring text under windows # # PURPOSE # # wmlxgettext is a python3 tool that replace the old (but very good) # perl script with the same name. # Replacing perl with python3 will ensure more portability. # # wmlxgettext is a tool that is directly used during wesnoth build process # to generate the pot files for the core campaigns. # # USAGE # # If you want to learn how to use wmlxgettext, read the online End-User # documentation at: # http://wmlxgettext-unoff.readthedocs.org/en/latest/enduser/index.html # # SOURCE CODE DOCUMENTATION # # While the source code contains some comments that explain what it does at # that point, the source code is mainly explained on source documentation at: # http://wmlxgettext-unoff.readthedocs.org/en/latest/srcdoc/index.html import os import re import sys import signal import warnings import argparse from datetime import datetime import pywmlx def commandline(args): parser = argparse.ArgumentParser( description='Generate .pot from WML/Lua file list.', usage='''wmlxgettext -o OUTPUT_DIR [--domain=DOMAIN] [--directory=START_PATH] [--recursive] [--initialdomain=INITIAL_DOMAIN] [--package-version=PACKAGE_VERSION] [--no-sort-by-file] [--no-text-colors] [--fuzzy] [--warnall] FILE1 FILE2 ... FILEN''' ) parser.add_argument( '--version', action='version', version='wmlxgettext 2023.05.17.py3' ) parser.add_argument( '-o', required=True, default=None, dest='folder', help= ('Destination path. You can specify "-" to mean STDOUT.' '[**REQUIRED ARGUMENT**]') ) parser.add_argument( '--domain', nargs='*', dest='domain', help= ('One or more textdomain values (on WML/Lua file) associated to the ' 'strings that will be actually translated. ') ) parser.add_argument( '--directory', default='.', dest='start_path', help=('Complete path of your "start directory". ' '(Default: current directory). The (relative) path to ' 'every WML/Lua file should start from this directory.') ) parser.add_argument( '--force-po', action='store_true', default=False, dest='force_po', help=('Write PO file even if empty.') ) parser.add_argument( '--initialdomain', default='wesnoth', dest='initdom', help=('Initial domain value on WML/Lua file when no textdomain ' 'set in that WML/Lua file.\nBy default, it is equal to ' '"wesnoth" and usually you don\'t need to change this value.') ) parser.add_argument( '--package-version', default='PACKAGE VERSION', dest='package_version', help=('Version number of your wesnoth add-on. You don\'t actually ' 'require to set this option since you can directly edit the ' 'po file produced by wmlxgettext.') ) parser.add_argument( '--no-sort-by-file', action='store_false', default=True, dest='sort_by_file', help=("By default, the list of input files is sorted so that they " "are processed in a deterministic order. Use this flag to " "process the files in the order that they're named on the " "command line.") ) parser.add_argument( '--no-text-colors', action='store_false', default=True, dest='text_col', help=("By default, warnings are displayed with colored text. You can " "disable this feature using this flag.") ) parser.add_argument( '--warnall', action='store_true', default=False, dest='warnall', help="Show all warnings. By default, some warnings are hidden." ) parser.add_argument( '--fuzzy', action='store_true', default=False, dest='fuzzy', help=("If you specify this flag, all sentences in the POT " "files created by wmlxgettext will be set as fuzzy.\n" "By default sentences are NOT set as fuzzy.") ) parser.add_argument( '--recursive', action='store_true', default=False, help=("If this option is used, wmlxgettext will scan recursively the " "directory set by the '--directory' parameter, and check " "every WML/Lua file. " "If this option is used, EXPLICIT LIST of files will be " "ignored.") ) parser.add_argument( 'filelist', help='List of WML/Lua files of your UMC (source files).', nargs='*' ) ''' Developer Options - not suitable for standard usage: --DMode is a reserved flag used to verify how wmlxgettext is internally working. When this flag is used (set to ON), an extra file (debug.txt) will be created. debug.txt will contain useful information to check if wmlxgettext is working as expected (but make sense only for wmlxgettext developers/contributors) ''' parser.add_argument( '--DMode', action='store_true', dest='debugmode', default=False, help=argparse.SUPPRESS ) return parser.parse_args(args) def main(): signal.signal(signal.SIGINT, sigint_handler) args = commandline(sys.argv[1:]) pywmlx.ansi_setEnabled(args.text_col) pywmlx.wincol_setEnabled(args.text_col) pywmlx.set_warnall(args.warnall) startPath = os.path.realpath(os.path.normpath(args.start_path)) sentlist = dict() fileno = 0 fdebug = None if args.folder == '-': args.folder = None if args.debugmode: fdebug = open('debug.txt', 'w', encoding='utf-8') pywmlx.statemachine.setup(sentlist, args.initdom, args.domain, args.warnall, fdebug) if args.recursive is False and args.filelist is None: pywmlx.wmlerr("bad command line", "FILELIST must not be empty. " "Please, run wmlxgettext again and, this time, add some file " "in FILELIST or use the --recursive option.") elif args.recursive is False and len(args.filelist) <= 0: pywmlx.wmlerr("bad command line", "FILELIST must not be empty. " "Please, run wmlxgettext again and, this time, add some file " "in FILELIST or use the --recursive option.") elif args.recursive is False: filelist = args.filelist # the following elif case implicitly expects that args.recursive is True elif args.filelist is not None: if len(args.filelist) > 0: pywmlx.wmlwarn("command line warning", "Option --recursive was " "used, but FILELIST is not empty. All extra file listed in " "FILELIST will be ignored.") # If we use the --recursive option we recursively scan the add-on # directory. # But we want that the file reference informations placed # in the .po file will remember the (relative) root name of the # addon. # This is why the autof.autoscan function returns a tuple of # values: # the first one is the parent directory of the original startPath # the second one is the filelist (with the "fixed" file references) # This way, we can override startPath with its parent directory # containing the main directory of the wesnoth add-on, without # introducing bugs. startPath, filelist = pywmlx.autof.autoscan(startPath) # this last case is equal to: # if args.recursive is True and args.filelist is None: else: startPath, filelist = pywmlx.autof.autoscan(startPath) if args.sort_by_file: filelist.sort() for fileno, fx in enumerate(filelist): fname = os.path.join(startPath, os.path.normpath(fx)) is_file = os.path.isfile(fname) if is_file: infile = None try: infile = open(fname, 'r', encoding="utf-8") except OSError as e: errmsg = 'cannot read file: ' + e.args[1] pywmlx.wmlerr(e.filename, errmsg, OSError) if fname.lower().endswith('.cfg'): pywmlx.statemachine.run(filebuf=infile, fileref=fx, fileno=fileno, startstate='wml_idle', waitwml=True) if fname.lower().endswith('.lua'): pywmlx.statemachine.run(filebuf=infile, fileref=fx, fileno=fileno, startstate='lua_idle', waitwml=False) infile.close() folder = None filename = None outfile = sys.stdout now = datetime.utcnow() if args.folder is not None: folder = os.path.realpath(os.path.normpath(args.folder)) if not os.path.isdir(folder) and "." in os.path.basename(folder): # Back-compat folder, filename = os.path.split(folder) os.makedirs(folder, exist_ok=True) if args.force_po and args.domain is not None: for domain in args.domain: if not domain in sentlist: sentlist[domain] = dict() for domain, d in sentlist.items(): if folder is None: sys.stdout.reconfigure(encoding="utf-8") else: try: outfile = open(os.path.join(folder, "{}.pot".format(domain) if filename is None else filename), 'w', encoding="utf-8") except OSError as e: errmsg = 'cannot write file: ' + e.args[1] pywmlx.wmlerr(e.filename, errmsg, OSError) pkgversion = args.package_version + '\\n"' print('msgid ""\nmsgstr ""', file=outfile) print('"Project-Id-Version:', pkgversion, file=outfile) print('"Report-Msgid-Bugs-To: https://bugs.wesnoth.org/\\n"', file=outfile) cdate = '{:04d}-{:02d}-{:02d} {:02d}:{:02d} UTC\\n"'.format(now.year, now.month, now.day, now.hour, now.minute) print('"POT-Creation-Date:', cdate, file=outfile) print('"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"', file=outfile) print('"Last-Translator: FULL NAME \\n"', file=outfile) print('"Language-Team: LANGUAGE \\n"', file=outfile) print('"MIME-Version: 1.0\\n"', file=outfile) print('"Content-Type: text/plain; charset=UTF-8\\n"', file=outfile) print('"Content-Transfer-Encoding: 8bit\\n"\n', file=outfile) for posentence in sorted(d.values(), key=lambda x: x.orderid): posentence.write(outfile, args.fuzzy) print('', file=outfile) if args.folder is not None: outfile.close() if args.debugmode: fdebug.close() def sigint_handler(signal, frame): """This function defines what happens when the SIGINT signal is encountered by pressing ctrl-c during runtime. When ctrl-c is pressed, a one-line message is displayed and Python exits with Status 1. This overrides Python's default behavior of displaying a traceback when ctrl-c is pressed. """ print ('Aborted by pressing ctrl-c') sys.exit(1) if __name__ == "__main__": main()