diff options
author | Gary Oberbrunner <garyo@oberbrunner.com> | 2012-08-01 01:36:08 (GMT) |
---|---|---|
committer | Gary Oberbrunner <garyo@oberbrunner.com> | 2012-08-01 01:36:08 (GMT) |
commit | 4b14de8984c550a36d5f29011e7a8e037bef12f3 (patch) | |
tree | 210d3f63b6c82ced0e8e13c2f6c1943928c40123 /src/engine | |
parent | 104cd10cf98ea0e6dcfdfba02db0100eed7bd8cb (diff) | |
parent | 611b809ac42c269cb337a2e20ad0d4363bc6fe91 (diff) | |
download | SCons-4b14de8984c550a36d5f29011e7a8e037bef12f3.zip SCons-4b14de8984c550a36d5f29011e7a8e037bef12f3.tar.gz SCons-4b14de8984c550a36d5f29011e7a8e037bef12f3.tar.bz2 |
Merged in ptomulik/scons, pull request #25: gettext toolset
Diffstat (limited to 'src/engine')
-rw-r--r-- | src/engine/MANIFEST-xml.in | 5 | ||||
-rw-r--r-- | src/engine/MANIFEST.in | 6 | ||||
-rw-r--r-- | src/engine/SCons/Environment.py | 13 | ||||
-rw-r--r-- | src/engine/SCons/Tool/GettextCommon.py | 429 | ||||
-rw-r--r-- | src/engine/SCons/Tool/gettext.py | 45 | ||||
-rw-r--r-- | src/engine/SCons/Tool/gettext.xml | 200 | ||||
-rw-r--r-- | src/engine/SCons/Tool/msgfmt.py | 102 | ||||
-rw-r--r-- | src/engine/SCons/Tool/msgfmt.xml | 102 | ||||
-rw-r--r-- | src/engine/SCons/Tool/msginit.py | 114 | ||||
-rw-r--r-- | src/engine/SCons/Tool/msginit.xml | 168 | ||||
-rw-r--r-- | src/engine/SCons/Tool/msgmerge.py | 98 | ||||
-rw-r--r-- | src/engine/SCons/Tool/msgmerge.xml | 158 | ||||
-rw-r--r-- | src/engine/SCons/Tool/xgettext.py | 333 | ||||
-rw-r--r-- | src/engine/SCons/Tool/xgettext.xml | 288 |
14 files changed, 2053 insertions, 8 deletions
diff --git a/src/engine/MANIFEST-xml.in b/src/engine/MANIFEST-xml.in index b6bafcf..d2df751 100644 --- a/src/engine/MANIFEST-xml.in +++ b/src/engine/MANIFEST-xml.in @@ -100,3 +100,8 @@ SCons/Tool/textfile.xml SCons/Tool/tlib.xml SCons/Tool/yacc.xml SCons/Tool/zip.xml +SCons/Tool/gettext.xml +SCons/Tool/msgfmt.xml +SCons/Tool/msginit.xml +SCons/Tool/msgmerge.xml +SCons/Tool/xgettext.xml diff --git a/src/engine/MANIFEST.in b/src/engine/MANIFEST.in index 377b7b7..4f175bd 100644 --- a/src/engine/MANIFEST.in +++ b/src/engine/MANIFEST.in @@ -183,3 +183,9 @@ SCons/Variables/ListVariable.py SCons/Variables/PackageVariable.py SCons/Variables/PathVariable.py SCons/Warnings.py +SCons/Tool/GettextCommon.py +SCons/Tool/gettext.py +SCons/Tool/msgfmt.py +SCons/Tool/msginit.py +SCons/Tool/msgmerge.py +SCons/Tool/xgettext.py diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py index 529e3a3..2c71e61 100644 --- a/src/engine/SCons/Environment.py +++ b/src/engine/SCons/Environment.py @@ -2231,14 +2231,11 @@ class Base(SubstitutionEnvironment): sources.append(s) build_source(node.all_children()) - # THIS CODE APPEARS TO HAVE NO EFFECT - # # get the final srcnode for all nodes, this means stripping any - # # attached build node by calling the srcnode function - # for file in sources: - # srcnode = file.srcnode() - # while srcnode != file.srcnode(): - # srcnode = file.srcnode() - + def final_source(node): + while (node != node.srcnode()): + node = node.srcnode() + return node + sources = map( final_source, sources ); # remove duplicates return list(set(sources)) diff --git a/src/engine/SCons/Tool/GettextCommon.py b/src/engine/SCons/Tool/GettextCommon.py new file mode 100644 index 0000000..86d7eaf --- /dev/null +++ b/src/engine/SCons/Tool/GettextCommon.py @@ -0,0 +1,429 @@ +"""SCons.Tool.GettextCommon module + +Used by several tools of `gettext` toolset. +""" + +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import SCons.Warnings +import re + +############################################################################# +class XgettextToolWarning(SCons.Warnings.Warning): pass +class XgettextNotFound(XgettextToolWarning): pass +class MsginitToolWarning(SCons.Warnings.Warning): pass +class MsginitNotFound(MsginitToolWarning): pass +class MsgmergeToolWarning(SCons.Warnings.Warning): pass +class MsgmergeNotFound(MsgmergeToolWarning): pass +class MsgfmtToolWarning(SCons.Warnings.Warning): pass +class MsgfmtNotFound(MsgfmtToolWarning): pass +############################################################################# +SCons.Warnings.enableWarningClass(XgettextToolWarning) +SCons.Warnings.enableWarningClass(XgettextNotFound) +SCons.Warnings.enableWarningClass(MsginitToolWarning) +SCons.Warnings.enableWarningClass(MsginitNotFound) +SCons.Warnings.enableWarningClass(MsgmergeToolWarning) +SCons.Warnings.enableWarningClass(MsgmergeNotFound) +SCons.Warnings.enableWarningClass(MsgfmtToolWarning) +SCons.Warnings.enableWarningClass(MsgfmtNotFound) +############################################################################# + +############################################################################# +class _POTargetFactory(object): + """ A factory of `PO` target files. + + Factory defaults differ from these of `SCons.Node.FS.FS`. We set `precious` + (this is required by builders and actions gettext) and `noclean` flags by + default for all produced nodes. + """ + def __init__( self, env, nodefault = True, alias = None, precious = True + , noclean = True ): + """ Object constructor. + + **Arguments** + + - *env* (`SCons.Environment.Environment`) + - *nodefault* (`boolean`) - if `True`, produced nodes will be ignored + from default target `'.'` + - *alias* (`string`) - if provided, produced nodes will be automatically + added to this alias, and alias will be set as `AlwaysBuild` + - *precious* (`boolean`) - if `True`, the produced nodes will be set as + `Precious`. + - *noclen* (`boolean`) - if `True`, the produced nodes will be excluded + from `Clean`. + """ + self.env = env + self.alias = alias + self.precious = precious + self.noclean = noclean + self.nodefault = nodefault + + def _create_node(self, name, factory, directory = None, create = 1): + """ Create node, and set it up to factory settings. """ + import SCons.Util + node = factory(name, directory, create) + node.set_noclean(self.noclean) + node.set_precious(self.precious) + if self.nodefault: + self.env.Ignore('.', node) + if self.alias: + self.env.AlwaysBuild(self.env.Alias(self.alias, node)) + return node + + def Entry(self, name, directory = None, create = 1): + """ Create `SCons.Node.FS.Entry` """ + return self._create_node(name, self.env.fs.Entry, directory, create) + + def File(self, name, directory = None, create = 1): + """ Create `SCons.Node.FS.File` """ + return self._create_node(name, self.env.fs.File, directory, create) +############################################################################# + +############################################################################# +_re_comment = re.compile(r'(#[^\n\r]+)$', re.M) +_re_lang = re.compile(r'([a-zA-Z0-9_]+)', re.M) +############################################################################# +def _read_linguas_from_files(env, linguas_files = None): + """ Parse `LINGUAS` file and return list of extracted languages """ + import SCons.Util + import SCons.Environment + global _re_comment + global _re_lang + if not SCons.Util.is_List(linguas_files) \ + and not SCons.Util.is_String(linguas_files) \ + and not isinstance(linguas_files, SCons.Node.FS.Base) \ + and linguas_files: + # If, linguas_files==True or such, then read 'LINGUAS' file. + linguas_files = [ 'LINGUAS' ] + if linguas_files is None: + return [] + fnodes = env.arg2nodes(linguas_files) + linguas = [] + for fnode in fnodes: + contents = _re_comment.sub("", fnode.get_text_contents()) + ls = [ l for l in _re_lang.findall(contents) if l ] + linguas.extend(ls) + return linguas +############################################################################# + +############################################################################# +from SCons.Builder import BuilderBase +############################################################################# +class _POFileBuilder(BuilderBase): + """ `PO` file builder. + + This is multi-target single-source builder. In typical situation the source + is single `POT` file, e.g. `messages.pot`, and there are multiple `PO` + targets to be updated from this `POT`. We must run + `SCons.Builder.BuilderBase._execute()` separatelly for each target to track + dependencies separatelly for each target file. + + **NOTE**: if we call `SCons.Builder.BuilderBase._execute(.., target, ...)` + with target being list of all targets, all targets would be rebuilt each time + one of the targets from this list is missing. This would happen, for example, + when new language `ll` enters `LINGUAS_FILE` (at this moment there is no + `ll.po` file yet). To avoid this, we override + `SCons.Builder.BuilerBase._execute()` and call it separatelly for each + target. Here we also append to the target list the languages read from + `LINGUAS_FILE`. + """ + # + #* The argument for overriding _execute(): We must use environment with + # builder overrides applied (see BuilderBase.__init__(). Here it comes for + # free. + #* The argument against using 'emitter': The emitter is called too late + # by BuilderBase._execute(). If user calls, for example: + # + # env.POUpdate(LINGUAS_FILE = 'LINGUAS') + # + # the builder throws error, because it is called with target=None, + # source=None and is trying to "generate" sources or target list first. + # If user calls + # + # env.POUpdate(['foo', 'baz'], LINGUAS_FILE = 'LINGUAS') + # + # the env.BuilderWrapper() calls our builder with target=None, + # source=['foo', 'baz']. The BuilderBase._execute() then splits execution + # and execute iterativelly (recursion) self._execute(None, source[i]). + # After that it calls emitter (which is quite too late). The emitter is + # also called in each iteration, what makes things yet worse. + def __init__(self, env, **kw): + if not 'suffix' in kw: + kw['suffix'] = '$POSUFFIX' + if not 'src_suffix' in kw: + kw['src_suffix'] = '$POTSUFFIX' + if not 'src_builder' in kw: + kw['src_builder'] = '_POTUpdateBuilder' + if not 'single_source' in kw: + kw['single_source'] = True + alias = None + if 'target_alias' in kw: + alias = kw['target_alias'] + del kw['target_alias'] + if not 'target_factory' in kw: + kw['target_factory'] = _POTargetFactory(env, alias=alias).File + BuilderBase.__init__(self, **kw) + + def _execute(self, env, target, source, *args, **kw): + """ Execute builder's actions. + + Here we append to `target` the languages read from `$LINGUAS_FILE` and + apply `SCons.Builder.BuilderBase._execute()` separatelly to each target. + The arguments and return value are same as for + `SCons.Builder.BuilderBase._execute()`. + """ + import SCons.Util + import SCons.Node + linguas_files = None + if env.has_key('LINGUAS_FILE') and env['LINGUAS_FILE']: + linguas_files = env['LINGUAS_FILE'] + # This prevents endless recursion loop (we'll be invoked once for + # each target appended here, we must not extend the list again). + env['LINGUAS_FILE'] = None + linguas = _read_linguas_from_files(env,linguas_files) + if SCons.Util.is_List(target): + target.extend(linguas) + elif target is not None: + target = [target] + linguas + else: + target = linguas + if not target: + # Let the SCons.BuilderBase to handle this patologic situation + return BuilderBase._execute( self, env, target, source, *args, **kw) + # The rest is ours + if not SCons.Util.is_List(target): + target = [ target ] + result = [] + for tgt in target: + r = BuilderBase._execute( self, env, [tgt], source, *args, **kw) + result.extend(r) + if linguas_files is not None: + env['LINGUAS_FILE'] = linguas_files + return SCons.Node.NodeList(result) +############################################################################# + +import SCons.Environment +############################################################################# +def _translate(env, target=[], source=SCons.Environment._null, *args, **kw): + """ Function for `Translate()` pseudo-builder """ + pot = env.POTUpdate(None, source, *args, **kw) + po = env.POUpdate(target, pot, *args, **kw) + return po +############################################################################# + +############################################################################# +class RPaths(object): + """ Callable object, which returns pathnames relative to SCons current + working directory. + + It seems like `SCons.Node.FS.Base.get_path()` returns absolute paths + for nodes that are outside of current working directory (`env.fs.getcwd()`). + Here, we often have `SConscript`, `POT` and `PO` files within `po/` + directory and source files (e.g. `*.c`) outside of it. When generating `POT` + template file, references to source files are written to `POT` template, so + a translator may later quickly jump to appropriate source file and line from + its `PO` editor (e.g. `poedit`). Relative paths in `PO` file are usually + interpreted by `PO` editor as paths relative to the place, where `PO` file + lives. The absolute paths would make resultant `POT` file nonportable, as + the references would be correct only on the machine, where `POT` file was + recently re-created. For such reason, we need a function, which always + returns relative paths. This is the purpose of `RPaths` callable object. + + The `__call__` method returns paths relative to current woking directory, but + we assume, that *xgettext(1)* is run from the directory, where target file is + going to be created. + + Note, that this may not work for files distributed over several hosts or + across different drives on windows. We assume here, that single local + filesystem holds both source files and target `POT` templates. + + Intended use of `RPaths` - in `xgettext.py`:: + + def generate(env): + from GettextCommon import RPaths + ... + sources = '$( ${_concat( "", SOURCES, "", __env__, XgettextRPaths, TARGET, SOURCES)} $)' + env.Append( + ... + XGETTEXTCOM = 'XGETTEXT ... ' + sources, + ... + XgettextRPaths = RPaths(env) + ) + """ + # NOTE: This callable object returns pathnames of dirs/files relative to + # current working directory. The pathname remains relative also for entries + # that are outside of current working directory (node, that + # SCons.Node.FS.File and siblings return absolute path in such case). For + # simplicity we compute path relative to current working directory, this + # seems be enough for our purposes (don't need TARGET variable and + # SCons.Defaults.Variable_Caller stuff). + + def __init__(self, env): + """ Initialize `RPaths` callable object. + + **Arguments**: + + - *env* - a `SCons.Environment.Environment` object, defines *current + working dir*. + """ + self.env = env + + # FIXME: I'm not sure, how it should be implemented (what the *args are in + # general, what is **kw). + def __call__(self, nodes, *args, **kw): + """ Return nodes' paths (strings) relative to current working directory. + + **Arguments**: + + - *nodes* ([`SCons.Node.FS.Base`]) - list of nodes. + - *args* - currently unused. + - *kw* - currently unused. + + **Returns**: + + - Tuple of strings, which represent paths relative to current working + directory (for given environment). + """ + # os.path.relpath is available only on python >= 2.6. We use our own + # implementation. It's taken from BareNecessities package: + # http://jimmyg.org/work/code/barenecessities/index.html + from posixpath import curdir + def relpath(path, start=curdir): + import posixpath + """Return a relative version of a path""" + if not path: + raise ValueError("no path specified") + start_list = posixpath.abspath(start).split(posixpath.sep) + path_list = posixpath.abspath(path).split(posixpath.sep) + # Work out how much of the filepath is shared by start and path. + i = len(posixpath.commonprefix([start_list, path_list])) + rel_list = [posixpath.pardir] * (len(start_list)-i) + path_list[i:] + if not rel_list: + return posixpath.curdir + return posixpath.join(*rel_list) + import os + import SCons.Node.FS + rpaths = () + cwd = self.env.fs.getcwd().get_abspath() + for node in nodes: + rpath = None + if isinstance(node, SCons.Node.FS.Base): + rpath = relpath(node.get_abspath(), cwd) + # FIXME: Other types possible here? + if rpath is not None: + rpaths += (rpath,) + return rpaths +############################################################################# + +############################################################################# +def _init_po_files(target, source, env): + """ Action function for `POInit` builder. """ + nop = lambda target, source, env : 0 + if env.has_key('POAUTOINIT'): + autoinit = env['POAUTOINIT'] + else: + autoinit = False + # Well, if everything outside works well, this loop should do single + # iteration. Otherwise we are rebuilding all the targets even, if just + # one has changed (but is this out fault?). + for tgt in target: + if not tgt.exists(): + if autoinit: + action = SCons.Action.Action('$MSGINITCOM', '$MSGINITCOMSTR') + else: + msg = 'File ' + repr(str(tgt)) + ' does not exist. ' \ + + 'If you are a translator, you can create it through: \n' \ + + '$MSGINITCOM' + action = SCons.Action.Action(nop, msg) + status = action([tgt], source, env) + if status: return status + return 0 +############################################################################# + +############################################################################# +def _detect_xgettext(env): + """ Detects *xgettext(1)* binary """ + if env.has_key('XGETTEXT'): + return env['XGETTEXT'] + xgettext = env.Detect('xgettext'); + if xgettext: + return xgettext + raise SCons.Errors.StopError(XgettextNotFound,"Could not detect xgettext") + return None +############################################################################# +def _xgettext_exists(env): + return _detect_xgettext(env) +############################################################################# + +############################################################################# +def _detect_msginit(env): + """ Detects *msginit(1)* program. """ + if env.has_key('MSGINIT'): + return env['MSGINIT'] + msginit = env.Detect('msginit'); + if msginit: + return msginit + raise SCons.Errors.StopError(MsginitNotFound, "Could not detect msginit") + return None +############################################################################# +def _msginit_exists(env): + return _detect_msginit(env) +############################################################################# + +############################################################################# +def _detect_msgmerge(env): + """ Detects *msgmerge(1)* program. """ + if env.has_key('MSGMERGE'): + return env['MSGMERGE'] + msgmerge = env.Detect('msgmerge'); + if msgmerge: + return msgmerge + raise SCons.Errors.StopError(MsgmergeNotFound, "Could not detect msgmerge") + return None +############################################################################# +def _msgmerge_exists(env): + return _detect_msgmerge(env) +############################################################################# + +############################################################################# +def _detect_msgfmt(env): + """ Detects *msgmfmt(1)* program. """ + if env.has_key('MSGFMT'): + return env['MSGFMT'] + msgfmt = env.Detect('msgfmt'); + if msgfmt: + return msgfmt + raise SCons.Errors.StopError(MsgfmtNotFound, "Could not detect msgfmt") + return None +############################################################################# +def _msgfmt_exists(env): + return _detect_msgfmt(env) +############################################################################# + +############################################################################# +def tool_list(platform, env): + """ List tools that shall be generated by top-level `gettext` tool """ + return [ 'xgettext', 'msginit', 'msgmerge', 'msgfmt' ] +############################################################################# + diff --git a/src/engine/SCons/Tool/gettext.py b/src/engine/SCons/Tool/gettext.py new file mode 100644 index 0000000..dd336b6 --- /dev/null +++ b/src/engine/SCons/Tool/gettext.py @@ -0,0 +1,45 @@ +"""gettext tool +""" + + +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +############################################################################# +def generate(env,**kw): + import SCons.Tool + from SCons.Tool.GettextCommon \ + import _translate, tool_list + for t in tool_list(env['PLATFORM'], env): + env.Tool(t) + env.AddMethod(_translate, 'Translate') +############################################################################# + +############################################################################# +def exists(env): + from SCons.Tool.GettextCommon \ + import _xgettext_exists, _msginit_exists, \ + _msgmerge_exists, _msgfmt_exists + return _xgettext_exists(env) and _msginit_exists(env) \ + and _msgmerge_exists(env) and _msgfmt_exists(env) +############################################################################# diff --git a/src/engine/SCons/Tool/gettext.xml b/src/engine/SCons/Tool/gettext.xml new file mode 100644 index 0000000..3a9a78d --- /dev/null +++ b/src/engine/SCons/Tool/gettext.xml @@ -0,0 +1,200 @@ +<!-- +__COPYRIGHT__ + +This file is processed by the bin/SConsDoc.py module. +See its __doc__ string for a discussion of the format. +--> +<tool name="gettext"> +<summary> +This is actually a toolset, which supports internationalization and +localization of sofware being constructed with SCons. The toolset loads +following tools: + +<itemizedlist mark='opencircle'> + <listitem><para> + &t-link-xgettext; - to extract internationalized messages from source code to + <literal>POT</literal> file(s), + </para></listitem> + <listitem><para> + &t-link-msginit; - may be optionally used to initialize <literal>PO</literal> + files, + </para></listitem> + <listitem><para> + &t-link-msgmerge; - to update <literal>PO</literal> files, that already contain + translated messages,</para></listitem> + <listitem><para> + &t-link-msgfmt; - to compile textual <literal>PO</literal> file to binary + installable <literal>MO</literal> file. + </para></listitem> +</itemizedlist> + +When you enable &t-gettext;, it internally loads all abovementioned tools, +so you're encouraged to see their individual documentation. + +Each of the above tools provides its own builder(s) which may be used to +perform particular activities related to software internationalization. You +may be however interested in <emphasis>top-level</emphasis> builder +&b-Translate; described few paragraphs later. + +To use &t-gettext; tools add <literal>'gettext'</literal> tool to your +environment: +<example> + env = Environment( tools = ['default', 'gettext'] ) +</example> +</summary> +<sets> +</sets> +<uses> +<!-- PLATFORM --> +</uses> +</tool> + +<builder name="Translate"> +<summary> +This pseudo-builder belongs to &t-link-gettext; toolset. The builder extracts +internationalized messages from source files, updates <literal>POT</literal> +template (if necessary) and then updates <literal>PO</literal> translations (if +necessary). If &cv-link-POAUTOINIT; is set, missing <literal>PO</literal> files +will be automatically created (i.e. without translator person intervention). +The variables &cv-link-LINGUAS_FILE; and &cv-link-POTDOMAIN; are taken into +acount too. All other construction variables used by &b-link-POTUpdate;, and +&b-link-POUpdate; work here too. + +<emphasis>Example 1</emphasis>. +The simplest way is to specify input files and output languages inline in +a SCons script when invoking &b-Translate; +<example> +# SConscript in 'po/' directory +env = Environment( tools = ["default", "gettext"] ) +env['POAUTOINIT'] = 1 +env.Translate(['en','pl'], ['../a.cpp','../b.cpp']) +</example> + +<emphasis>Example 2</emphasis>. +If you wish, you may also stick to conventional style known from +<productname>autotools</productname>, i.e. using +<filename>POTFILES.in</filename> and <filename>LINGUAS</filename> files +<example> +# LINGUAS +en pl +#end +</example> + +<example> +# POTFILES.in +a.cpp +b.cpp +# end +</example> + +<example> +# SConscript +env = Environment( tools = ["default", "gettext"] ) +env['POAUTOINIT'] = 1 +env['XGETTEXTPATH'] = ['../'] +env.Translate(LINGUAS_FILE = 1, XGETTEXTFROM = 'POTFILES.in') +</example> + +The last approach is perhaps the recommended one. It allows easily split +internationalization/localization onto separate SCons scripts, where a script +in source tree is responsible for translations (from sources to +<literal>PO</literal> files) and script(s) under variant directories are +responsible for compilation of <literal>PO</literal> to <literal>MO</literal> +files to and for installation of <literal>MO</literal> files. The "gluing +factor" synchronizing these two scripts is then the content of +<filename>LINGUAS</filename> file. Note, that the updated +<literal>POT</literal> and <literal>PO</literal> files are usually going to be +committed back to the repository, so they must be updated within the source +directory (and not in variant directories). Additionaly, the file listing of +<filename>po/</filename> directory contains <filename>LINGUAS</filename> file, +so the source tree looks familiar to translators, and they may work with the +project in their usual way. + +<emphasis>Example 3</emphasis>. +Let's prepare a development tree as below +<example> + project/ + + SConstruct + + build/ + + src/ + + po/ + + SConscript + + SConscript.i18n + + POTFILES.in + + LINGUAS +</example> +with <filename>build</filename> being variant directory. Write the top-level +<filename>SConstruct</filename> script as follows +<example> + # SConstruct + env = Environment( tools = ["default", "gettext"] ) + VariantDir('build', 'src', duplicate = 0) + env['POAUTOINIT'] = 1 + SConscript('src/po/SConscript.i18n', exports = 'env') + SConscript('build/po/SConscript', exports = 'env') +</example> +the <filename>src/po/SConscript.i18n</filename> as +<example> + # src/po/SConscript.i18n + Import('env') + env.Translate(LINGUAS_FILE=1, XGETTEXTFROM='POTFILES.in', XGETTEXTPATH=['../']) +</example> +and the <filename>src/po/SConscript</filename> +<example> + # src/po/SConscript + Import('env') + env.MOFiles(LINGUAS_FILE = 1) +</example> +Such setup produces <literal>POT</literal> and <literal>PO</literal> files +under source tree in <filename>src/po/</filename> and binary +<literal>MO</literal> files under variant tree in +<filename>build/po/</filename>. This way the <literal>POT</literal> and +<literal>PO</literal> files are separated from other output files, which must +not be committed back to source repositories (e.g. <literal>MO</literal> +files). + +<note><para>In above example, the <literal>PO</literal> files are not updated, +nor created automatically when you issue <command>scons '.'</command> command. +The files must be updated (created) by hand via <command>scons +po-update</command> and then <literal>MO</literal> files can be compiled by +running <command>scons '.'</command>.</para></note> + +</summary> +</builder> + +<cvar name="POTDOMAIN"> +<summary> +The &cv-POTDOMAIN; defines default domain, used to generate +<literal>POT</literal> filename as <filename>&cv-POTDOMAIN;.pot</filename> when +no <literal>POT</literal> file name is provided by the user. This applies to +&b-link-POTUpdate;, &b-link-POInit; and &b-link-POUpdate; builders (and +builders, that use them, e.g. &b-Translate;). Normally (if &cv-POTDOMAIN; is +not defined), the builders use <filename>messages.pot</filename> as default +<literal>POT</literal> file name. +</summary> +</cvar> + +<cvar name="POAUTOINIT"> +<summary> +The &cv-POAUTOINIT; variable, if set to <literal>True</literal> (on non-zero +numeric value), let the &t-link-msginit; tool to automatically initialize +<emphasis>missing</emphasis> <literal>PO</literal> files with +<command>msginit(1)</command>. This applies to both, +&b-link-POInit; and &b-link-POUpdate; builders (and others that use any of +them). +</summary> +</cvar> + +<cvar name="LINGUAS_FILE"> +<summary> +The &cv-LINGUAS_FILE; defines file(s) containing list of additional linguas +to be processed by &b-link-POInit;, &b-link-POUpdate; or &b-link-MOFiles; +builders. It also affects &b-link-Translate; builder. If the variable contains +a string, it defines name of the list file. The &cv-LINGUAS_FILE; may be a +list of file names as well. If &cv-LINGUAS_FILE; is set to +<literal>True</literal> (or non-zero numeric value), the list will be read from +default file named +<filename>LINGUAS</filename>. + +</summary> +</cvar> diff --git a/src/engine/SCons/Tool/msgfmt.py b/src/engine/SCons/Tool/msgfmt.py new file mode 100644 index 0000000..83b54f7 --- /dev/null +++ b/src/engine/SCons/Tool/msgfmt.py @@ -0,0 +1,102 @@ +""" msgfmt tool """ + +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +from SCons.Builder import BuilderBase +############################################################################# +class _MOFileBuilder(BuilderBase): + """ The builder class for `MO` files. + + The reason for this builder to exists and its purpose is quite simillar + as for `_POFileBuilder`. This time, we extend list of sources, not targets, + and call `BuilderBase._execute()` only once (as we assume single-target + here). + """ + + def _execute(self, env, target, source, *args, **kw): + # Here we add support for 'LINGUAS_FILE' keyword. Emitter is not suitable + # in this case, as it is called too late (after multiple sources + # are handled single_source builder. + import SCons.Util + from SCons.Tool.GettextCommon import _read_linguas_from_files + linguas_files = None + if env.has_key('LINGUAS_FILE') and env['LINGUAS_FILE'] is not None: + linguas_files = env['LINGUAS_FILE'] + # This should prevent from endless recursion. + env['LINGUAS_FILE'] = None + # We read only languages. Suffixes shall be added automatically. + linguas = _read_linguas_from_files(env, linguas_files) + if SCons.Util.is_List(source): + source.extend(linguas) + elif source is not None: + source = [source] + linguas + else: + source = linguas + result = BuilderBase._execute(self,env,target,source,*args, **kw) + if linguas_files is not None: + env['LINGUAS_FILE'] = linguas_files + return result +############################################################################# + +############################################################################# +def _create_mo_file_builder(env, **kw): + """ Create builder object for `MOFiles` builder """ + import SCons.Action + # FIXME: What factory use for source? Ours or their? + kw['action'] = SCons.Action.Action('$MSGFMTCOM','$MSGFMTCOMSTR') + kw['suffix'] = '$MOSUFFIX' + kw['src_suffix'] = '$POSUFFIX' + kw['src_builder'] = '_POUpdateBuilder' + kw['single_source'] = True + return _MOFileBuilder(**kw) +############################################################################# + +############################################################################# +def generate(env,**kw): + """ Generate `msgfmt` tool """ + import SCons.Util + from SCons.Tool.GettextCommon import _detect_msgfmt + env['MSGFMT'] = _detect_msgfmt(env) + env.SetDefault( + MSGFMTFLAGS = [ SCons.Util.CLVar('-c') ], + MSGFMTCOM = '$MSGFMT $MSGFMTFLAGS -o $TARGET $SOURCE', + MSGFMTCOMSTR = '', + MOSUFFIX = ['.mo'], + POSUFFIX = ['.po'] + ) + env.Append( BUILDERS = { 'MOFiles' : _create_mo_file_builder(env) } ) +############################################################################# + +############################################################################# +def exists(env): + """ Check if the tool exists """ + from SCons.Tool.GettextCommon import _msgfmt_exists + return _msgfmt_exists(env) +############################################################################# + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/msgfmt.xml b/src/engine/SCons/Tool/msgfmt.xml new file mode 100644 index 0000000..3c5db8f --- /dev/null +++ b/src/engine/SCons/Tool/msgfmt.xml @@ -0,0 +1,102 @@ +<!-- +__COPYRIGHT__ + +This file is processed by the bin/SConsDoc.py module. +See its __doc__ string for a discussion of the format. +--> +<tool name="msgfmt"> +<summary> +This scons tool is a part of scons &t-link-gettext; toolset. It provides scons +interface to <command>msgfmt(1)</command> command, which generates binary +message catalog (<literal>MO</literal>) from a textual translation description +(<literal>PO</literal>). +</summary> +<sets> +MOSUFFIX +MSGFMT +MSGFMTCOM +MSGFMTCOMSTR +MSGFMTFLAGS +POSUFFIX +</sets> +<uses> +LINGUAS_FILE +</uses> +</tool> + +<builder name="MOFiles"> +<summary> +This builder belongs to &t-link-msgfmt; tool. The builder compiles +<literal>PO</literal> files to <literal>MO</literal> files. + +<emphasis>Example 1</emphasis>. +Create <filename>pl.mo</filename> and <filename>en.mo</filename> by compiling +<filename>pl.po</filename> and <filename>en.po</filename>: +<example> + # ... + env.MOFiles(['pl', 'en']) +</example> + +<emphasis>Example 2</emphasis>. +Compile files for languages defined in <filename>LINGUAS</filename> file: +<example> + # ... + env.MOFiles(LINGUAS_FILE = 1) +</example> + +<emphasis>Example 3</emphasis>. +Create <filename>pl.mo</filename> and <filename>en.mo</filename> by compiling +<filename>pl.po</filename> and <filename>en.po</filename> plus files for +languages defined in <filename>LINGUAS</filename> file: +<example> + # ... + env.MOFiles(['pl', 'en'], LINGUAS_FILE = 1) +</example> + +<emphasis>Example 4</emphasis>. +Compile files for languages defined in <filename>LINGUAS</filename> file +(another version): +<example> + # ... + env['LINGUAS_FILE'] = 1 + env.MOFiles() +</example> +</summary> +</builder> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="MOSUFFIX"> +<summary> +Suffix used for <literal>MO</literal> files (default: <literal>'.mo'</literal>). +See &t-link-msgfmt; tool and &b-link-MOFiles; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="MSGFMT"> +<summary> +Absolute path to <command>msgfmt(1)</command> binary, found by +<function>Detect()</function>. +See &t-link-msgfmt; tool and &b-link-MOFiles; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="MSGFMTCOM"> +<summary> +Complete command line to run <command>msgfmt(1)</command> program. +See &t-link-msgfmt; tool and &b-link-MOFiles; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="MSGFMTCOMSTR"> +<summary> +String to display when <command>msgfmt(1)</command> is invoked +(default: <literal>''</literal>, which means ``print &cv-link-MSGFMTCOM;''). +See &t-link-msgfmt; tool and &b-link-MOFiles; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="MSGFMTFLAGS"> +<summary> +Additional flags to <command>msgfmt(1)</command>. +See &t-link-msgfmt; tool and &b-link-MOFiles; builder. +</summary> +</cvar> diff --git a/src/engine/SCons/Tool/msginit.py b/src/engine/SCons/Tool/msginit.py new file mode 100644 index 0000000..87b3eec --- /dev/null +++ b/src/engine/SCons/Tool/msginit.py @@ -0,0 +1,114 @@ +""" msginit tool + +Tool specific initialization of msginit tool. +""" + +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import SCons.Warnings +import SCons.Builder +import re + +############################################################################# +def _optional_no_translator_flag(env): + """ Return '--no-translator' flag if we run *msginit(1)* in non-interactive + mode.""" + import SCons.Util + if env.has_key('POAUTOINIT'): + autoinit = env['POAUTOINIT'] + else: + autoinit = False + if autoinit: + return [SCons.Util.CLVar('--no-translator')] + else: + return [SCons.Util.CLVar('')] +############################################################################# + +############################################################################# +def _POInitBuilder(env, **kw): + """ Create builder object for `POInit` builder. """ + import SCons.Action + from SCons.Tool.GettextCommon import _init_po_files, _POFileBuilder + action = SCons.Action.Action(_init_po_files, None) + return _POFileBuilder(env, action=action, target_alias='$POCREATE_ALIAS') +############################################################################# + +############################################################################# +from SCons.Environment import _null +############################################################################# +def _POInitBuilderWrapper(env, target=None, source=_null, **kw): + """ Wrapper for _POFileBuilder. We use it to make user's life easier. + + This wrapper checks for `$POTDOMAIN` construction variable (or override in + `**kw`) and treats it appropriatelly. + """ + if source is _null: + if 'POTDOMAIN' in kw: + domain = kw['POTDOMAIN'] + elif env.has_key('POTDOMAIN'): + domain = env['POTDOMAIN'] + else: + domain = 'messages' + source = [ domain ] # NOTE: Suffix shall be appended automatically + return env._POInitBuilder(target, source, **kw) +############################################################################# + +############################################################################# +def generate(env,**kw): + """ Generate the `msginit` tool """ + import SCons.Util + from SCons.Tool.GettextCommon import _detect_msginit + env['MSGINIT'] = _detect_msginit(env) + msginitcom = '$MSGINIT ${_MSGNoTranslator(__env__)} -l ${_MSGINITLOCALE}' \ + + ' $MSGINITFLAGS -i $SOURCE -o $TARGET' + # NOTE: We set POTSUFFIX here, in case the 'xgettext' is not loaded + # (sometimes we really don't need it) + env.SetDefault( + POSUFFIX = ['.po'], + POTSUFFIX = ['.pot'], + _MSGINITLOCALE = '${TARGET.filebase}', + _MSGNoTranslator = _optional_no_translator_flag, + MSGINITCOM = msginitcom, + MSGINITCOMSTR = '', + MSGINITFLAGS = [ ], + POAUTOINIT = False, + POCREATE_ALIAS = 'po-create' + ) + env.Append( BUILDERS = { '_POInitBuilder' : _POInitBuilder(env) } ) + env.AddMethod(_POInitBuilderWrapper, 'POInit') + env.AlwaysBuild(env.Alias('$POCREATE_ALIAS')) +############################################################################# + +############################################################################# +def exists(env): + """ Check if the tool exists """ + from SCons.Tool.GettextCommon import _msginit_exists + return _msginit_exists(env) +############################################################################# + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/msginit.xml b/src/engine/SCons/Tool/msginit.xml new file mode 100644 index 0000000..612b9d9 --- /dev/null +++ b/src/engine/SCons/Tool/msginit.xml @@ -0,0 +1,168 @@ +<!-- +__COPYRIGHT__ + +This file is processed by the bin/SConsDoc.py module. +See its __doc__ string for a discussion of the format. +--> +<tool name="msginit"> +<summary> +This scons tool is a part of scons &t-link-gettext; toolset. It provides +scons interface to <command>msginit(1)</command> program, which creates new +<literal>PO</literal> file, initializing the meta information with values from +user's environment (or options). +</summary> +<sets> +MSGINIT +MSGINITCOM +MSGINITCOMSTR +MSGINITFLAGS +POAUTOINIT +POCREATE_ALIAS +POSUFFIX +POTSUFFIX +_MSGINITLOCALE +</sets> +<uses> +POTDOMAIN +LINGUAS_FILE +POAUTOINIT +</uses> +</tool> + +<builder name="POInit"> +<summary> +This builder belongs to &t-link-msginit; tool. The builder initializes missing +<literal>PO</literal> file(s) if &cv-link-POAUTOINIT; is set. If +&cv-link-POAUTOINIT; is not set (default), &b-POInit; prints instruction for +user (that is supposed to be a translator), telling how the +<literal>PO</literal> file should be initialized. In normal projects +<emphasis>you should not use &b-POInit; and use &b-link-POUpdate; +instead</emphasis>. &b-link-POUpdate; chooses intelligently between +<command>msgmerge(1)</command> and <command>msginit(1)</command>. &b-POInit; +always uses <command>msginit(1)</command> and should be regarded as builder for +special purposes or for temporary use (e.g. for quick, one time initialization +of a bunch of <literal>PO</literal> files) or for tests. + +Target nodes defined through &b-POInit; are not built by default (they're +<literal>Ignore</literal>d from <literal>'.'</literal> node) but are added to +special <literal>Alias</literal> (<literal>'po-create'</literal> by default). +The alias name may be changed through the &cv-link-POCREATE_ALIAS; +construction variable. All <literal>PO</literal> files defined through +&b-POInit; may be easily initialized by <command>scons po-create</command>. + +<emphasis>Example 1</emphasis>. +Initialize <filename>en.po</filename> and <filename>pl.po</filename> from +<filename>messages.pot</filename>: +<example> + # ... + env.POInit(['en', 'pl']) # messages.pot --> [en.po, pl.po] +</example> + +<emphasis>Example 2</emphasis>. +Initialize <filename>en.po</filename> and <filename>pl.po</filename> from +<filename>foo.pot</filename>: +<example> + # ... + env.POInit(['en', 'pl'], ['foo']) # foo.pot --> [en.po, pl.po] +</example> + +<emphasis>Example 3</emphasis>. +Initialize <filename>en.po</filename> and <filename>pl.po</filename> from +<filename>foo.pot</filename> but using &cv-link-POTDOMAIN; construction +variable: +<example> + # ... + env.POInit(['en', 'pl'], POTDOMAIN='foo') # foo.pot --> [en.po, pl.po] +</example> + +<emphasis>Example 4</emphasis>. +Initialize <literal>PO</literal> files for languages defined in +<filename>LINGUAS</filename> file. The files will be initialized from template +<filename>messages.pot</filename>: +<example> + # ... + env.POInit(LINGUAS_FILE = 1) # needs 'LINGUAS' file +</example> + +<emphasis>Example 5</emphasis>. +Initialize <filename>en.po</filename> and <filename>pl.pl</filename> +<literal>PO</literal> files plus files for languages defined in +<filename>LINGUAS</filename> file. The files will be initialized from template +<filename>messages.pot</filename>: +<example> + # ... + env.POInit(['en', 'pl'], LINGUAS_FILE = 1) +</example> + +<emphasis>Example 6</emphasis>. +You may preconfigure your environment first, and then initialize +<literal>PO</literal> files: +<example> + # ... + env['POAUTOINIT'] = 1 + env['LINGUAS_FILE'] = 1 + env['POTDOMAIN'] = 'foo' + env.POInit() +</example> +which has same efect as: +<example> + # ... + env.POInit(POAUTOINIT = 1, LINGUAS_FILE = 1, POTDOMAIN = 'foo') +</example> +</summary> +</builder> + +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="POCREATE_ALIAS"> +<summary> +Common alias for all <literal>PO</literal> files created with &b-POInit; +builder (default: <literal>'po-create'</literal>). +See &t-link-msginit; tool and &b-link-POInit; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="POSUFFIX"> +<summary> +Suffix used for <literal>PO</literal> files (default: <literal>'.po'</literal>) +See &t-link-msginit; tool and &b-link-POInit; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="MSGINIT"> +<summary> +Path to <command>msginit(1)</command> program (found via +<literal>Detect()</literal>). +See &t-link-msginit; tool and &b-link-POInit; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="MSGINITCOM"> +<summary> +Complete command line to run <command>msginit(1)</command> program. +See &t-link-msginit; tool and &b-link-POInit; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="MSGINITCOMSTR"> +<summary> +String to display when <command>msginit(1)</command> is invoked +(default: <literal>''</literal>, which means ``print &cv-link-MSGINITCOM;''). +See &t-link-msginit; tool and &b-link-POInit; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="MSGINITFLAGS"> +<summary> +List of additional flags to <command>msginit(1)</command> (default: +<literal>[]</literal>). +See &t-link-msginit; tool and &b-link-POInit; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="_MSGINITLOCALE"> +<summary> +Internal ``macro''. Computes locale (language) name based on target filename +(default: <literal>'${TARGET.filebase}' </literal>). +</summary> +See &t-link-msginit; tool and &b-link-POInit; builder. +</cvar> diff --git a/src/engine/SCons/Tool/msgmerge.py b/src/engine/SCons/Tool/msgmerge.py new file mode 100644 index 0000000..78eb2c5 --- /dev/null +++ b/src/engine/SCons/Tool/msgmerge.py @@ -0,0 +1,98 @@ +""" msgmerget tool + +Tool specific initialization for `msgmerge` tool. +""" + +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +############################################################################# +def _update_or_init_po_files(target, source, env): + """ Action function for `POUpdate` builder """ + import SCons.Action + from SCons.Tool.GettextCommon import _init_po_files + for tgt in target: + if tgt.rexists(): + action = SCons.Action.Action('$MSGMERGECOM', '$MSGMERGECOMSTR') + else: + action = _init_po_files + status = action([tgt], source, env) + if status : return status + return 0 +############################################################################# + +############################################################################# +def _POUpdateBuilder(env, **kw): + """ Create an object of `POUpdate` builder """ + import SCons.Action + from SCons.Tool.GettextCommon import _POFileBuilder + action = SCons.Action.Action(_update_or_init_po_files, None) + return _POFileBuilder(env, action=action, target_alias='$POUPDATE_ALIAS') +############################################################################# + +############################################################################# +from SCons.Environment import _null +############################################################################# +def _POUpdateBuilderWrapper(env, target=None, source=_null, **kw): + """ Wrapper for `POUpdate` builder - make user's life easier """ + if source is _null: + if 'POTDOMAIN' in kw: + domain = kw['POTDOMAIN'] + elif env.has_key('POTDOMAIN') and env['POTDOMAIN']: + domain = env['POTDOMAIN'] + else: + domain = 'messages' + source = [ domain ] # NOTE: Suffix shall be appended automatically + return env._POUpdateBuilder(target, source, **kw) +############################################################################# + +############################################################################# +def generate(env,**kw): + """ Generate the `xgettext` tool """ + from SCons.Tool.GettextCommon import _detect_msgmerge + env['MSGMERGE'] = _detect_msgmerge(env) + env.SetDefault( + POTSUFFIX = ['.pot'], + POSUFFIX = ['.po'], + MSGMERGECOM = '$MSGMERGE $MSGMERGEFLAGS --update $TARGET $SOURCE', + MSGMERGECOMSTR = '', + MSGMERGEFLAGS = [ ], + POUPDATE_ALIAS = 'po-update' + ) + env.Append(BUILDERS = { '_POUpdateBuilder':_POUpdateBuilder(env) }) + env.AddMethod(_POUpdateBuilderWrapper, 'POUpdate') + env.AlwaysBuild(env.Alias('$POUPDATE_ALIAS')) +############################################################################# + +############################################################################# +def exists(env): + """ Check if the tool exists """ + from SCons.Tool.GettextCommon import _msgmerge_exists + return _msgmerge_exists(env) +############################################################################# + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/msgmerge.xml b/src/engine/SCons/Tool/msgmerge.xml new file mode 100644 index 0000000..69c848b --- /dev/null +++ b/src/engine/SCons/Tool/msgmerge.xml @@ -0,0 +1,158 @@ +<!-- +__COPYRIGHT__ + +This file is processed by the bin/SConsDoc.py module. +See its __doc__ string for a discussion of the format. +--> +<tool name="msgmerge"> +<summary> +This scons tool is a part of scons &t-link-gettext; toolset. It provides +scons interface to <command>msgmerge(1)</command> command, which merges two +Uniform style <filename>.po</filename> files together. +</summary> +<sets> +MSGMERGE +MSGMERGECOM +MSGMERGECOMSTR +MSGMERGEFLAGS +POSUFFIX +POTSUFFIX +POUPDATE_ALIAS +</sets> +<uses> +POTDOMAIN +LINGUAS_FILE +POAUTOINIT +</uses> +</tool> + +<builder name="POUpdate"> +<summary> +The builder belongs to &t-link-msgmerge; tool. The builder updates +<literal>PO</literal> files with <command>msgmerge(1)</command>, or initializes +missing <literal>PO</literal> files as described in documentation of +&t-link-msginit; tool and &b-link-POInit; builder (see also +&cv-link-POAUTOINIT;). Note, that &b-POUpdate; <emphasis>does not add its +targets to <literal>po-create</literal> alias</emphasis> as &b-link-POInit; +does. + +Target nodes defined through &b-POUpdate; are not built by default +(they're <literal>Ignore</literal>d from <literal>'.'</literal> node). Instead, +they are added automatically to special <literal>Alias</literal> +(<literal>'po-update'</literal> by default). The alias name may be changed +through the &cv-link-POUPDATE_ALIAS; construction variable. You can easilly +update <literal>PO</literal> files in your project by <command>scons +po-update</command>. + +<emphasis>Example 1.</emphasis> +Update <filename>en.po</filename> and <filename>pl.po</filename> from +<filename>messages.pot</filename> template (see also &cv-link-POTDOMAIN;), +assuming that the later one exists or there is rule to build it (see +&b-link-POTUpdate;): +<example> + # ... + env.POUpdate(['en','pl']) # messages.pot --> [en.po, pl.po] +</example> + +<emphasis>Example 2.</emphasis> +Update <filename>en.po</filename> and <filename>pl.po</filename> from +<filename>foo.pot</filename> template: +<example> + # ... + env.POUpdate(['en', 'pl'], ['foo']) # foo.pot --> [en.po, pl.pl] +</example> + +<emphasis>Example 3.</emphasis> +Update <filename>en.po</filename> and <filename>pl.po</filename> from +<filename>foo.pot</filename> (another version): +<example> + # ... + env.POUpdate(['en', 'pl'], POTDOMAIN='foo') # foo.pot -- > [en.po, pl.pl] +</example> + +<emphasis>Example 4.</emphasis> +Update files for languages defined in <filename>LINGUAS</filename> file. The +files are updated from <filename>messages.pot</filename> template: +<example> + # ... + env.POUpdate(LINGUAS_FILE = 1) # needs 'LINGUAS' file +</example> + +<emphasis>Example 5.</emphasis> +Same as above, but update from <filename>foo.pot</filename> template: +<example> + # ... + env.POUpdate(LINGUAS_FILE = 1, source = ['foo']) +</example> + +<emphasis>Example 6.</emphasis> +Update <filename>en.po</filename> and <filename>pl.po</filename> plus files for +languages defined in <filename>LINGUAS</filename> file. The files are updated +from <filename>messages.pot</filename> template: +<example> + # produce 'en.po', 'pl.po' + files defined in 'LINGUAS': + env.POUpdate(['en', 'pl' ], LINGUAS_FILE = 1) +</example> + +<emphasis>Example 7.</emphasis> +Use &cv-link-POAUTOINIT; to automatically initialize <literal>PO</literal> file +if it doesn't exist: +<example> + # ... + env.POUpdate(LINGUAS_FILE = 1, POAUTOINIT = 1) +</example> + +<emphasis>Example 8.</emphasis> +Update <literal>PO</literal> files for languages defined in +<filename>LINGUAS</filename> file. The files are updated from +<filename>foo.pot</filename> template. All necessary settings are +pre-configured via environment. +<example> + # ... + env['POAUTOINIT'] = 1 + env['LINGUAS_FILE'] = 1 + env['POTDOMAIN'] = 'foo' + env.POUpdate() +</example> + +</summary> +</builder> + +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="POUPDATE_ALIAS"> +<summary> +Common alias for all <literal>PO</literal> files being defined with +&b-link-POUpdate; builder (default: <literal>'po-update'</literal>). +See &t-link-msgmerge; tool and &b-link-POUpdate; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="MSGMERGE"> +<summary> +Absolute path to <command>msgmerge(1)</command> binary as found by +<function>Detect()</function>. +See &t-link-msgmerge; tool and &b-link-POUpdate; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="MSGMERGECOM"> +<summary> +Complete command line to run <command>msgmerge(1)</command> command. +See &t-link-msgmerge; tool and &b-link-POUpdate; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="MSGMERGECOMSTR"> +<summary> +String to be displayed when <command>msgmerge(1)</command> is invoked +(default: <literal>''</literal>, which means ``print &cv-link-MSGMERGECOM;''). +See &t-link-msgmerge; tool and &b-link-POUpdate; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="MSGMERGEFLAGS"> +<summary> +Additional flags to <command>msgmerge(1)</command> command. +See &t-link-msgmerge; tool and &b-link-POUpdate; builder. +</summary> +</cvar> diff --git a/src/engine/SCons/Tool/xgettext.py b/src/engine/SCons/Tool/xgettext.py new file mode 100644 index 0000000..f2f542b --- /dev/null +++ b/src/engine/SCons/Tool/xgettext.py @@ -0,0 +1,333 @@ +""" xgettext tool + +Tool specific initialization of `xgettext` tool. +""" + +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +############################################################################# +class _CmdRunner(object): + """ Callabe object, which runs shell command storing its stdout and stderr to + variables. It also provides `strfunction()` method, which shall be used by + scons Action objects to print command string. """ + + def __init__( self, command, commandstr = None): + self.out = None + self.err = None + self.status = None + self.command = command + self.commandstr = commandstr + + def __call__(self, target, source, env): + import SCons.Action + import subprocess + import os + import sys + kw = { + 'stdin' : 'devnull', + 'stdout' : subprocess.PIPE, + 'stderr' : subprocess.PIPE, + 'universal_newlines' : True, + 'shell' : True + } + command = env.subst(self.command, target = target, source = source) + proc = SCons.Action._subproc(env, command, **kw) + self.out, self.err = proc.communicate() + self.status = proc.wait() + if self.err: sys.stderr.write(unicode(self.err)) + return self.status + + def strfunction(self, target, source, env): + import os + comstr = self.commandstr + if env.subst(comstr, target = target, source = source) == "": + comstr = self.command + s = env.subst(comstr, target = target, source = source) + return s +############################################################################# + +############################################################################# +def _update_pot_file(target, source, env): + """ Action function for `POTUpdate` builder """ + import re + import os + import SCons.Action + nop = lambda target, source, env : 0 + + # Save scons cwd and os cwd (NOTE: they may be different. After the job, we + # revert ech one to its original state). + save_cwd = env.fs.getcwd() + save_os_cwd = os.getcwd() + chdir = target[0].dir + chdir_str = repr(chdir.get_abspath()) + # Print chdir message (employ SCons.Action.Action for that. It knows better + # than me how to to this correctly). + env.Execute(SCons.Action.Action(nop, "Entering " + chdir_str)) + # Go to target's directory and do our job + env.fs.chdir(chdir, 1) # Go into target's directory + try: + cmd = _CmdRunner('$XGETTEXTCOM', '$XGETTEXTCOMSTR') + action = SCons.Action.Action(cmd, strfunction=cmd.strfunction) + status = action([ target[0] ], source, env) + except: + # Something went wrong. + env.Execute(SCons.Action.Action(nop, "Leaving " + chdir_str)) + # Revert working dirs to previous state and re-throw exception. + env.fs.chdir(save_cwd, 0) + os.chdir(save_os_cwd) + raise + # Print chdir message. + env.Execute(SCons.Action.Action(nop, "Leaving " + chdir_str)) + # Revert working dirs to previous state. + env.fs.chdir(save_cwd, 0) + os.chdir(save_os_cwd) + # If the command was not successfull, return error code. + if status: return status + + new_content = cmd.out + + if not new_content: + # When xgettext finds no internationalized messages, no *.pot is created + # (because we don't want to bother translators with empty POT files). + needs_update = False + explain = "no internationalized messages encountered" + else: + if target[0].exists(): + # If the file already exists, it's left unaltered unless its messages + # are outdated (w.r.t. to these recovered by xgettext from sources). + old_content = target[0].get_text_contents() + re_cdate = re.compile(r'^"POT-Creation-Date: .*"$[\r\n]?', re.M) + old_content_nocdate = re.sub(re_cdate,"",old_content) + new_content_nocdate = re.sub(re_cdate,"",new_content) + if(old_content_nocdate == new_content_nocdate): + # Messages are up-to-date + needs_update = False + explain = "messages in file found to be up-to-date" + else: + # Messages are outdated + needs_update = True + explain = "messages in file were outdated" + else: + # No POT file found, create new one + needs_update = True + explain = "new file" + if needs_update: + # Print message employing SCons.Action.Action for that. + msg = "Writting " + repr(str(target[0])) + " (" + explain + ")" + env.Execute(SCons.Action.Action(nop, msg)) + f = open(str(target[0]),"w") + f.write(new_content) + f.close() + return 0 + else: + # Print message employing SCons.Action.Action for that. + msg = "Not writting " + repr(str(target[0])) + " (" + explain + ")" + env.Execute(SCons.Action.Action(nop, msg)) + return 0 +############################################################################# + +############################################################################# +from SCons.Builder import BuilderBase +############################################################################# +class _POTBuilder(BuilderBase): + def _execute(self, env, target, source, *args): + if not target: + if env.has_key('POTDOMAIN') and env['POTDOMAIN']: + domain = env['POTDOMAIN'] + else: + domain = 'messages' + target = [ domain ] + return BuilderBase._execute(self, env, target, source, *args) +############################################################################# + +############################################################################# +def _scan_xgettext_from_files(target, source, env, files = None, path = None): + """ Parses `POTFILES.in`-like file and returns list of extracted file names. + """ + import re + import SCons.Util + import SCons.Node.FS + + if files is None: + return 0 + if not SCons.Util.is_List(files): + files = [ files ] + + if path is None: + if env.has_key('XGETTEXTPATH'): + path = env['XGETTEXTPATH'] + else: + path = [] + if not SCons.Util.is_List(path): + path = [ path ] + + path = SCons.Util.flatten(path) + + dirs = () + for p in path: + if not isinstance(p, SCons.Node.FS.Base): + if SCons.Util.is_String(p): + p = env.subst(p, source = source, target = target) + p = env.arg2nodes(p, env.fs.Dir) + dirs += tuple(p) + # cwd is the default search path (when no path is defined by user) + if not dirs: + dirs = (env.fs.getcwd(),) + + # Parse 'POTFILE.in' files. + re_comment = re.compile(r'^#[^\n\r]*$\r?\n?', re.M) + re_emptyln = re.compile(r'^[ \t\r]*$\r?\n?', re.M) + re_trailws = re.compile(r'[ \t\r]+$') + for f in files: + # Find files in search path $XGETTEXTPATH + if isinstance(f, SCons.Node.FS.Base) and f.rexists(): + contents = f.get_text_contents() + contents = re_comment.sub("", contents) + contents = re_emptyln.sub("", contents) + contents = re_trailws.sub("", contents) + depnames = contents.splitlines() + for depname in depnames: + depfile = SCons.Node.FS.find_file(depname, dirs) + if not depfile: + depfile = env.arg2nodes(depname, dirs[0].File) + env.Depends(target, depfile) + return 0 +############################################################################# + +############################################################################# +def _pot_update_emitter(target, source, env): + """ Emitter function for `POTUpdate` builder """ + from SCons.Tool.GettextCommon import _POTargetFactory + import SCons.Util + import SCons.Node.FS + + if env.has_key('XGETTEXTFROM'): + xfrom = env['XGETTEXTFROM'] + else: + return target, source + if not SCons.Util.is_List(xfrom): + xfrom = [ xfrom ] + + xfrom = SCons.Util.flatten(xfrom) + + # Prepare list of 'POTFILE.in' files. + files = [] + for xf in xfrom: + if not isinstance(xf, SCons.Node.FS.Base): + if SCons.Util.is_String(xf): + # Interpolate variables in strings + xf = env.subst(xf, source = source, target = target) + xf = env.arg2nodes(xf) + files.extend(xf) + if files: + env.Depends(target, files) + _scan_xgettext_from_files(target, source, env, files) + return target, source +############################################################################# + +############################################################################# +from SCons.Environment import _null +############################################################################# +def _POTUpdateBuilderWrapper(env, target=None, source=_null, **kw): + return env._POTUpdateBuilder(target, source, **kw) +############################################################################# + +############################################################################# +def _POTUpdateBuilder(env, **kw): + """ Creates `POTUpdate` builder object """ + import SCons.Action + from SCons.Tool.GettextCommon import _POTargetFactory + kw['action'] = SCons.Action.Action(_update_pot_file, None) + kw['suffix'] = '$POTSUFFIX' + kw['target_factory'] = _POTargetFactory(env, alias='$POTUPDATE_ALIAS').File + kw['emitter'] = _pot_update_emitter + return _POTBuilder(**kw) +############################################################################# + +############################################################################# +def generate(env,**kw): + """ Generate `xgettext` tool """ + import SCons.Util + from SCons.Tool.GettextCommon import RPaths, _detect_xgettext + + env['XGETTEXT'] = _detect_xgettext(env) + # NOTE: sources="$SOURCES" would work as well. However, we use following + # construction to convert absolute paths provided by scons onto paths + # relative to current working dir. Note, that scons expands $SOURCE(S) to + # absolute paths for sources $SOURCE(s) outside of current subtree (e.g. in + # "../"). With source=$SOURCE these absolute paths would be written to the + # resultant *.pot file (and its derived *.po files) as references to lines in + # source code (e.g. referring lines in *.c files). Such references would be + # correct (e.g. in poedit) only on machine on which *.pot was generated and + # would be of no use on other hosts (having a copy of source code located + # in different place in filesystem). + sources = '$( ${_concat( "", SOURCES, "", __env__, XgettextRPaths, TARGET' \ + + ', SOURCES)} $)' + + # NOTE: the output from $XGETTEXTCOM command must go to stdout, not to a file. + # This is required by the POTUpdate builder's action. + xgettextcom = '$XGETTEXT $XGETTEXTFLAGS $_XGETTEXTPATHFLAGS' \ + + ' $_XGETTEXTFROMFLAGS -o - ' + sources + + xgettextpathflags = '$( ${_concat( XGETTEXTPATHPREFIX, XGETTEXTPATH' \ + + ', XGETTEXTPATHSUFFIX, __env__, RDirs, TARGET, SOURCES)} $)' + xgettextfromflags = '$( ${_concat( XGETTEXTFROMPREFIX, XGETTEXTFROM' \ + + ', XGETTEXTFROMSUFFIX, __env__, target=TARGET, source=SOURCES)} $)' + + env.SetDefault( + _XGETTEXTDOMAIN = '${TARGET.filebase}', + XGETTEXTFLAGS = [ ], + XGETTEXTCOM = xgettextcom, + XGETTEXTCOMSTR = '', + XGETTEXTPATH = [ ], + XGETTEXTPATHPREFIX = '-D', + XGETTEXTPATHSUFFIX = '', + XGETTEXTFROM = None, + XGETTEXTFROMPREFIX = '-f', + XGETTEXTFROMSUFFIX = '', + _XGETTEXTPATHFLAGS = xgettextpathflags, + _XGETTEXTFROMFLAGS = xgettextfromflags, + POTSUFFIX = ['.pot'], + POTUPDATE_ALIAS = 'pot-update', + XgettextRPaths = RPaths(env) + ) + env.Append( BUILDERS = { + '_POTUpdateBuilder' : _POTUpdateBuilder(env) + } ) + env.AddMethod(_POTUpdateBuilderWrapper, 'POTUpdate') + env.AlwaysBuild(env.Alias('$POTUPDATE_ALIAS')) +############################################################################# + +############################################################################# +def exists(env): + """ Check, whether the tool exists """ + from SCons.Tool.GettextCommon import _xgettext_exists + return _xgettext_exists(env) +############################################################################# + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/xgettext.xml b/src/engine/SCons/Tool/xgettext.xml new file mode 100644 index 0000000..31f5819 --- /dev/null +++ b/src/engine/SCons/Tool/xgettext.xml @@ -0,0 +1,288 @@ +<!-- +__COPYRIGHT__ + +This file is processed by the bin/SConsDoc.py module. +See its __doc__ string for a discussion of the format. +--> +<tool name="xgettext"> +<summary> +This scons tool is a part of scons &t-link-gettext; toolset. It provides +scons interface to <command>xgettext(1)</command> +program, which extracts internationalized messages from source code. The tool +provides &b-POTUpdate; builder to make <literal>PO</literal> +<emphasis>Template</emphasis> files. +</summary> +<sets> +POTSUFFIX +POTUPDATE_ALIAS +XGETTEXTCOM +XGETTEXTCOMSTR +XGETTEXTFLAGS +XGETTEXTFROM +XGETTEXTFROMPREFIX +XGETTEXTFROMSUFFIX +XGETTEXTPATH +XGETTEXTPATHPREFIX +XGETTEXTPATHSUFFIX +_XGETTEXTDOMAIN +_XGETTEXTFROMFLAGS +_XGETTEXTPATHFLAGS +</sets> +<uses> +POTDOMAIN +</uses> +</tool> + +<builder name="POTUpdate"> +<summary> +The builder belongs to &t-link-xgettext; tool. The builder updates target +<literal>POT</literal> file if exists or creates one if it doesn't. The node is +not built by default (i.e. it is <literal>Ignore</literal>d from +<literal>'.'</literal>), but only on demand (i.e. when given +<literal>POT</literal> file is required or when special alias is invoked). This +builder adds its targe node (<filename>messages.pot</filename>, say) to a +special alias (<literal>pot-update</literal> by default, see +&cv-link-POTUPDATE_ALIAS;) so you can update/create them easily with +<command>scons pot-update</command>. The file is not written until there is no +real change in internationalized messages (or in comments that enter +<literal>POT</literal> file). + +<note> <para>You may see <command>xgettext(1)</command> being invoked by the +&t-link-xgettext; tool even if there is no real change in internationalized +messages (so the <literal>POT</literal> file is not being updated). This +happens every time a source file has changed. In such case we invoke +<command>xgettext(1)</command> and compare its output with the content of +<literal>POT</literal> file to decide whether the file should be updated or +not.</para></note> + +<emphasis>Example 1.</emphasis> +Let's create <filename>po/</filename> directory and place following +<filename>SConstruct</filename> script there: +<example> + # SConstruct in 'po/' subdir + env = Environment( tools = ['default', 'xgettext'] ) + env.POTUpdate(['foo'], ['../a.cpp', '../b.cpp']) + env.POTUpdate(['bar'], ['../c.cpp', '../d.cpp']) +</example> +Then invoke scons few times: +<example> + user@host:$ scons # Does not create foo.pot nor bar.pot + user@host:$ scons foo.pot # Updates or creates foo.pot + user@host:$ scons pot-update # Updates or creates foo.pot and bar.pot + user@host:$ scons -c # Does not clean foo.pot nor bar.pot. +</example> +the results shall be as the comments above say. + +<emphasis>Example 2.</emphasis> +The &b-POTUpdate; builder may be used with no target specified, in which +case default target <filename>messages.pot</filename> will be used. The +default target may also be overriden by setting &cv-link-POTDOMAIN; construction +variable or providing it as an override to &b-POTUpdate; builder: +<example> + # SConstruct script + env = Environment( tools = ['default', 'xgettext'] ) + env['POTDOMAIN'] = "foo" + env.POTUpdate(source = ["a.cpp", "b.cpp"]) # Creates foo.pot ... + env.POTUpdate(POTDOMAIN = "bar", source = ["c.cpp", "d.cpp"]) # and bar.pot +</example> + +<emphasis>Example 3.</emphasis> +The sources may be specified within separate file, for example +<filename>POTFILES.in</filename>: +<example> + # POTFILES.in in 'po/' subdirectory + ../a.cpp + ../b.cpp + # end of file +</example> +The name of the file (<filename>POTFILES.in</filename>) containing the list of +sources is provided via &cv-link-XGETTEXTFROM;: +<example> + # SConstruct file in 'po/' subdirectory + env = Environment( tools = ['default', 'xgettext'] ) + env.POTUpdate(XGETTEXTFROM = 'POTFILES.in') +</example> + +<emphasis>Example 4.</emphasis> +You may use &cv-link-XGETTEXTPATH; to define source search path. Assume, for +example, that you have files <filename>a.cpp</filename>, +<filename>b.cpp</filename>, <filename>po/SConstruct</filename>, +<filename>po/POTFILES.in</filename>. Then your <literal>POT</literal>-related +files could look as below: +<example> + # POTFILES.in in 'po/' subdirectory + a.cpp + b.cpp + # end of file +</example> + +<example> + # SConstruct file in 'po/' subdirectory + env = Environment( tools = ['default', 'xgettext'] ) + env.POTUpdate(XGETTEXTFROM = 'POTFILES.in', XGETTEXTPATH='../') +</example> + +<emphasis>Example 5.</emphasis> +Multiple search directories may be defined within a list, i.e. +<literal>XGETTEXTPATH = ['dir1', 'dir2', ...]</literal>. The order in the list +determines the search order of source files. The path to the first file found +is used. + +Let's create <filename>0/1/po/SConstruct</filename> script: +<example> + # SConstruct file in '0/1/po/' subdirectory + env = Environment( tools = ['default', 'xgettext'] ) + env.POTUpdate(XGETTEXTFROM = 'POTFILES.in', XGETTEXTPATH=['../', '../../']) +</example> +and <filename>0/1/po/POTFILES.in</filename>: +<example> + # POTFILES.in in '0/1/po/' subdirectory + a.cpp + # end of file +</example> +Write two <filename>*.cpp</filename> files, the first one is +<filename>0/a.cpp</filename>: +<example> + /* 0/a.cpp */ + gettext("Hello from ../../a.cpp") +</example> +and the second is <filename>0/1/a.cpp</filename>: +<example> + /* 0/1/a.cpp */ + gettext("Hello from ../a.cpp") +</example> +then run scons. You'll obtain <literal>0/1/po/messages.pot</literal> with the +message <literal>"Hello from ../a.cpp"</literal>. When you reverse order in +<varname>$XGETTEXTFOM</varname>, i.e. when you write SConscript as +<example> + # SConstruct file in '0/1/po/' subdirectory + env = Environment( tools = ['default', 'xgettext'] ) + env.POTUpdate(XGETTEXTFROM = 'POTFILES.in', XGETTEXTPATH=['../../', '../']) +</example> then the <filename>messages.pot</filename> will contain +<literal>msgid "Hello from ../../a.cpp"</literal> line and not +<literal>msgid "Hello from ../a.cpp"</literal>. + +</summary> + +</builder> + +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="POTSUFFIX"> +<summary> +Suffix used for PO Template files (default: <literal>'.pot'</literal>). +See &t-link-xgettext; tool and &b-link-POTUpdate; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="POTUPDATE_ALIAS"> +<summary> +Name of the common phony target for all PO Templates created with +&b-link-POUpdate; (default: <literal>'pot-update'</literal>). +See &t-link-xgettext; tool and &b-link-POTUpdate; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="XGETTEXT"> +<summary> +Path to <command>xgettext(1)</command> program (found via +<function>Detect()</function>). +See &t-link-xgettext; tool and &b-link-POTUpdate; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="XGETTEXTCOM"> +<summary> +Complete xgettext command line. +See &t-link-xgettext; tool and &b-link-POTUpdate; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="XGETTEXTCOMSTR"> +<summary> +A string that is shown when <command>xgettext(1)</command> command is invoked +(default: <literal>''</literal>, which means "print &cv-link-XGETTEXTCOM;"). +See &t-link-xgettext; tool and &b-link-POTUpdate; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="XGETTEXTFLAGS"> +<summary> +Additional flags to <command>xgettext(1)</command>. +See &t-link-xgettext; tool and &b-link-POTUpdate; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="XGETTEXTFROM"> +<summary> +Name of file containing list of <command>xgettext(1)</command>'s source +files. Autotools' users know this as <filename>POTFILES.in</filename> so they +will in most cases set <literal>XGETTEXTFROM="POTFILES.in"</literal> here. +The &cv-XGETTEXTFROM; files have same syntax and semantics as the well known +GNU <filename>POTFILES.in</filename>. +See &t-link-xgettext; tool and &b-link-POTUpdate; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="XGETTEXTPATH"> +<summary> +List of directories, there <command>xgettext(1)</command> will look for +source files (default: <literal>[]</literal>). +<note><para> +This variable works only together with &cv-link-XGETTEXTFROM; +</para></note> +See also &t-link-xgettext; tool and &b-link-POTUpdate; builder. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="XGETTEXTPATHPREFIX"> +<summary> +This flag is used to add single search path to +<command>xgettext(1)</command>'s commandline (default: +<literal>'-D'</literal>). +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="XGETTEXTPATHSUFFIX"> +<summary> +(default: <literal>''</literal>) +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="XGETTEXTFROMPREFIX"> +<summary> +This flag is used to add single &cv-link-XGETTEXTFROM; file to +<command>xgettext(1)</command>'s commandline (default: +<literal>'-f'</literal>). +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="XGETTEXTFROMSUFFIX"> +<summary> +(default: <literal>''</literal>) +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="_XGETTEXTDOMAIN"> +<summary> +Internal "macro". Generates <command>xgettext</command> domain name +form source and target (default: <literal>'${TARGET.filebase}'</literal>). +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="_XGETTEXTFROMFLAGS"> +<summary> +Internal "macro". Genrates list of <literal>-D<dir></literal> flags +from the &cv-link-XGETTEXTPATH; list. +</summary> +</cvar> +<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --> +<cvar name="_XGETTEXTPATHFLAGS"> +<summary> +Internal "macro". Generates list of <literal>-f<file></literal> flags +from &cv-link-XGETTEXTFROM;. +</summary> +</cvar> + +<!-- + +--> |