andrej@1731: #! /usr/bin/env python andrej@1731: # -*- coding: iso-8859-1 -*- andrej@1731: # andrej@1731: # PYTHON MODULE: MKI18N.PY andrej@1731: # ========= andrej@1731: # andrej@1731: # Abstract: Make Internationalization (i18n) files for an application. andrej@1731: # andrej@1731: # Copyright Pierre Rouleau. 2003. Released to public domain. andrej@1731: # andrej@1731: # Last update: Saturday, November 8, 2003. @ 15:55:18. andrej@1731: # andrej@1731: # File: ROUP2003N01::C:/dev/python/mki18n.py andrej@1731: # andrej@1731: # RCS $Header: //software/official/MKS/MKS_SI/TV_NT/dev/Python/rcs/mki18n.py 1.5 2003/11/05 19:40:04 PRouleau Exp $ andrej@1731: # andrej@1731: # Update history: andrej@1731: # andrej@1731: # - File created: Saturday, June 7, 2003. by Pierre Rouleau andrej@1731: # - 10/06/03 rcs : RCS Revision 1.1 2003/06/10 10:06:12 PRouleau andrej@1731: # - 10/06/03 rcs : RCS Initial revision andrej@1731: # - 23/08/03 rcs : RCS Revision 1.2 2003/06/10 10:54:27 PRouleau andrej@1731: # - 23/08/03 P.R.: [code:fix] : The strings encoded in this file are encode in iso-8859-1 format. Added the encoding andrej@1731: # notification to Python to comply with Python's 2.3 PEP 263. andrej@1731: # - 23/08/03 P.R.: [feature:new] : Added the '-e' switch which is used to force the creation of the empty English .mo file. andrej@1731: # - 22/10/03 P.R.: [code] : incorporated utility functions in here to make script self sufficient. andrej@1731: # - 05/11/03 rcs : RCS Revision 1.4 2003/10/22 06:39:31 PRouleau andrej@1731: # - 05/11/03 P.R.: [code:fix] : included the unixpath() in this file. andrej@1731: # - 08/11/03 rcs : RCS Revision 1.5 2003/11/05 19:40:04 PRouleau andrej@1731: # andrej@1731: # RCS $Log: $ andrej@1731: # andrej@1731: # andrej@1731: # ----------------------------------------------------------------------------- andrej@1731: """ andrej@1731: mki18n allows you to internationalize your software. You can use it to andrej@1731: create the GNU .po files (Portable Object) and the compiled .mo files andrej@1731: (Machine Object). andrej@1731: andrej@1731: mki18n module can be used from the command line or from within a script (see andrej@1731: the Usage at the end of this page). andrej@1731: andrej@1731: Table of Contents andrej@1731: ----------------- andrej@1731: andrej@1731: makePO() -- Build the Portable Object file for the application -- andrej@1731: catPO() -- Concatenate one or several PO files with the application domain files. -- andrej@1731: makeMO() -- Compile the Portable Object files into the Machine Object stored in the right location. -- andrej@1731: printUsage -- Displays how to use this script from the command line -- andrej@1731: andrej@1731: Scriptexecution -- Runs when invoked from the command line -- andrej@1731: andrej@1731: andrej@1731: NOTE: this module uses GNU gettext utilities. andrej@1731: andrej@1731: You can get the gettext tools from the following sites: andrej@1731: andrej@1731: - `GNU FTP site for gettetx`_ where several versions (0.10.40, 0.11.2, 0.11.5 and 0.12.1) are available. andrej@1731: Note that you need to use `GNU libiconv`_ to use this. Get it from the `GNU andrej@1731: libiconv ftp site`_ and get version 1.9.1 or later. Get the Windows .ZIP andrej@1731: files and install the packages inside c:/gnu. All binaries will be stored andrej@1731: inside c:/gnu/bin. Just put c:/gnu/bin inside your PATH. You will need andrej@1731: the following files: andrej@1731: andrej@1731: - `gettext-runtime-0.12.1.bin.woe32.zip`_ andrej@1731: - `gettext-tools-0.12.1.bin.woe32.zip`_ andrej@1731: - `libiconv-1.9.1.bin.woe32.zip`_ andrej@1731: andrej@1731: andrej@1731: .. _GNU libiconv: http://www.gnu.org/software/libiconv/ andrej@1731: .. _GNU libiconv ftp site: http://www.ibiblio.org/pub/gnu/libiconv/ andrej@1731: .. _gettext-runtime-0.12.1.bin.woe32.zip: ftp://ftp.gnu.org/gnu/gettext/gettext-runtime-0.12.1.bin.woe32.zip andrej@1731: .. _gettext-tools-0.12.1.bin.woe32.zip: ftp://ftp.gnu.org/gnu/gettext/gettext-tools-0.12.1.bin.woe32.zip andrej@1731: .. _libiconv-1.9.1.bin.woe32.zip: http://www.ibiblio.org/pub/gnu/libiconv/libiconv-1.9.1.bin.woe32.zip andrej@1731: andrej@1731: """ andrej@1731: # ----------------------------------------------------------------------------- andrej@1731: # Module Import andrej@1731: # ------------- andrej@1731: # andrej@1881: andrej@1881: from __future__ import absolute_import andrej@1826: from __future__ import print_function andrej@1731: import os andrej@1731: import sys andrej@1832: import re andrej@1731: import wx andrej@1731: andrej@1731: # ----------------------------------------------------------------------------- andrej@1731: # Global variables andrej@1731: # ---------------- andrej@1731: # andrej@1731: andrej@1758: __author__ = "Pierre Rouleau" andrej@1742: __version__ = "$Revision: 1.5 $" andrej@1731: andrej@1731: # ----------------------------------------------------------------------------- andrej@1731: andrej@1736: andrej@1731: def getlanguageDict(): andrej@1731: languageDict = {} andrej@1731: andrej@1731: if wx.VERSION >= (3, 0, 0): andrej@1847: _app = wx.App() andrej@1847: else: andrej@1847: _app = wx.PySimpleApp() andrej@1731: andrej@1731: for lang in [x for x in dir(wx) if x.startswith("LANGUAGE")]: andrej@1731: i = wx.Locale(wx.LANGUAGE_DEFAULT).GetLanguageInfo(getattr(wx, lang)) andrej@1731: if i: andrej@1731: languageDict[i.CanonicalName] = i.Description andrej@1731: andrej@1731: return languageDict andrej@1731: andrej@1731: andrej@1756: def verbosePrint(verbose, str): andrej@1756: if verbose: andrej@1826: print(str) andrej@1756: andrej@1756: andrej@1744: def processCustomFiles(filein, fileout, regexp, prefix=''): andrej@1731: appfil_file = open(filein, 'r') andrej@1731: messages_file = open(fileout, 'r') andrej@1731: messages = messages_file.read() andrej@1731: messages_file.close() andrej@1731: messages_file = open(fileout, 'a') andrej@1731: messages_file.write('\n') andrej@1731: messages_file.write('#: %s\n' % prefix) andrej@1731: messages_file.write('\n') andrej@1731: andrej@1731: words_found = {} andrej@1731: for filepath in appfil_file.xreadlines(): andrej@1731: code_file = open(filepath.strip(), 'r') andrej@1731: for match in regexp.finditer(code_file.read()): andrej@1731: word = match.group(1) andrej@1731: if not words_found.get(word, False) and messages.find("msgid \"%s\"\nmsgstr \"\"" % word) == -1: andrej@1731: words_found[word] = True andrej@1731: messages_file.write('\n') andrej@1734: messages_file.write("msgid \"%s\"\n" % word) andrej@1731: messages_file.write("msgstr \"\"\n") andrej@1731: code_file.close() andrej@1731: andrej@1731: messages_file.close() andrej@1731: appfil_file.close() andrej@1731: andrej@1731: andrej@1731: # ----------------------------------------------------------------------------- andrej@1731: # m a k e P O ( ) -- Build the Portable Object file for the application -- andrej@1731: # ^^^^^^^^^^^^^^^ andrej@1731: # andrej@1739: def makePO(applicationDirectoryPath, applicationDomain=None, verbose=0): andrej@1731: """Build the Portable Object Template file for the application. andrej@1731: andrej@1731: makePO builds the .pot file for the application stored inside andrej@1731: a specified directory by running xgettext for all application source andrej@1731: files. It finds the name of all files by looking for a file called 'app.fil'. andrej@1731: If this file does not exists, makePo raises an IOError exception. andrej@1731: By default the application domain (the application andrej@1731: name) is the same as the directory name but it can be overridden by the andrej@1731: 'applicationDomain' argument. andrej@1731: andrej@1731: makePO always creates a new file called messages.pot. If it finds files andrej@1731: of the form app_xx.po where 'app' is the application name and 'xx' is one andrej@1731: of the ISO 639 two-letter language codes, makePO resynchronizes those andrej@1731: files with the latest extracted strings (now contained in messages.pot). andrej@1731: This process updates all line location number in the language-specific andrej@1731: .po files and may also create new entries for translation (or comment out andrej@1731: some). The .po file is not changed, instead a new file is created with andrej@1731: the .new extension appended to the name of the .po file. andrej@1731: andrej@1731: By default the function does not display what it is doing. Set the andrej@1731: verbose argument to 1 to force it to print its commands. andrej@1731: """ andrej@1731: andrej@1731: if applicationDomain is None: andrej@1740: applicationName = fileBaseOf(applicationDirectoryPath, withPath=0) andrej@1731: else: andrej@1731: applicationName = applicationDomain andrej@1731: currentDir = os.getcwd() andrej@1731: os.chdir(applicationDirectoryPath) andrej@1731: filelist = 'app.fil' andrej@1731: if not os.path.exists(filelist): andrej@1740: raise IOError(2, 'No module file: ' % filelist) andrej@1731: andrej@1731: fileout = 'messages.pot' andrej@1731: # Steps: andrej@1731: # Use xgettext to parse all application modules andrej@1731: # The following switches are used: andrej@1731: # andrej@1731: # -s : sort output by string content (easier to use when we need to merge several .po files) andrej@1731: # --files-from=app.fil : The list of files is taken from the file: app.fil andrej@1731: # --output= : specifies the name of the output file (using a .pot extension) andrej@1731: cmd = 'xgettext -s --no-wrap --language=Python --files-from=' + filelist + ' --output=' + fileout + ' --package-name ' + applicationName andrej@1756: verbosePrint(verbose, cmd) andrej@1731: os.system(cmd) andrej@1731: andrej@1731: XSD_STRING_MODEL = re.compile("]*\>") andrej@1731: processCustomFiles(filelist, fileout, XSD_STRING_MODEL, 'Extra XSD strings') andrej@1731: andrej@1731: XML_TC6_STRING_MODEL = re.compile("\s*\s*", re.MULTILINE | re.DOTALL) andrej@1731: processCustomFiles(filelist, fileout, XML_TC6_STRING_MODEL, 'Extra TC6 documentation strings') andrej@1731: andrej@1731: # generate messages.po andrej@1731: cmd = 'msginit --no-wrap --no-translator -i %s -l en_US.UTF-8 -o messages.po' % (fileout) andrej@1756: verbosePrint(verbose, cmd) andrej@1731: os.system(cmd) andrej@1731: andrej@1731: languageDict = getlanguageDict() andrej@1731: andrej@1731: for langCode in languageDict.keys(): andrej@1731: if langCode == 'en': andrej@1731: pass andrej@1731: else: andrej@1739: langPOfileName = "%s_%s.po" % (applicationName, langCode) andrej@1731: if os.path.exists(langPOfileName): andrej@1731: cmd = 'msgmerge -s --no-wrap "%s" %s > "%s.new"' % (langPOfileName, fileout, langPOfileName) andrej@1756: verbosePrint(verbose, cmd) andrej@1731: os.system(cmd) andrej@1731: os.chdir(currentDir) andrej@1731: andrej@1736: andrej@1739: def catPO(applicationDirectoryPath, listOf_extraPo, applicationDomain=None, targetDir=None, verbose=0): andrej@1731: """Concatenate one or several PO files with the application domain files. andrej@1731: """ andrej@1731: andrej@1731: if applicationDomain is None: andrej@1740: applicationName = fileBaseOf(applicationDirectoryPath, withPath=0) andrej@1731: else: andrej@1731: applicationName = applicationDomain andrej@1731: currentDir = os.getcwd() andrej@1731: os.chdir(applicationDirectoryPath) andrej@1731: andrej@1731: languageDict = getlanguageDict() andrej@1731: andrej@1731: for langCode in languageDict.keys(): andrej@1731: if langCode == 'en': andrej@1731: pass andrej@1731: else: andrej@1739: langPOfileName = "%s_%s.po" % (applicationName, langCode) andrej@1731: if os.path.exists(langPOfileName): andrej@1731: fileList = '' andrej@1731: for fileName in listOf_extraPo: andrej@1740: fileList += ("%s_%s.po " % (fileName, langCode)) andrej@1731: cmd = "msgcat -s --no-wrap %s %s > %s.cat" % (langPOfileName, fileList, langPOfileName) andrej@1756: verbosePrint(verbose, cmd) andrej@1731: os.system(cmd) andrej@1731: if targetDir is None: andrej@1731: pass andrej@1731: else: andrej@1740: mo_targetDir = "%s/%s/LC_MESSAGES" % (targetDir, langCode) andrej@1740: cmd = "msgfmt --output-file=%s/%s.mo %s_%s.po.cat" % (mo_targetDir, applicationName, applicationName, langCode) andrej@1756: verbosePrint(verbose, cmd) andrej@1731: os.system(cmd) andrej@1731: os.chdir(currentDir) andrej@1731: andrej@1736: andrej@1740: def makeMO(applicationDirectoryPath, targetDir='./locale', applicationDomain=None, verbose=0, forceEnglish=0): andrej@1731: """Compile the Portable Object files into the Machine Object stored in the right location. andrej@1731: andrej@1731: makeMO converts all translated language-specific PO files located inside andrej@1731: the application directory into the binary .MO files stored inside the andrej@1731: LC_MESSAGES sub-directory for the found locale files. andrej@1731: andrej@1731: makeMO searches for all files that have a name of the form 'app_xx.po' andrej@1731: inside the application directory specified by the first argument. The andrej@1731: 'app' is the application domain name (that can be specified by the andrej@1731: applicationDomain argument or is taken from the directory name). The 'xx' andrej@1731: corresponds to one of the ISO 639 two-letter language codes. andrej@1731: andrej@1731: makeMo stores the resulting files inside a sub-directory of `targetDir` andrej@1731: called xx/LC_MESSAGES where 'xx' corresponds to the 2-letter language andrej@1731: code. andrej@1731: """ andrej@1756: andrej@1731: if targetDir is None: andrej@1731: targetDir = './locale' andrej@1756: andrej@1756: verbosePrint(verbose, "Target directory for .mo files is: %s" % targetDir) andrej@1731: andrej@1731: if applicationDomain is None: andrej@1740: applicationName = fileBaseOf(applicationDirectoryPath, withPath=0) andrej@1731: else: andrej@1731: applicationName = applicationDomain andrej@1731: currentDir = os.getcwd() andrej@1731: os.chdir(applicationDirectoryPath) andrej@1731: andrej@1731: languageDict = getlanguageDict() andrej@1731: andrej@1731: for langCode in languageDict.keys(): andrej@1742: if (langCode == 'en') and (forceEnglish == 0): andrej@1731: pass andrej@1731: else: andrej@1739: langPOfileName = "%s_%s.po" % (applicationName, langCode) andrej@1731: if os.path.exists(langPOfileName): andrej@1740: mo_targetDir = "%s/%s/LC_MESSAGES" % (targetDir, langCode) andrej@1731: if not os.path.exists(mo_targetDir): andrej@1731: mkdir(mo_targetDir) andrej@1740: cmd = 'msgfmt --output-file="%s/%s.mo" "%s_%s.po"' % (mo_targetDir, applicationName, applicationName, langCode) andrej@1756: verbosePrint(verbose, cmd) andrej@1731: os.system(cmd) andrej@1731: os.chdir(currentDir) andrej@1731: andrej@1736: andrej@1739: def printUsage(errorMsg=None): andrej@1731: """Displays how to use this script from the command line.""" andrej@1826: print(""" andrej@1731: ################################################################################## andrej@1731: # mki18n : Make internationalization files. # andrej@1731: # Uses the GNU gettext system to create PO (Portable Object) files # andrej@1731: # from source code, coimpile PO into MO (Machine Object) files. # andrej@1731: # Supports C,C++,Python source files. # andrej@1731: # # andrej@1731: # Usage: mki18n {OPTION} [appDirPath] # andrej@1731: # # andrej@1731: # Options: # andrej@1731: # -e : When -m is used, forces English .mo file creation # andrej@1731: # -h : prints this help # andrej@1731: # -m : make MO from existing PO files # andrej@1731: # -p : make PO, update PO files: Creates a new messages.pot # andrej@1731: # file. Creates a dom_xx.po.new for every existing # andrej@1731: # language specific .po file. ('xx' stands for the ISO639 # andrej@1731: # two-letter language code and 'dom' stands for the # andrej@1731: # application domain name). mki18n requires that you # andrej@1731: # write a 'app.fil' file which contains the list of all # andrej@1731: # source code to parse. # andrej@1731: # -v : verbose (prints comments while running) # andrej@1731: # --domain=appName : specifies the application domain name. By default # andrej@1731: # the directory name is used. # andrej@1731: # --moTarget=dir : specifies the directory where .mo files are stored. # andrej@1731: # If not specified, the target is './locale' # andrej@1731: # # andrej@1731: # You must specify one of the -p or -m option to perform the work. You can # andrej@1731: # specify the path of the target application. If you leave it out mki18n # andrej@1731: # will use the current directory as the application main directory. # andrej@1731: # # andrej@1826: ##################################################################################""") andrej@1731: if errorMsg: andrej@1826: print("\n ERROR: %s" % errorMsg) andrej@1731: andrej@1736: andrej@1740: def fileBaseOf(filename, withPath=0): andrej@1757: """fileBaseOf(filename,withPath) ---> string andrej@1757: andrej@1757: Return base name of filename. The returned string never includes the extension. andrej@1757: Use os.path.basename() to return the basename with the extension. The andrej@1757: second argument is optional. If specified and if set to 'true' (non zero) andrej@1757: the string returned contains the full path of the file name. Otherwise the andrej@1757: path is excluded. andrej@1757: andrej@1757: [Example] andrej@1757: >>> fn = 'd:/dev/telepath/tvapp/code/test.html' andrej@1757: >>> fileBaseOf(fn) andrej@1757: 'test' andrej@1757: >>> fileBaseOf(fn) andrej@1757: 'test' andrej@1757: >>> fileBaseOf(fn,1) andrej@1757: 'd:/dev/telepath/tvapp/code/test' andrej@1757: >>> fileBaseOf(fn,0) andrej@1757: 'test' andrej@1757: >>> fn = 'abcdef' andrej@1757: >>> fileBaseOf(fn) andrej@1757: 'abcdef' andrej@1757: >>> fileBaseOf(fn,1) andrej@1757: 'abcdef' andrej@1757: >>> fn = "abcdef." andrej@1757: >>> fileBaseOf(fn) andrej@1757: 'abcdef' andrej@1757: >>> fileBaseOf(fn,1) andrej@1757: 'abcdef' andrej@1757: """ andrej@1757: pos = filename.rfind('.') andrej@1757: if pos > 0: andrej@1757: filename = filename[:pos] andrej@1757: if withPath: andrej@1757: return filename andrej@1757: else: andrej@1757: return os.path.basename(filename) andrej@1736: andrej@1736: andrej@1739: def mkdir(directory): andrej@1757: """Create a directory (and possibly the entire tree). andrej@1757: andrej@1757: The os.mkdir() will fail to create a directory if one of the andrej@1757: directory in the specified path does not exist. mkdir() andrej@1757: solves this problem. It creates every intermediate directory andrej@1757: required to create the final path. Under Unix, the function andrej@1757: only supports forward slash separator, but under Windows and MacOS andrej@1757: the function supports the forward slash and the OS separator (backslash andrej@1757: under windows). andrej@1757: """ andrej@1757: andrej@1757: # translate the path separators andrej@1757: directory = unixpath(directory) andrej@1757: # build a list of all directory elements andrej@1757: aList = filter(lambda x: len(x) > 0, directory.split('/')) andrej@1757: theLen = len(aList) andrej@1757: # if the first element is a Windows-style disk drive andrej@1757: # concatenate it with the first directory andrej@1757: if aList[0].endswith(':'): andrej@1757: if theLen > 1: andrej@1757: aList[1] = aList[0] + '/' + aList[1] andrej@1757: del aList[0] andrej@1757: theLen -= 1 andrej@1757: # if the original directory starts at root, andrej@1757: # make sure the first element of the list andrej@1757: # starts at root too andrej@1757: if directory[0] == '/': andrej@1757: aList[0] = '/' + aList[0] andrej@1757: # Now iterate through the list, check if the andrej@1757: # directory exists and if not create it andrej@1757: theDir = '' andrej@1757: for i in range(theLen): andrej@1757: theDir += aList[i] andrej@1757: if not os.path.exists(theDir): andrej@1757: os.mkdir(theDir) andrej@1757: theDir += '/' andrej@1731: andrej@1736: andrej@1739: def unixpath(thePath): andrej@1757: r"""Return a path name that contains Unix separator. andrej@1757: andrej@1757: [Example] andrej@1757: >>> unixpath(r"d:\test") andrej@1757: 'd:/test' andrej@1757: >>> unixpath("d:/test/file.txt") andrej@1757: 'd:/test/file.txt' andrej@1757: >>> andrej@1757: """ andrej@1757: thePath = os.path.normpath(thePath) andrej@1757: if os.sep == '/': andrej@1757: return thePath andrej@1757: else: andrej@1757: return thePath.replace(os.sep, '/') andrej@1731: andrej@1749: andrej@1731: # ----------------------------------------------------------------------------- andrej@1731: andrej@1731: # S c r i p t e x e c u t i o n -- Runs when invoked from the command line -- andrej@1731: # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ andrej@1731: # andrej@1731: if __name__ == "__main__": andrej@1731: import getopt # command line parsing andrej@1731: argc = len(sys.argv) andrej@1731: if argc == 1: andrej@1731: printUsage('Missing argument: specify at least one of -m or -p (or both).') andrej@1731: sys.exit(1) andrej@1731: # If there is some arguments, parse the command line andrej@1754: validOptions = "ehmpv" andrej@1731: validLongOptions = ['domain=', 'moTarget='] andrej@1731: option = {} andrej@1731: option['forceEnglish'] = 0 andrej@1731: option['mo'] = 0 andrej@1731: option['po'] = 0 andrej@1731: option['verbose'] = 0 andrej@1731: option['domain'] = None andrej@1731: option['moTarget'] = None andrej@1756: optionKey = { andrej@1756: '-e': 'forceEnglish', andrej@1756: '-m': 'mo', andrej@1756: '-p': 'po', andrej@1756: '-v': 'verbose', andrej@1756: '--domain': 'domain', andrej@1756: '--moTarget': 'moTarget', andrej@1756: } andrej@1844: exit_code = 1 andrej@1731: try: andrej@1740: optionList, pargs = getopt.getopt(sys.argv[1:], validOptions, validLongOptions) andrej@1731: except getopt.GetoptError, e: andrej@1731: printUsage(e[0]) andrej@1731: sys.exit(1) andrej@1740: for (opt, val) in optionList: andrej@1828: if opt == '-h': andrej@1731: printUsage() andrej@1731: sys.exit(0) andrej@1756: option[optionKey[opt]] = 1 if val == '' else val andrej@1731: if len(pargs) == 0: andrej@1731: appDirPath = os.getcwd() andrej@1731: if option['verbose']: andrej@1826: print("No project directory given. Using current one: %s" % appDirPath) andrej@1731: elif len(pargs) == 1: andrej@1731: appDirPath = pargs[0] andrej@1731: else: andrej@1731: printUsage('Too many arguments (%u). Use double quotes if you have space in directory name' % len(pargs)) andrej@1731: sys.exit(1) andrej@1731: if option['domain'] is None: andrej@1731: # If no domain specified, use the name of the target directory andrej@1731: option['domain'] = fileBaseOf(appDirPath) andrej@1731: if option['verbose']: andrej@1826: print("Application domain used is: '%s'" % option['domain']) andrej@1731: if option['po']: andrej@1731: try: andrej@1740: makePO(appDirPath, option['domain'], option['verbose']) andrej@1844: exit_code = 0 andrej@1731: except IOError, e: andrej@1731: printUsage(e[1] + '\n You must write a file app.fil that contains the list of all files to parse.') andrej@1731: if option['mo']: andrej@1740: makeMO(appDirPath, option['moTarget'], option['domain'], option['verbose'], option['forceEnglish']) andrej@1844: exit_code = 0 andrej@1844: sys.exit(exit_code) andrej@1844: andrej@1844: andrej@1844: # -----------------------------------------------------------------------------