@@@ cython and python 3 wip
authorMark Wooding <mdw@distorted.org.uk>
Mon, 7 Oct 2019 01:18:29 +0000 (02:18 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Mon, 7 Oct 2019 01:18:29 +0000 (02:18 +0100)
14 files changed:
codec.pyx
codec.pyx.in
defs.pxi
fdutils.pyx
fwatch.pyx
grim.h
mLib.pyx
mdup.pyx
report.pyx
setup.py
str.pyx
test.py
url.pyx
utils.pyx

index 1274afd..0bef296 100644 (file)
--- a/codec.pyx
+++ b/codec.pyx
 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
 ###--------------------------------------------------------------------------
-### Base classes.
-
-cdef extern from 'mLib/codec.h':
-
-  ctypedef struct codec
-
-  ctypedef struct codec_class:
-    char *name
-    codec *(*encoder)(unsigned f, char *ind, unsigned max)
-    codec *(*decoder)(unsigned f)
-
-  ctypedef struct codec_ops:
-    codec_class *c
-    int (*code)(codec *c, void *p, size_t, dstr *d)
-    void (*destroy)(codec *c)
-  ctypedef struct codec:
-    codec_ops *ops
-
-  enum:
-    CDCF_LOWERC
-    CDCF_IGNCASE
-    CDCF_NOEQPAD
-    CDCF_IGNEQPAD
-    CDCF_IGNEQMID
-    CDCF_IGNZPAD
-    CDCF_IGNNEWL
-    CDCF_IGNSPC
-    CDCF_IGNINVCH
-    CDCF_IGNJUNK
-
-  enum:
-    CDCERR_OK
-    CDCERR_INVCH
-    CDCERR_INVEQPAD
-    CDCERR_INVZPAD
-
-  char *_codec_strerror "codec_strerror"(int err)
+### Constants.
+
+CDCF_LOWERC = _CDCF_LOWERC
+CDCF_IGNCASE = _CDCF_IGNCASE
+CDCF_NOEQPAD = _CDCF_NOEQPAD
+CDCF_IGNEQPAD = _CDCF_IGNEQPAD
+CDCF_IGNEQMID = _CDCF_IGNEQMID
+CDCF_IGNZPAD = _CDCF_IGNZPAD
+CDCF_IGNNEWL = _CDCF_IGNNEWL
+CDCF_IGNSPC = _CDCF_IGNSPC
+CDCF_IGNINVCH = _CDCF_IGNINVCH
+CDCF_IGNJUNK = _CDCF_IGNJUNK
 
 class CDCF:
-  LOWERC = CDCF_LOWERC
-  IGNCASE = CDCF_IGNCASE
-  NOEQPAD = CDCF_NOEQPAD
-  IGNEQPAD = CDCF_IGNEQPAD
-  IGNEQMID = CDCF_IGNEQMID
-  IGNZPAD = CDCF_IGNZPAD
-  IGNNEWL = CDCF_IGNNEWL
-  IGNSPC = CDCF_IGNSPC
-  IGNINVCH = CDCF_IGNINVCH
-  IGNJUNK = CDCF_IGNJUNK
+  ## DEPRECATED.  Use the original names at module level.
+  LOWERC = _CDCF_LOWERC
+  IGNCASE = _CDCF_IGNCASE
+  NOEQPAD = _CDCF_NOEQPAD
+  IGNEQPAD = _CDCF_IGNEQPAD
+  IGNEQMID = _CDCF_IGNEQMID
+  IGNZPAD = _CDCF_IGNZPAD
+  IGNNEWL = _CDCF_IGNNEWL
+  IGNSPC = _CDCF_IGNSPC
+  IGNINVCH = _CDCF_IGNINVCH
+  IGNJUNK = _CDCF_IGNJUNK
+
+CDCERR_OK = _CDCERR_OK
+CDCERR_INVCH = _CDCERR_INVCH
+CDCERR_INVEQPAD = _CDCERR_INVEQPAD
+CDCERR_INVZPAD = _CDCERR_INVZPAD
 
 class CDCERR:
-  OK = CDCERR_OK
-  INVCH = CDCERR_INVCH
-  INVEQPAD = CDCERR_INVEQPAD
-  INVZPAD = CDCERR_INVZPAD
+  ## DEPRECATED.  Use the original names at module level.
+  OK = _CDCERR_OK
+  INVCH = _CDCERR_INVCH
+  INVEQPAD = _CDCERR_INVEQPAD
+  INVZPAD = _CDCERR_INVZPAD
+
+###--------------------------------------------------------------------------
+### Base classes.
 
 class CodecError (Exception):
   """
   Exception from decoding operation.
 
-  Attributes: err = CDCERR.* code, msg = message string
+  Attributes: `err' = `CDCERR_*' code, `msg' = message string
   """
   def __init__(me, err):
     me.err = err
-    me.msg = _codec_strerror(err)
+    me.msg = <str>_codec_strerror(err)
   def __str__(me):
     return me.msg
 
 def codec_strerror(err):
-  """codec_strerror(ERR) -> STR: message for CDCERR.* code"""
-  return _codec_strerror(err)
-
-cdef int code(codec *c, void *p, size_t len, dstr *d) except -1:
-  cdef int err
-  err = c.ops.code(c, p, len, d)
-  if err:
-    raise CodecError(err)
-  return 0
+  """codec_strerror(ERR) -> STR: message for `CDCERR_*' code"""
+  return <str>_codec_strerror(err)
 
 cdef class _BaseCodec:
   """Abstract superclass for codecs."""
   cdef codec *c
   def __cinit__(me, *hunoz, **hukairz):
     me.c = NULL
-  def __init__(me, *hunoz, **hukairz):
-    raise TypeError, 'abstract class'
   def __dealloc__(me):
     if me.c is not NULL:
       me.c.ops.destroy(me.c)
-  cdef code(me, text, int finishp):
-    cdef void *p
-    cdef Py_ssize_t len
-    cdef dstr d
+  def __init__(me, *hunoz, **hukairz):
+    raise TypeError('abstract class')
+  cdef int _code(me, const void *p, Py_ssize_t sz,
+                 dstr *d, bint finishp) except -1:
     cdef int err
     if me.c is NULL:
-      raise ValueError, 'Encoding finished'
-    DCREATE(&d)
-    try:
-      PyObject_AsReadBuffer(text, <cvp *>&p, &len)
-      code(me.c, p, len, &d)
-      if finishp:
-        code(me.c, NULL, 0, &d)
-        me.c.ops.destroy(me.c)
-        me.c = NULL
-      return PyString_FromStringAndSize(d.buf, d.len)
-    finally:
-      dstr_destroy(&d)
+      raise ValueError('Encoding finished')
+    err = me.c.ops.code(me.c, p, sz, d)
+    if err:
+      raise CodecError(err)
+    if finishp:
+      err = me.c.ops.code(me.c, p, sz, d)
+      me.c.ops.destroy(me.c)
+      me.c = NULL
+      if err:
+        raise CodecError(err)
+    return 0
+
   def done(me):
     """C.done() -> OUT: final output"""
     me.code('', True)
 
 cdef class _BaseEncoder (_BaseCodec):
-  def encode(me, text, finishp = False):
+  def encode(me, input, finishp = False):
     """C.encode(IN, [finishp = False]) -> OUT: continue/finish encoding"""
-    return me.code(text, finishp)
+    cdef const void *p
+    cdef Py_ssize_t sz
+    cdef dstr d
+    DCREATE(&d)
+    try:
+      PyObject_AsReadBuffer(input, &p, &sz)
+      me._code(p, sz, &d, finishp)
+      return TEXT_FROMSTRLEN(d.buf, d.len)
+    finally:
+      dstr_destroy(&d)
+  def done(me):
+    """C.done() -> OUT: final output"""
+    cdef dstr d
+    DCREATE(&d)
+    try:
+      me._code(NULL, 0, &d, True)
+      return TEXT_FROMSTRLEN(d.buf, d.len)
+    finally:
+      dstr_destroy(&d)
 
 cdef class _BaseDecoder (_BaseCodec):
-  def decode(me, text, finishp = False):
+  def decode(me, input, finishp = False):
     """C.decode(IN, [finishp = False]) -> OUT: continue/finish decoding"""
-    return me.code(text, finishp)
+    cdef const char *p
+    cdef Py_ssize_t sz
+    cdef dstr d
+    DCREATE(&d)
+    try:
+      TEXT_PTRLEN(input, &p, &sz)
+      me._code(p, sz, &d, finishp)
+      return BIN_FROMSTRLEN(d.buf, d.len)
+    finally:
+      dstr_destroy(&d)
+  def done(me):
+    """C.done() -> OUT: final output"""
+    cdef dstr d
+    DCREATE(&d)
+    try:
+      me._code(NULL, 0, &d, True)
+      return BIN_FROMSTRLEN(d.buf, d.len)
+    finally:
+      dstr_destroy(&d)
 
 ###--------------------------------------------------------------------------
 ### Base64.
@@ -155,60 +167,60 @@ cdef extern from 'mLib/base64.h':
 
 cdef class Base64Encoder (_BaseEncoder):
   """
-  Base64Encoder([indent = '\\n'], [maxline = 72], [flags = CDCF.IGNJUNK])
+  Base64Encoder([indent = '\\n'], [maxline = 72], [flags = 0])
 
   Base64 encoder.
   """
-  def __init__(me, indent = '\n', maxline = 72, flags = CDCF_IGNJUNK):
-    me.c = base64_class.encoder(flags, indent, maxline)
+  def __init__(me, indent = '\n', maxline = 72, flags = 0):
+    me.c = base64_class.encoder(flags, TEXT_PTR(indent), maxline)
 
 cdef class Base64Decoder (_BaseDecoder):
   """
-  Base64Decoder([indent = '\\n'], [maxline = 72], [flags = CDCF.IGNJUNK])
+  Base64Decoder([flags = CDCF_IGNSPC | CDCF_IGNNEWL])
 
   Base64 decoder.
   """
-  def __init__(me, flags = CDCF_IGNJUNK):
+  def __init__(me, flags = _CDCF_IGNSPC | _CDCF_IGNNEWL):
     me.c = base64_class.decoder(flags)
 
 cdef class File64Encoder (_BaseEncoder):
   """
-  File64Encoder([indent = '\\n'], [maxline = 72], [flags = CDCF.IGNJUNK])
+  File64Encoder([indent = '\\n'], [maxline = 72], [flags = 0])
 
   Base64 encoder, using `%' instead of `/', so encoded strings are safe as
   filenames.
   """
-  def __init__(me, indent = '\n', maxline = 72, flags = CDCF_IGNJUNK):
-    me.c = file64_class.encoder(flags, indent, maxline)
+  def __init__(me, indent = '\n', maxline = 72, flags = 0):
+    me.c = file64_class.encoder(flags, TEXT_PTR(indent), maxline)
 
 cdef class File64Decoder (_BaseDecoder):
   """
-  File64Decoder([indent = '\\n'], [maxline = 72], [flags = CDCF.IGNJUNK])
+  File64Decoder([flags = CDCF_IGNSPC | CDCF_IGNNEWL])
 
   Base64 encoder, using `%' instead of `/', so encoded strings are safe as
   filenames.
   """
-  def __init__(me, flags = CDCF_IGNJUNK):
+  def __init__(me, flags = _CDCF_IGNSPC | _CDCF_IGNNEWL):
     me.c = file64_class.decoder(flags)
 
 cdef class Base64URLEncoder (_BaseEncoder):
   """
-  Base64URLEncoder([indent = '\\n'], [maxline = 72], [flags = CDCF.IGNJUNK])
+  Base64URLEncoder([indent = '\\n'], [maxline = 72], [flags = 0])
 
   Base64 encoder, using `-' and `_' instead of `+' and `/', so encoded
   strings are safe as URL components.
   """
-  def __init__(me, indent = '\n', maxline = 72, flags = CDCF_IGNJUNK):
-    me.c = base64url_class.encoder(flags, indent, maxline)
+  def __init__(me, indent = '\n', maxline = 72, flags = 0):
+    me.c = base64url_class.encoder(flags, TEXT_PTR(indent), maxline)
 
 cdef class Base64URLDecoder (_BaseDecoder):
   """
-  Base64URLDecoder([indent = '\\n'], [maxline = 72], [flags = CDCF.IGNJUNK])
+  Base64URLDecoder([flags = CDCF_IGNSPC | CDCF_IGNNEWL])
 
   Base64 decoder, using `-' and `_' instead of `+' and `/', so encoded
   strings are safe as URL components.
   """
-  def __init__(me, flags = CDCF_IGNJUNK):
+  def __init__(me, flags = _CDCF_IGNSPC | _CDCF_IGNNEWL):
     me.c = base64url_class.decoder(flags)
 
 ###--------------------------------------------------------------------------
@@ -220,40 +232,40 @@ cdef extern from 'mLib/base32.h':
 
 cdef class Base32Encoder (_BaseEncoder):
   """
-  Base32Encoder([indent = '\\n'], [maxline = 72], [flags = CDCF.IGNJUNK])
+  Base32Encoder([indent = '\\n'], [maxline = 72], [flags = 0])
 
   Base32 encoder.
   """
-  def __init__(me, indent = '\n', maxline = 72, flags = CDCF_IGNJUNK):
-    me.c = base32_class.encoder(flags, indent, maxline)
+  def __init__(me, indent = '\n', maxline = 72, flags = 0):
+    me.c = base32_class.encoder(flags, TEXT_PTR(indent), maxline)
 
 cdef class Base32Decoder (_BaseDecoder):
   """
-  Base32Decoder([indent = '\\n'], [maxline = 72], [flags = CDCF.IGNJUNK])
+  Base32Decoder([flags = CDCF_IGNSPC | CDCF_IGNNEWL | CDCF_IGNCASE])
 
   Base32 decoder.
   """
-  def __init__(me, flags = CDCF_IGNJUNK):
+  def __init__(me, flags = _CDCF_IGNSPC | _CDCF_IGNNEWL | _CDCF_IGNCASE):
     me.c = base32_class.decoder(flags)
 
 cdef class Base32HexEncoder (_BaseEncoder):
   """
-  Base32Encoder([indent = '\\n'], [maxline = 72], [flags = CDCF.IGNJUNK])
+  Base32Encoder([indent = '\\n'], [maxline = 72], [flags = 0])
 
   Base32 encoder, using digits and letters in ascending order, rather than
   avoiding digits which visually resemble letters.
   """
-  def __init__(me, indent = '\n', maxline = 72, flags = CDCF_IGNJUNK):
-    me.c = base32hex_class.encoder(flags, indent, maxline)
+  def __init__(me, indent = '\n', maxline = 72, flags = 0):
+    me.c = base32hex_class.encoder(flags, TEXT_PTR(indent), maxline)
 
 cdef class Base32HexDecoder (_BaseDecoder):
   """
-  Base32Decoder([indent = '\\n'], [maxline = 72], [flags = CDCF.IGNJUNK])
+  Base32Decoder([flags = CDCF_IGNSPC | CDCF_IGNNEWL | CDCF_IGNCASE])
 
   Base32 decoder, using digits and letters in ascending order, rather than
   avoiding digits which visually resemble letters.
   """
-  def __init__(me, flags = CDCF_IGNJUNK):
+  def __init__(me, flags = _CDCF_IGNSPC | _CDCF_IGNNEWL | _CDCF_IGNCASE):
     me.c = base32hex_class.decoder(flags)
 
 ###--------------------------------------------------------------------------
@@ -264,23 +276,20 @@ cdef extern from 'mLib/hex.h':
 
 cdef class HexEncoder (_BaseEncoder):
   """
-  HexEncoder([indent = '\\n'], [maxline = 72],
-             [flags = CDCF.IGNJUNK | CDCF.LOWERC])
+  HexEncoder([indent = '\\n'], [maxline = 72], [flags = CDCF_LOWERC])
 
   Hexadecimal encoder.
   """
-  def __init__(me, indent = '\n', maxline = 72,
-               flags = CDCF_IGNJUNK | CDCF_LOWERC):
-    me.c = hex_class.encoder(flags, indent, maxline)
+  def __init__(me, indent = '\n', maxline = 72, flags = _CDCF_LOWERC):
+    me.c = hex_class.encoder(flags, TEXT_PTR(indent), maxline)
 
 cdef class HexDecoder (_BaseDecoder):
   """
-  HexDecoder([indent = '\\n'], [maxline = 72],
-             [flags = CDCF.IGNJUNK | CDCF.LOWERC])
+  HexDecoder([flags = CDCF_IGNSPC | CDCF_IGNNEWL | CDCF_IGNCASE])
 
   Hexadecimal decoder.
   """
-  def __init__(me, flags = CDCF_IGNJUNK | CDCF_LOWERC):
+  def __init__(me, flags = _CDCF_IGNSPC | _CDCF_IGNNEWL | _CDCF_IGNCASE):
     me.c = hex_class.decoder(flags)
 
 ###----- That's all, folks --------------------------------------------------
index 3da1b83..4f57935 100644 (file)
@@ -40,59 +40,62 @@ cdef class %CLASS%Encode:
   Obsolete %CLASS% encoder.
   """
   cdef %PREFIX%_ctx ctx
-  def __cinit__(me, *hunoz, **hukairz):
+  def __cinit__(me):
     _%PREFIX%_init(&me.ctx)
     me.ctx.indent = NULL
+  def __dealloc__(me):
+    if me.ctx.indent:
+      xfree(<void *>me.ctx.indent)
   def __init__(me, indent = '\n', maxline = 72):
     if me.ctx.indent:
       xfree(<void *>me.ctx.indent)
-    me.ctx.indent = xstrdup(indent)
+    me.ctx.indent = xstrdup(TEXT_PTR(indent))
     me.ctx.maxline = maxline
-  def __dealloc__(me):
+  @property
+  def indent(me):
+    """E.indent -> INT: indent level for new lines"""
+    return me.ctx.indent
+  @indent.setter
+  def indent(me, indent):
     if me.ctx.indent:
       xfree(<void *>me.ctx.indent)
-  property indent:
-    """E.indent -> INT: indent level for new lines"""
-    def __get__(me):
-      return me.ctx.indent
-    def __set__(me, indent):
-      if me.ctx.indent:
-        xfree(<void *>me.ctx.indent)
-      me.ctx.indent = xstrdup(indent)
-  property maxline:
+    if indent is None:
+      me.ctx.indent = NULL
+    else:
+      me.ctx.indent = xstrdup(TEXT_PTR(indent))
+  @property
+  def maxline(me):
     """E.maxline -> INT: maximum length of line, or 0 to prevent splitting"""
-    def __get__(me):
-      return me.ctx.maxline
-    def __set__(me, maxline):
+    return me.ctx.maxline
+  @maxline.setter
+  def maxline(me, maxline):
       me.ctx.maxline = maxline
-  def encode(me, text):
+  def encode(me, input):
     """E.encode(IN) -> OUT: continue encoding"""
-    cdef void *p
-    cdef Py_ssize_t len
+    cdef const void *p
+    cdef Py_ssize_t sz
     cdef dstr d
     DCREATE(&d)
     try:
-      PyObject_AsReadBuffer(text, <cvp *>&p, &len)
-      _%PREFIX%_encode(&me.ctx, p, len, &d)
-      rc = PyString_FromStringAndSize(d.buf, d.len)
+      PyObject_AsReadBuffer(input, &p, &sz)
+      _%PREFIX%_encode(&me.ctx, p, sz, &d)
+      return TEXT_FROMSTRLEN(d.buf, d.len)
     finally:
       dstr_destroy(&d)
-    return rc
   def done(me):
     """E.done() -> OUT: finish encoding, returning final output"""
     cdef dstr d
     DCREATE(&d)
     try:
       _%PREFIX%_encode(&me.ctx, NULL, 0, &d)
-      rc = PyString_FromStringAndSize(d.buf, d.len)
+      return TEXT_FROMSTRLEN(d.buf, d.len)
     finally:
       dstr_destroy(&d)
-    return rc
 
-def %PREFIX%_encode(text, *arg, **kw):
+def %PREFIX%_encode(input, *arg, **kw):
   """%PREFIX%_encode(IN, [ARGS...]) -> OUT: %CLASS%-encode the string IN"""
   e = %CLASS%Encode(*arg, **kw)
-  return e.encode(text) + e.done()
+  return e.encode(input) + e.done()
 
 cdef class %CLASS%Decode:
   """
@@ -101,36 +104,34 @@ cdef class %CLASS%Decode:
   Obsolete %CLASS% decoder.
   """
   cdef %PREFIX%_ctx ctx
-  def __cinit__(me, *hunoz, **hukairz):
+  def __cinit__(me):
     _%PREFIX%_init(&me.ctx)
     me.ctx.indent = NULL
-  def decode(me, text):
+  def decode(me, input):
     """D.encode(IN) -> OUT: continue decoding"""
-    cdef void *p
-    cdef Py_ssize_t len
+    cdef const char *p
+    cdef Py_ssize_t sz
     cdef dstr d
     DCREATE(&d)
     try:
-      PyObject_AsReadBuffer(text, <cvp *>&p, &len)
-      _%PREFIX%_decode(&me.ctx, p, len, &d)
-      rc = PyString_FromStringAndSize(d.buf, d.len)
+      TEXT_PTRLEN(input, &p, &sz)
+      _%PREFIX%_decode(&me.ctx, p, sz, &d)
+      return BIN_FROMSTRLEN(d.buf, d.len)
     finally:
       dstr_destroy(&d)
-    return rc
   def done(me):
     """D.done() -> OUT: finish decoding, returning final output"""
     cdef dstr d
     DCREATE(&d)
     try:
       _%PREFIX%_decode(&me.ctx, NULL, 0, &d)
-      rc = PyString_FromStringAndSize(d.buf, d.len)
+      return BIN_FROMSTRLEN(d.buf, d.len)
     finally:
       dstr_destroy(&d)
-    return rc
 
-def %PREFIX%_decode(text, *arg, **kw):
+def %PREFIX%_decode(input, *arg, **kw):
   """%PREFIX%_decode(IN) -> OUT: %CLASS%-decode the string IN"""
   d = %CLASS%Decode(*arg, **kw)
-  return d.decode(text) + d.done()
+  return d.decode(input) + d.done()
 
 ###----- That's all, folks --------------------------------------------------
index 1ed4cb7..903a6d0 100644 (file)
--- a/defs.pxi
+++ b/defs.pxi
@@ -170,13 +170,46 @@ cdef extern from 'mLib/str.h':
     STRF_PREFIX
   char *str_qword(char **pp, unsigned f)
   size_t str_qsplit(char *p, char **v, size_t c, char **rest, unsigned f)
-  int str_matchx(char *p, char *s, unsigned f)
+  bint str_matchx(char *p, char *s, unsigned f)
   void str_sanitize(char *d, char *p, size_t sz)
 
 cdef extern from 'mLib/versioncmp.h':
   int _versioncmp "versioncmp" (char *va, char *vb)
 
 ###--------------------------------------------------------------------------
+### Binary encoding functions.
+
+cdef extern from 'mLib/codec.h':
+  ctypedef struct codec
+  ctypedef struct codec_class:
+    char *name
+    codec *(*encoder)(unsigned f, char *ind, unsigned max)
+    codec *(*decoder)(unsigned f)
+  ctypedef struct codec_ops:
+    codec_class *c
+    int (*code)(codec *c, const void *p, size_t, dstr *d)
+    void (*destroy)(codec *c)
+  ctypedef struct codec:
+    codec_ops *ops
+  enum:
+    _CDCF_LOWERC "CDCF_LOWERC"
+    _CDCF_IGNCASE "CDCF_IGNCASE"
+    _CDCF_NOEQPAD "CDCF_NOEQPAD"
+    _CDCF_IGNEQPAD "CDCF_IGNEQPAD"
+    _CDCF_IGNEQMID "CDCF_IGNEQMID"
+    _CDCF_IGNZPAD "CDCF_IGNZPAD"
+    _CDCF_IGNNEWL "CDCF_IGNNEWL"
+    _CDCF_IGNSPC "CDCF_IGNSPC"
+    _CDCF_IGNINVCH "CDCF_IGNINVCH"
+    _CDCF_IGNJUNK "CDCF_IGNJUNK"
+  enum:
+    _CDCERR_OK "CDCERR_OK"
+    _CDCERR_INVCH "CDCERR_INVCH"
+    _CDCERR_INVEQPAD "CDCERR_INVEQPAD"
+    _CDCERR_INVZPAD "CDCERR_INVZPAD"
+  char *_codec_strerror "codec_strerror" (int err)
+
+###--------------------------------------------------------------------------
 ### Form-urlencoding functions.
 
 cdef extern from 'mLib/url.h':
@@ -406,8 +439,8 @@ cdef extern from 'mLib/fwatch.h':
     pass
   void fwatch_init(fwatch *f, char *name)
   void fwatch_initfd(fwatch *f, int fd)
-  int fwatch_update(fwatch *f, char *name)
-  int fwatch_updatefd(fwatch *f, int fd)
+  bint fwatch_update(fwatch *f, char *name)
+  bint fwatch_updatefd(fwatch *f, int fd)
 
 ###--------------------------------------------------------------------------
 ### File descriptor hacking.
@@ -439,8 +472,13 @@ cdef extern from 'mLib/daemonize.h':
 
 cdef extern from 'grim.h':
   int PSIZEOF(void *x)
+  object BIN_FROMSTRLEN(const void *p, Py_ssize_t sz)
+  void *BIN_PTR(object bin)
+  void BIN_SETLEN(object bin, Py_ssize_t sz)
+  int TEXT_CHECK(object p)
+  char *TEXT_PTR(str s)
   void TEXT_PTRLEN(str s, const char **p, Py_ssize_t *sz)
-  object TEXT_FROMSTRLEN(const char *p, Py_ssize_t sz)
-  ctypedef void *cvp
+  str TEXT_FROMSTR(const char *p)
+  str TEXT_FROMSTRLEN(const char *p, Py_ssize_t sz)
 
 ###----- That's all, folks --------------------------------------------------
index 71135e6..124ef23 100644 (file)
@@ -48,16 +48,16 @@ def fdsend(sock, file, buffer):
   fdsend(SOCK, FILE, BUFFER) -> RC:
     send FILE over Unix-domain socket SOCK, along with BUFFER
   """
-  cdef void *p
+  cdef const void *p
   cdef Py_ssize_t len
   cdef int rc
-  PyObject_AsReadBuffer(buffer, <cvp *>&p, &len)
+  PyObject_AsReadBuffer(buffer, &p, &len)
   rc = fdpass_send(_getfd(sock), _getfd(file), p, len)
   if rc < 0:
     _oserror()
   return rc
 
-def fdrecv(sock, unsigned size):
+def fdrecv(sock, size_t size):
   """
   fdrecv(SOCK, SIZE) -> FD, BUFFER
     receive file FD and BUFFER of length up to SIZE from Unix-domain SOCK
@@ -67,13 +67,11 @@ def fdrecv(sock, unsigned size):
   cdef Py_ssize_t len
   cdef PyObject *obj
   cdef int fd
-  buf = PyString_FromStringAndSize(NULL, size)
-  p = PyString_AS_STRING(buf)
-  len = fdpass_recv(_getfd(sock), &fd, p, size)
+  buf = BIN_FROMSTRLEN(NULL, size)
+  len = fdpass_recv(_getfd(sock), &fd, BIN_PTR(buf), size)
   if len < 0:
     _oserror()
-  obj = <PyObject *>buf
-  _PyString_Resize(&obj, len)
-  return fd, <object>obj
+  BIN_SETLEN(buf, len)
+  return fd, buf
 
 ###----- That's all, folks --------------------------------------------------
index eedb872..086526b 100644 (file)
@@ -30,24 +30,38 @@ cdef class FWatch:
   FILE may be a string, file descriptor, or an object with a `fileno' method.
   """
   cdef fwatch fw
-  cdef public file
-  def __cinit__(me, file):
-    me._init(file)
-  def __init__(me, file):
-    me._init(file)
-  cdef _init(me, file):
-    if isinstance(file, str):
-      fwatch_init(&me.fw, file)
+  cdef fobj
+  cdef const char *fname
+  cdef int fd
+  def __cinit__(me):
+    me.fname = NULL
+    me.fd = -1
+  cdef _setfile(me, object file):
+    if TEXT_CHECK(file):
+      me.fname = TEXT_PTR(file)
+      me.fd = -1
     else:
-      fwatch_initfd(&me.fw, _getfd(file))
-    me.file = file
+      me.fd = _getfd(file)
+      me.fname = NULL
+    me.fobj = file
+  def __init__(me, object file):
+    me._setfile(file)
+    if me.fname:
+      fwatch_init(&me.fw, me.fname)
+    else:
+      fwatch_initfd(&me.fw, me.fd)
+  @property
+  def file(me):
+    return me.fobj
+  @file.setter
+  def file(me, file):
+    me._setfile(file)
   def update(me):
     """FW.update() -> RC: nonzero if the file has changed state"""
     cdef int rc
-    if isinstance(me.file, str):
-      rc = fwatch_update(&me.fw, me.file)
+    if me.fname:
+      return fwatch_update(&me.fw, me.fname)
     else:
-      rc = fwatch_updatefd(&me.fw, _getfd(me.file))
-    return rc
+      return fwatch_updatefd(&me.fw, me.fd)
 
 ###----- That's all, folks --------------------------------------------------
diff --git a/grim.h b/grim.h
index 016e2ef..4495ee5 100644 (file)
--- a/grim.h
+++ b/grim.h
@@ -80,6 +80,7 @@ typedef const void *cvp;
 #define BIN_PTR(obj) PyBytes_AS_STRING(obj)
 #define BIN_LEN(obj) PyBytes_GET_SIZE(obj)
 #define BIN_FROMSTR(str) PyBytes_FromString(str)
+#define BIN_FROMSTRLEN(str, len) PyBytes_FromStringAndSize(str, len)
 #define BIN_FORMAT PyBytes_FromFormat
 #define BIN_SETLEN(obj, len) do Py_SIZE(obj) = (len); while (0)
 
@@ -131,6 +132,7 @@ typedef const void *cvp;
 #define BIN_PTR(obj) PyString_AS_STRING(obj)
 #define BIN_LEN(obj) PyString_GET_SIZE(obj)
 #define BIN_FROMSTR(str) PyString_FromString(str)
+#define BIN_FROMSTRLEN(str, len) PyString_FromStringAndSize(str, len)
 #define BIN_FORMAT PyString_FromFormat
 #define BIN_SETLEN(obj, len) do Py_SIZE(obj) = (len); while (0)
 
index bae8e30..e9e2d7e 100644 (file)
--- a/mLib.pyx
+++ b/mLib.pyx
@@ -51,22 +51,22 @@ include 'atom.pyx'
 include 'assoc.pyx'
 
 ## String utilities.
-#include 'str.pyx'
+include 'str.pyx'
 
 ## Encodings.
-#include 'codec.pyx'
-#include 'base64.pyx'
-#include 'base32.pyx'
-#include 'hex.pyx'
-#include 'url.pyx'
+include 'codec.pyx'
+include 'base64.pyx'
+include 'base32.pyx'
+include 'hex.pyx'
+include 'url.pyx'
 
 ## Error reporting.
-#include 'report.pyx'
+include 'report.pyx'
 
 ## File utilities.
-#include 'fwatch.pyx'
-#include 'fdutils.pyx'
-#include 'mdup.pyx'
+include 'fwatch.pyx'
+include 'fdutils.pyx'
+include 'mdup.pyx'
 
 ## Other useful stuff.
 #include 'stuff.pyx'
index 7256211..12e727c 100644 (file)
--- a/mdup.pyx
+++ b/mdup.pyx
@@ -32,20 +32,23 @@ def mdup(v):
     with CUR reflecting the new file descriptors even on error.  Returns the
     same LIST on success.
   """
-  cdef mdup_fd *vv
+  cdef mdup_fd *vv = NULL
   cdef size_t n
   cdef int i
   cdef int rc
 
   n = len(v)
   vv = <mdup_fd *>xmalloc(n * PSIZEOF(vv))
-  for 0 <= i < n:
-    vv[i].cur, vv[i].want = v[i]
-  rc = _mdup(vv, n)
-  for 0 <= i < n:
-    v[i] = vv[i].cur, vv[i].want
-  if rc < 0:
-    _oserror()
+  try:
+    for 0 <= i < n:
+      vv[i].cur, vv[i].want = v[i]
+    rc = _mdup(vv, n)
+    for 0 <= i < n:
+      v[i] = vv[i].cur, vv[i].want
+    if rc < 0:
+      _oserror()
+  finally:
+    xfree(vv)
   return v
 
 ###----- That's all, folks --------------------------------------------------
index 9d19ed2..e42a86d 100644 (file)
 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
 quis = '<UNNAMED>'
-cdef char *_progstring
-_progstring = NULL
+cdef char *_progstring = NULL
 
-def ego(char *prog):
+def ego(object prog):
   """ego(PROG): set program name"""
   global quis, _progstring
   if _progstring:
     xfree(_progstring)
-  _progstring = xstrdup(prog)
+  _progstring = xstrdup(TEXT_PTR(prog))
   _ego(_progstring)
-  quis = _quis()
+  quis = TEXT_FROMSTR(_quis())
 
-def moan(char *msg):
+def moan(object msg):
   """moan(MSG): report a warning"""
-  _moan('%s', msg)
-def die(char *msg, rc = 126):
+  _moan('%s', TEXT_PTR(msg))
+def die(object msg, rc = 126):
   """die(MSG, [rc = 126]): report a fatal error and exit"""
-  _moan('%s', msg)
-  raise SystemExit, rc
+  _moan('%s', TEXT_PTR(msg))
+  raise SystemExit(rc)
 
 ###----- That's all, folks --------------------------------------------------
index 1ec65dc..bc0c2d5 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -22,18 +22,20 @@ mLib = DC.Extension('mLib', [pyxc,
 ## The `cythonize' function generates the C sources immediately, so we have
 ## to generate its inputs even earlier.
 genfiles = [
-#            MS.Derive('base64.pyx', 'codec.pyx.in',
-#                      {'CLASS': 'Base64', 'PREFIX': 'base64'}),
-#            MS.Derive('base32.pyx', 'codec.pyx.in',
-#                      {'CLASS': 'Base32', 'PREFIX': 'base32'}),
-#            MS.Derive('hex.pyx', 'codec.pyx.in',
-#                      {'CLASS': 'Hex', 'PREFIX': 'hex'})
+            MS.Derive('base64.pyx', 'codec.pyx.in',
+                      {'CLASS': 'Base64', 'PREFIX': 'base64'}),
+            MS.Derive('base32.pyx', 'codec.pyx.in',
+                      {'CLASS': 'Base32', 'PREFIX': 'base32'}),
+            MS.Derive('hex.pyx', 'codec.pyx.in',
+                      {'CLASS': 'Hex', 'PREFIX': 'hex'})
 ]
 for g in genfiles: g.gen()
 
 ## Generate the main C code.
 if OS.path.exists(pyxc): OS.rename(pyxc, "mLib.c")
-CB.cythonize("mLib.pyx", compile_time_env = dict(PYVERSION = PYVERSION))
+CB.cythonize("mLib.pyx",
+             compile_time_env = dict(PYVERSION = PYVERSION),
+             compiler_directives = dict(c_string_encoding = 'utf8'))
 OS.rename("mLib.c", pyxc)
 
 MS.setup(name = 'mLib-python',
diff --git a/str.pyx b/str.pyx
index f1a2ee5..97dfa6a 100644 (file)
--- a/str.pyx
+++ b/str.pyx
 ### along with mLib/Python; if not, write to the Free Software Foundation,
 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
-def word(char *p, quotep = False):
+def word(object p, object quotep = False):
   """word(STR, [quotep = False]) -> WORD, REST"""
-  cdef unsigned f
-  cdef char *op
-  cdef char *pp
+  cdef unsigned f = 0
+  cdef char *op = xstrdup(TEXT_PTR(p))
+  cdef char *pp = op
   cdef char *q
   cdef object w
   cdef object r
 
-  f = 0
   if quotep:
-    f = f | STRF_QUOTE
-  pp = op = xstrdup(p)
+    f |= STRF_QUOTE
   q = str_qword(&pp, f)
   if q is NULL:
     w = None
   else:
-    w = q
+    w = <str>q
   if pp is NULL:
     r = ''
   else:
-    r = pp
+    r = <str>pp
   xfree(op)
   return w, r
 
-def split(char *p, int n = -1, quotep = False):
+def split(object p, int n = -1, quotep = False):
   """split(STR, [n = -1], [quotep = False]) -> WORDS, REST"""
-  cdef unsigned f
-  cdef char *op
-  cdef char *pp
+  cdef unsigned f = 0
+  cdef char *op = xstrdup(TEXT_PTR(p))
+  cdef char *pp = op
   cdef char *q
-  cdef object l
+  cdef object l = []
   cdef object r
 
-  f = 0
   if quotep:
-    f = f | STRF_QUOTE
-  l = []
-  op = pp = xstrdup(p)
+    f |= STRF_QUOTE
   while n != 0:
     q = str_qword(&pp, f)
     if q is NULL:
       break
-    l.append(q)
+    l.append(<str>q)
     if n > 0:
-      n = n - 1
+      n -= 1
   if pp is NULL:
     r = ''
   else:
-    r = pp
+    r = <str>pp
   xfree(op)
   return l, r
 
-def match(char *p, char *s, prefixp = False):
+def match(object p, object s, prefixp = False):
   """match(PAT, STR, [prefixp = False]) -> BOOL"""
-  cdef unsigned f
+  cdef unsigned f = 0
 
-  f = 0
   if prefixp:
-    f = f | STRF_PREFIX
-  return _tobool(str_matchx(p, s, f))
+    f |= STRF_PREFIX
+  return str_matchx(TEXT_PTR(p), TEXT_PTR(s), f)
 
-def sanitize(char *p, int n = -1):
+def sanitize(object s, int n = -1):
   """sanitize(STR, [n = -1]) -> STR"""
+  cdef Py_ssize_t nn
+  cdef const char *ss = _text_strlen(s, &nn)
   cdef char *buf
   cdef object d
 
   if n < 0:
-    n = strlen(p)
+    n = nn
   buf = <char *>xmalloc(n + 1)
-  str_sanitize(buf, p, n + 1)
-  d = buf
+  str_sanitize(buf, ss, n + 1)
+  d = <str>buf
   xfree(buf)
   return d
 
-def versioncmp(char *va, char *vb):
+def versioncmp(object va, object vb):
   """versioncmp(V0, V1) -> -1 | 0 | +1"""
-  return _versioncmp(va, vb)
+  return _versioncmp(TEXT_PTR(va), TEXT_PTR(vb))
 
 ###----- That's all, folks --------------------------------------------------
diff --git a/test.py b/test.py
index 33f781e..9cd09e2 100644 (file)
--- a/test.py
+++ b/test.py
@@ -1,6 +1,10 @@
 #! /usr/bin/python
 ### -*- coding: utf-8 -*-
 
+import contextlib as CTX
+import fcntl as FC
+import os as OS
+import socket as S
 import sys as SYS
 import mLib as M
 
@@ -19,10 +23,12 @@ def must_equal(x, y):
 print(";; test begins (Python %s)" % SYS.version)
 
 ## crc32
+print(";; test crc32...")
 must_equal(M.crc32(_bin("abc")), 0x352441c2)
 must_equal(M.CRC32().chunk(_bin("a")).chunk(_bin("bc")).done(), 0x352441c2)
 
 ## unihash
+print(";; test unihash...")
 assert M.Unihash().key is None
 must_equal(M.Unihash.hash(_bin("abc")), 0xbf71f6a2)
 must_equal(M.Unihash().chunk(_bin("a")).chunk(_bin("bc")).done(), 0xbf71f6a2)
@@ -35,6 +41,7 @@ M.setglobalkey(0x8498a262)
 must_equal(M.Unihash.hash(_bin("abc")), 0xecd1e2a2)
 
 ## atom
+print(";; test atom...")
 foo = M.Atom("foo")
 bar = M.Atom("bär")
 assert foo != bar
@@ -63,8 +70,176 @@ def test_mapping(mapcls, keya, keyz):
   must_equal(list(tab.keys()), [keya])
   must_equal(list(tab.values()), [42])
   must_equal(list(tab.items()), [(keya, 42)])
+print(";; test assoc...")
 test_mapping(M.AssocTable, foo, bar)
+print(";; test sym...")
 test_mapping(M.SymTable, 'foo', 'bar')
 
+## str
+print(";; test str...")
+must_equal(M.word(" foo  bar baz"), ("foo", "bar baz"))
+must_equal(M.word("  'foo \\' bar'  baz ", quotep = True),
+           ("foo ' bar", "baz "))
+must_equal(M.split(' one "two \\" three" four five six', 3, quotep = True),
+           (["one", 'two " three', "four"], "five six"))
+assert M.match("foo*bar", "food is served at the bar")
+must_equal(M.sanitize("some awful string!", 12), "some_awful_s")
+assert M.versioncmp("1.2.5~pre99", "1.2.5") < 0
+assert M.versioncmp("1.2.5pre99", "1.2.5") > 0
+
+## codec
+ref = _bin("just >some ?string to encode")
+def test_codecclass(enccls, deccls, encref):
+  enc = enccls("!", 8, M.CDCF_LOWERC)
+  e = enc.encode(ref[:10])
+  e += enc.encode(ref[10:])
+  e += enc.done()
+  must_equal(e, encref)
+  encref = encref.replace("!", "")
+  dec = deccls(M.CDCF_IGNCASE)
+  d = dec.decode(encref[:15])
+  d += dec.decode(encref[15:])
+  d += dec.done()
+  must_equal(d, ref)
+  try: deccls().decode("???")
+  except M.CodecError as e:
+    if e.err == M.CDCERR_INVCH: pass
+    else: raise
+  else: assert False
+def test_oldcodec(enccls, deccls, encref):
+  enc = enccls()
+  enc.indent = "!"
+  enc.maxline = 8
+  e = enc.encode(ref[:10])
+  e += enc.encode(ref[10:])
+  e += enc.done()
+  must_equal(e, encref)
+  encref = encref.replace("!", "")
+  dec = deccls()
+  d = dec.decode(encref[:15])
+  d += dec.decode(encref[15:])
+  d += dec.done()
+  must_equal(d, ref)
+
+print(";; test base64...")
+test_codecclass(M.Base64Encoder, M.Base64Decoder,
+                "anVzdCA+!c29tZSA/!c3RyaW5n!IHRvIGVu!Y29kZQ==")
+test_codecclass(M.File64Encoder, M.File64Decoder,
+                "anVzdCA+!c29tZSA%!c3RyaW5n!IHRvIGVu!Y29kZQ==")
+test_codecclass(M.Base64URLEncoder, M.Base64URLDecoder,
+                "anVzdCA-!c29tZSA_!c3RyaW5n!IHRvIGVu!Y29kZQ==")
+test_oldcodec(M.Base64Encode, M.Base64Decode,
+              "anVzdCA+!c29tZSA/!c3RyaW5n!IHRvIGVu!Y29kZQ==")
+
+print(";; test base32...")
+test_codecclass(M.Base32Encoder, M.Base32Decoder,
+                "nj2xg5ba!hzzw63lf!ea7xg5ds!nfxgoidu!n4qgk3td!n5sgk===")
+test_codecclass(M.Base32HexEncoder, M.Base32HexDecoder,
+                "d9qn6t10!7ppmurb5!40vn6t3i!d5n6e83k!dsg6arj3!dti6a===")
+test_oldcodec(M.Base32Encode, M.Base32Decode,
+              "NJ2XG5BA!HZZW63LF!EA7XG5DS!NFXGOIDU!N4QGK3TD!N5SGK===")
+
+print(";; test hex...")
+test_codecclass(M.HexEncoder, M.HexDecoder,
+                "6a757374!203e736f!6d65203f!73747269!6e672074!6f20656e!636f6465")
+test_oldcodec(M.HexEncode, M.HexDecode,
+              "6a757374!203e736f!6d65203f!73747269!6e672074!6f20656e!636f6465")
+
+## url
+print(";; test url...")
+uenc = M.URLEncode()
+uenc.encode("one", "some/string!")
+uenc.strictp = True
+uenc.encode("two", "some other/string!")
+uenc.laxp = True
+uenc.encode("three", "another!string")
+r = uenc.result
+must_equal(r, "one=some/string%21&two=some+other%2fstring%21&three=another!string")
+must_equal(list(M.URLDecode(r)),
+           [("one", "some/string!"),
+            ("two", "some other/string!"),
+            ("three", "another!string")])
+
+## report
+@CTX.contextmanager
+def stderr_test(want_out):
+  pin, pout = OS.pipe()
+  fin = OS.fdopen(pin, 'r')
+  olderr = OS.dup(2)
+  OS.dup2(pout, 2)
+  OS.close(pout)
+  try:
+    yield
+    OS.close(2)
+    out = fin.read()
+  finally:
+    fin.close()
+    OS.dup2(olderr, 2)
+  must_equal(out, want_out)
+print(";; test report...")
+M.ego("my/path/name")
+must_equal(M.quis, "name")
+with stderr_test("name: test moaning\n"):
+  M.moan("test moaning")
+with stderr_test("name: test death\n"):
+  try: M.die("test death")
+  except SystemExit as e: assert e.code == 126
+  else: assert False
+
+## fwatch
+print(";; test fwatch...")
+fd = OS.open(",test-stamp", OS.O_WRONLY | OS.O_CREAT, 0o666)
+fw = M.FWatch(fd)
+assert not fw.update()
+OS.write(fd, _bin("stuff\n"))
+assert fw.update()
+assert fw.file is fd
+fd2 = OS.open(",test-stamp.new", OS.O_WRONLY | OS.O_CREAT, 0o666)
+OS.rename(",test-stamp.new", ",test-stamp")
+assert not fw.update()
+fw.file = ",test-stamp"
+assert fw.update()
+OS.close(fd)
+OS.close(fd2)
+OS.unlink(",test-stamp")
+
+## fdflags
+print(";; test fdflags...")
+pin, pout = OS.pipe()
+ofl = FC.fcntl(pin, FC.F_GETFL)
+ofd = FC.fcntl(pout, FC.F_GETFD)
+fout = OS.fdopen(pout, 'wb')
+M.fdflags(pin, fbic = OS.O_NONBLOCK, fxor = OS.O_NONBLOCK)
+assert FC.fcntl(pin, FC.F_GETFL) == ofl | OS.O_NONBLOCK
+M.fdflags(pin, fbic = OS.O_NONBLOCK, fxor = 0)
+assert FC.fcntl(pin, FC.F_GETFL) == ofl&~OS.O_NONBLOCK
+M.fdflags(fout, fdbic = FC.FD_CLOEXEC, fdxor = FC.FD_CLOEXEC)
+assert FC.fcntl(pout, FC.F_GETFD) == ofd | FC.FD_CLOEXEC
+M.fdflags(fout, fdbic = FC.FD_CLOEXEC, fdxor = 0)
+assert FC.fcntl(pout, FC.F_GETFD) == ofd&~FC.FD_CLOEXEC
+OS.close(pin)
+fout.close()
+
+## fdpass
+print(";; test fdpass...")
+pin, pout = OS.pipe()
+fin = OS.fdopen(pin, 'r')
+OS.write(pout, _bin("Hello, world!"))
+OS.close(pout)
+sk0, sk1 = S.socketpair(S.AF_UNIX, S.SOCK_STREAM)
+M.fdsend(sk0, fin, _bin("Have a pipe!"))
+fin.close()
+sk0.close()
+fd, msg = M.fdrecv(sk1, 16)
+sk1.close()
+must_equal(msg, _bin("Have a pipe!"))
+data = OS.read(fd, 16)
+OS.close(fd)
+must_equal(data, _bin("Hello, world!"))
+
+## mdup
+## print(";; test mdup...")
+
+
 ## Done!
 print(";; test completed OK")
diff --git a/url.pyx b/url.pyx
index 72467f1..b620294 100644 (file)
--- a/url.pyx
+++ b/url.pyx
@@ -31,53 +31,56 @@ cdef class URLEncode:
   def __cinit__(me, *hunoz, **hukairz):
     url_initenc(&me.ctx)
     DCREATE(&me.d)
+  def __dealloc__(me):
+    dstr_destroy(&me.d)
   def __init__(me, strictp = False, laxp = False, semip = False):
     cdef unsigned f
     f = 0
     if strictp:
-      f = f | URLF_STRICT
+      f |= URLF_STRICT
     if laxp:
-      f = f | URLF_LAX
+      f |= URLF_LAX
     if semip:
-      f = f | URLF_SEMI
+      f |= URLF_SEMI
     me.ctx.f = f
-  def encode(me, char *name, char *value):
+  def encode(me, object name, object value):
     """UE.encode(NAME, VALUE): encode a key/value pair"""
-    url_enc(&me.ctx, &me.d, name, value)
+    url_enc(&me.ctx, &me.d, TEXT_PTR(name), TEXT_PTR(value))
     return me
-  property result:
+  @property
+  def result(me):
     """UE.result -> STR: the encoded string"""
-    def __get__(me):
-      return PyString_FromStringAndSize(me.d.buf, me.d.len)
-  property strictp:
+    return TEXT_FROMSTRLEN(me.d.buf, me.d.len)
+  @property
+  def strictp(me):
     """UE.strictp -> BOOL: strictly escape non-alphanumerics?"""
-    def __get__(me):
-      return _tobool(me.ctx.f & URLF_STRICT)
-    def __set__(me, val):
-      if val:
-        me.ctx.f = me.ctx.f | URLF_STRICT
-      else:
-        me.ctx.f = me.ctx.f & ~URLF_STRICT
-  property laxp:
+    return <bint>(me.ctx.f&URLF_STRICT)
+  @strictp.setter
+  def strictp(me, val):
+    if val:
+      me.ctx.f |= URLF_STRICT
+    else:
+      me.ctx.f &= ~URLF_STRICT
+  @property
+  def laxp(me):
     """UE.laxp -> BOOL: only escape obviously unsafe characters?"""
-    def __get__(me):
-      return _tobool(me.ctx.f & URLF_LAX)
-    def __set__(me, val):
-      if val:
-        me.ctx.f = me.ctx.f | URLF_LAX
-      else:
-        me.ctx.f = me.ctx.f & ~URLF_LAX
-  property semip:
+    return <bint>(me.ctx.f&URLF_LAX)
+  @laxp.setter
+  def laxp(me, val):
+    if val:
+      me.ctx.f |= URLF_LAX
+    else:
+      me.ctx.f &= ~URLF_LAX
+  @property
+  def semip(me):
     """UE.semip -> BOOL: separate key/value pairs with semicolons?"""
-    def __get__(me):
-      return _tobool(me.ctx.f & URLF_SEMI)
-    def __set__(me, val):
-      if val:
-        me.ctx.f = me.ctx.f | URLF_SEMI
-      else:
-        me.ctx.f = me.ctx.f & ~URLF_SEMI
-  def __del__(me):
-    dstr_destroy(&me.d)
+    return <bint>(me.ctx.f&URLF_SEMI)
+  @semip.setter
+  def semip(me, val):
+    if val:
+      me.ctx.f |= URLF_SEMI
+    else:
+      me.ctx.f &= ~URLF_SEMI
 
 cdef class URLDecode:
   """URLDecode(STR, [semip = False]): iterator over (KEY, VALUE) pairs"""
@@ -85,15 +88,17 @@ cdef class URLDecode:
   cdef char *p
 
   def __cinit__(me, *hunoz, **hukairz):
-    me.p = xstrdup('')
+    me.p = NULL
     url_initdec(&me.ctx, me.p)
-  def __init__(me, char *string, semip = False):
+  def __dealloc__(me):
+    xfree(me.p)
+  def __init__(me, object string, semip = False):
     cdef unsigned f
     f = 0
     if semip:
-      f = f | URLF_SEMI
+      f |= URLF_SEMI
     xfree(me.p)
-    me.p = xstrdup(string)
+    me.p = xstrdup(TEXT_PTR(string))
     me.ctx.p = me.p
     me.ctx.f = f
   def __iter__(me):
@@ -108,23 +113,22 @@ cdef class URLDecode:
     DCREATE(&v)
     anyp = url_dec(&me.ctx, &n, &v)
     if anyp:
-      nn = PyString_FromStringAndSize(n.buf, n.len)
-      vv = PyString_FromStringAndSize(v.buf, v.len)
+      nn = TEXT_FROMSTRLEN(n.buf, n.len)
+      vv = TEXT_FROMSTRLEN(v.buf, v.len)
     dstr_destroy(&n)
     dstr_destroy(&v)
     if not anyp:
       raise StopIteration
     return nn, vv
-  property semip:
-    """UD.semip -> BOOL: key/value pairs separated with semicolons?"""
-    def __get__(me):
-      return _tobool(me.ctx.f & URLF_SEMI)
-    def __set__(me, val):
-      if val:
-        me.ctx.f = me.ctx.f | URLF_SEMI
-      else:
-        me.ctx.f = me.ctx.f & ~URLF_SEMI
-  def __del__(me):
-    xfree(me.p)
+  @property
+  def semip(me):
+    """UE.semip -> BOOL: separate key/value pairs with semicolons?"""
+    return <bint>(me.ctx.f&URLF_SEMI)
+  @semip.setter
+  def semip(me, val):
+    if val:
+      me.ctx.f |= URLF_SEMI
+    else:
+      me.ctx.f &= ~URLF_SEMI
 
 ###----- That's all, folks --------------------------------------------------
index 17fa416..eab722f 100644 (file)
--- a/utils.pyx
+++ b/utils.pyx
 ### along with mLib/Python; if not, write to the Free Software Foundation,
 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
-#cdef object _oserror():
-#  raise OSError, (errno, strerror(errno))
+cdef const char *_text_strlen(object s, Py_ssize_t *sz) except NULL:
+  cdef const char *p
+  TEXT_PTRLEN(s, &p, sz)
+  return p
 
-#cdef int _getfd(object fdobj):
-#  try:
-#    fd = int(fdobj)
-#  except TypeError:
-#    ##PyErr_Clear()
-#    fd = fdobj.fileno()
-#  return fd
+cdef object _oserror():
+  raise OSError(errno, strerror(errno))
+
+cdef int _getfd(object fdobj):
+  try:
+    fd = int(fdobj)
+  except TypeError:
+    fd = fdobj.fileno()
+  return fd
 
 #cdef object _checkcallable(object f, object what):
 #  if f is not None and not callable(f):