bytes and string musings
authorStefan Israelsson Tampe <stefan.itampe@gmail.com>
Mon, 13 Aug 2018 21:22:09 +0000 (23:22 +0200)
committerStefan Israelsson Tampe <stefan.itampe@gmail.com>
Mon, 13 Aug 2018 21:22:09 +0000 (23:22 +0200)
modules/language/python/bytes.scm
modules/language/python/compile.scm
modules/language/python/dict.scm
modules/language/python/module/_python.scm
modules/language/python/module/gettext.py [new file with mode: 0644]
modules/language/python/module/struct.scm
modules/language/python/string.scm

index 635358dfb4454bf4b31503411717e1374c9bd787..daf7adcc47121f9bde12e94a8358402b6215b7e2 100644 (file)
   #:use-module (language python hash)
   #:use-module (language python bool)
   #:use-module (language python persist)
-  #:export (<py-bytes> pybytes-listing bytes bytearray bytes->bytevector
+  #:export (<py-bytes> bv-scm pybytes-listing bytes bytearray bytes->bytevector
+                       py-decode
                       <py-bytearray> pybytesarray-listing scm-bytevector))
 
+(define (bv-scm x)
+  (slot-ref (bytes x) 'bytes))
+
 (define (scm-bytevector x)
   (slot-ref (bytes x) 'bytes))
 
@@ -25,7 +29,8 @@
 (define b-set!  bytevector-u8-set!)
 (define b-make  make-bytevector)
 (define b-len   bytevector-length)
-
+(define (b->list x) (bytevector->u8-list (bv-scm x)))
+(define list->b u8-list->bytevector)
 (define-class <py-bytes> () bytes)
 (define-class <py-bytearray> () n vec)
 
             (n (slot-ref o 'n)))
         (apply g bytearray b n l)))))
 
+(define-syntax-rule (define-py** g (f m o nn . u) code ...)
+  (begin
+    (define (g m o nn . u) code ...)
+    (define-method (f (o <bytevector>) . l)
+      (apply g idd o (b-len o) l))
+    (define-method (f (o <py-bytes>) . l)
+      (let ((b (slot-ref o 'bytes)))
+        (apply g bytes b (b-len b) l)))
+    (define-method (f (o <py-bytearray>) . l)
+      (let ((b (slot-ref o 'vec))
+            (n (slot-ref o 'n)))
+        (apply g bytearray b n l)))
+    (define-method (f (o <p>) . l)
+      (aif it (ref o 'g)
+           (apply it l)
+           (next-method)))))
+
 (define-py* -bool (bool m o nn) (if (= (len o) 0) #f o))
 
 (define-method (write (b <py-bytes>) . l)
             (lp (+ i 1) (+ j 1)))))
     (bytes s)))
 
-;;;py-decode
+(define-py** decode (py-decode bytes o n . l)
+  (let lp ((i 0) (r '()))
+    (if (< i n)
+        (lp (+ i 1) (cons (b-ref o i) r))
+        (utf8->string (list->b (reverse r))))))
+
 ;;;py-encode
 
 (define-py* endswith (py-endswith bytes o n suff . l)
 (define-py (py-title title s)
   (string-titlecase s))
 
-#;
-(define-py (py-split s . l)
-  (define ws (f+ (f-reg "[ \t\n]")))
-  (define r
-    (f-or! (f-seq f-eof (f-out '()))
-           (f-cons (f-seq (mk-token (f* (f-reg! "."))) f-eof) (f-out '()))))
-  (define (u ws)  (mk-token (f+ (f-not! ws))))
-  (define (tok ws i)
-    (if (= i 0)
-        (f-list (mk-token (f* (f-reg! "."))))
-        (let ((e (mk-token (f* (f-not! ws)))))
-          (f-seq (f? ws)
-                 (f-cons e
-                         (let lp ((i i))
-                           (if (> (- i 1) 0)
-                               (f-or! (f-seq (f? ws) f-eof (f-out '()))
-                                      (f-cons (f-seq ws e) (Ds (lp (- i 1)))))
-                               r)))))))
-  
-  (define N 1000000000000)
-  (let ((e (call-with-values
-               (lambda ()
-                 (match l
-                   (()      (values ws          N))
-                   ((sep)   (values (f-tag sep) N))
-                   ((sep n) (values (f-tag sep) n))))
-             tok)))
-    (parse s e)))
-
+(define-py* split (py-split bytes o n tag)
+  (let ((tag (b->list tag)))
+    (let lp ((i 0) (r '()))
+      (if (< i n)
+          (if (eq? (car tag) (b-ref o i))
+              (let lp2 ((j i) (tag tag))
+                (if (null? tag)
+                    (cons (bytes (list->b (reverse r)))
+                          (lp (+ i 1) '()))
+                    (if (< j n)
+                        (if (eq? (car tag) (b-ref o j))
+                            (lp2 (+ j 1) (cdr tag))
+                            (lp (+ i 1) (cons (b-ref o i) r)))
+                        (lp (+ i 1) (cons (b-ref o i) r)))))
+              (lp (+ i 1) (cons (b-ref o i) r)))
+          '()))))
 #;
 (define-py (py-rsplit s . l)
   (reverse
index 032b1c2a515aebf8ed23d0d2d793f54b6b1bdab0..36182a4e5e700a41bc27e0fda1096daec11062c9 100644 (file)
    ((translate)  (S 'py-translate))
    ((zfill)      (S 'py-zfill))
 
+   ;;bytevectors
+   ((decode)     (B 'py-decode))
    ;;DICTS
    ((copy)       (Di 'py-copy))
    ((fromkeys)   (Di 'py-fromkeys))
                           r))               
                (class   (exp vs class))
                (vs  (union (list class) vs))
-               (ns  (scope code vs))
-               (ls  (diff ns vs))
+               (ns  (scope code '()))
+               (ls  ns #;(diff ns vs))
 
                (parents (match parents
                           (() #f)
       ((_ v)
        (begin
          (dont-warn (syntax->datum #'v))
-         #'(if (module-defined? (current-module) 'v)
+         #'(if (and #f (module-defined? (current-module) 'v))
                (values)
                (define! 'v #f)))))))
 
index 11615804ff8e118240a76505a2b09c1fa70d735a..1d825b6b6c92283dbf3334e070c8e4a34c1c7d76 100644 (file)
      (begin
        (define-method (nm (o class) l ...) code ...)
        ...
-       (define-method (nm (o   <p>) l ...)
+       (define-method (nm (o   <p>) . v)
          (aif it (ref o 'n)
-              (it l ...)
+              (apply it v)
               (next-method)))))
     ((_ (nm n o l ... . u) (class code ...) ...)
      (begin
        (define-method (nm (o class) l ... . u) code ...)
        ...
-       (define-method (nm (o   <p>) l ... . u)
+       (define-method (nm (o   <p>) . v)
          (aif it (ref o 'n) 
-              (apply it l ... u)
+              (apply it v)
               (next-method)))))))
 
 
index 4865ef16dfc902ff9719f742a9d2d9c9516c4a4a..577c2c23b64fa4d199b5a3b2c0ee278fde7a1662 100644 (file)
 (define classmethod  class-method)
 (define staticmethod static-method)
 
-(define (enumerate l)
+(def (enumerate l (= start 0))
   ((make-generator ()
     (lambda (yield)
-       (for ((x : l)) ((i 0))
+       (for ((x : l)) ((i start))
             (yield i x)
            (+ i 1))))))
 
diff --git a/modules/language/python/module/gettext.py b/modules/language/python/module/gettext.py
new file mode 100644 (file)
index 0000000..80a9ebe
--- /dev/null
@@ -0,0 +1,673 @@
+module(gettext)
+
+"""Internationalization and localization support.
+
+This module provides internationalization (I18N) and localization (L10N)
+support for your Python programs by providing an interface to the GNU gettext
+message catalog library.
+
+I18N refers to the operation by which a program is made aware of multiple
+languages.  L10N refers to the adaptation of your program, once
+internationalized, to the local language and cultural habits.
+
+"""
+
+# This module represents the integration of work, contributions, feedback, and
+# suggestions from the following people:
+#
+# Martin von Loewis, who wrote the initial implementation of the underlying
+# C-based libintlmodule (later renamed _gettext), along with a skeletal
+# gettext.py implementation.
+#
+# Peter Funk, who wrote fintl.py, a fairly complete wrapper around intlmodule,
+# which also included a pure-Python implementation to read .mo files if
+# intlmodule wasn't available.
+#
+# James Henstridge, who also wrote a gettext.py module, which has some
+# interesting, but currently unsupported experimental features: the notion of
+# a Catalog class and instances, and the ability to add to a catalog file via
+# a Python API.
+#
+# Barry Warsaw integrated these modules, wrote the .install() API and code,
+# and conformed all C and Python code to Python's coding standards.
+#
+# Francois Pinard and Marc-Andre Lemburg also contributed valuably to this
+# module.
+#
+# J. David Ibanez implemented plural forms. Bruno Haible fixed some bugs.
+#
+# TODO:
+# - Lazy loading of .mo files.  Currently the entire catalog is loaded into
+#   memory, but that's probably bad for large translated programs.  Instead,
+#   the lexical sort of original strings in GNU .mo files should be exploited
+#   to do binary searches and lazy initializations.  Or you might want to use
+#   the undocumented double-hash algorithm for .mo files with hash tables, but
+#   you'll need to study the GNU gettext code to do this.
+#
+# - Support Solaris .mo file formats.  Unfortunately, we've been unable to
+#   find this format documented anywhere.
+
+
+import locale, copy, io, os, re, struct, sys
+from errno import ENOENT
+
+
+__all__ = ['NullTranslations', 'GNUTranslations', 'Catalog',
+           'find', 'translation', 'install', 'textdomain', 'bindtextdomain',
+           'bind_textdomain_codeset',
+           'dgettext', 'dngettext', 'gettext', 'lgettext', 'ldgettext',
+           'ldngettext', 'lngettext', 'ngettext',
+           ]
+
+_default_localedir = os.path.join(sys.base_prefix, 'share', 'locale')
+
+# Expression parsing for plural form selection.
+#
+# The gettext library supports a small subset of C syntax.  The only
+# incompatible difference is that integer literals starting with zero are
+# decimal.
+#
+# https://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms
+# http://git.savannah.gnu.org/cgit/gettext.git/tree/gettext-runtime/intl/plural.y
+
+_token_pattern = re.compile(r"""
+        (?P<WHITESPACES>[ \t]+)                    | # spaces and horizontal tabs
+        (?P<NUMBER>[0-9]+\b)                       | # decimal integer
+        (?P<NAME>n\b)                              | # only n is allowed
+        (?P<PARENTHESIS>[()])                      |
+        (?P<OPERATOR>[-*/%+?:]|[><!]=?|==|&&|\|\|) | # !, *, /, %, +, -, <, >,
+                                                     # <=, >=, ==, !=, &&, ||,
+                                                     # ? :
+                                                     # unary and bitwise ops
+                                                     # not allowed
+        (?P<INVALID>\w+|.)                           # invalid token
+    """, re.VERBOSE|re.DOTALL)
+
+def _tokenize(plural):
+    for mo in re.finditer(_token_pattern, plural):
+        kind = mo.lastgroup
+        if kind == 'WHITESPACES':
+            continue
+        value = mo.group(kind)
+        if kind == 'INVALID':
+            raise ValueError('invalid token in plural form: %s' % value)
+        yield value
+    yield ''
+
+def _error(value):
+    if value:
+        return ValueError('unexpected token in plural form: %s' % value)
+    else:
+        return ValueError('unexpected end of plural form')
+
+_binary_ops = (
+    ('||',),
+    ('&&',),
+    ('==', '!='),
+    ('<', '>', '<=', '>='),
+    ('+', '-'),
+    ('*', '/', '%'),
+)
+_binary_ops = {op: i for i, ops in enumerate(_binary_ops, 1) for op in ops}
+_c2py_ops = {'||': 'or', '&&': 'and', '/': '//'}
+
+def _parse(tokens, priority=-1):
+    result = ''
+    nexttok = next(tokens)
+    while nexttok == '!':
+        result += 'not '
+        nexttok = next(tokens)
+
+    if nexttok == '(':
+        sub, nexttok = _parse(tokens)
+        result = '%s(%s)' % (result, sub)
+        if nexttok != ')':
+            raise ValueError('unbalanced parenthesis in plural form')
+    elif nexttok == 'n':
+        result = '%s%s' % (result, nexttok)
+    else:
+        try:
+            value = int(nexttok, 10)
+        except ValueError:
+            raise _error(nexttok) from None
+        result = '%s%d' % (result, value)
+    nexttok = next(tokens)
+
+    j = 100
+    while nexttok in _binary_ops:
+        i = _binary_ops[nexttok]
+        if i < priority:
+            break
+        # Break chained comparisons
+        if i in (3, 4) and j in (3, 4):  # '==', '!=', '<', '>', '<=', '>='
+            result = '(%s)' % result
+        # Replace some C operators by their Python equivalents
+        op = _c2py_ops.get(nexttok, nexttok)
+        right, nexttok = _parse(tokens, i + 1)
+        result = '%s %s %s' % (result, op, right)
+        j = i
+    if j == priority == 4:  # '<', '>', '<=', '>='
+        result = '(%s)' % result
+
+    if nexttok == '?' and priority <= 0:
+        if_true, nexttok = _parse(tokens, 0)
+        if nexttok != ':':
+            raise _error(nexttok)
+        if_false, nexttok = _parse(tokens)
+        result = '%s if %s else %s' % (if_true, result, if_false)
+        if priority == 0:
+            result = '(%s)' % result
+
+    return result, nexttok
+
+def _as_int(n):
+    try:
+        i = round(n)
+    except TypeError:
+        raise TypeError('Plural value must be an integer, got %s' %
+                        (n.__class__.__name__,)) from None
+    return n
+
+def c2py(plural):
+    """Gets a C expression as used in PO files for plural forms and returns a
+    Python function that implements an equivalent expression.
+    """
+
+    if len(plural) > 1000:
+        raise ValueError('plural form expression is too long')
+    try:
+        result, nexttok = _parse(_tokenize(plural))
+        if nexttok:
+            raise _error(nexttok)
+
+        depth = 0
+        for c in result:
+            if c == '(':
+                depth += 1
+                if depth > 20:
+                    # Python compiler limit is about 90.
+                    # The most complex example has 2.
+                    raise ValueError('plural form expression is too complex')
+            elif c == ')':
+                depth -= 1
+
+        ns = {'_as_int': _as_int}
+        exec('''if True:
+            def func(n):
+                if not isinstance(n, int):
+                    n = _as_int(n)
+                return int(%s)
+            ''' % result, ns)
+        return ns['func']
+    except RecursionError:
+        # Recursion error can be raised in _parse() or exec().
+        raise ValueError('plural form expression is too complex')
+
+
+def _expand_lang(loc):
+    loc = locale.normalize(loc)
+    COMPONENT_CODESET   = 1 << 0
+    COMPONENT_TERRITORY = 1 << 1
+    COMPONENT_MODIFIER  = 1 << 2
+    # split up the locale into its base components
+    mask = 0
+    pos = loc.find('@')
+    if pos >= 0:
+        modifier = loc[pos:]
+        loc = loc[:pos]
+        mask |= COMPONENT_MODIFIER
+    else:
+        modifier = ''
+    pos = loc.find('.')
+    if pos >= 0:
+        codeset = loc[pos:]
+        loc = loc[:pos]
+        mask |= COMPONENT_CODESET
+    else:
+        codeset = ''
+    pos = loc.find('_')
+    if pos >= 0:
+        territory = loc[pos:]
+        loc = loc[:pos]
+        mask |= COMPONENT_TERRITORY
+    else:
+        territory = ''
+    language = loc
+    ret = []
+    for i in range(mask+1):
+        if not (i & ~mask):  # if all components for this combo exist ...
+            val = language
+            if i & COMPONENT_TERRITORY: val += territory
+            if i & COMPONENT_CODESET:   val += codeset
+            if i & COMPONENT_MODIFIER:  val += modifier
+            ret.append(val)
+    ret.reverse()
+    return ret
+
+
+
+class NullTranslations:
+    def __init__(self, fp=None):
+        self._info = {}
+        self._charset = None
+        self._output_charset = None
+        self._fallback = None
+        if fp is not None:
+            self._parse(fp)
+
+    def _parse(self, fp):
+        pass
+
+    def add_fallback(self, fallback):
+        if self._fallback:
+            self._fallback.add_fallback(fallback)
+        else:
+            self._fallback = fallback
+
+    def gettext(self, message):
+        if self._fallback:
+            return self._fallback.gettext(message)
+        return message
+
+    def lgettext(self, message):
+        if self._fallback:
+            return self._fallback.lgettext(message)
+        if self._output_charset:
+            return message.encode(self._output_charset)
+        return message.encode(locale.getpreferredencoding())
+
+    def ngettext(self, msgid1, msgid2, n):
+        if self._fallback:
+            return self._fallback.ngettext(msgid1, msgid2, n)
+        if n == 1:
+            return msgid1
+        else:
+            return msgid2
+
+    def lngettext(self, msgid1, msgid2, n):
+        if self._fallback:
+            return self._fallback.lngettext(msgid1, msgid2, n)
+        if n == 1:
+            tmsg = msgid1
+        else:
+            tmsg = msgid2
+        if self._output_charset:
+            return tmsg.encode(self._output_charset)
+        return tmsg.encode(locale.getpreferredencoding())
+
+    def info(self):
+        return self._info
+
+    def charset(self):
+        return self._charset
+
+    def output_charset(self):
+        return self._output_charset
+
+    def set_output_charset(self, charset):
+        self._output_charset = charset
+
+    def install(self, names=None):
+        import builtins
+        builtins.__dict__['_'] = self.gettext
+        if hasattr(names, "__contains__"):
+            if "gettext" in names:
+                builtins.__dict__['gettext'] = builtins.__dict__['_']
+            if "ngettext" in names:
+                builtins.__dict__['ngettext'] = self.ngettext
+            if "lgettext" in names:
+                builtins.__dict__['lgettext'] = self.lgettext
+            if "lngettext" in names:
+                builtins.__dict__['lngettext'] = self.lngettext
+
+
+class GNUTranslations(NullTranslations):
+    # Magic number of .mo files
+    LE_MAGIC = 0x950412de
+    BE_MAGIC = 0xde120495
+
+    # Acceptable .mo versions
+    VERSIONS = (0, 1)
+
+    def _get_versions(self, version):
+        """Returns a tuple of major version, minor version"""
+        return (version >> 16, version & 0xffff)
+
+    def _parse(self, fp):
+        """Override this method to support alternative .mo formats."""
+        pk('_parse')
+        unpack = pk('unpack',struct.unpack)
+        pk(1)
+        filename = getattr(fp, 'name', '')
+        pk(2,filename)
+        # Parse the .mo file header, which consists of 5 little endian 32
+        # bit words.
+        self._catalog = catalog = {}        
+        self.plural = lambda n: int(n != 1) # germanic plural by default
+        pk(3)
+        buf = fp.read()
+        buflen = len(buf)
+        pk(4,buf)
+        # Are we big endian or little endian?
+        magic = unpack('<I', buf[:4])[0]
+        pk('magic')
+        pk('magic',magic,'LE',self.LE_MAGIC,'BE',self.BE_MAGIC)
+        if magic == self.LE_MAGIC:
+            version, msgcount, masteridx, transidx = unpack('<4I', buf[4:20])
+            pk(version,msgcount,masteridx,transidx)
+            ii = '<II'
+        elif magic == self.BE_MAGIC:
+            version, msgcount, masteridx, transidx = unpack('>4I', buf[4:20])
+            ii = '>II'
+        else:
+            raise OSError(0, 'Bad magic number', filename)
+
+        major_version, minor_version = self._get_versions(version)
+        pk('version',major_version,self.VERSIONS)
+        if major_version not in self.VERSIONS:
+            raise OSError(0, 'Bad version number ' + str(major_version), filename)
+
+        # Now put all messages from the .mo file buffer into the catalog
+        # dictionary.
+        for i in range(0, msgcount):
+            mlen, moff = unpack(ii, buf[masteridx:masteridx+8])
+            mend = moff + mlen
+            tlen, toff = unpack(ii, buf[transidx:transidx+8])
+            tend = toff + tlen
+            if mend < buflen and tend < buflen:
+                msg = buf[moff:mend]
+                tmsg = buf[toff:tend]
+            else:
+                raise OSError(0, 'File is corrupt', filename)
+            pk('msg',mlen,tlen,msg,tmsg)
+            # See if we're looking at GNU .mo conventions for metadata
+            if mlen == 0:
+                # Catalog description
+                lastk = None
+                for b_item in pk('split',tmsg.split(b'\n')):
+                    pk('item',b_item)
+                    item = b_item.decode().strip()
+                    pk('decode',item)
+                    if not item:
+                        continue
+                    k = v = None
+                    if ':' in item:
+                        k, v = item.split(':', 1)
+                        k = k.strip().lower()
+                        v = v.strip()
+                        pk('pair',k,v)
+                        self._info[k] = v
+                        lastk = k
+                    elif lastk:
+                        self._info[lastk] += '\n' + item
+                        pk('lastk',lastk,self._info[lastk])
+                    if k == 'content-type':
+                        self._charset = v.split('charset=')[1]
+                        pk('charset',self._charset)
+                    elif k == 'plural-forms':
+                        v = v.split(';')
+                        plural = v[1].split('plural=')[1]
+                        self.plural = c2py(plural)
+                        pk('plural',self.plural)
+            # Note: we unconditionally convert both msgids and msgstrs to
+            # Unicode using the character encoding specified in the charset
+            # parameter of the Content-Type header.  The gettext documentation
+            # strongly encourages msgids to be us-ascii, but some applications
+            # require alternative encodings (e.g. Zope's ZCML and ZPT).  For
+            # traditional gettext applications, the msgid conversion will
+            # cause no problems since us-ascii should always be a subset of
+            # the charset encoding.  We may want to fall back to 8-bit msgids
+            # if the Unicode conversion fails.
+            charset = self._charset or 'ascii'
+            if b'\x00' in msg:
+                # Plural forms
+                msgid1, msgid2 = msg.split(b'\x00')
+                tmsg = tmsg.split(b'\x00')
+                msgid1 = str(msgid1, charset)
+                for i, x in enumerate(tmsg):
+                    catalog[(msgid1, i)] = str(x, charset)
+            else:
+                catalog[str(msg, charset)] = str(tmsg, charset)
+            # advance to next entry in the seek tables
+            masteridx += 8
+            transidx += 8
+
+    def lgettext(self, message):
+        missing = object()
+        tmsg = self._catalog.get(message, missing)
+        if tmsg is missing:
+            if self._fallback:
+                return self._fallback.lgettext(message)
+            tmsg = message
+        if self._output_charset:
+            return tmsg.encode(self._output_charset)
+        return tmsg.encode(locale.getpreferredencoding())
+
+    def lngettext(self, msgid1, msgid2, n):
+        try:
+            tmsg = self._catalog[(msgid1, self.plural(n))]
+        except KeyError:
+            if self._fallback:
+                return self._fallback.lngettext(msgid1, msgid2, n)
+            if n == 1:
+                tmsg = msgid1
+            else:
+                tmsg = msgid2
+        if self._output_charset:
+            return tmsg.encode(self._output_charset)
+        return tmsg.encode(locale.getpreferredencoding())
+
+    def gettext(self, message):
+        missing = object()
+        tmsg = self._catalog.get(message, missing)
+        if tmsg is missing:
+            if self._fallback:
+                return self._fallback.gettext(message)
+            return message
+        return tmsg
+
+    def ngettext(self, msgid1, msgid2, n):
+        try:
+            tmsg = self._catalog[(msgid1, self.plural(n))]
+        except KeyError:
+            if self._fallback:
+                return self._fallback.ngettext(msgid1, msgid2, n)
+            if n == 1:
+                tmsg = msgid1
+            else:
+                tmsg = msgid2
+        return tmsg
+
+
+# Locate a .mo file using the gettext strategy
+def find(domain, localedir=None, languages=None, all=False):
+    # Get some reasonable defaults for arguments that were not supplied
+    if localedir is None:
+        localedir = _default_localedir
+    if languages is None:
+        languages = []
+        for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'):
+            val = os.environ.get(envar)
+            if val:
+                languages = val.split(':')
+                break
+        if 'C' not in languages:
+            languages.append('C')
+    # now normalize and expand the languages
+    nelangs = []
+    for lang in languages:
+        for nelang in _expand_lang(lang):
+            if nelang not in nelangs:
+                nelangs.append(nelang)
+    # select a language
+    if all:
+        result = []
+    else:
+        result = None
+    for lang in nelangs:
+        if lang == 'C':
+            break
+        mofile = os.path.join(localedir, lang, 'LC_MESSAGES', '%s.mo' % domain)
+        mofile_lp = os.path.join("/usr/share/locale-langpack", lang,
+                               'LC_MESSAGES', '%s.mo' % domain)
+
+       # first look into the standard locale dir, then into the 
+       # langpack locale dir
+
+       # standard mo file
+        if os.path.exists(mofile):
+            if all:
+                result.append(mofile)
+            else:
+                return mofile
+        
+       # langpack mofile -> use it
+        if os.path.exists(mofile_lp): 
+            if all:
+                result.append(mofile_lp)
+            else:
+                return mofile_lp
+
+    return result
+
+
+
+# a mapping between absolute .mo file path and Translation object
+_translations = {}
+
+def translation(domain, localedir=None, languages=None,
+                class_=None, fallback=False, codeset=None):
+    if class_ is None:
+        class_ = GNUTranslations
+    mofiles = find(domain, localedir, languages, all=True)
+    if not mofiles:
+        if fallback:
+            return NullTranslations()
+        raise OSError(ENOENT, 'No translation file found for domain', domain)
+    # Avoid opening, reading, and parsing the .mo file after it's been done
+    # once.
+    result = None
+    for mofile in mofiles:
+        pk('mofile',mofile)
+        key = pk((class_, os.path.abspath(mofile)))
+        t = pk('tr',_translations.get(key))
+        if t is None:
+            with open(mofile, 'rb') as fp:
+                pk('process')
+                t = _translations.setdefault(key, pk('cl',class_(fp)))
+        # Copy the translation object to allow setting fallbacks and
+        # output charset. All other instance data is shared with the
+        # cached object.
+        pk('t1',t)
+        t = copy.copy(t)
+        pk('t2',t)
+        if codeset:
+            t.set_output_charset(codeset)
+        if result is None:
+            result = t
+        else:
+            result.add_fallback(t)
+    return result
+
+
+def install(domain, localedir=None, codeset=None, names=None):
+    t = translation(domain, localedir, fallback=True, codeset=codeset)
+    t.install(names)
+
+
+
+# a mapping b/w domains and locale directories
+_localedirs = {}
+# a mapping b/w domains and codesets
+_localecodesets = {}
+# current global domain, `messages' used for compatibility w/ GNU gettext
+_current_domain = 'messages'
+
+
+def textdomain(domain=None):
+    global _current_domain
+    if domain is not None:
+        _current_domain = domain
+    return _current_domain
+
+
+def bindtextdomain(domain, localedir=None):
+    global _localedirs
+    if localedir is not None:
+        _localedirs[domain] = localedir
+    return _localedirs.get(domain, _default_localedir)
+
+
+def bind_textdomain_codeset(domain, codeset=None):
+    global _localecodesets
+    if codeset is not None:
+        _localecodesets[domain] = codeset
+    return _localecodesets.get(domain)
+
+
+def dgettext(domain, message):
+    try:
+        t = translation(domain, _localedirs.get(domain, None),
+                        codeset=_localecodesets.get(domain))
+    except OSError:
+        return message
+    return t.gettext(message)
+
+def ldgettext(domain, message):
+    codeset = _localecodesets.get(domain)
+    try:
+        t = translation(domain, _localedirs.get(domain, None), codeset=codeset)
+    except OSError:
+        return message.encode(codeset or locale.getpreferredencoding())
+    return t.lgettext(message)
+
+def dngettext(domain, msgid1, msgid2, n):
+    try:
+        t = translation(domain, _localedirs.get(domain, None),
+                        codeset=_localecodesets.get(domain))
+    except OSError:
+        if n == 1:
+            return msgid1
+        else:
+            return msgid2
+    return t.ngettext(msgid1, msgid2, n)
+
+def ldngettext(domain, msgid1, msgid2, n):
+    codeset = _localecodesets.get(domain)
+    try:
+        t = translation(domain, _localedirs.get(domain, None), codeset=codeset)
+    except OSError:
+        if n == 1:
+            tmsg = msgid1
+        else:
+            tmsg = msgid2
+        return tmsg.encode(codeset or locale.getpreferredencoding())
+    return t.lngettext(msgid1, msgid2, n)
+
+def gettext(message):
+    return dgettext(_current_domain, message)
+
+def lgettext(message):
+    return ldgettext(_current_domain, message)
+
+def ngettext(msgid1, msgid2, n):
+    return dngettext(_current_domain, msgid1, msgid2, n)
+
+def lngettext(msgid1, msgid2, n):
+    return ldngettext(_current_domain, msgid1, msgid2, n)
+
+# dcgettext() has been deemed unnecessary and is not implemented.
+
+# James Henstridge's Catalog constructor from GNOME gettext.  Documented usage
+# was:
+#
+#    import gettext
+#    cat = gettext.Catalog(PACKAGE, localedir=LOCALEDIR)
+#    _ = cat.gettext
+#    print _('Hello World')
+
+# The resulting catalog object currently don't support access through a
+# dictionary API, which was supported (but apparently unused) in GNOME
+# gettext.
+
+Catalog = translation
index b3d5b9f473c947325e0794095600153b61900123..05b38f23ea44a31ca65b1ab0a3d84985d5a631a1 100644 (file)
@@ -7,6 +7,7 @@
   #:use-module (language python bool)
   #:use-module (language python yield)
   #:use-module (language python def)
+  #:use-module (language python bytes)
   #:use-module (language python exceptions)
   #:export (calcsize pack pack_into unpack unpack_from
                      iter_unpack Struct error))
@@ -44,7 +45,7 @@
                            'little
                            'big)))
   (define standard (not (member type '("@"))))
-  
+    
   ;; Util
   (define (incr i n)
     (+ i (if pack
   (define c-i4
     (lambda (k)
       (lambda (bv i)
+        (pk bv i)
         (cons (bytevector-s32-ref bv i end)
               (k bv (incr i 4))))))
 
                      (cons (list (- n 1) tp) l))))
                bv i)))
          (() '()))))
-   bv n))
+   (bv-scm bv) n))
               
 (define (unpack format buffer)  
-  (unpacker (analyze format) buffer 0))
+  (unpacker (pk (analyze format)) buffer 0))
 
 (define unpack_from
   (lam (format buffer (= offset 0)) 
index 829e3a2b506ab696c9347a9d7a7a6afdecc437b8..a560801a59865ea925582d495425106b33d91365 100644 (file)
 
 (define-py (py-split split s . l)
   (define ws (f+ (f-reg "[ \t\n]")))
-  (define r
+  (define (r ws)
     (f-or! (f-seq f-eof (f-out '()))
-           (f-cons (f-seq (mk-token (f* (f-reg! "."))) f-eof) (f-out '()))))
+           (f-cons (f-seq (f? ws) (mk-token (f* (f-reg! "."))) f-eof)
+                   (f-out '()))))
   (define (u ws)  (mk-token (f+ (f-not! ws))))
   (define (tok ws i)
     (if (= i 0)
         (f-list (mk-token (f* (f-reg! "."))))
         (let ((e (mk-token (f* (f-not! ws)))))
           (f-seq (f? ws)
-                 (f-cons e
+                 (f-cons* e
                          (let lp ((i i))
                            (if (> (- i 1) 0)
                                (f-or! (f-seq (f? ws) f-eof (f-out '()))
                                       (f-cons (f-seq ws e) (Ds (lp (- i 1)))))
-                               r)))))))
+                               (r ws))))))))
   
   (define N 1000000000000)
   (let ((e (call-with-values