diff options
Diffstat (limited to 'python')
| -rw-r--r-- | python/setup.py | 37 | ||||
| -rw-r--r-- | python/src/uclmodule.c | 156 | ||||
| -rwxr-xr-x | python/test.sh | 6 | ||||
| -rwxr-xr-x | python/test_uclmodule.py | 100 |
4 files changed, 299 insertions, 0 deletions
diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 000000000000..b2b8981705fb --- /dev/null +++ b/python/setup.py @@ -0,0 +1,37 @@ +import distutils.ccompiler +import distutils.sysconfig +from distutils.core import setup, Extension +import os + + +compiler = distutils.ccompiler.new_compiler() +search_paths=[os.path.expanduser('~/{}'), '/opt/local/{}', '/usr/local/{}', '/usr/{}'] +lib_paths = [ a.format("lib") for a in search_paths] +inc_paths = [ a.format("include") for a in search_paths] + +uclmodule = Extension('ucl', + include_dirs = inc_paths, + library_dirs = lib_paths, + libraries = ['ucl'], + sources = ['src/uclmodule.c'], + runtime_library_dirs = lib_paths, + language='c') + +setup(name='ucl', + version='1.0', + description='ucl parser and emmitter', + ext_modules = [uclmodule], + author="Eitan Adler", + author_email="lists@eitanadler.com", + url="https://github.com/vstakhov/libucl/", + license="MIT", + classifiers=["Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: DFSG approved", + "License :: OSI Approved :: MIT License", + "Programming Language :: C", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Software Development :: Libraries", + ] + ) diff --git a/python/src/uclmodule.c b/python/src/uclmodule.c new file mode 100644 index 000000000000..d63b73aad9d8 --- /dev/null +++ b/python/src/uclmodule.c @@ -0,0 +1,156 @@ +// Attempts to load a UCL structure from a string +#include <ucl.h> +#include <Python.h> + +static PyObject* +_basic_ucl_type(ucl_object_t const * const obj) { + if (obj->type == UCL_INT) { + return Py_BuildValue("L", (long long)ucl_object_toint (obj)); + } + else if (obj->type == UCL_FLOAT) { + return Py_BuildValue("d", ucl_object_todouble (obj)); + } + else if (obj->type == UCL_STRING) { + return Py_BuildValue("s", ucl_object_tostring (obj)); + } + else if (obj->type == UCL_BOOLEAN) { + // maybe used 'p' here? + return Py_BuildValue("s", ucl_object_tostring_forced (obj)); + } + else if (obj->type == UCL_TIME) { + return Py_BuildValue("d", ucl_object_todouble (obj)); + } + return NULL; +} + +static PyObject* +_iterate_valid_ucl(ucl_object_t const * obj) { + const ucl_object_t *tmp; + ucl_object_iter_t it = NULL; + + tmp = obj; + + while ((obj = ucl_iterate_object (tmp, &it, false))) { + + PyObject* val; + + val = _basic_ucl_type(obj); + if (!val) { + PyObject* key = NULL; + if (obj->key != NULL) { + key = Py_BuildValue("s", ucl_object_key(obj)); + } + + PyObject* ret; + ret = PyDict_New(); + if (obj->type == UCL_OBJECT) { + val = PyDict_New(); + const ucl_object_t *cur; + ucl_object_iter_t it_obj = NULL; + while ((cur = ucl_iterate_object (obj, &it_obj, true))) { + PyObject* keyobj = Py_BuildValue("s",ucl_object_key(cur)); + PyDict_SetItem(val, keyobj, _iterate_valid_ucl(cur)); + } + } + else if (obj->type == UCL_ARRAY) { + val = PyList_New(0); + const ucl_object_t *cur; + ucl_object_iter_t it_obj = NULL; + while ((cur = ucl_iterate_object (obj, &it_obj, true))) { + PyList_Append(val, _iterate_valid_ucl(cur)); + } + } + else if (obj->type == UCL_USERDATA) { + // XXX: this should be + // PyBytes_FromStringAndSize; where is the + // length from? + val = PyBytes_FromString(obj->value.ud); + } + } + return val; + } + + PyErr_SetString(PyExc_SystemError, "unhandled type"); + return NULL; +} + +static PyObject* +_internal_load_ucl(char* uclstr) { + PyObject* ret; + + struct ucl_parser *parser = ucl_parser_new (UCL_PARSER_NO_TIME); + + bool r = ucl_parser_add_string(parser, uclstr, 0); + if (r) { + if (ucl_parser_get_error (parser)) { + PyErr_SetString(PyExc_ValueError, ucl_parser_get_error(parser)); + ucl_parser_free(parser); + ret = NULL; + goto return_with_parser; + } else { + ucl_object_t* uclobj = ucl_parser_get_object(parser); + ret = _iterate_valid_ucl(uclobj); + ucl_object_unref(uclobj); + goto return_with_parser; + } + + } else { + PyErr_SetString(PyExc_ValueError, ucl_parser_get_error (parser)); + ret = NULL; + goto return_with_parser; + } + +return_with_parser: + ucl_parser_free(parser); + return ret; +} + +static PyObject* +ucl_load(PyObject *self, PyObject *args) { + char* uclstr; + if (PyArg_ParseTuple(args, "z", &uclstr)) { + if (!uclstr) { + Py_RETURN_NONE; + } + return _internal_load_ucl(uclstr); + } + return NULL; +} + +static PyObject* +ucl_validate(PyObject *self, PyObject *args) { + char *uclstr, *schema; + if (PyArg_ParseTuple(args, "zz", &uclstr, &schema)) { + if (!uclstr || !schema) { + Py_RETURN_NONE; + } + PyErr_SetString(PyExc_NotImplementedError, "schema validation is not yet supported"); + return NULL; + } + return NULL; +} + +static PyMethodDef uclMethods[] = { + {"load", ucl_load, METH_VARARGS, "Load UCL from stream"}, + {"validate", ucl_validate, METH_VARARGS, "Validate ucl stream against schema"}, + {NULL, NULL, 0, NULL} +}; + +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef uclmodule = { + PyModuleDef_HEAD_INIT, + "ucl", + NULL, + -1, + uclMethods +}; + +PyMODINIT_FUNC +PyInit_ucl(void) { + return PyModule_Create(&uclmodule); +} +#else +void initucl(void) { + Py_InitModule("ucl", uclMethods); +} +#endif diff --git a/python/test.sh b/python/test.sh new file mode 100755 index 000000000000..53af6a3fd370 --- /dev/null +++ b/python/test.sh @@ -0,0 +1,6 @@ +#!/bin/sh +set -xe +python3.4 setup.py build_ext --inplace +./test_uclmodule.py -v +rm -rfv build +rm ucl.so diff --git a/python/test_uclmodule.py b/python/test_uclmodule.py new file mode 100755 index 000000000000..de295dc2000f --- /dev/null +++ b/python/test_uclmodule.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +import json +import unittest +import ucl +import sys + +if sys.version_info[:2] == (2, 7): + unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp + + +class TestUcl(unittest.TestCase): + def test_no_args(self): + with self.assertRaises(TypeError): + ucl.load() + + def test_multi_args(self): + with self.assertRaises(TypeError): + ucl.load(0,0) + + def test_none(self): + r = ucl.load(None) + self.assertEqual(r, None) + + def test_int(self): + r = ucl.load("a : 1") + self.assertEqual(ucl.load("a : 1"), { "a" : 1 } ) + + def test_braced_int(self): + self.assertEqual(ucl.load("{a : 1}"), { "a" : 1 } ) + + def test_nested_int(self): + self.assertEqual(ucl.load("a : { b : 1 }"), { "a" : { "b" : 1 } }) + + def test_str(self): + self.assertEqual(ucl.load("a : b"), {"a" : "b"}) + + def test_float(self): + self.assertEqual(ucl.load("a : 1.1"), {"a" : 1.1}) + + def test_empty_ucl(self): + r = ucl.load("{}") + self.assertEqual(r, {}) + + def test_single_brace(self): + self.assertEqual(ucl.load("{"), {}) + + def test_single_back_brace(self): + ucl.load("}") + + def test_single_square_forward(self): + self.assertEqual(ucl.load("["), []) + + def test_invalid_ucl(self): + with self.assertRaisesRegex(ValueError, "unfinished key$"): + ucl.load('{ "var"') + + def test_comment_ignored(self): + self.assertEqual(ucl.load("{/*1*/}"), {}) + + def test_1_in(self): + with open("../tests/basic/1.in", "r") as in1: + self.assertEqual(ucl.load(in1.read()), {'key1': 'value'}) + + def test_every_type(self): + totest="""{ + "key1": value; + "key2": value2; + "key3": "value;" + "key4": 1.0, + "key5": -0xdeadbeef + "key6": 0xdeadbeef.1 + "key7": 0xreadbeef + "key8": -1e-10, + "key9": 1 + "key10": true + "key11": no + "key12": yes + }""" + correct = { + 'key1': 'value', + 'key2': 'value2', + 'key3': 'value;', + 'key4': 1.0, + 'key5': -3735928559, + 'key6': '0xdeadbeef.1', + 'key7': '0xreadbeef', + 'key8': -1e-10, + 'key9': 1, + 'key10': 'true', + 'key11': 'false', + 'key12': 'true', + } + self.assertEqual(ucl.load(totest), correct) + + def test_validation_useless(self): + with self.assertRaises(NotImplementedError): + ucl.validate("","") + +if __name__ == '__main__': + unittest.main() |
