stringmap 0.1.0

An immutable string map with pointers into frozen memory that can be shared between C, Ruby, Python and Rust.
import collections
import itertools

from cffi import FFI


class StringMap(collections.Mapping):
    """An immutable string-string map that shares easily with C .

    >>> from stringmap import StringMap
    ... m = StringMap(a='a', bb='b', ccc='c')

    >>> m['bb']
    'b'

    >>> len(m)
    3

    >>> m.keys()
    ['a', 'bb', 'ccc']

    >>> for k in m:
    ...     print k
    a
    bb
    ccc
    """
    def __init__(self, *stuff, **values):
        ptrs = ((ffi.new('const char[]', k), ffi.new('const char[]', v))
                for (k, v) in StringMap._unstuff(*stuff, **values))
        ptrs = zip(*ptrs)
        keys, vals = [ffi.new('const char*[]', list(arr)) for arr in ptrs]
        count = len(keys)
        ptr = lib.stringmap_new(count, keys, vals)
        self._underyling = ffi.gc(ptr, lib.stringmap_free)

    def keys(self):
        return list(self)

    def get(self, key):
        ptr = lib.stringmap_get(self._underyling, ffi.new('const char[]', key))
        if ptr != ffi.NULL:
            return ffi.string(ptr)

    def __len__(self):
        return lib.stringmap_len(self._underyling)

    def __iter__(self):
        n = 0
        ptr = lib.stringmap_keys(self._underyling)
        while ptr[n] != ffi.NULL:
            yield ffi.string(ptr[n])
            n += 1

    def __getitem__(self, key):
        item = self.get(key)
        if item is not None:
            return item
        raise KeyError(key)

    def items(self):
        return list(self.iter_items())

    def iter_items(self):
        return ((k, self[k]) for k in self)

    @staticmethod
    def _unstuff(*stuff, **values):
        acc = []
        for stuff in stuff:
            acc += [_unstuff_it(stuff)]
        acc += [values.items()]
        return list(itertools.chain(*acc))


def _unstuff_it(stuff):
    if isinstance(stuff, collections.Mapping):
        return stuff.items()
    if isinstance(stuff, collections.Iterable):
        return stuff
    raise ValueError('Please pass only sequences of pairs or actual mappings.')


ffi = FFI()
ffi.cdef("""
typedef struct stringmap_t stringmap_t;

stringmap_t* stringmap_new(size_t count, char const* const* keys, char const* const* values);

char const* const* stringmap_keys(stringmap_t const* m);

char const* stringmap_get(stringmap_t const* m, char const* key);

size_t stringmap_len(stringmap_t const* m);

void stringmap_free(stringmap_t* m);
""")
lib = ffi.dlopen('target/debug/libstringmap.dylib')