Am 04.12.19 um 02:02 schrieb Mads Kiilerich: > On 12/2/19 3:08 AM, Wolfgang Scherer wrote: >>> Is your point that the problem would be fixed by providing an empty >>> kallithea/i18n/en/LC_MESSAGES/kallithea.mo (and building a corresponding >>> .po)? >> Unfortunately, the translation file cannot be empty. Anything missing in the >> translation file is looked up in the next translation file of the chain. > > > So ... there is no problem in the simple case with one preferred translation, > and fallback to "untranslated". Yes. > > But any i18n code that support multiple prioritized translations with > fall-through between translations must know that if the default > "untranslated" language is in the prioritized list of languages, then that > language has 100% coverage even without any actual translations, and it > should never look further in the list.
I heartily agree. And it actually is implemented just like that in package gettext. > I don't think Kallithea is doing anything in that area. It rely on TurboGears > (and webob and what not). If it doesn't work correctly, then it must be > because we don't use these modules correctly, or because a bug in these > libraries (which we then may have to work around until fixed). Kallithea uses TurboGears (tg), which in turn uses the GNU gettext package. > But before trying to work around in Kallithea: Exactly where is the problem? The attached :func:`check_i18n.run.` identifies the relevant modules and functions used for translation. 1. The first question is "what is the default language?". Somehow, there is the assumption, that it would be "en". This is True for e.g., PHP: "en_US_POSIX" https://www.php.net/manual/en/class.locale.php, Android: "en" https://developer.android.com/guide/topics/resources/multilingual-support. The Android example also shows, that the resolution rules are basically arbitrary. But :mod:`gettext` uses "C" as the default language and **not** "en" (and is correct in doing so, since "C" is not necessarily the same as "en"). :func:`gettext.find` also stops when "C" is found in the list of languages, just as expected. So :mod:`gettext` does not have a bug, just the standardized POSIX default locale C instead of the rather adhoc and arbitrary "en". 2. TurboGear has no concept of a default locale. The "C" locale is implicitely ignored, since the list of languages is fed to :func:`gettext.find` one language at a time. I.e., if "C" appears somewhere between supported locales, the list is not terminated. This can be considered a bug, but it is not necessarily so. So, even if TurboGear was to be modified to recognize the default locale "C" as a termination point, that would not solve the problem of "en" not being recognized as default locale, if followed by a supported language. Summarily, nobody can actually be blamed, however the problem still exists. The strategies to solve it are in order of (my subjective) usefulness: 1. Modify :func:`tg.i18n._gettranslator` to support a default locale ("C" **not** "en") which terminates the fallback translator chain. The default should be configurable, e.g. "i18n.default = en" (see attached :file:`check_i18n.py`). The necessary changes are minimal if not trivial. I have no idea, how welcoming the library maintainers are, but I will submit a pull request to modify the current behavior. 2. An "en" language file is supplied. This is the fastest fix. (This was probably the reason for the removed "en" language file in the first place). 3. The Accept-Language header is terminated at "en" by some middleware before the request is passed on to :mod:`tg.i18n`. This is ugly, but does not depend on a library modification. > > /Mads
#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2019, Wolfgang Scherer, <Wolfgang.Scherer at gmx.de> # # This file is part of Kallithea Deployment. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # (progn (forward-line 1) (snip-insert "py_doc.main" t t "python") (insert "\n")) r""" check_i18n.py - ::fillme:: ====== ==================== usage: check_i18n.py [OPTIONS] ::fillme:: or import check_i18n ====== ==================== :rem:`|||:sec:|||`\ Options =========================== .. compound:: ===================== ================================================== -q, --quiet suppress warnings -v, --verbose verbose test output -d, --debug[=NUM] show debug information -h, --help display this help message -t, --test run doc tests ===================== ================================================== .. adhoc Options ===================== ================================================== --template list show available templates --eide[=COMM] Emacs IDE template list (implies `--template` list) --template[=NAME] extract named template to standard output; default NAME is ``-`` --extract[=DIR] extract adhoc files to directory DIR (default: :file:`.`) --explode[=DIR] explode script with adhoc in directory DIR (default :file:`__adhoc__`) --setup[=install] explode script into temporary directory and call :command:`python setup.py install` --implode implode script with adhoc ===================== ================================================== :rem:`|||:sec:|||`\ Description =============================== :rem:`|||:sec:|||`\ Module ========================== :rem:`|||:sec:|||`\ Automatic Exports ===================================== >>> for ex in __all__: printf(sformat('from {0} import {1}', __name__, ex)) :rem:`|||:sec:|||`\ Explicit Exports ==================================== >>> if '__all_internal__' in globals(): ... for ex in __all_internal__: ... printf(sformat('from {0} import {1}', __name__, ex)) .. _END_OF_HELP_check_i18n: :rem:`|||:sec:|||`\ Details =========================== """ # (progn (forward-line 1) (snip-insert "py.b.future.with" t t "python") (insert "\n")) # for python 2.5 from __future__ import with_statement # (progn (forward-line 1) (snip-insert "py.main.pyramid.activate" t t "py") (insert "")) # -------------------------------------------------- # |||:sec:||| COMPATIBILITY # -------------------------------------------------- import sys # (progn (forward-line 1) (snip-insert "py.b.printf" t t "py") (insert "\n")) # adapted from http://www.daniweb.com/software-development/python/code/217214 try: printf = eval("print") # python 3.0 case except SyntaxError: printf_dict = dict() try: exec("from __future__ import print_function\nprintf=print", printf_dict) printf = printf_dict["printf"] # 2.6 case except SyntaxError: def printf(*args, **kwd): # 2.4, 2.5, define our own Print function fout = kwd.get("file", sys.stdout) w = fout.write if args: w(str(args[0])) sep = kwd.get("sep", " ") for a in args[1:]: w(sep) w(str(a)) w(kwd.get("end", "\n")) del printf_dict # (progn (forward-line 1) (snip-insert "py.b.sformat" t t "py") (insert "\n")) try: ('{0}').format(0) def sformat (fmtspec, *args, **kwargs): return fmtspec.format(*args, **kwargs) except AttributeError: try: import stringformat def sformat (fmtspec, *args, **kwargs): return stringformat.FormattableString(fmtspec).format( *args, **kwargs) except ImportError: printf('error: stringformat missing. Try `easy_install stringformat`.', file=sys.stderr) # (progn (forward-line 1) (snip-insert "py.b.isstring" t t "python" " --key isstring_onlyx") (insert "\n")) try: from ws_seq_type import isstring, issequence, sequence_type, UCHAR_FMT except ImportError: # (progn (forward-line 1) (snip-insert "py.f.isstring" t t "py") (insert "\n")) exec(''' def isstring(obj): return isinstance(obj, basestring) '''.strip()) try: isstring("") UCHAR_FMT = 'u"{0}u{1:04x}"' except NameError: def isstring(obj): return isinstance(obj, str) or isinstance(obj, bytes) UCHAR_FMT = '"{0}u{1:04x}"' # (progn (forward-line 1) (snip-insert "py.f.issequence" t t "py") (insert "\n")) def issequence(arg, or_dict=False, or_seq=True): # ||:fnc:|| if not isstring(arg): if hasattr(arg, 'items'): return or_dict if hasattr(arg, '__getitem__'): return True if hasattr(arg, '__iter__'): return or_seq return False # (progn (forward-line 1) (snip-insert-mode "py.f.sequence_type" t) (insert "\n")) _st_strg = (True, False, False, False) _st_list = (False, True, False, False) _st_dict = (False, False, True, False) _st_seq = (False, False, False, True) _st_none = (False, False, False, False) def sequence_type(value): # ||:fnc:|| if isstring(value): return _st_strg if hasattr(value, 'items'): return _st_dict if hasattr(value, '__getitem__'): return _st_list if hasattr(value, '__iter__'): return _st_seq return _st_none # (progn (forward-line 1) (snip-insert-mode "py.f.uchar" t) (insert "\n")) def uchar(num): '''Make UNICODE character.''' return eval(sformat(UCHAR_FMT,'\\', num)) # (progn (forward-line 1) (snip-insert "py.b.dict.items" t t "py") (insert "\n")) try: getattr(dict(), 'iteritems') except AttributeError: ditems = lambda d: getattr(d, 'items')() dkeys = lambda d: getattr(d, 'keys')() dvalues = lambda d: getattr(d, 'values')() else: ditems = lambda d: getattr(d, 'iteritems')() dkeys = lambda d: getattr(d, 'iterkeys')() dvalues = lambda d: getattr(d, 'itervalues')() # (progn (forward-line 1) (snip-insert "py.b.xrange" t t "py") (insert "\n")) # `xrange` returns a generator, which `range` already does for python3 # note: use l.. and g.. to get list/generator versions try: xrange(0) lrange = lambda *args, **kwargs: range(*args, **kwargs) except NameError: xrange = range lrange = lambda *args, **kwargs: list(range(*args, **kwargs)) grange = xrange # `xfilter` returns a list, `filter` may return a generator. This is # different from the range/xrange semantics! if isinstance(filter(str, []), list): xfilter = filter gfilter = lambda _f, _s, *args, **kwargs: (_e for _e in _s if _f(_e)) else: xfilter = lambda *args, **kwargs: list(filter(*args, **kwargs)) gfilter = filter lfilter = xfilter # `xmap` returns a list, `map` may return a generator. This is # different from the range/xrange semantics! if isinstance(map(str, []), list): xmap = map gmap = lambda _f, _s, *args, **kwargs: (_f(_e) for _e in _s) else: xmap = lambda *args, **kwargs: list(map(*args, **kwargs)) gmap = map lmap = xmap # `long` is gone in python3 try: isinstance(int, long) except NameError: long = int # (progn (forward-line 1) (snip-insert "py_f.lfind" t t "python") (insert "\n")) def lfind(l, elt): try: return l.index(elt) except ValueError: return -1 import os import re # -------------------------------------------------- # |||:sec:||| CONFIGURATION # -------------------------------------------------- __all__ = [] __all_internal__ = [] # (progn (forward-line 1) (snip-insert "py.b.dbg.def" t t "python") (insert "")) dbg_fwid = globals().get('dbg_fwid', 15) # (progn (forward-line 1) (snip-insert "py.b.canonize.module" t t "python") (insert "")) # (progn (forward-line 1) (snip-insert "py.b.strings" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.f.strclean" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.b.logging" t t "python") (insert "")) # (progn (forward-line 1) (snip-insert "py.b.ordereddict" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.b.dbg.setup" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.main.project.libdir" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.main.sql.alchemy" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.main.sql.ws" t t "py") (insert "\n")) # @:adhoc_run_time:@ #import adhoc # @:adhoc:@ # (progn (forward-line 1) (snip-insert "py.b.posix" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.b.os.system.sh" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.b.prog.path" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py_b.line-loop" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py_b.table_standalone" t t "python") (insert "")) # (progn (forward-line 1) (snip-insert "py.wsrfid.pylons.imports" t t "python") (insert "")) # (progn (forward-line 1) (snip-insert "py.main.wsgi.get.app" t t "python") (insert "")) # (progn (forward-line 1) (snip-insert "py_wsrfid.config_delayed" t t "python") (insert "\n")) # (progn (forward-line 1) (snip-insert "py_b.wsrfid.config_translate_shortcuts" t t "python") (insert "\n")) # (progn (forward-line 1) (snip-insert "py_b.dba_imports" t t "python") (insert "\n")) # (progn (forward-line 1) (snip-insert "py_b.dba_datainit" t t "python") (insert "\n")) # (progn (forward-line 1) (snip-insert "py_jsmo.imports" t t "python") (insert "\n")) # |:here:| # -------------------------------------------------- # |||:sec:||| DATA # -------------------------------------------------- # -------------------------------------------------- # |||:sec:||| CLASSES # -------------------------------------------------- # (progn (forward-line -1) (insert "\n") (snip-insert "py.s.class" t t "py") (backward-symbol-tag 1 "fillme" "::")) # -------------------------------------------------- # |||:sec:||| FUNCTIONS # -------------------------------------------------- # (progn (forward-line -1) (insert "\n") (snip-insert "py.s.func" t t "py") (backward-symbol-tag 1 "fillme" "::")) # (progn (forward-line 1) (snip-insert "py_wsrfid.customization" t t "python" " --key cust_delayed_skipx") (insert "\n")) # (progn (forward-line 1) (snip-insert "py_b.dba_setup_sql" t t "python") (insert "\n")) # (progn (forward-line 1) (snip-insert "py_b.dba_id_maps" t t "python") (insert "\n")) # (progn (forward-line 1) (snip-insert "py_b.dba_commands_init" t t "python") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.wsrfid.module.route" t t "py") (insert "")) # -------------------------------------------------- # |||:sec:||| UTILITIES # -------------------------------------------------- # (progn (forward-line 1) (snip-insert "py.wsrfid.dispatch.request" t t "py") (insert "")) # (progn (forward-line 1) (snip-insert "py.f.findfile" t t "py") (insert "")) # (progn (forward-line 1) (snip-insert "py_f.next_numfile_seq" t t "python" " --key xwith_docstrings --key xwith_all_append") (insert "\n")) # (progn (forward-line 1) (snip-insert "py_f.add_prefix_indent" t t "python") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.c.placeholder.template" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.c.key.hash.ordered.dict" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.c.progress" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.f.hl" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.f.single.quote" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.f.remove.match" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.f.printenv" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.f.uname.s" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py_f.decoded_email_headers" t t "python") (insert "\n")) # (progn (forward-line 1) (snip-insert "py_f.print_utf8" t t "python") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.f.printe" t t "py") (insert "")) def printe_(*args, **kwargs): kwargs['file'] = kwargs.get('file', sys.stderr) printf(*args, **kwargs) if 'printe' not in globals(): # or globals().get('_is_main_', (__name__ == '__main__')): printe = printe_ printd = printe_ printw = printe_ printx = printe_ # (progn (forward-line 1) (snip-insert "py.f.dbg.squeeze" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.f.dbg.indent" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.f.quick.dump" t t "python") (insert "\n")) # (progn (forward-line 1) (snip-insert "py_b.all.reverse" t t "python") (insert "\n")) if '__all_internal__' in globals(): import sys if 'sphinx.directives' in sys.modules: __all__[:0] = __all_internal__ __all_internal__ = list(reversed(__all_internal__)) __all__ = list(reversed(__all__)) def run(parameters): # ||:fnc:|| """Application runner, when called as __main__.""" # (progn (forward-line 1) (snip-insert "py.bf.sql.ws" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.bf.file.arg.loop" t t "py") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.wsrfid.wsuvv.run" t t "py" " --key py_wsrfid.wsuvv_run --key skip_for_new") (insert "\n")) # (progn (forward-line 1) (snip-insert "py_shell.run" t t "python") (insert "\n")) # from pyjsmo.result import Result, IResult, ERR_NONE # result = IResult() # |:here:| import gettext as _gettext import tg import tg.i18n from tg.i18n import _parse_locale import kallithea tg.i18n.printf = printf tg.i18n.printe = printf tg.i18n.sformat = sformat tg.i18n.dbg_fwid = dbg_fwid # |:here:| APP_DOMAIN = 'kallithea' LOCALE_DIR = os.path.abspath('kallithea/i18n') TG_CONF = tg.config.current_conf() TG_CONF['localedir'] = LOCALE_DIR TG_CONF['package'] = kallithea # |:here:| ACCEPTED_LANGUAGE_SETS = ( ('fr', 'en_US', 'en', 'de_CH', 'de',), ('fr', 'c', 'de_ch', 'de',), ) def find_lang_gettext(lang, tgl=None, tg_config=None, **kwargs): r""" # -------------------------------------------------- # |||:LOG:||| find_lang_gettext, i18n.default: disable # -------------------------------------------------- # :DBG: accepted : ]('fr', 'en_US', 'en', 'de_CH', 'de')[ # :DBG: supported : ]['fr', 'de'][ # -------------------------------------------------- # :DBG: accepted : ]('fr', 'c', 'de_ch', 'de')[ # :DBG: supported : ]['fr'][ - `en` is not found - `c` terminates the fallback chain """ mofiles = _gettext.find(APP_DOMAIN, localedir=LOCALE_DIR, languages=lang, all=True) supported_languages = [_m.split('/')[-3] for _m in mofiles] return supported_languages, mofiles def find_lang_tg(lang, tgl=None, tg_config=None, **kwargs): r""" # -------------------------------------------------- # |||:LOG:||| find_lang_tg, i18n.default: disable # -------------------------------------------------- # :DBG: accepted : ]('fr', 'en_US', 'en', 'de_CH', 'de')[ # :DBG: supported : ]['fr', 'de_CH', 'de'][ # -------------------------------------------------- # :DBG: accepted : ]('fr', 'c', 'de_ch', 'de')[ # :DBG: supported : ]['fr', 'de_ch', 'de'][ - `en` is not found - `c` is not found """ translator = tg.i18n._get_translator((list(lang)), tgl, tg_config, **kwargs) return translator.tg_supported_lang, [] def find_lang_tg_dflt(lang, tgl=None, tg_config=None, **kwargs): r""" # -------------------------------------------------- # |||:LOG:||| find_lang_tg_dflt, i18n.default: C # -------------------------------------------------- # :DBG: accepted : ]('fr', 'en_US', 'en', 'de_CH', 'de')[ # :DBG: supported : ]['fr', 'de_CH', 'de'][ # -------------------------------------------------- # :DBG: accepted : ]('fr', 'c', 'de_ch', 'de')[ # :DBG: supported : ]['fr'][ - `en` is not found - `c` terminates the fallback chain # -------------------------------------------------- # |||:LOG:||| find_lang_tg_dflt, i18n.default: en # -------------------------------------------------- # :DBG: accepted : ]('fr', 'en_US', 'en', 'de_CH', 'de')[ # :DBG: supported : ]['fr'][ # -------------------------------------------------- # :DBG: accepted : ]('fr', 'c', 'de_ch', 'de')[ # :DBG: supported : ]['fr'][ - `en` terminates the fallback chain - `c` terminates the fallback chain """ # copied from tg.i18n._get_translator() if tg_config: conf = tg_config else: if tgl: conf = tgl.config else: # pragma: no cover #backward compatibility with explicit calls without #specifying local context or config. conf = tg.config.current_conf() # |:added:| default = conf.get('i18n.default') or 'C' # |:added:| mofiles = [] supported_languages = [] for l in lang: # |:added:| if _parse_locale(l)[0] in (default.lower(), 'c'): break # |:added:| mo = _gettext.find(APP_DOMAIN, localedir=LOCALE_DIR, languages=[l], all=False) if mo is not None: mofiles.append(mo) supported_languages.append(l) return supported_languages, mofiles for find_lang, i18n_default in ( (find_lang_gettext, 'disable'), # use non-existent locale to ensure old behavior (find_lang_tg, 'disable'), # use non-existent locale to ensure old behavior (find_lang_tg_dflt, ''), # use empty locale to ensure new default behavior (find_lang_tg_dflt, 'en'), # use empty locale to ensure new default behavior ): TG_CONF['i18n.default'] = i18n_default printe("# --------------------------------------------------") printe(sformat("# |||"":LOG:||| {0}, i18n.default: {i18n_default}", find_lang.__name__, i18n_default=i18n_default or 'C')) for accepted in ACCEPTED_LANGUAGE_SETS: supported, mofiles = find_lang(accepted) printe("# --------------------------------------------------") printe(sformat("# "":DBG: {1:<{0}s}: ]{2!s}[", dbg_fwid, "accepted", (accepted))) printe(sformat("# "":DBG: {1:<{0}s}: ]{2!s}[", dbg_fwid, "supported", (supported))) # |:here:| if False: printe("# --------------------------------------------------") printe(sformat("# |||"":LOG:||| {0}, i18n.default: {i18n_default}", '_expand_lang', i18n_default=i18n_default or 'C')) for accepted in ACCEPTED_LANGUAGE_SETS: printe("# --------------------------------------------------") printe(sformat("# "":DBG: {1:<{0}s}: ]{2!s}[", dbg_fwid, "accepted", (accepted))) for lang in accepted: expanded = _gettext._expand_lang(lang) printe(sformat("# "":DBG: {1:<{0}s}: {2!s:<5s} => ]{3!s}[", dbg_fwid, "expanded", lang, (expanded))) # |:here:| return # result.report() # return max(result.error, ERR_NONE) # -------------------------------------------------- # |||:sec:||| MAIN # -------------------------------------------------- _quiet = False _verbose = False _debug = False # (progn (forward-line 1) (snip-insert "py_f.argparse_callbacks" t t "python") (insert "\n")) # (progn (forward-line 1) (snip-insert "py_main.py" t t "python" " --key xpy_main_minimal") (insert "\n")) # (progn (forward-line 1) (snip-insert "py.f.setdefaultencoding" t t "py") (insert "\n")) #file_encoding_is_clean = True def setdefaultencoding(encoding=None, quiet=False, context=None): if context is None: context = globals() if context.get('file_encoding_is_clean'): return if encoding is None: encoding='utf-8' try: isinstance('', basestring) if not hasattr(sys, '_setdefaultencoding'): if not quiet: printf('''\ Add this to /etc/python2.x/sitecustomize.py, or put it in local sitecustomize.py and adjust PYTHONPATH=".:${PYTHONPATH}":: try: import sys setattr(sys, '_setdefaultencoding', getattr(sys, 'setdefaultencoding')) except AttributeError: pass Running with reload(sys) hack ... ''', file=sys.stderr) reload(sys) setattr(sys, '_setdefaultencoding', getattr(sys, 'setdefaultencoding')) sys._setdefaultencoding(encoding) except NameError: # python3 already has utf-8 default encoding ;-) pass def main(argv=None, context=None): # ||:fnc:|| if argv is None: argv = sys.argv if context is None: context = globals() context.get( 'setup_logger', globals().get( 'setup_logger', lambda *args: None))(context, True) try: import argparse except ImportError: printe('error: argparse missing. Try `easy_install argparse`.') sys.exit(1) parser = argparse.ArgumentParser(add_help=False, conflict_handler='resolve') # parser.add_argument('--sum', dest='accumulate', action='store_const', # const=sum, default=max, # help='sum the integers (default: find the max)') # |:opt:| add options context.get('_argparse_begin', lambda _p, *_args: _p)(parser, context) parser.add_argument( '-q', '--quiet', action='store_const', const=-2, dest='debug', default=0, help='suppress warnings') parser.add_argument( '-v', '--verbose', action='store_const', const=-1, dest='debug', default=0, help='verbose test output') parser.add_argument( '-d', '--debug', nargs='?', action='store', type=int, metavar='NUM', default = 0, const = 1, help='show debug information') parser.add_argument( '-h', '--help', action='store_true', help="display this help message") parser.add_argument( '-t', '--test', action='store_true', help='run doc tests') class AdHocAction(argparse.Action): options = ('implode', 'setup', 'explode', 'extract', 'template', 'eide') def __call__(self, parser, namespace, values, option_string=None): for _opt in self.options: setattr(namespace, 'adhoc_' + _opt, False) setattr(namespace, 'adhoc_' + option_string[2:], True) setattr(namespace, 'adhoc_arg', values) parser.add_argument( '--implode', nargs=0, action=AdHocAction, dest='adhoc_implode', default=False, help='implode script with adhoc') parser.add_argument( '--setup', nargs='?', action=AdHocAction, type=str, metavar='install', dest='adhoc_setup', default=False, const='install', help='explode script into temporary directory and call' ' `python setup.py install`') parser.add_argument( '--explode', nargs='?', action=AdHocAction, type=str, metavar='DIR', dest='adhoc_explode', default=False, const='__adhoc__', help='explode script with adhoc in directory DIR' ' (default: `__adhoc__`)') parser.add_argument( '--extract', nargs='?', action=AdHocAction, type=str, metavar='DIR', dest='adhoc_extract', default=False, const = '.', help='extract files to directory DIR (default: `.`)') parser.add_argument( '--template', nargs='?', action=AdHocAction, type=str, metavar='NAME', dest='adhoc_template', default=False, const = '-', help='extract named template to standard output. default NAME is ``-``') parser.add_argument( '--eide', nargs='?', action=AdHocAction, type=str, metavar='COMM', dest='adhoc_eide', default=False, const = '', help='Emacs IDE template list (implies --template list).') parser.add_argument( '--ap-help', action='store_true', help="internal help message") context.get('_argparse_end', lambda _p, *_args: _p)(parser, context) if not context.get('_argparse_have_sub_commands'): # all options and arguments are known # all non-option arguments are consumed by `_parameters.args` parser.add_argument( 'args', nargs='*', metavar='arg', #'args', nargs='+', metavar='arg', #type=argparse.FileType('r'), default=sys.stdin, help='a series of arguments') _parameters = parser.parse_args(argv[1:]) else: # (progn (forward-line 1) (snip-insert "py_f.args_split_range" t t "python") (insert "\n")) def args_split_range(args): next_range = [] for arg in args: next_range.append(arg) if not arg.startswith('-'): break if next_range and not next_range[0].startswith('-'): next_range = [] return next_range, args[len(next_range):] # for sub-commands with their own options: pre-parse to first # non-option argument _parameters = None args = argv[1:] while True: next_range, args = args_split_range(args) if not next_range and not _parameters is None: break _parameters, unknown_args = parser.parse_known_args(next_range, _parameters) if unknown_args: unknown_args.extend(args) args = unknown_args next_range = [] break _parameters.args = args # generate argparse help if _parameters.ap_help: parser.print_help() return 0 # standard help if _parameters.help: help_ = re.sub('\n+[.][.] _END_OF_HELP.*(?s)', '', context['__doc__']) sys.stdout.write(help_ + '\n') return 0 context['_debug'] = _parameters.debug if context['_debug'] > 0: context['_verbose'] = True context['_quiet'] = False elif context['_debug'] < 0: context['_verbose'] = (context['_debug'] == -1) context['_quiet'] = not(context['_verbose']) context['_debug'] = 0 _parameters.debug = context['_debug'] _parameters.verbose = context['_verbose'] _parameters.quiet = context['_quiet'] if context['_debug']: cmd_line = argv sys.stderr.write(sformat( "{0}{3:^{1}} {4:<{2}s}: ]{5!s}[\n", context.get('dbg_comm', '# '), context.get('dbg_twid', 11), context.get('dbg_fwid', 15), ':DBG:', 'cmd_line', cmd_line)) # at least use `quiet` to suppress the setdefaultencoding warning context.get('setdefaultencoding', globals().get('setdefaultencoding', lambda *_a, **_k: None))( quiet=context['_quiet'] or _parameters.test) # |:opt:| handle options # adhoc: implode/setup/explode/extract adhoc_get_opt = lambda opt: getattr( _parameters, 'adhoc_' + opt, None) adhoc_op = sum(((adhoc_get_opt(_opt) and 1) or 0 for _opt in AdHocAction.options)) if adhoc_op: for _opt in AdHocAction.options: setattr(_parameters, _opt, adhoc_get_opt(_opt)) adhoc_export = ( _parameters.setup or _parameters.explode or _parameters.extract) file_ = context['__file__'] source = None have_adhoc = 'AdHoc' in context have_rt_adhoc = 'RtAdHoc' in context # shall adhoc be imported if _parameters.implode or not have_rt_adhoc: # shall this file be compiled adhoc_compile = not (have_rt_adhoc) os_path = os.defpath for pv in ('PATH', 'path'): try: os_path = os.environ[pv] break except KeyError: pass os_path = os_path.split(os.pathsep) for path_dir in os_path: if not path_dir: continue if path_dir not in sys.path: sys.path.append(path_dir) if not have_adhoc: try: import adhoc context['AdHoc'] = adhoc.AdHoc except ImportError: adhoc_compile = False try: from rt_adhoc import RtAdHoc as Adhoc context['AdHoc'] = AdHoc except ImportError: pass else: adhoc_compile = False context['AdHoc'] = context['RtAdHoc'] AdHoc = context['AdHoc'] AdHoc.quiet = context['_quiet'] AdHoc.verbose = context['_verbose'] AdHoc.debug = context['_debug'] AdHoc.include_path.append(os.path.dirname(file_)) AdHoc.extra_templates = [ ] AdHoc.template_process_hooks = { } if _parameters.eide: AdHoc.tt_ide = True AdHoc.tt_comment = _parameters.adhoc_arg or '' AdHoc.tt_prefix = '. (shell-command "' AdHoc.tt_suffix = '")' _parameters.template = True _parameters.adhoc_arg = 'list' if adhoc_compile: ah = AdHoc() source = ah.compileFile(file_) else: file_, source = AdHoc.std_source_param(file_) # implode if _parameters.implode: # @:adhoc_enable:@ # if not context['_quiet']: # map(sys.stderr.write, # ["warning: ", os.path.basename(file_), # " already imploded!\n"]) # @:adhoc_enable:@ AdHoc.write_source('-', source) # explode elif _parameters.setup or _parameters.explode: _here = os.path.abspath('.') _clean_dir = False AdHoc.export_dir = _parameters.adhoc_arg if _parameters.setup: import tempfile _clean_dir = True AdHoc.export_dir = tempfile.mkdtemp('_setup', '__adhoc__') try: AdHoc.export(file_, source) if _parameters.setup: sq = lambda string: ''.join(("'", re.sub("'", """'\\''""", string), "'")) os.chdir(AdHoc.export_dir) os.system(sformat('{0} setup.py {1}', sq(sys.executable), sq(_parameters.adhoc_arg))) finally: if _clean_dir: try: os.chdir(_here) except: pass import shutil shutil.rmtree(AdHoc.export_dir) # extract elif _parameters.extract: AdHoc.extract_dir = _parameters.adhoc_arg AdHoc.extract(file_, source) # template elif _parameters.template: template_name = _parameters.adhoc_arg if not template_name: template_name = '-' if template_name == 'list': sys.stdout.write( '\n'.join(AdHoc.template_table(file_, source)) + '\n') else: template = AdHoc.get_named_template( template_name, file_, source) AdHoc.write_source('-', template) # restore for subsequent calls to main if not have_adhoc: del(AdHoc) return 0 # run doc tests if _parameters.test: import warnings warnings.simplefilter('default') import doctest # for :file:`__init__.py`, :func:`_canonize_module_` does not register the module in `sys.modules`. _canon_name = _module_name = context['__name__'] context.get('_canonize_module_', globals().get( '_canonize_module_', lambda *args: context.__setitem__('__name__', _canon_name)))(context['__name__'], context['__name__'] == '__main__') if context['__name__'] not in sys.modules: sys.modules[context['__name__']] = sys.modules[_module_name] try: logger = logging.getLogger() logger.setLevel(logging.DEBUG) except NameError: pass context.get('_doctest_hook_', lambda *args, **kwargs: None)(context) result = doctest.testmod(sys.modules[context['__name__']], verbose = context['_verbose']) return result.failed # run program final = False ecode = 0 try: try: ecode = context['run'](_parameters) except IOError: (t, e, tb) = sys.exc_info() del(tb) # ignore SIGPIPE import errno if e.errno != errno.EPIPE: raise except SystemExit: raise except: # |:info:| this is used, since module cgitb does not work so well ... (t, e, tb) = sys.exc_info() if not final or context['_debug']: import traceback printf(''.join(traceback.format_tb(tb)), file=sys.stderr, end='') printf(sformat('{0}: {1}', t.__name__, e), file=sys.stderr) del(tb) ecode = 1 return ecode if globals().get('_is_main_', (__name__ == '__main__')): #sys.argv.insert(1, '--debug') # |:debug:| result = main(sys.argv, globals()) sys.exit(result) # |:here:| # (progn (forward-line 1) (snip-insert "py.t.ide" t t "py") (insert "\n")) # # :ide-menu: Emacs IDE Main Menu - Buffer @BUFFER@ # . M-x `eIDE-menu' (eIDE-menu "z") # :ide: CSCOPE ON # . (cscope-minor-mode) # :ide: CSCOPE OFF # . (cscope-minor-mode (quote ( nil ))) # :ide: TAGS: forced update # . (compile (concat "cd /home/ws/project/ws-rfid && make -k FORCED=1 tags")) # :ide: TAGS: update # . (compile (concat "cd /home/ws/project/ws-rfid && make -k tags")) # :ide: +-#+ # . Utilities () # :ide: TOC: Generate TOC with py-toc.py # . (progn (save-buffer) (compile (concat "py-toc.py ./" (file-name-nondirectory (buffer-file-name)) " "))) # :ide: CMD: Fold region with line continuation # . (shell-command-on-region (region-beginning) (region-end) "fold --spaces -width 79 | sed 's, $,,;1!s,^, ,;$!s,$,\\\\,'" nil nil nil t) # :ide: CMD: Fold region and replace with line continuation # . (shell-command-on-region (region-beginning) (region-end) "fold --spaces --width 79 | sed 's, $,,;1!s,^, ,;$!s,$,\\\\,'" t nil nil t) # :ide: +-#+ # . Fold () # :ide: CMD: Remove 8 spaces and add `>>> ' to region # . (shell-command-on-region (region-beginning) (region-end) "sed 's,^ ,,;/^[ ]*##/d;/^[ ]*#/{;s,^ *# *,,p;d;};/^[ ]*$/!s,^,>>> ,'" nil nil nil t) # :ide: CMD: Remove 4 spaces and add `>>> ' to region # . (shell-command-on-region (region-beginning) (region-end) "sed 's,^ ,,;/^[ ]*##/d;/^[ ]*#/{;s,^ *# *,,p;d;};/^[ ]*$/!s,^,>>> ,'" nil nil nil t) # :ide: +-#+ # . Doctest () # :ide: LINT: Check 80 column width ignoring IDE Menus # . (let ((args " | /srv/ftp/pub/check-80-col.sh -")) (compile (concat "sed 's,^\\(\\|. \\|.. \\|... \\)\\(:ide\\|[.] \\).*,,' " (buffer-file-name) " " args " | sed 's,^-," (buffer-file-name) ",'"))) # :ide: LINT: Check 80 column width # . (let ((args "")) (compile (concat "/srv/ftp/pub/check-80-col.sh " (buffer-file-name) " " args))) # :ide: +-#+ # . Lint Tools () # :ide: DELIM: @: SYM :@ @:fillme:@ adhoc tag # . (symbol-tag-normalize-delimiter (cons (cons nil "@:") (cons ":@" nil)) t) # :ide: +-#+ # . Delimiters () # :ide: COMPILE: Run with --ap-help # . (progn (save-buffer) (compile (concat "python ./" (file-name-nondirectory (buffer-file-name)) " --ap-help"))) # :ide: COMPILE: Run with --help # . (progn (save-buffer) (compile (concat "python ./" (file-name-nondirectory (buffer-file-name)) " --help"))) # :ide: COMPILE: Run with --test # . (progn (save-buffer) (compile (concat "python ./" (file-name-nondirectory (buffer-file-name)) " --test"))) # :ide: COMPILE: Run with --test --verbose # . (progn (save-buffer) (compile (concat "python ./" (file-name-nondirectory (buffer-file-name)) " --test --verbose"))) # :ide: COMPILE: Run with --debug # . (progn (save-buffer) (compile (concat "python ./" (file-name-nondirectory (buffer-file-name)) " --debug"))) # :ide: +-#+ # . Compile with standard arguments () # :ide: OCCUR-OUTLINE: Python Source Code # . (x-symbol-tag-occur-outline "sec" '("|||:" ":|||") (cons (cons "^\\([ \t\r]*\\(def\\|class\\)[ ]+\\|[A-Za-z_]?\\)" nil) (cons nil "\\([ \t\r]*(\\|[ \t]*=\\)"))) # :ide: MENU-OUTLINE: Python Source Code # . (x-eIDE-menu-outline "sec" '("|||:" ":|||") (cons (cons "^\\([ \t\r]*\\(def\\|class\\)[ ]+\\|[A-Za-z_]?\\)" nil) (cons nil "\\([ \t\r]*(\\|[ \t]*=\\)"))) # :ide: +-#+ # . Outline () # :ide: INFO: SQLAlchemy - SQL Expression Language - Reference # . (let ((ref-buffer "*sqa-expr-ref*")) (if (not (get-buffer ref-buffer)) (shell-command (concat "w3m -dump -cols " (number-to-string (1- (window-width))) " 'http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/expressions.html'") ref-buffer) (display-buffer ref-buffer t))) # :ide: INFO: SQLAlchemy - SQL Expression Language - Tutorial # . (let ((ref-buffer "*sqa-expr-tutor*")) (if (not (get-buffer ref-buffer)) (shell-command (concat "w3m -dump -cols " (number-to-string (1- (window-width))) " 'http://www.sqlalchemy.org/docs/05/sqlexpression.html'") ref-buffer) (display-buffer ref-buffer t))) # :ide: INFO: SQLAlchemy - Query # . (let ((ref-buffer "*sqa-query*")) (if (not (get-buffer ref-buffer)) (shell-command (concat "w3m -dump -cols " (number-to-string (1- (window-width))) " 'http://www.sqlalchemy.org/docs/orm/query.html'") ref-buffer) (display-buffer ref-buffer t))) # :ide: +-#+ # . SQLAlchemy Reference () # :ide: INFO: Python - argparse # . (let ((ref-buffer "*python-argparse*")) (if (not (get-buffer ref-buffer)) (shell-command (concat "w3m -dump -cols " (number-to-string (1- (window-width))) " 'http://docs.python.org/library/argparse.html'") ref-buffer) (display-buffer ref-buffer t))) # :ide: INFO: Python Documentation # . (let ((ref-buffer "*w3m*")) (if (get-buffer ref-buffer) (display-buffer ref-buffer t)) (other-window 1) (w3m-goto-url "http://docs.python.org/index.html" nil nil)) # :ide: INFO: Python Reference # . (let* ((ref-buffer "*python-ref*") (local "/home/ws/project/ws-util/python/reference/PQR2.7.html") (url (or (and (file-exists-p local) local) "'http://rgruet.free.fr/PQR27/PQR2.7.html'"))) (unless (get-buffer ref-buffer) (get-buffer-create ref-buffer) (with-current-buffer ref-buffer (shell-command (concat "snc txt.py.reference 2>/dev/null") ref-buffer) (goto-char (point-min)) (if (eobp) (shell-command (concat "w3m -dump -cols " (number-to-string (1- (window-width))) " " url) ref-buffer)))) (display-buffer ref-buffer t)) # :ide: +-#+ # . Python Reference () # :ide: COMPILE: Run with --eide # . (progn (save-buffer) (shell-command (concat "python ./" (file-name-nondirectory (buffer-file-name)) " --eide") (concat "*templates: " (file-name-nondirectory (buffer-file-name)) "*"))) # :ide: COMPILE: Run with python3 --test # . (progn (save-buffer) (compile (concat "python3 ./" (file-name-nondirectory (buffer-file-name)) " --test"))) # :ide: COMPILE: Run with python3 w/o args # . (progn (save-buffer) (compile (concat "python3 ./" (file-name-nondirectory (buffer-file-name)) " "))) # :ide: COMPILE: Run w/o args # . (progn (save-buffer) (compile (concat "python ./" (file-name-nondirectory (buffer-file-name)) " "))) # :ide: COMPILE: Run with --test # . (progn (save-buffer) (compile (concat "cd ../kallithea; \"${HOME}\"/project/kallithea/kallithea-venv/bin/python " (buffer-file-name) " --test"))) # :ide: COMPILE: Run w/o args # . (progn (save-buffer) (compile (concat "cd ../kallithea; \"${HOME}\"/project/kallithea/kallithea-venv/bin/python " (buffer-file-name) " "))) # :ide: +-#+ # . Compile () # # Local Variables: # mode: python # comment-start: "#" # comment-start-skip: "#+" # comment-column: 0 # truncate-lines: t # End:
_______________________________________________ kallithea-general mailing list kallithea-general@sfconservancy.org https://lists.sfconservancy.org/mailman/listinfo/kallithea-general