summaryrefslogtreecommitdiffhomepage
path: root/libs/luci-lib-jsonc/src
diff options
context:
space:
mode:
Diffstat (limited to 'libs/luci-lib-jsonc/src')
-rw-r--r--libs/luci-lib-jsonc/src/Makefile17
-rw-r--r--libs/luci-lib-jsonc/src/jsonc.c388
-rw-r--r--libs/luci-lib-jsonc/src/jsonc.luadoc134
3 files changed, 539 insertions, 0 deletions
diff --git a/libs/luci-lib-jsonc/src/Makefile b/libs/luci-lib-jsonc/src/Makefile
new file mode 100644
index 0000000000..e15fbac382
--- /dev/null
+++ b/libs/luci-lib-jsonc/src/Makefile
@@ -0,0 +1,17 @@
+JSONC_CFLAGS = -std=gnu99 -I$(STAGING_DIR)/usr/include/json-c/
+JSONC_LDFLAGS = -llua -lm -ljson-c
+JSONC_OBJ = jsonc.o
+JSONC_LIB = jsonc.so
+
+%.o: %.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) $(LUA_CFLAGS) $(JSONC_CFLAGS) $(FPIC) -c -o $@ $<
+
+compile: $(JSONC_OBJ)
+ $(CC) $(LDFLAGS) -shared -o $(JSONC_LIB) $(JSONC_OBJ) $(JSONC_LDFLAGS)
+
+install: compile
+ mkdir -p $(DESTDIR)/usr/lib/lua/luci
+ cp $(JSONC_LIB) $(DESTDIR)/usr/lib/lua/luci/$(JSONC_LIB)
+
+clean:
+ rm -f *.o *.so
diff --git a/libs/luci-lib-jsonc/src/jsonc.c b/libs/luci-lib-jsonc/src/jsonc.c
new file mode 100644
index 0000000000..49cb21f5bc
--- /dev/null
+++ b/libs/luci-lib-jsonc/src/jsonc.c
@@ -0,0 +1,388 @@
+/*
+Copyright 2015 Jo-Philipp Wich <jow@openwrt.org>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+#define _GNU_SOURCE
+
+#include <math.h>
+#include <stdbool.h>
+#include <json-c/json.h>
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#define LUCI_JSONC "luci.jsonc"
+#define LUCI_JSONC_PARSER "luci.jsonc.parser"
+
+struct json_state {
+ struct json_object *obj;
+ struct json_tokener *tok;
+ enum json_tokener_error err;
+};
+
+static void _json_to_lua(lua_State *L, struct json_object *obj);
+static struct json_object * _lua_to_json(lua_State *L, int index);
+
+static int json_new(lua_State *L)
+{
+ struct json_state *s;
+ struct json_tokener *tok = json_tokener_new();
+
+ if (!tok)
+ return 0;
+
+ s = lua_newuserdata(L, sizeof(*s));
+
+ if (!s)
+ {
+ json_tokener_free(tok);
+ return 0;
+ }
+
+ s->tok = tok;
+ s->obj = NULL;
+ s->err = json_tokener_continue;
+
+ luaL_getmetatable(L, LUCI_JSONC_PARSER);
+ lua_setmetatable(L, -2);
+
+ return 1;
+}
+
+static int json_parse(lua_State *L)
+{
+ size_t len;
+ const char *json = luaL_checklstring(L, 1, &len);
+ struct json_state s = {
+ .tok = json_tokener_new()
+ };
+
+ if (!s.tok)
+ return 0;
+
+ s.obj = json_tokener_parse_ex(s.tok, json, len);
+ s.err = json_tokener_get_error(s.tok);
+
+ if (s.obj)
+ {
+ _json_to_lua(L, s.obj);
+ json_object_put(s.obj);
+ }
+ else
+ {
+ lua_pushnil(L);
+ }
+
+ if (s.err == json_tokener_continue)
+ s.err = json_tokener_error_parse_eof;
+
+ if (s.err)
+ lua_pushstring(L, json_tokener_error_desc(s.err));
+
+ json_tokener_free(s.tok);
+ return (1 + !!s.err);
+}
+
+static int json_stringify(lua_State *L)
+{
+ struct json_object *obj = _lua_to_json(L, 1);
+ bool pretty = lua_toboolean(L, 2);
+ int flags = 0;
+
+ if (pretty)
+ flags |= JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED;
+
+ lua_pushstring(L, json_object_to_json_string_ext(obj, flags));
+ return 1;
+}
+
+
+static int json_parse_chunk(lua_State *L)
+{
+ size_t len;
+ struct json_state *s = luaL_checkudata(L, 1, LUCI_JSONC_PARSER);
+ const char *chunk = luaL_checklstring(L, 2, &len);
+
+ s->obj = json_tokener_parse_ex(s->tok, chunk, len);
+ s->err = json_tokener_get_error(s->tok);
+
+ if (!s->err)
+ {
+ lua_pushboolean(L, true);
+ return 1;
+ }
+ else if (s->err == json_tokener_continue)
+ {
+ lua_pushboolean(L, false);
+ return 1;
+ }
+
+ lua_pushnil(L);
+ lua_pushstring(L, json_tokener_error_desc(s->err));
+ return 2;
+}
+
+static void _json_to_lua(lua_State *L, struct json_object *obj)
+{
+ int n;
+
+ switch (json_object_get_type(obj))
+ {
+ case json_type_object:
+ lua_newtable(L);
+ json_object_object_foreach(obj, key, val)
+ {
+ _json_to_lua(L, val);
+ lua_setfield(L, -2, key);
+ }
+ break;
+
+ case json_type_array:
+ lua_newtable(L);
+ for (n = 0; n < json_object_array_length(obj); n++)
+ {
+ _json_to_lua(L, json_object_array_get_idx(obj, n));
+ lua_rawseti(L, -2, n + 1);
+ }
+ break;
+
+ case json_type_boolean:
+ lua_pushboolean(L, json_object_get_boolean(obj));
+ break;
+
+ case json_type_int:
+ lua_pushinteger(L, json_object_get_int(obj));
+ break;
+
+ case json_type_double:
+ lua_pushnumber(L, json_object_get_double(obj));
+ break;
+
+ case json_type_string:
+ lua_pushstring(L, json_object_get_string(obj));
+ break;
+
+ case json_type_null:
+ lua_pushnil(L);
+ break;
+ }
+}
+
+static int json_parse_get(lua_State *L)
+{
+ struct json_state *s = luaL_checkudata(L, 1, LUCI_JSONC_PARSER);
+
+ if (!s->obj || s->err)
+ lua_pushnil(L);
+ else
+ _json_to_lua(L, s->obj);
+
+ return 1;
+}
+
+static int _lua_test_array(lua_State *L, int index)
+{
+ int max = 0;
+ lua_Number idx;
+
+ lua_pushnil(L);
+
+ /* check for non-integer keys */
+ while (lua_next(L, index))
+ {
+ if (lua_type(L, -2) != LUA_TNUMBER)
+ goto out;
+
+ idx = lua_tonumber(L, -2);
+
+ if (idx != (lua_Number)(lua_Integer)idx)
+ goto out;
+
+ if (idx <= 0)
+ goto out;
+
+ if (idx > max)
+ max = idx;
+
+ lua_pop(L, 1);
+ continue;
+
+out:
+ lua_pop(L, 2);
+ return 0;
+ }
+
+ /* check for holes */
+ //for (i = 1; i <= max; i++)
+ //{
+ // lua_rawgeti(L, index, i);
+ //
+ // if (lua_isnil(L, -1))
+ // {
+ // lua_pop(L, 1);
+ // return 0;
+ // }
+ //
+ // lua_pop(L, 1);
+ //}
+
+ return max;
+}
+
+static struct json_object * _lua_to_json(lua_State *L, int index)
+{
+ lua_Number nd, ni;
+ struct json_object *obj;
+ const char *key;
+ int i, max;
+
+ switch (lua_type(L, index))
+ {
+ case LUA_TTABLE:
+ max = _lua_test_array(L, index);
+
+ if (max > 0)
+ {
+ obj = json_object_new_array();
+
+ if (!obj)
+ return NULL;
+
+ for (i = 1; i <= max; i++)
+ {
+ lua_rawgeti(L, index, i);
+
+ json_object_array_put_idx(obj, i - 1,
+ _lua_to_json(L, lua_gettop(L)));
+
+ lua_pop(L, 1);
+ }
+
+ return obj;
+ }
+
+ obj = json_object_new_object();
+
+ if (!obj)
+ return NULL;
+
+ lua_pushnil(L);
+
+ while (lua_next(L, index))
+ {
+ lua_pushvalue(L, -2);
+ key = lua_tostring(L, -1);
+
+ json_object_object_add(obj, key,
+ _lua_to_json(L, lua_gettop(L) - 1));
+
+ lua_pop(L, 2);
+ }
+
+ return obj;
+
+ case LUA_TNIL:
+ return NULL;
+
+ case LUA_TBOOLEAN:
+ return json_object_new_boolean(lua_toboolean(L, index));
+
+ case LUA_TNUMBER:
+ nd = lua_tonumber(L, index);
+ ni = lua_tointeger(L, index);
+
+ if (nd == ni)
+ return json_object_new_int(nd);
+
+ return json_object_new_double(nd);
+
+ case LUA_TSTRING:
+ return json_object_new_string(lua_tostring(L, index));
+ }
+
+ return NULL;
+}
+
+static int json_parse_set(lua_State *L)
+{
+ struct json_state *s = luaL_checkudata(L, 1, LUCI_JSONC_PARSER);
+
+ s->err = 0;
+ s->obj = _lua_to_json(L, 2);
+
+ return 0;
+}
+
+static int json_tostring(lua_State *L)
+{
+ struct json_state *s = luaL_checkudata(L, 1, LUCI_JSONC_PARSER);
+ bool pretty = lua_toboolean(L, 2);
+ int flags = 0;
+
+ if (pretty)
+ flags |= JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED;
+
+ lua_pushstring(L, json_object_to_json_string_ext(s->obj, flags));
+ return 1;
+}
+
+static int json_gc(lua_State *L)
+{
+ struct json_state *s = luaL_checkudata(L, 1, LUCI_JSONC_PARSER);
+
+ if (s->obj)
+ json_object_put(s->obj);
+
+ if (s->tok)
+ json_tokener_free(s->tok);
+
+ return 0;
+}
+
+
+static const luaL_reg jsonc_methods[] = {
+ { "new", json_new },
+ { "parse", json_parse },
+ { "stringify", json_stringify },
+
+ { }
+};
+
+static const luaL_reg jsonc_parser_methods[] = {
+ { "parse", json_parse_chunk },
+ { "get", json_parse_get },
+ { "set", json_parse_set },
+ { "stringify", json_tostring },
+
+ { "__gc", json_gc },
+ { "__tostring", json_tostring },
+
+ { }
+};
+
+
+int luaopen_luci_jsonc(lua_State *L)
+{
+ luaL_register(L, LUCI_JSONC, jsonc_methods);
+
+ luaL_newmetatable(L, LUCI_JSONC_PARSER);
+ luaL_register(L, NULL, jsonc_parser_methods);
+ lua_pushvalue(L, -1);
+ lua_setfield(L, -2, "__index");
+ lua_pop(L, 1);
+
+ return 1;
+}
diff --git a/libs/luci-lib-jsonc/src/jsonc.luadoc b/libs/luci-lib-jsonc/src/jsonc.luadoc
new file mode 100644
index 0000000000..2ee9cebdc8
--- /dev/null
+++ b/libs/luci-lib-jsonc/src/jsonc.luadoc
@@ -0,0 +1,134 @@
+--- LuCI JSON parsing and serialization library.
+-- The luci.jsonc class is a high level Lua binding to the JSON-C library to
+-- allow reading and writing JSON data with minimal overhead.
+module "luci.jsonc"
+
+---[[
+Construct a new luci.jsonc.parser instance.
+@class function
+@sort 1
+@name new
+@return A `luci.jsonc.parser` object representing a JSON-C tokener.
+@usage `parser = luci.jsonc.new()`
+]]
+
+---[[
+Parse a complete JSON string and convert it into a Lua data structure.
+@class function
+@sort 2
+@name parse
+@param json A string containing the JSON data to parse, must be either a
+ JSON array or a JSON object.
+@return On success, a table containing the parsed JSON data is returned, on
+ failure the function returns `nil` and a string containing the reason of
+ the parse error.
+@usage `data = luci.jsonc.parse('{ "name": "John", "age": 34 }')
+print(data.name) -- "John"`
+@see stringify
+]]
+
+---[[
+Convert given Lua data into a JSON string.
+
+This function recursively converts the given Lua data into a JSON string,
+ignoring any unsupported data. Lua tables are converted into JSON arrays if they
+only contain integer keys, mixed tables are turned into JSON objects with any
+existing numeric keys converted into strings.
+
+Lua functions, coroutines and userdata objects are ignored and Lua numbers are
+converted to integers if they do not contain fractional values.
+
+@class function
+@sort 3
+@name stringify
+@param data The Lua data to convert, can be a table, string, boolean or number.
+@param pretty A boolean value indicating whether the resulting JSON should be
+ pretty printed.
+@return Returns a string containing the JSON representation of the given Lua
+ data.
+@usage `json = luci.jsonc.stringify({ item = true, values = { 1, 2, 3 } })
+print(json) -- '{"item":true,"values":[1,2,3]}'`
+@see parse
+]]
+
+
+--- LuCI JSON parser instance.
+-- A JSON parser instance is useful to parse JSON data chunk by chunk, without
+-- the need to assemble all data in advance.
+-- @cstyle instance
+module "luci.jsonc.parser"
+
+---[[
+Parses one chunk of JSON data.
+
+@class function
+@sort 1
+@name parser.parse
+@see parser.get
+@param json String containing the JSON fragment to parse
+@return <ul>
+ <li>`true` if a complete JSON object has been parsed and no further input is
+ expected.</li>
+ <li>`false` if further input is required</li>
+ <li>`nil` if an error was encountered while parsing the current chunk.
+ In this case a string describing the parse error is returned as second
+ value.</li></ul>
+@usage `parser = luci.jsonc.new()
+
+while true do
+ chunk = ... -- fetch a cunk of data, e.g. from a socket
+ finish, errmsg = <b>parser.parse(chunk)</b>
+
+ if finish == nil then
+ error("Cannot parse JSON: " .. errmsg)
+ end
+
+ if finish == true then
+ break
+ end
+end`
+]]
+
+---[[
+Convert parsed JSON data into Lua table.
+
+@class function
+@sort 2
+@name parser.get
+@see parser.parse
+@return Parsed JSON object converted into a Lua table or `nil` if the parser
+ didn't finish or encountered an error.
+@usage `parser = luci.jsonc.new()
+parser:parse('{ "example": "test" }')
+
+data = parser:get()
+print(data.example) -- "test"`
+]]
+
+---[[
+Put Lua data into the parser.
+
+@class function
+@sort 3
+@name parser.set
+@see parser.stringify
+@param data Lua data to put into the parser object. The data is converted to an
+ internal JSON representation that can be dumped with `stringify()`.
+ The conversion follows the rules described in `luci.jsonc.stringify`.
+@return Nothing is returned.
+@usage `parser = luci.jsonc.new()
+parser:set({ "some", "data" })`
+]]
+
+---[[
+Serialize current parser state as JSON.
+
+@class function
+@sort 4
+@name parser.stringify
+@param pretty A boolean value indicating whether the resulting JSON should be pretty printed.
+@return Returns the serialized JSON data of this parser instance.
+@usage `parser = luci.jsonc.new()
+parser:parse('{ "example": "test" }')
+print(parser:serialize()) -- '{"example":"test"}'`
+]]