aboutsummaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/setup.py37
-rw-r--r--python/src/uclmodule.c156
-rwxr-xr-xpython/test.sh6
-rwxr-xr-xpython/test_uclmodule.py100
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()