diff options
Diffstat (limited to 'libs/luci-lib-jsonc/src')
-rw-r--r-- | libs/luci-lib-jsonc/src/Makefile | 17 | ||||
-rw-r--r-- | libs/luci-lib-jsonc/src/jsonc.c | 388 | ||||
-rw-r--r-- | libs/luci-lib-jsonc/src/jsonc.luadoc | 134 |
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"}'` +]] |