Update libucl to latest version
This commit is contained in:
parent
8f2b0cccb4
commit
1eea90048f
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/vendor/libucl/dist/; revision=275221 svn path=/vendor/libucl/20141129/; revision=275222; tag=vendor/libucl/20141129
23
COPYING
Normal file
23
COPYING
Normal file
@ -0,0 +1,23 @@
|
||||
Copyright (c) 2013-2014, Vsevolod Stakhov <vsevolod@highsecure.ru>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
16
ChangeLog.md
16
ChangeLog.md
@ -4,3 +4,19 @@
|
||||
|
||||
- Streamline emitter has been added, so it is now possible to output partial `ucl` objects
|
||||
- Emitter now is more flexible due to emitter_context structure
|
||||
|
||||
### 0.5.1
|
||||
- Fixed number of bugs and memory leaks
|
||||
|
||||
### 0.5.2
|
||||
|
||||
- Allow userdata objects to be emitted and destructed
|
||||
- Use userdata objects to store lua function references
|
||||
|
||||
### Libucl 0.6
|
||||
|
||||
- Reworked macro interface
|
||||
|
||||
### Libucl 0.6.1
|
||||
|
||||
- Various utilities fixes
|
||||
|
@ -4,4 +4,8 @@ EXTRA_DIST = uthash README.md
|
||||
pkgconfigdir = $(libdir)/pkgconfig
|
||||
pkgconfig_DATA = libucl.pc
|
||||
|
||||
SUBDIRS = src tests utils doc
|
||||
if LUA_SUB
|
||||
LUA_SUBDIR = lua
|
||||
endif
|
||||
|
||||
SUBDIRS = src tests utils doc $(LUA_SUBDIR)
|
@ -33,6 +33,7 @@ OBJECTS = $(OBJDIR)/ucl_hash.o \
|
||||
$(OBJDIR)/ucl_util.o \
|
||||
$(OBJDIR)/ucl_parser.o \
|
||||
$(OBJDIR)/ucl_emitter.o \
|
||||
$(OBJDIR)/ucl_emitter_utils.o \
|
||||
$(OBJDIR)/ucl_schema.o \
|
||||
$(OBJDIR)/xxhash.o
|
||||
|
||||
@ -51,6 +52,8 @@ $(OBJDIR)/ucl_parser.o: $(SRCDIR)/ucl_parser.c $(HDEPS)
|
||||
$(CC) -o $(OBJDIR)/ucl_parser.o $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) -c $(SRCDIR)/ucl_parser.c
|
||||
$(OBJDIR)/ucl_emitter.o: $(SRCDIR)/ucl_emitter.c $(HDEPS)
|
||||
$(CC) -o $(OBJDIR)/ucl_emitter.o $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) -c $(SRCDIR)/ucl_emitter.c
|
||||
$(OBJDIR)/ucl_emitter_utils.o: $(SRCDIR)/ucl_emitter_utils.c $(HDEPS)
|
||||
$(CC) -o $(OBJDIR)/ucl_emitter_utils.o $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) -c $(SRCDIR)/ucl_emitter_utils.c
|
||||
$(OBJDIR)/ucl_hash.o: $(SRCDIR)/ucl_hash.c $(HDEPS)
|
||||
$(CC) -o $(OBJDIR)/ucl_hash.o $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) -c $(SRCDIR)/ucl_hash.c
|
||||
$(OBJDIR)/ucl_schema.o: $(SRCDIR)/ucl_schema.c $(HDEPS)
|
||||
@ -61,7 +64,7 @@ $(OBJDIR)/xxhash.o: $(SRCDIR)/xxhash.c $(HDEPS)
|
||||
clean:
|
||||
$(RM) $(OBJDIR)/*.o $(OBJDIR)/$(SONAME) $(OBJDIR)/$(SONAME) $(OBJDIR)/chargen $(OBJDIR)/test_basic $(OBJDIR)/test_speed $(OBJDIR)/objdump $(OBJDIR)/test_generate
|
||||
$(RMDIR) $(OBJDIR)
|
||||
|
||||
|
||||
# Utils
|
||||
|
||||
chargen: utils/chargen.c $(OBJDIR)/$(SONAME)
|
||||
@ -75,7 +78,7 @@ test: $(OBJDIR) $(OBJDIR)/$(SONAME) $(OBJDIR)/test_basic $(OBJDIR)/test_speed $(
|
||||
|
||||
run-test: test
|
||||
TEST_DIR=$(TESTDIR) $(TESTDIR)/run_tests.sh $(OBJDIR)/test_basic $(OBJDIR)/test_speed $(OBJDIR)/test_generate
|
||||
|
||||
|
||||
$(OBJDIR)/test_basic: $(TESTDIR)/test_basic.c $(OBJDIR)/$(SONAME)
|
||||
$(CC) -o $(OBJDIR)/test_basic $(CPPFLAGS) $(COPT_FLAGS) $(CFLAGS) $(C_COMMON_FLAGS) $(SSL_CFLAGS) $(FETCH_FLAGS) $(LDFLAGS) $(TESTDIR)/test_basic.c $(LD_UCL_FLAGS)
|
||||
$(OBJDIR)/test_speed: $(TESTDIR)/test_speed.c $(OBJDIR)/$(SONAME)
|
||||
|
58
README.md
58
README.md
@ -223,15 +223,57 @@ UCL supports external macros both multiline and single line ones:
|
||||
....
|
||||
};
|
||||
```
|
||||
There are two internal macros provided by UCL:
|
||||
|
||||
* `include` - read a file `/path/to/file` or an url `http://example.com/file` and include it to the current place of
|
||||
UCL configuration;
|
||||
* `try\_include` - try to read a file or url and include it but do not create a fatal error if a file or url is not accessible;
|
||||
* `includes` - read a file or an url like the previous macro, but fetch and check the signature file (which is obtained
|
||||
by `.sig` suffix appending).
|
||||
Moreover, each macro can accept an optional list of arguments in braces. These
|
||||
arguments themselves are the UCL object that is parsed and passed to a macro as
|
||||
options:
|
||||
|
||||
Public keys which are used for the last command are specified by the concrete UCL user.
|
||||
```nginx
|
||||
.macro(param=value) "something";
|
||||
.macro(param={key=value}) "something";
|
||||
.macro(.include "params.conf") "something";
|
||||
.macro(#this is multiline macro
|
||||
param = [value1, value2]) "something";
|
||||
.macro(key="()") "something";
|
||||
```
|
||||
|
||||
UCL also provide a convenient `include` macro to load content from another files
|
||||
to the current UCL object. This macro accepts either path to file:
|
||||
|
||||
```nginx
|
||||
.include "/full/path.conf"
|
||||
.include "./relative/path.conf"
|
||||
.include "${CURDIR}/path.conf"
|
||||
```
|
||||
|
||||
or URL (if ucl is built with url support provided by either `libcurl` or `libfetch`):
|
||||
|
||||
.include "http://example.com/file.conf"
|
||||
|
||||
`.include` macro supports a set of options:
|
||||
|
||||
* `try` (default: **false**) - if this option is `true` than UCL treats errors on loading of
|
||||
this file as non-fatal. For example, such a file can be absent but it won't stop the parsing
|
||||
of the top-level document.
|
||||
* `sign` (default: **false**) - if this option is `true` UCL loads and checks the signature for
|
||||
a file from path named `<FILEPATH>.sig`. Trusted public keys should be provided for UCL API after
|
||||
parser is created but before any configurations are parsed.
|
||||
* `glob` (default: **false**) - if this option is `true` UCL treats the filename as GLOB pattern and load
|
||||
all files that matches the specified pattern (normally the format of patterns is defined in `glob` manual page
|
||||
for your operating system). This option is meaningless for URL includes.
|
||||
* `url` (default: **true**) - allow URL includes.
|
||||
* `priority` (default: 0) - specify priority for the include (see below).
|
||||
|
||||
Priorities are used by UCL parser to manage the policy of objects rewriting during including other files
|
||||
as following:
|
||||
|
||||
* If we have two objects with the same priority then we form an implicit array
|
||||
* If a new object has bigger priority then we overwrite an old one
|
||||
* If a new object has lower priority then we ignore it
|
||||
|
||||
By default, the priority of top-level object is set to zero (lowest priority). Currently,
|
||||
you can define up to 16 priorities (from 0 to 15). Includes with bigger priorities will
|
||||
rewrite keys from the objects with lower priorities as specified by the policy.
|
||||
|
||||
### Variables support
|
||||
|
||||
@ -317,7 +359,7 @@ ucl: emitted compact json in 0.0991 seconds
|
||||
ucl: emitted yaml in 0.1354 seconds
|
||||
```
|
||||
|
||||
You can do your own benchmarks by running `make test` in libucl top directory.
|
||||
You can do your own benchmarks by running `make check` in libucl top directory.
|
||||
|
||||
## Conclusion
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
PROJECT(libucl C)
|
||||
|
||||
SET(LIBUCL_VERSION_MAJOR 0)
|
||||
SET(LIBUCL_VERSION_MINOR 2)
|
||||
SET(LIBUCL_VERSION_PATCH 9)
|
||||
SET(LIBUCL_VERSION_MINOR 5)
|
||||
SET(LIBUCL_VERSION_PATCH 0)
|
||||
|
||||
SET(LIBUCL_VERSION "${LIBUCL_VERSION_MAJOR}.${LIBUCL_VERSION_MINOR}.${LIBUCL_VERSION_PATCH}")
|
||||
|
||||
@ -86,6 +86,8 @@ INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/../uthash")
|
||||
SET(UCLSRC ../src/ucl_util.c
|
||||
../src/ucl_parser.c
|
||||
../src/ucl_emitter.c
|
||||
../src/ucl_emitter_streamline.c
|
||||
../src/ucl_emitter_utils.c
|
||||
../src/ucl_hash.c
|
||||
../src/ucl_schema.c
|
||||
../src/xxhash.c)
|
||||
@ -98,6 +100,18 @@ ENDIF (BUILD_SHARED_LIBS)
|
||||
ADD_LIBRARY(ucl ${LIB_TYPE} ${UCLSRC})
|
||||
SET_TARGET_PROPERTIES(ucl PROPERTIES VERSION ${LIBUCL_VERSION} SOVERSION ${LIBUCL_VERSION_MAJOR})
|
||||
|
||||
IF(WITH_LUA)
|
||||
SET(UCL_LUA_SRC ../lua/lua_ucl.c)
|
||||
ADD_LIBRARY(lua-ucl ${LIB_TYPE} ${UCL_LUA_SRC})
|
||||
IF(ENABLE_LUAJIT MATCHES "ON")
|
||||
TARGET_LINK_LIBRARIES(lua-ucl "${LUAJIT_LIBRARY}")
|
||||
ELSE(ENABLE_LUAJIT MATCHES "ON")
|
||||
TARGET_LINK_LIBRARIES(lua-ucl "${LUA_LIBRARY}")
|
||||
ENDIF(ENABLE_LUAJIT MATCHES "ON")
|
||||
TARGET_LINK_LIBRARIES(lua-ucl ucl)
|
||||
SET_TARGET_PROPERTIES(lua-ucl PROPERTIES VERSION ${LIBUCL_VERSION} SOVERSION ${LIBUCL_VERSION_MAJOR})
|
||||
ENDIF(WITH_LUA)
|
||||
|
||||
IF(HAVE_FETCH_H)
|
||||
TARGET_LINK_LIBRARIES(ucl fetch)
|
||||
ELSE(HAVE_FETCH_H)
|
||||
|
29
configure.ac
29
configure.ac
@ -1,12 +1,13 @@
|
||||
m4_define([maj_ver], [0])
|
||||
m4_define([med_ver], [5])
|
||||
m4_define([min_ver], [0])
|
||||
m4_define([so_version], [2:0:0])
|
||||
m4_define([med_ver], [6])
|
||||
m4_define([min_ver], [1])
|
||||
m4_define([so_version], [3:0:1])
|
||||
m4_define([ucl_version], [maj_ver.med_ver.min_ver])
|
||||
|
||||
AC_INIT([libucl],[ucl_version],[https://github.com/vstakhov/libucl],[libucl])
|
||||
AC_CONFIG_SRCDIR([configure.ac])
|
||||
AM_INIT_AUTOMAKE([1.11 foreign silent-rules -Wall -Wportability no-dist-gzip dist-xz])
|
||||
AM_INIT_AUTOMAKE([1.11 foreign -Wall -Wportability no-dist-gzip dist-xz])
|
||||
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
|
||||
|
||||
UCL_VERSION=ucl_version
|
||||
SO_VERSION=so_version
|
||||
@ -57,6 +58,9 @@ AC_ARG_ENABLE([regex], AS_HELP_STRING([--enable-regex],
|
||||
AC_ARG_ENABLE([signatures], AS_HELP_STRING([--enable-signatures],
|
||||
[Enable signatures check (requires openssl) @<:@default=no@:>@]), [],
|
||||
[enable_signatures=no])
|
||||
AC_ARG_ENABLE([lua], AS_HELP_STRING([--enable-lua],
|
||||
[Enable lua API build (requires lua libraries and headers) @<:@default=no@:>@]), [],
|
||||
[enable_lua=no])
|
||||
AC_ARG_ENABLE([utils],
|
||||
AS_HELP_STRING([--enable-utils], [Build and install utils @<:@default=no@:>@]),
|
||||
[case "${enableval}" in
|
||||
@ -99,6 +103,21 @@ AS_IF([test "x$enable_regex" = "xyes"], [
|
||||
])
|
||||
AC_SUBST(LIBREGEX_LIB)
|
||||
|
||||
AS_IF([test "x$enable_lua" = "xyes"], [
|
||||
AX_PROG_LUA([5.1], [], [
|
||||
AX_LUA_HEADERS([
|
||||
AX_LUA_LIBS([
|
||||
AC_DEFINE(HAVE_LUA, 1, [Define to 1 for lua support.])
|
||||
with_lua="yes"
|
||||
], [AC_MSG_ERROR([unable to find the lua libraries])
|
||||
])
|
||||
], [AC_MSG_ERROR([unable to find the lua header files])
|
||||
])
|
||||
], [AC_MSG_ERROR([unable to find the lua interpreter])])
|
||||
], [with_lua="no"])
|
||||
|
||||
AM_CONDITIONAL([LUA_SUB], [test "$with_lua" = "yes"])
|
||||
|
||||
AS_IF([test "x$enable_urls" = "xyes"], [
|
||||
AC_CHECK_HEADER([fetch.h], [
|
||||
AC_DEFINE(HAVE_FETCH_H, 1, [Define to 1 if you have the <fetch.h> header file.])
|
||||
@ -155,9 +174,11 @@ AC_LINK_IFELSE([
|
||||
|
||||
AC_CONFIG_FILES(Makefile \
|
||||
src/Makefile \
|
||||
lua/Makefile
|
||||
tests/Makefile \
|
||||
utils/Makefile \
|
||||
doc/Makefile \
|
||||
lua/libucl.rockspec \
|
||||
libucl.pc)
|
||||
AC_CONFIG_FILES([stamp-h], [echo timestamp > stamp-h])
|
||||
AC_OUTPUT
|
||||
|
194
doc/lua_api.md
Normal file
194
doc/lua_api.md
Normal file
@ -0,0 +1,194 @@
|
||||
## Module `ucl`
|
||||
|
||||
This lua module allows to parse objects from strings and to store data into
|
||||
ucl objects. It uses `libucl` C library to parse and manipulate with ucl objects.
|
||||
|
||||
Example:
|
||||
|
||||
~~~lua
|
||||
local ucl = require("ucl")
|
||||
|
||||
local parser = ucl.parser()
|
||||
local res,err = parser:parse_string('{key=value}')
|
||||
|
||||
if not res then
|
||||
print('parser error: ' .. err)
|
||||
else
|
||||
local obj = parser:get_object()
|
||||
local got = ucl.to_format(obj, 'json')
|
||||
endif
|
||||
|
||||
local table = {
|
||||
str = 'value',
|
||||
num = 100500,
|
||||
null = ucl.null,
|
||||
func = function ()
|
||||
return 'huh'
|
||||
end
|
||||
|
||||
|
||||
print(ucl.to_format(table, 'ucl'))
|
||||
-- Output:
|
||||
--[[
|
||||
num = 100500;
|
||||
str = "value";
|
||||
null = null;
|
||||
func = "huh";
|
||||
--]]
|
||||
~~~
|
||||
|
||||
###Brief content:
|
||||
|
||||
**Functions**:
|
||||
|
||||
> [`ucl_object_push_lua(L, obj, allow_array)`](#function-ucl_object_push_lual-obj-allow_array)
|
||||
|
||||
> [`ucl.to_format(var, format)`](#function-uclto_formatvar-format)
|
||||
|
||||
|
||||
|
||||
**Methods**:
|
||||
|
||||
> [`parser:parse_file(name)`](#method-parserparse_filename)
|
||||
|
||||
> [`parser:parse_string(input)`](#method-parserparse_stringinput)
|
||||
|
||||
> [`parser:get_object()`](#method-parserget_object)
|
||||
|
||||
|
||||
## Functions
|
||||
|
||||
The module `ucl` defines the following functions.
|
||||
|
||||
### Function `ucl_object_push_lua(L, obj, allow_array)`
|
||||
|
||||
This is a `C` function to push `UCL` object as lua variable. This function
|
||||
converts `obj` to lua representation using the following conversions:
|
||||
|
||||
- *scalar* values are directly presented by lua objects
|
||||
- *userdata* values are converted to lua function objects using `LUA_REGISTRYINDEX`,
|
||||
this can be used to pass functions from lua to c and vice-versa
|
||||
- *arrays* are converted to lua tables with numeric indicies suitable for `ipairs` iterations
|
||||
- *objects* are converted to lua tables with string indicies
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `L {lua_State}`: lua state pointer
|
||||
- `obj {ucl_object_t}`: object to push
|
||||
- `allow_array {bool}`: expand implicit arrays (should be true for all but partial arrays)
|
||||
|
||||
**Returns:**
|
||||
|
||||
- `{int}`: `1` if an object is pushed to lua
|
||||
|
||||
Back to [module description](#module-ucl).
|
||||
|
||||
### Function `ucl.to_format(var, format)`
|
||||
|
||||
Converts lua variable `var` to the specified `format`. Formats supported are:
|
||||
|
||||
- `json` - fine printed json
|
||||
- `json-compact` - compacted json
|
||||
- `config` - fine printed configuration
|
||||
- `ucl` - same as `config`
|
||||
- `yaml` - embedded yaml
|
||||
|
||||
If `var` contains function, they are called during output formatting and if
|
||||
they return string value, then this value is used for ouptut.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `var {variant}`: any sort of lua variable (if userdata then metafield `__to_ucl` is searched for output)
|
||||
- `format {string}`: any available format
|
||||
|
||||
**Returns:**
|
||||
|
||||
- `{string}`: string representation of `var` in the specific `format`.
|
||||
|
||||
Example:
|
||||
|
||||
~~~lua
|
||||
local table = {
|
||||
str = 'value',
|
||||
num = 100500,
|
||||
null = ucl.null,
|
||||
func = function ()
|
||||
return 'huh'
|
||||
end
|
||||
|
||||
|
||||
print(ucl.to_format(table, 'ucl'))
|
||||
-- Output:
|
||||
--[[
|
||||
num = 100500;
|
||||
str = "value";
|
||||
null = null;
|
||||
func = "huh";
|
||||
--]]
|
||||
~~~
|
||||
|
||||
Back to [module description](#module-ucl).
|
||||
|
||||
|
||||
## Methods
|
||||
|
||||
The module `ucl` defines the following methods.
|
||||
|
||||
### Method `parser:parse_file(name)`
|
||||
|
||||
Parse UCL object from file.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `name {string}`: filename to parse
|
||||
|
||||
**Returns:**
|
||||
|
||||
- `{bool[, string]}`: if res is `true` then file has been parsed successfully, otherwise an error string is also returned
|
||||
|
||||
Example:
|
||||
|
||||
~~~lua
|
||||
local parser = ucl.parser()
|
||||
local res,err = parser:parse_file('/some/file.conf')
|
||||
|
||||
if not res then
|
||||
print('parser error: ' .. err)
|
||||
else
|
||||
-- Do something with object
|
||||
end
|
||||
~~~
|
||||
|
||||
Back to [module description](#module-ucl).
|
||||
|
||||
### Method `parser:parse_string(input)`
|
||||
|
||||
Parse UCL object from file.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `input {string}`: string to parse
|
||||
|
||||
**Returns:**
|
||||
|
||||
- `{bool[, string]}`: if res is `true` then file has been parsed successfully, otherwise an error string is also returned
|
||||
|
||||
Back to [module description](#module-ucl).
|
||||
|
||||
### Method `parser:get_object()`
|
||||
|
||||
Get top object from parser and export it to lua representation.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
nothing
|
||||
|
||||
**Returns:**
|
||||
|
||||
- `{variant or nil}`: ucl object as lua native variable
|
||||
|
||||
Back to [module description](#module-ucl).
|
||||
|
||||
|
||||
Back to [top](#).
|
||||
|
69
include/lua_ucl.h
Normal file
69
include/lua_ucl.h
Normal file
@ -0,0 +1,69 @@
|
||||
/* Copyright (c) 2014, Vsevolod Stakhov
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef LUA_UCL_H_
|
||||
#define LUA_UCL_H_
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
#include "ucl.h"
|
||||
|
||||
/**
|
||||
* Closure structure for lua function storing inside UCL
|
||||
*/
|
||||
struct ucl_lua_funcdata {
|
||||
lua_State *L;
|
||||
int idx;
|
||||
char *ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize lua UCL API
|
||||
*/
|
||||
UCL_EXTERN int luaopen_ucl (lua_State *L);
|
||||
|
||||
/**
|
||||
* Import UCL object from lua state
|
||||
* @param L lua state
|
||||
* @param idx index of object at the lua stack to convert to UCL
|
||||
* @return new UCL object or NULL, the caller should unref object after using
|
||||
*/
|
||||
UCL_EXTERN ucl_object_t* ucl_object_lua_import (lua_State *L, int idx);
|
||||
|
||||
/**
|
||||
* Push an object to lua
|
||||
* @param L lua state
|
||||
* @param obj object to push
|
||||
* @param allow_array traverse over implicit arrays
|
||||
*/
|
||||
UCL_EXTERN int ucl_object_push_lua (lua_State *L,
|
||||
const ucl_object_t *obj, bool allow_array);
|
||||
|
||||
UCL_EXTERN struct ucl_lua_funcdata* ucl_object_toclosure (
|
||||
const ucl_object_t *obj);
|
||||
|
||||
#endif /* LUA_UCL_H_ */
|
187
include/ucl.h
187
include/ucl.h
@ -147,7 +147,8 @@ typedef enum ucl_emitter {
|
||||
typedef enum ucl_parser_flags {
|
||||
UCL_PARSER_KEY_LOWERCASE = 0x1, /**< Convert all keys to lower case */
|
||||
UCL_PARSER_ZEROCOPY = 0x2, /**< Parse input in zero-copy mode if possible */
|
||||
UCL_PARSER_NO_TIME = 0x4 /**< Do not parse time and treat time values as strings */
|
||||
UCL_PARSER_NO_TIME = 0x4, /**< Do not parse time and treat time values as strings */
|
||||
UCL_PARSER_NO_IMPLICIT_ARRAYS = 0x8 /** Create explicit arrays instead of implicit ones */
|
||||
} ucl_parser_flags_t;
|
||||
|
||||
/**
|
||||
@ -171,9 +172,12 @@ typedef enum ucl_string_flags {
|
||||
* Basic flags for an object
|
||||
*/
|
||||
typedef enum ucl_object_flags {
|
||||
UCL_OBJECT_ALLOCATED_KEY = 1, /**< An object has key allocated internally */
|
||||
UCL_OBJECT_ALLOCATED_VALUE = 2, /**< An object has a string value allocated internally */
|
||||
UCL_OBJECT_NEED_KEY_ESCAPE = 4 /**< The key of an object need to be escaped on output */
|
||||
UCL_OBJECT_ALLOCATED_KEY = 0x1, /**< An object has key allocated internally */
|
||||
UCL_OBJECT_ALLOCATED_VALUE = 0x2, /**< An object has a string value allocated internally */
|
||||
UCL_OBJECT_NEED_KEY_ESCAPE = 0x4, /**< The key of an object need to be escaped on output */
|
||||
UCL_OBJECT_EPHEMERAL = 0x8, /**< Temporary object that does not need to be freed really */
|
||||
UCL_OBJECT_MULTILINE = 0x10, /**< String should be displayed as multiline string */
|
||||
UCL_OBJECT_MULTIVALUE = 0x20 /**< Object is a key with multiple values */
|
||||
} ucl_object_flags_t;
|
||||
|
||||
/**
|
||||
@ -195,14 +199,21 @@ typedef struct ucl_object_s {
|
||||
const char *key; /**< Key of an object */
|
||||
struct ucl_object_s *next; /**< Array handle */
|
||||
struct ucl_object_s *prev; /**< Array handle */
|
||||
unsigned char* trash_stack[2]; /**< Pointer to allocated chunks */
|
||||
unsigned keylen; /**< Lenght of a key */
|
||||
unsigned len; /**< Size of an object */
|
||||
enum ucl_type type; /**< Real type */
|
||||
uint16_t ref; /**< Reference count */
|
||||
uint32_t keylen; /**< Lenght of a key */
|
||||
uint32_t len; /**< Size of an object */
|
||||
uint32_t ref; /**< Reference count */
|
||||
uint16_t flags; /**< Object flags */
|
||||
uint16_t type; /**< Real type */
|
||||
unsigned char* trash_stack[2]; /**< Pointer to allocated chunks */
|
||||
} ucl_object_t;
|
||||
|
||||
/**
|
||||
* Destructor type for userdata objects
|
||||
* @param ud user specified data pointer
|
||||
*/
|
||||
typedef void (*ucl_userdata_dtor)(void *ud);
|
||||
typedef const char* (*ucl_userdata_emitter)(void *ud);
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
@ -238,6 +249,31 @@ UCL_EXTERN ucl_object_t* ucl_object_new (void) UCL_WARN_UNUSED_RESULT;
|
||||
*/
|
||||
UCL_EXTERN ucl_object_t* ucl_object_typed_new (ucl_type_t type) UCL_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Create new object with type and priority specified
|
||||
* @param type type of a new object
|
||||
* @param priority priority of an object
|
||||
* @return new object
|
||||
*/
|
||||
UCL_EXTERN ucl_object_t* ucl_object_new_full (ucl_type_t type, unsigned priority)
|
||||
UCL_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Create new object with userdata dtor
|
||||
* @param dtor destructor function
|
||||
* @return new object
|
||||
*/
|
||||
UCL_EXTERN ucl_object_t* ucl_object_new_userdata (ucl_userdata_dtor dtor,
|
||||
ucl_userdata_emitter emitter) UCL_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Perform deep copy of an object copying everything
|
||||
* @param other object to copy
|
||||
* @return new object with refcount equal to 1
|
||||
*/
|
||||
UCL_EXTERN ucl_object_t * ucl_object_copy (const ucl_object_t *other)
|
||||
UCL_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Return the type of an object
|
||||
* @return the object type
|
||||
@ -293,7 +329,7 @@ UCL_EXTERN ucl_object_t* ucl_object_frombool (bool bv) UCL_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Insert a object 'elt' to the hash 'top' and associate it with key 'key'
|
||||
* @param top destination object (will be created automatically if top is NULL)
|
||||
* @param top destination object (must be of type UCL_OBJECT)
|
||||
* @param elt element to insert (must NOT be NULL)
|
||||
* @param key key to associate with this object (either const or preallocated)
|
||||
* @param keylen length of the key (or 0 for NULL terminated keys)
|
||||
@ -306,7 +342,7 @@ UCL_EXTERN bool ucl_object_insert_key (ucl_object_t *top, ucl_object_t *elt,
|
||||
/**
|
||||
* Replace a object 'elt' to the hash 'top' and associate it with key 'key', old object will be unrefed,
|
||||
* if no object has been found this function works like ucl_object_insert_key()
|
||||
* @param top destination object (will be created automatically if top is NULL)
|
||||
* @param top destination object (must be of type UCL_OBJECT)
|
||||
* @param elt element to insert (must NOT be NULL)
|
||||
* @param key key to associate with this object (either const or preallocated)
|
||||
* @param keylen length of the key (or 0 for NULL terminated keys)
|
||||
@ -316,6 +352,15 @@ UCL_EXTERN bool ucl_object_insert_key (ucl_object_t *top, ucl_object_t *elt,
|
||||
UCL_EXTERN bool ucl_object_replace_key (ucl_object_t *top, ucl_object_t *elt,
|
||||
const char *key, size_t keylen, bool copy_key);
|
||||
|
||||
/**
|
||||
* Merge the keys from one object to another object. Overwrite on conflict
|
||||
* @param top destination object (must be of type UCL_OBJECT)
|
||||
* @param elt element to insert (must be of type UCL_OBJECT)
|
||||
* @param copy copy rather than reference the elements
|
||||
* @return true if all keys have been merged
|
||||
*/
|
||||
UCL_EXTERN bool ucl_object_merge (ucl_object_t *top, ucl_object_t *elt, bool copy);
|
||||
|
||||
/**
|
||||
* Delete a object associated with key 'key', old object will be unrefered,
|
||||
* @param top object
|
||||
@ -335,8 +380,9 @@ UCL_EXTERN bool ucl_object_delete_key (ucl_object_t *top,
|
||||
|
||||
|
||||
/**
|
||||
* Delete key from `top` object returning the object deleted. This object is not
|
||||
* released
|
||||
* Removes `key` from `top` object, returning the object that was removed. This
|
||||
* object is not released, caller must unref the returned object when it is no
|
||||
* longer needed.
|
||||
* @param top object
|
||||
* @param key key to remove
|
||||
* @param keylen length of the key (or 0 for NULL terminated keys)
|
||||
@ -346,8 +392,9 @@ UCL_EXTERN ucl_object_t* ucl_object_pop_keyl (ucl_object_t *top, const char *key
|
||||
size_t keylen) UCL_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Delete key from `top` object returning the object deleted. This object is not
|
||||
* released
|
||||
* Removes `key` from `top` object returning the object that was removed. This
|
||||
* object is not released, caller must unref the returned object when it is no
|
||||
* longer needed.
|
||||
* @param top object
|
||||
* @param key key to remove
|
||||
* @return removed object or NULL if object has not been found
|
||||
@ -356,9 +403,9 @@ UCL_EXTERN ucl_object_t* ucl_object_pop_key (ucl_object_t *top, const char *key)
|
||||
UCL_WARN_UNUSED_RESULT;
|
||||
|
||||
/**
|
||||
* Insert a object 'elt' to the hash 'top' and associate it with key 'key', if the specified key exist,
|
||||
* try to merge its content
|
||||
* @param top destination object (will be created automatically if top is NULL)
|
||||
* Insert a object 'elt' to the hash 'top' and associate it with key 'key', if
|
||||
* the specified key exist, try to merge its content
|
||||
* @param top destination object (must be of type UCL_OBJECT)
|
||||
* @param elt element to insert (must NOT be NULL)
|
||||
* @param key key to associate with this object (either const or preallocated)
|
||||
* @param keylen length of the key (or 0 for NULL terminated keys)
|
||||
@ -369,8 +416,8 @@ UCL_EXTERN bool ucl_object_insert_key_merged (ucl_object_t *top, ucl_object_t *e
|
||||
const char *key, size_t keylen, bool copy_key);
|
||||
|
||||
/**
|
||||
* Append an element to the front of array object
|
||||
* @param top destination object (will be created automatically if top is NULL)
|
||||
* Append an element to the end of array object
|
||||
* @param top destination object (must NOT be NULL)
|
||||
* @param elt element to append (must NOT be NULL)
|
||||
* @return true if value has been inserted
|
||||
*/
|
||||
@ -379,7 +426,7 @@ UCL_EXTERN bool ucl_array_append (ucl_object_t *top,
|
||||
|
||||
/**
|
||||
* Append an element to the start of array object
|
||||
* @param top destination object (will be created automatically if top is NULL)
|
||||
* @param top destination object (must NOT be NULL)
|
||||
* @param elt element to append (must NOT be NULL)
|
||||
* @return true if value has been inserted
|
||||
*/
|
||||
@ -387,8 +434,19 @@ UCL_EXTERN bool ucl_array_prepend (ucl_object_t *top,
|
||||
ucl_object_t *elt);
|
||||
|
||||
/**
|
||||
* Removes an element `elt` from the array `top`. Caller must unref the returned object when it is not
|
||||
* needed.
|
||||
* Merge all elements of second array into the first array
|
||||
* @param top destination array (must be of type UCL_ARRAY)
|
||||
* @param elt array to copy elements from (must be of type UCL_ARRAY)
|
||||
* @param copy copy elements instead of referencing them
|
||||
* @return true if arrays were merged
|
||||
*/
|
||||
UCL_EXTERN bool ucl_array_merge (ucl_object_t *top, ucl_object_t *elt,
|
||||
bool copy);
|
||||
|
||||
/**
|
||||
* Removes an element `elt` from the array `top`, returning the object that was
|
||||
* removed. This object is not released, caller must unref the returned object
|
||||
* when it is no longer needed.
|
||||
* @param top array ucl object
|
||||
* @param elt element to remove
|
||||
* @return removed element or NULL if `top` is NULL or not an array
|
||||
@ -411,35 +469,50 @@ UCL_EXTERN const ucl_object_t* ucl_array_head (const ucl_object_t *top);
|
||||
UCL_EXTERN const ucl_object_t* ucl_array_tail (const ucl_object_t *top);
|
||||
|
||||
/**
|
||||
* Removes the last element from the array `top`. Caller must unref the returned object when it is not
|
||||
* needed.
|
||||
* Removes the last element from the array `top`, returning the object that was
|
||||
* removed. This object is not released, caller must unref the returned object
|
||||
* when it is no longer needed.
|
||||
* @param top array ucl object
|
||||
* @return removed element or NULL if `top` is NULL or not an array
|
||||
*/
|
||||
UCL_EXTERN ucl_object_t* ucl_array_pop_last (ucl_object_t *top);
|
||||
|
||||
/**
|
||||
* Return object identified by an index of the array `top`
|
||||
* @param obj object to get a key from (must be of type UCL_ARRAY)
|
||||
* @param index index to return
|
||||
* @return object at the specified index or NULL if index is not found
|
||||
*/
|
||||
UCL_EXTERN const ucl_object_t* ucl_array_find_index (const ucl_object_t *top,
|
||||
unsigned int index);
|
||||
|
||||
/**
|
||||
* Removes the first element from the array `top`. Caller must unref the returned object when it is not
|
||||
* needed.
|
||||
* Removes the first element from the array `top`, returning the object that was
|
||||
* removed. This object is not released, caller must unref the returned object
|
||||
* when it is no longer needed.
|
||||
* @param top array ucl object
|
||||
* @return removed element or NULL if `top` is NULL or not an array
|
||||
*/
|
||||
UCL_EXTERN ucl_object_t* ucl_array_pop_first (ucl_object_t *top);
|
||||
|
||||
/**
|
||||
* Return object identified by index of the array `top`
|
||||
* @param top object to get a key from (must be of type UCL_ARRAY)
|
||||
* @param index array index to return
|
||||
* @return object at the specified index or NULL if index is not found
|
||||
*/
|
||||
UCL_EXTERN const ucl_object_t* ucl_array_find_index (const ucl_object_t *top,
|
||||
unsigned int index);
|
||||
|
||||
/**
|
||||
* Replace an element in an array with a different element, returning the object
|
||||
* that was replaced. This object is not released, caller must unref the
|
||||
* returned object when it is no longer needed.
|
||||
* @param top destination object (must be of type UCL_ARRAY)
|
||||
* @param elt element to append (must NOT be NULL)
|
||||
* @param index array index in destination to overwrite with elt
|
||||
* @return object that was replaced or NULL if index is not found
|
||||
*/
|
||||
ucl_object_t *
|
||||
ucl_array_replace_index (ucl_object_t *top, ucl_object_t *elt,
|
||||
unsigned int index);
|
||||
|
||||
/**
|
||||
* Append a element to another element forming an implicit array
|
||||
* @param head head to append (may be NULL)
|
||||
* @param elt new element
|
||||
* @return true if element has been inserted
|
||||
* @return the new implicit array
|
||||
*/
|
||||
UCL_EXTERN ucl_object_t * ucl_elt_append (ucl_object_t *head,
|
||||
ucl_object_t *elt);
|
||||
@ -533,7 +606,7 @@ UCL_EXTERN const char* ucl_object_tolstring (const ucl_object_t *obj, size_t *tl
|
||||
* Return object identified by a key in the specified object
|
||||
* @param obj object to get a key from (must be of type UCL_OBJECT)
|
||||
* @param key key to search
|
||||
* @return object matched the specified key or NULL if key is not found
|
||||
* @return object matching the specified key or NULL if key was not found
|
||||
*/
|
||||
UCL_EXTERN const ucl_object_t* ucl_object_find_key (const ucl_object_t *obj,
|
||||
const char *key);
|
||||
@ -543,7 +616,7 @@ UCL_EXTERN const ucl_object_t* ucl_object_find_key (const ucl_object_t *obj,
|
||||
* @param obj object to get a key from (must be of type UCL_OBJECT)
|
||||
* @param key key to search
|
||||
* @param klen length of a key
|
||||
* @return object matched the specified key or NULL if key is not found
|
||||
* @return object matching the specified key or NULL if key was not found
|
||||
*/
|
||||
UCL_EXTERN const ucl_object_t* ucl_object_find_keyl (const ucl_object_t *obj,
|
||||
const char *key, size_t klen);
|
||||
@ -575,6 +648,7 @@ UCL_EXTERN const char* ucl_object_keyl (const ucl_object_t *obj, size_t *len);
|
||||
/**
|
||||
* Increase reference count for an object
|
||||
* @param obj object to ref
|
||||
* @return the referenced object
|
||||
*/
|
||||
UCL_EXTERN ucl_object_t* ucl_object_ref (const ucl_object_t *obj);
|
||||
|
||||
@ -611,6 +685,21 @@ UCL_EXTERN int ucl_object_compare (const ucl_object_t *o1,
|
||||
UCL_EXTERN void ucl_object_array_sort (ucl_object_t *ar,
|
||||
int (*cmp)(const ucl_object_t *o1, const ucl_object_t *o2));
|
||||
|
||||
/**
|
||||
* Get the priority for specific UCL object
|
||||
* @param obj any ucl object
|
||||
* @return priority of an object
|
||||
*/
|
||||
UCL_EXTERN unsigned int ucl_object_get_priority (const ucl_object_t *obj);
|
||||
|
||||
/**
|
||||
* Set explicit priority of an object.
|
||||
* @param obj any ucl object
|
||||
* @param priority new priroity value (only 4 least significant bits are considred)
|
||||
*/
|
||||
UCL_EXTERN void ucl_object_set_priority (ucl_object_t *obj,
|
||||
unsigned int priority);
|
||||
|
||||
/**
|
||||
* Opaque iterator object
|
||||
*/
|
||||
@ -640,11 +729,14 @@ UCL_EXTERN const ucl_object_t* ucl_iterate_object (const ucl_object_t *obj,
|
||||
* Macro handler for a parser
|
||||
* @param data the content of macro
|
||||
* @param len the length of content
|
||||
* @param arguments arguments object
|
||||
* @param ud opaque user data
|
||||
* @param err error pointer
|
||||
* @return true if macro has been parsed
|
||||
*/
|
||||
typedef bool (*ucl_macro_handler) (const unsigned char *data, size_t len, void* ud);
|
||||
typedef bool (*ucl_macro_handler) (const unsigned char *data, size_t len,
|
||||
const ucl_object_t *arguments,
|
||||
void* ud);
|
||||
|
||||
/* Opaque parser */
|
||||
struct ucl_parser;
|
||||
@ -702,12 +794,23 @@ UCL_EXTERN void ucl_parser_set_variables_handler (struct ucl_parser *parser,
|
||||
* @param parser parser structure
|
||||
* @param data the pointer to the beginning of a chunk
|
||||
* @param len the length of a chunk
|
||||
* @param err if *err is NULL it is set to parser error
|
||||
* @return true if chunk has been added and false in case of error
|
||||
*/
|
||||
UCL_EXTERN bool ucl_parser_add_chunk (struct ucl_parser *parser,
|
||||
const unsigned char *data, size_t len);
|
||||
|
||||
/**
|
||||
* Load new chunk to a parser with the specified priority
|
||||
* @param parser parser structure
|
||||
* @param data the pointer to the beginning of a chunk
|
||||
* @param len the length of a chunk
|
||||
* @param priority the desired priority of a chunk (only 4 least significant bits
|
||||
* are considered for this parameter)
|
||||
* @return true if chunk has been added and false in case of error
|
||||
*/
|
||||
UCL_EXTERN bool ucl_parser_add_chunk_priority (struct ucl_parser *parser,
|
||||
const unsigned char *data, size_t len, unsigned priority);
|
||||
|
||||
/**
|
||||
* Load ucl object from a string
|
||||
* @param parser parser structure
|
||||
@ -835,7 +938,7 @@ struct ucl_emitter_context {
|
||||
/** A set of output operations */
|
||||
const struct ucl_emitter_operations *ops;
|
||||
/** Current amount of indent tabs */
|
||||
unsigned int ident;
|
||||
unsigned int indent;
|
||||
/** Top level object */
|
||||
const ucl_object_t *top;
|
||||
/** The rest of context */
|
||||
|
@ -7,5 +7,5 @@ Name: LibUCL
|
||||
Description: Universal configuration library
|
||||
Version: @UCL_VERSION@
|
||||
Libs: -L${libdir} -lucl
|
||||
Libs.private: @LIBS_EXTRA@
|
||||
Libs.private: @LIBS_EXTRA@ @LUA_LIB@
|
||||
Cflags: -I${includedir}/
|
||||
|
26
lua/Makefile.am
Normal file
26
lua/Makefile.am
Normal file
@ -0,0 +1,26 @@
|
||||
ucl_common_cflags= -I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/include \
|
||||
-I$(top_srcdir)/uthash \
|
||||
-Wall -W -Wno-unused-parameter -Wno-pointer-sign
|
||||
luaexec_LTLIBRARIES= ucl.la
|
||||
ucl_la_SOURCES= lua_ucl.c
|
||||
ucl_la_CFLAGS= $(ucl_common_cflags) \
|
||||
@LUA_INCLUDE@
|
||||
ucl_la_LDFLAGS = -module -export-dynamic -avoid-version
|
||||
ucl_la_LIBADD= $(top_srcdir)/src/libucl.la \
|
||||
@LIBFETCH_LIBS@ \
|
||||
@LIBCRYPTO_LIB@ \
|
||||
@LIBREGEX_LIB@ \
|
||||
@CURL_LIBS@ \
|
||||
@LUA_LIB@
|
||||
|
||||
include_HEADERS= $(top_srcdir)/include/lua_ucl.h
|
||||
|
||||
ROCKSPEC = $(PACKAGE)-$(VERSION)-1.rockspec
|
||||
EXTRA_DIST = $(PACKAGE).rockspec.in \
|
||||
test.lua
|
||||
DISTCLEANFILES = $(PACKAGE).rockspec
|
||||
|
||||
$(ROCKSPEC): $(PACKAGE).rockspec dist
|
||||
sed -e 's/@MD5@/'`$(MD5SUM) $(distdir).tar.gz | \
|
||||
cut -d " " -f 1`'/g' < $(PACKAGE).rockspec > $@
|
26
lua/libucl.rockspec.in
Normal file
26
lua/libucl.rockspec.in
Normal file
@ -0,0 +1,26 @@
|
||||
package="@PACKAGE@"
|
||||
version="@VERSION@-1"
|
||||
source = {
|
||||
url = "https://github.com/downloads/vstakhov/@PACKAGE@/@PACKAGE@-@VERSION@.tar.gz",
|
||||
md5 = "@MD5@",
|
||||
dir = "@PACKAGE@-@VERSION@"
|
||||
}
|
||||
description = {
|
||||
summary = "UCL - json like configuration language",
|
||||
detailed = [[
|
||||
UCL is heavily infused by nginx configuration as the example
|
||||
of a convenient configuration system.
|
||||
However, UCL is fully compatible with JSON format and is able
|
||||
to parse json files.
|
||||
]],
|
||||
homepage = "http://github.com/vstakhov/@PACKAGE@/",
|
||||
license = ""
|
||||
}
|
||||
dependencies = {
|
||||
"lua >= 5.1"
|
||||
}
|
||||
build = {
|
||||
type = "command",
|
||||
build_command = "LUA=$(LUA) CPPFLAGS=-I$(LUA_INCDIR) ./configure --prefix=$(PREFIX) --libdir=$(LIBDIR) --datadir=$(LUADIR) && make clean && make",
|
||||
install_command = "make install"
|
||||
}
|
820
lua/lua_ucl.c
Normal file
820
lua/lua_ucl.c
Normal file
@ -0,0 +1,820 @@
|
||||
/* Copyright (c) 2014, Vsevolod Stakhov
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file lua ucl bindings
|
||||
*/
|
||||
|
||||
#include "ucl.h"
|
||||
#include "ucl_internal.h"
|
||||
#include "lua_ucl.h"
|
||||
#include <strings.h>
|
||||
|
||||
/***
|
||||
* @module ucl
|
||||
* This lua module allows to parse objects from strings and to store data into
|
||||
* ucl objects. It uses `libucl` C library to parse and manipulate with ucl objects.
|
||||
* @example
|
||||
local ucl = require("ucl")
|
||||
|
||||
local parser = ucl.parser()
|
||||
local res,err = parser:parse_string('{key=value}')
|
||||
|
||||
if not res then
|
||||
print('parser error: ' .. err)
|
||||
else
|
||||
local obj = parser:get_object()
|
||||
local got = ucl.to_format(obj, 'json')
|
||||
endif
|
||||
|
||||
local table = {
|
||||
str = 'value',
|
||||
num = 100500,
|
||||
null = ucl.null,
|
||||
func = function ()
|
||||
return 'huh'
|
||||
end
|
||||
}
|
||||
|
||||
print(ucl.to_format(table, 'ucl'))
|
||||
-- Output:
|
||||
--[[
|
||||
num = 100500;
|
||||
str = "value";
|
||||
null = null;
|
||||
func = "huh";
|
||||
--]]
|
||||
*/
|
||||
|
||||
#define PARSER_META "ucl.parser.meta"
|
||||
#define EMITTER_META "ucl.emitter.meta"
|
||||
#define NULL_META "null.emitter.meta"
|
||||
|
||||
static int ucl_object_lua_push_array (lua_State *L, const ucl_object_t *obj);
|
||||
static int ucl_object_lua_push_scalar (lua_State *L, const ucl_object_t *obj, bool allow_array);
|
||||
static ucl_object_t* ucl_object_lua_fromtable (lua_State *L, int idx);
|
||||
static ucl_object_t* ucl_object_lua_fromelt (lua_State *L, int idx);
|
||||
|
||||
static void *ucl_null;
|
||||
|
||||
/**
|
||||
* Push a single element of an object to lua
|
||||
* @param L
|
||||
* @param key
|
||||
* @param obj
|
||||
*/
|
||||
static void
|
||||
ucl_object_lua_push_element (lua_State *L, const char *key,
|
||||
const ucl_object_t *obj)
|
||||
{
|
||||
lua_pushstring (L, key);
|
||||
ucl_object_push_lua (L, obj, true);
|
||||
lua_settable (L, -3);
|
||||
}
|
||||
|
||||
static void
|
||||
lua_ucl_userdata_dtor (void *ud)
|
||||
{
|
||||
struct ucl_lua_funcdata *fd = (struct ucl_lua_funcdata *)ud;
|
||||
|
||||
luaL_unref (fd->L, LUA_REGISTRYINDEX, fd->idx);
|
||||
if (fd->ret != NULL) {
|
||||
free (fd->ret);
|
||||
}
|
||||
free (fd);
|
||||
}
|
||||
|
||||
static const char *
|
||||
lua_ucl_userdata_emitter (void *ud)
|
||||
{
|
||||
struct ucl_lua_funcdata *fd = (struct ucl_lua_funcdata *)ud;
|
||||
const char *out = "";
|
||||
|
||||
lua_rawgeti (fd->L, LUA_REGISTRYINDEX, fd->idx);
|
||||
|
||||
lua_pcall (fd->L, 0, 1, 0);
|
||||
out = lua_tostring (fd->L, -1);
|
||||
|
||||
if (out != NULL) {
|
||||
/* We need to store temporary string in a more appropriate place */
|
||||
if (fd->ret) {
|
||||
free (fd->ret);
|
||||
}
|
||||
fd->ret = strdup (out);
|
||||
}
|
||||
|
||||
lua_settop (fd->L, 0);
|
||||
|
||||
return fd->ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a single object to lua
|
||||
* @param L
|
||||
* @param obj
|
||||
* @return
|
||||
*/
|
||||
static int
|
||||
ucl_object_lua_push_object (lua_State *L, const ucl_object_t *obj,
|
||||
bool allow_array)
|
||||
{
|
||||
const ucl_object_t *cur;
|
||||
ucl_object_iter_t it = NULL;
|
||||
int nelt = 0;
|
||||
|
||||
if (allow_array && obj->next != NULL) {
|
||||
/* Actually we need to push this as an array */
|
||||
return ucl_object_lua_push_array (L, obj);
|
||||
}
|
||||
|
||||
/* Optimize allocation by preallocation of table */
|
||||
while (ucl_iterate_object (obj, &it, true) != NULL) {
|
||||
nelt ++;
|
||||
}
|
||||
|
||||
lua_createtable (L, 0, nelt);
|
||||
it = NULL;
|
||||
|
||||
while ((cur = ucl_iterate_object (obj, &it, true)) != NULL) {
|
||||
ucl_object_lua_push_element (L, ucl_object_key (cur), cur);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push an array to lua as table indexed by integers
|
||||
* @param L
|
||||
* @param obj
|
||||
* @return
|
||||
*/
|
||||
static int
|
||||
ucl_object_lua_push_array (lua_State *L, const ucl_object_t *obj)
|
||||
{
|
||||
const ucl_object_t *cur;
|
||||
int i = 1, nelt = 0;
|
||||
|
||||
/* Optimize allocation by preallocation of table */
|
||||
LL_FOREACH (obj, cur) {
|
||||
nelt ++;
|
||||
}
|
||||
|
||||
lua_createtable (L, nelt, 0);
|
||||
|
||||
LL_FOREACH (obj, cur) {
|
||||
ucl_object_push_lua (L, cur, false);
|
||||
lua_rawseti (L, -2, i);
|
||||
i ++;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a simple object to lua depending on its actual type
|
||||
*/
|
||||
static int
|
||||
ucl_object_lua_push_scalar (lua_State *L, const ucl_object_t *obj,
|
||||
bool allow_array)
|
||||
{
|
||||
struct ucl_lua_funcdata *fd;
|
||||
|
||||
if (allow_array && obj->next != NULL) {
|
||||
/* Actually we need to push this as an array */
|
||||
return ucl_object_lua_push_array (L, obj);
|
||||
}
|
||||
|
||||
switch (obj->type) {
|
||||
case UCL_BOOLEAN:
|
||||
lua_pushboolean (L, ucl_obj_toboolean (obj));
|
||||
break;
|
||||
case UCL_STRING:
|
||||
lua_pushstring (L, ucl_obj_tostring (obj));
|
||||
break;
|
||||
case UCL_INT:
|
||||
#if LUA_VERSION_NUM >= 501
|
||||
lua_pushinteger (L, ucl_obj_toint (obj));
|
||||
#else
|
||||
lua_pushnumber (L, ucl_obj_toint (obj));
|
||||
#endif
|
||||
break;
|
||||
case UCL_FLOAT:
|
||||
case UCL_TIME:
|
||||
lua_pushnumber (L, ucl_obj_todouble (obj));
|
||||
break;
|
||||
case UCL_NULL:
|
||||
lua_getfield (L, LUA_REGISTRYINDEX, "ucl.null");
|
||||
break;
|
||||
case UCL_USERDATA:
|
||||
fd = (struct ucl_lua_funcdata *)obj->value.ud;
|
||||
lua_rawgeti (L, LUA_REGISTRYINDEX, fd->idx);
|
||||
break;
|
||||
default:
|
||||
lua_pushnil (L);
|
||||
break;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/***
|
||||
* @function ucl_object_push_lua(L, obj, allow_array)
|
||||
* This is a `C` function to push `UCL` object as lua variable. This function
|
||||
* converts `obj` to lua representation using the following conversions:
|
||||
*
|
||||
* - *scalar* values are directly presented by lua objects
|
||||
* - *userdata* values are converted to lua function objects using `LUA_REGISTRYINDEX`,
|
||||
* this can be used to pass functions from lua to c and vice-versa
|
||||
* - *arrays* are converted to lua tables with numeric indicies suitable for `ipairs` iterations
|
||||
* - *objects* are converted to lua tables with string indicies
|
||||
* @param {lua_State} L lua state pointer
|
||||
* @param {ucl_object_t} obj object to push
|
||||
* @param {bool} allow_array expand implicit arrays (should be true for all but partial arrays)
|
||||
* @return {int} `1` if an object is pushed to lua
|
||||
*/
|
||||
int
|
||||
ucl_object_push_lua (lua_State *L, const ucl_object_t *obj, bool allow_array)
|
||||
{
|
||||
switch (obj->type) {
|
||||
case UCL_OBJECT:
|
||||
return ucl_object_lua_push_object (L, obj, allow_array);
|
||||
case UCL_ARRAY:
|
||||
return ucl_object_lua_push_array (L, obj->value.av);
|
||||
default:
|
||||
return ucl_object_lua_push_scalar (L, obj, allow_array);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse lua table into object top
|
||||
* @param L
|
||||
* @param top
|
||||
* @param idx
|
||||
*/
|
||||
static ucl_object_t *
|
||||
ucl_object_lua_fromtable (lua_State *L, int idx)
|
||||
{
|
||||
ucl_object_t *obj, *top = NULL;
|
||||
size_t keylen;
|
||||
const char *k;
|
||||
bool is_array = true;
|
||||
int max = INT_MIN;
|
||||
|
||||
if (idx < 0) {
|
||||
/* For negative indicies we want to invert them */
|
||||
idx = lua_gettop (L) + idx + 1;
|
||||
}
|
||||
/* Check for array */
|
||||
lua_pushnil (L);
|
||||
while (lua_next (L, idx) != 0) {
|
||||
if (lua_type (L, -2) == LUA_TNUMBER) {
|
||||
double num = lua_tonumber (L, -2);
|
||||
if (num == (int)num) {
|
||||
if (num > max) {
|
||||
max = num;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Keys are not integer */
|
||||
lua_pop (L, 2);
|
||||
is_array = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Keys are not numeric */
|
||||
lua_pop (L, 2);
|
||||
is_array = false;
|
||||
break;
|
||||
}
|
||||
lua_pop (L, 1);
|
||||
}
|
||||
|
||||
/* Table iterate */
|
||||
if (is_array) {
|
||||
int i;
|
||||
|
||||
top = ucl_object_typed_new (UCL_ARRAY);
|
||||
for (i = 1; i <= max; i ++) {
|
||||
lua_pushinteger (L, i);
|
||||
lua_gettable (L, idx);
|
||||
obj = ucl_object_lua_fromelt (L, lua_gettop (L));
|
||||
if (obj != NULL) {
|
||||
ucl_array_append (top, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
lua_pushnil (L);
|
||||
top = ucl_object_typed_new (UCL_OBJECT);
|
||||
while (lua_next (L, idx) != 0) {
|
||||
/* copy key to avoid modifications */
|
||||
k = lua_tolstring (L, -2, &keylen);
|
||||
obj = ucl_object_lua_fromelt (L, lua_gettop (L));
|
||||
|
||||
if (obj != NULL) {
|
||||
ucl_object_insert_key (top, obj, k, keylen, true);
|
||||
}
|
||||
lua_pop (L, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return top;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single element from lua to object obj
|
||||
* @param L
|
||||
* @param obj
|
||||
* @param idx
|
||||
*/
|
||||
static ucl_object_t *
|
||||
ucl_object_lua_fromelt (lua_State *L, int idx)
|
||||
{
|
||||
int type;
|
||||
double num;
|
||||
ucl_object_t *obj = NULL;
|
||||
struct ucl_lua_funcdata *fd;
|
||||
|
||||
type = lua_type (L, idx);
|
||||
|
||||
switch (type) {
|
||||
case LUA_TSTRING:
|
||||
obj = ucl_object_fromstring_common (lua_tostring (L, idx), 0, 0);
|
||||
break;
|
||||
case LUA_TNUMBER:
|
||||
num = lua_tonumber (L, idx);
|
||||
if (num == (int64_t)num) {
|
||||
obj = ucl_object_fromint (num);
|
||||
}
|
||||
else {
|
||||
obj = ucl_object_fromdouble (num);
|
||||
}
|
||||
break;
|
||||
case LUA_TBOOLEAN:
|
||||
obj = ucl_object_frombool (lua_toboolean (L, idx));
|
||||
break;
|
||||
case LUA_TUSERDATA:
|
||||
if (lua_topointer (L, idx) == ucl_null) {
|
||||
obj = ucl_object_typed_new (UCL_NULL);
|
||||
}
|
||||
break;
|
||||
case LUA_TTABLE:
|
||||
case LUA_TFUNCTION:
|
||||
case LUA_TTHREAD:
|
||||
if (luaL_getmetafield (L, idx, "__gen_ucl")) {
|
||||
if (lua_isfunction (L, -1)) {
|
||||
lua_settop (L, 3); /* gen, obj, func */
|
||||
lua_insert (L, 1); /* func, gen, obj */
|
||||
lua_insert (L, 2); /* func, obj, gen */
|
||||
lua_call(L, 2, 1);
|
||||
obj = ucl_object_lua_fromelt (L, 1);
|
||||
}
|
||||
lua_pop (L, 2);
|
||||
}
|
||||
else {
|
||||
if (type == LUA_TTABLE) {
|
||||
obj = ucl_object_lua_fromtable (L, idx);
|
||||
}
|
||||
else if (type == LUA_TFUNCTION) {
|
||||
fd = malloc (sizeof (*fd));
|
||||
if (fd != NULL) {
|
||||
lua_pushvalue (L, idx);
|
||||
fd->L = L;
|
||||
fd->ret = NULL;
|
||||
fd->idx = luaL_ref (L, LUA_REGISTRYINDEX);
|
||||
|
||||
obj = ucl_object_new_userdata (lua_ucl_userdata_dtor,
|
||||
lua_ucl_userdata_emitter);
|
||||
obj->type = UCL_USERDATA;
|
||||
obj->value.ud = (void *)fd;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @function ucl_object_lua_import(L, idx)
|
||||
* Extracts ucl object from lua variable at `idx` position,
|
||||
* @see ucl_object_push_lua for conversion definitions
|
||||
* @param {lua_state} L lua state machine pointer
|
||||
* @param {int} idx index where the source variable is placed
|
||||
* @return {ucl_object_t} new ucl object extracted from lua variable. Reference count of this object is 1,
|
||||
* this object thus needs to be unref'ed after usage.
|
||||
*/
|
||||
ucl_object_t *
|
||||
ucl_object_lua_import (lua_State *L, int idx)
|
||||
{
|
||||
ucl_object_t *obj;
|
||||
int t;
|
||||
|
||||
t = lua_type (L, idx);
|
||||
switch (t) {
|
||||
case LUA_TTABLE:
|
||||
obj = ucl_object_lua_fromtable (L, idx);
|
||||
break;
|
||||
default:
|
||||
obj = ucl_object_lua_fromelt (L, idx);
|
||||
break;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
static int
|
||||
lua_ucl_parser_init (lua_State *L)
|
||||
{
|
||||
struct ucl_parser *parser, **pparser;
|
||||
int flags = 0;
|
||||
|
||||
if (lua_gettop (L) >= 1) {
|
||||
flags = lua_tonumber (L, 1);
|
||||
}
|
||||
|
||||
parser = ucl_parser_new (flags);
|
||||
if (parser == NULL) {
|
||||
lua_pushnil (L);
|
||||
}
|
||||
|
||||
pparser = lua_newuserdata (L, sizeof (parser));
|
||||
*pparser = parser;
|
||||
luaL_getmetatable (L, PARSER_META);
|
||||
lua_setmetatable (L, -2);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static struct ucl_parser *
|
||||
lua_ucl_parser_get (lua_State *L, int index)
|
||||
{
|
||||
return *((struct ucl_parser **) luaL_checkudata(L, index, PARSER_META));
|
||||
}
|
||||
|
||||
/***
|
||||
* @method parser:parse_file(name)
|
||||
* Parse UCL object from file.
|
||||
* @param {string} name filename to parse
|
||||
* @return {bool[, string]} if res is `true` then file has been parsed successfully, otherwise an error string is also returned
|
||||
@example
|
||||
local parser = ucl.parser()
|
||||
local res,err = parser:parse_file('/some/file.conf')
|
||||
|
||||
if not res then
|
||||
print('parser error: ' .. err)
|
||||
else
|
||||
-- Do something with object
|
||||
end
|
||||
*/
|
||||
static int
|
||||
lua_ucl_parser_parse_file (lua_State *L)
|
||||
{
|
||||
struct ucl_parser *parser;
|
||||
const char *file;
|
||||
int ret = 2;
|
||||
|
||||
parser = lua_ucl_parser_get (L, 1);
|
||||
file = luaL_checkstring (L, 2);
|
||||
|
||||
if (parser != NULL && file != NULL) {
|
||||
if (ucl_parser_add_file (parser, file)) {
|
||||
lua_pushboolean (L, true);
|
||||
ret = 1;
|
||||
}
|
||||
else {
|
||||
lua_pushboolean (L, false);
|
||||
lua_pushstring (L, ucl_parser_get_error (parser));
|
||||
}
|
||||
}
|
||||
else {
|
||||
lua_pushboolean (L, false);
|
||||
lua_pushstring (L, "invalid arguments");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/***
|
||||
* @method parser:parse_string(input)
|
||||
* Parse UCL object from file.
|
||||
* @param {string} input string to parse
|
||||
* @return {bool[, string]} if res is `true` then file has been parsed successfully, otherwise an error string is also returned
|
||||
*/
|
||||
static int
|
||||
lua_ucl_parser_parse_string (lua_State *L)
|
||||
{
|
||||
struct ucl_parser *parser;
|
||||
const char *string;
|
||||
size_t llen;
|
||||
int ret = 2;
|
||||
|
||||
parser = lua_ucl_parser_get (L, 1);
|
||||
string = luaL_checklstring (L, 2, &llen);
|
||||
|
||||
if (parser != NULL && string != NULL) {
|
||||
if (ucl_parser_add_chunk (parser, (const unsigned char *)string, llen)) {
|
||||
lua_pushboolean (L, true);
|
||||
ret = 1;
|
||||
}
|
||||
else {
|
||||
lua_pushboolean (L, false);
|
||||
lua_pushstring (L, ucl_parser_get_error (parser));
|
||||
}
|
||||
}
|
||||
else {
|
||||
lua_pushboolean (L, false);
|
||||
lua_pushstring (L, "invalid arguments");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/***
|
||||
* @method parser:get_object()
|
||||
* Get top object from parser and export it to lua representation.
|
||||
* @return {variant or nil} ucl object as lua native variable
|
||||
*/
|
||||
static int
|
||||
lua_ucl_parser_get_object (lua_State *L)
|
||||
{
|
||||
struct ucl_parser *parser;
|
||||
ucl_object_t *obj;
|
||||
int ret = 1;
|
||||
|
||||
parser = lua_ucl_parser_get (L, 1);
|
||||
obj = ucl_parser_get_object (parser);
|
||||
|
||||
if (obj != NULL) {
|
||||
ret = ucl_object_push_lua (L, obj, false);
|
||||
/* no need to keep reference */
|
||||
ucl_object_unref (obj);
|
||||
}
|
||||
else {
|
||||
lua_pushnil (L);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
lua_ucl_parser_gc (lua_State *L)
|
||||
{
|
||||
struct ucl_parser *parser;
|
||||
|
||||
parser = lua_ucl_parser_get (L, 1);
|
||||
ucl_parser_free (parser);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
lua_ucl_parser_mt (lua_State *L)
|
||||
{
|
||||
luaL_newmetatable (L, PARSER_META);
|
||||
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setfield(L, -2, "__index");
|
||||
|
||||
lua_pushcfunction (L, lua_ucl_parser_gc);
|
||||
lua_setfield (L, -2, "__gc");
|
||||
|
||||
lua_pushcfunction (L, lua_ucl_parser_parse_file);
|
||||
lua_setfield (L, -2, "parse_file");
|
||||
|
||||
lua_pushcfunction (L, lua_ucl_parser_parse_string);
|
||||
lua_setfield (L, -2, "parse_string");
|
||||
|
||||
lua_pushcfunction (L, lua_ucl_parser_get_object);
|
||||
lua_setfield (L, -2, "get_object");
|
||||
|
||||
lua_pop (L, 1);
|
||||
}
|
||||
|
||||
static int
|
||||
lua_ucl_to_string (lua_State *L, const ucl_object_t *obj, enum ucl_emitter type)
|
||||
{
|
||||
unsigned char *result;
|
||||
|
||||
result = ucl_object_emit (obj, type);
|
||||
|
||||
if (result != NULL) {
|
||||
lua_pushstring (L, (const char *)result);
|
||||
free (result);
|
||||
}
|
||||
else {
|
||||
lua_pushnil (L);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
lua_ucl_to_json (lua_State *L)
|
||||
{
|
||||
ucl_object_t *obj;
|
||||
int format = UCL_EMIT_JSON;
|
||||
|
||||
if (lua_gettop (L) > 1) {
|
||||
if (lua_toboolean (L, 2)) {
|
||||
format = UCL_EMIT_JSON_COMPACT;
|
||||
}
|
||||
}
|
||||
|
||||
obj = ucl_object_lua_import (L, 1);
|
||||
if (obj != NULL) {
|
||||
lua_ucl_to_string (L, obj, format);
|
||||
ucl_object_unref (obj);
|
||||
}
|
||||
else {
|
||||
lua_pushnil (L);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
lua_ucl_to_config (lua_State *L)
|
||||
{
|
||||
ucl_object_t *obj;
|
||||
|
||||
obj = ucl_object_lua_import (L, 1);
|
||||
if (obj != NULL) {
|
||||
lua_ucl_to_string (L, obj, UCL_EMIT_CONFIG);
|
||||
ucl_object_unref (obj);
|
||||
}
|
||||
else {
|
||||
lua_pushnil (L);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/***
|
||||
* @function ucl.to_format(var, format)
|
||||
* Converts lua variable `var` to the specified `format`. Formats supported are:
|
||||
*
|
||||
* - `json` - fine printed json
|
||||
* - `json-compact` - compacted json
|
||||
* - `config` - fine printed configuration
|
||||
* - `ucl` - same as `config`
|
||||
* - `yaml` - embedded yaml
|
||||
*
|
||||
* If `var` contains function, they are called during output formatting and if
|
||||
* they return string value, then this value is used for ouptut.
|
||||
* @param {variant} var any sort of lua variable (if userdata then metafield `__to_ucl` is searched for output)
|
||||
* @param {string} format any available format
|
||||
* @return {string} string representation of `var` in the specific `format`.
|
||||
* @example
|
||||
local table = {
|
||||
str = 'value',
|
||||
num = 100500,
|
||||
null = ucl.null,
|
||||
func = function ()
|
||||
return 'huh'
|
||||
end
|
||||
}
|
||||
|
||||
print(ucl.to_format(table, 'ucl'))
|
||||
-- Output:
|
||||
--[[
|
||||
num = 100500;
|
||||
str = "value";
|
||||
null = null;
|
||||
func = "huh";
|
||||
--]]
|
||||
*/
|
||||
static int
|
||||
lua_ucl_to_format (lua_State *L)
|
||||
{
|
||||
ucl_object_t *obj;
|
||||
int format = UCL_EMIT_JSON;
|
||||
|
||||
if (lua_gettop (L) > 1) {
|
||||
if (lua_type (L, 2) == LUA_TNUMBER) {
|
||||
format = lua_tonumber (L, 2);
|
||||
if (format < 0 || format >= UCL_EMIT_YAML) {
|
||||
lua_pushnil (L);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else if (lua_type (L, 2) == LUA_TSTRING) {
|
||||
const char *strtype = lua_tostring (L, 2);
|
||||
|
||||
if (strcasecmp (strtype, "json") == 0) {
|
||||
format = UCL_EMIT_JSON;
|
||||
}
|
||||
else if (strcasecmp (strtype, "json-compact") == 0) {
|
||||
format = UCL_EMIT_JSON_COMPACT;
|
||||
}
|
||||
else if (strcasecmp (strtype, "yaml") == 0) {
|
||||
format = UCL_EMIT_YAML;
|
||||
}
|
||||
else if (strcasecmp (strtype, "config") == 0 ||
|
||||
strcasecmp (strtype, "ucl") == 0) {
|
||||
format = UCL_EMIT_CONFIG;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
obj = ucl_object_lua_import (L, 1);
|
||||
if (obj != NULL) {
|
||||
lua_ucl_to_string (L, obj, format);
|
||||
ucl_object_unref (obj);
|
||||
}
|
||||
else {
|
||||
lua_pushnil (L);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
lua_ucl_null_tostring (lua_State* L)
|
||||
{
|
||||
lua_pushstring (L, "null");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
lua_ucl_null_mt (lua_State *L)
|
||||
{
|
||||
luaL_newmetatable (L, NULL_META);
|
||||
|
||||
lua_pushcfunction (L, lua_ucl_null_tostring);
|
||||
lua_setfield (L, -2, "__tostring");
|
||||
|
||||
lua_pop (L, 1);
|
||||
}
|
||||
|
||||
int
|
||||
luaopen_ucl (lua_State *L)
|
||||
{
|
||||
lua_ucl_parser_mt (L);
|
||||
lua_ucl_null_mt (L);
|
||||
|
||||
/* Create the refs weak table: */
|
||||
lua_createtable (L, 0, 2);
|
||||
lua_pushliteral (L, "v"); /* tbl, "v" */
|
||||
lua_setfield (L, -2, "__mode");
|
||||
lua_pushvalue (L, -1); /* tbl, tbl */
|
||||
lua_setmetatable (L, -2); /* tbl */
|
||||
lua_setfield (L, LUA_REGISTRYINDEX, "ucl.refs");
|
||||
|
||||
lua_newtable (L);
|
||||
|
||||
lua_pushcfunction (L, lua_ucl_parser_init);
|
||||
lua_setfield (L, -2, "parser");
|
||||
|
||||
lua_pushcfunction (L, lua_ucl_to_json);
|
||||
lua_setfield (L, -2, "to_json");
|
||||
|
||||
lua_pushcfunction (L, lua_ucl_to_config);
|
||||
lua_setfield (L, -2, "to_config");
|
||||
|
||||
lua_pushcfunction (L, lua_ucl_to_format);
|
||||
lua_setfield (L, -2, "to_format");
|
||||
|
||||
ucl_null = lua_newuserdata (L, 0);
|
||||
luaL_getmetatable (L, NULL_META);
|
||||
lua_setmetatable (L, -2);
|
||||
|
||||
lua_pushvalue (L, -1);
|
||||
lua_setfield (L, LUA_REGISTRYINDEX, "ucl.null");
|
||||
|
||||
lua_setfield (L, -2, "null");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct ucl_lua_funcdata*
|
||||
ucl_object_toclosure (const ucl_object_t *obj)
|
||||
{
|
||||
if (obj == NULL || obj->type != UCL_USERDATA) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (struct ucl_lua_funcdata*)obj->value.ud;
|
||||
}
|
48
lua/test.lua
Normal file
48
lua/test.lua
Normal file
@ -0,0 +1,48 @@
|
||||
local ucl = require("ucl")
|
||||
|
||||
function test_simple()
|
||||
local expect =
|
||||
'['..
|
||||
'"float",1.5,'..
|
||||
'"integer",5,'..
|
||||
'"true",true,'..
|
||||
'"false",false,'..
|
||||
'"null",null,'..
|
||||
'"string","hello",'..
|
||||
'"array",[1,2],'..
|
||||
'"object",{"key":"value"}'..
|
||||
']'
|
||||
|
||||
-- Input to to_value matches the output of to_string:
|
||||
local parser = ucl.parser()
|
||||
local res,err = parser:parse_string(expect)
|
||||
if not res then
|
||||
print('parser error: ' .. err)
|
||||
return 1
|
||||
end
|
||||
|
||||
local obj = parser:get_object()
|
||||
local got = ucl.to_json(obj, true)
|
||||
if expect == got then
|
||||
return 0
|
||||
else
|
||||
print(expect .. " == " .. tostring(got))
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
test_simple()
|
||||
|
||||
local table = {
|
||||
str = 'value',
|
||||
num = 100500,
|
||||
null = ucl.null,
|
||||
func = function ()
|
||||
return 'huh'
|
||||
end,
|
||||
badfunc = function()
|
||||
print("I'm bad")
|
||||
end
|
||||
}
|
||||
|
||||
print(ucl.to_format(table, 'ucl'))
|
4
m4/.gitignore
vendored
Normal file
4
m4/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
# Ignore everything in this directory
|
||||
*
|
||||
# Except this file
|
||||
!.gitignore
|
606
m4/ax_lua.m4
Normal file
606
m4/ax_lua.m4
Normal file
@ -0,0 +1,606 @@
|
||||
# ===========================================================================
|
||||
# http://www.gnu.org/software/autoconf-archive/ax_lua.html
|
||||
# ===========================================================================
|
||||
#
|
||||
# SYNOPSIS
|
||||
#
|
||||
# AX_PROG_LUA[([MINIMUM-VERSION], [TOO-BIG-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])]
|
||||
# AX_LUA_HEADERS[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])]
|
||||
# AX_LUA_LIBS[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])]
|
||||
# AX_LUA_READLINE[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])]
|
||||
#
|
||||
# DESCRIPTION
|
||||
#
|
||||
# Detect a Lua interpreter, optionally specifying a minimum and maximum
|
||||
# version number. Set up important Lua paths, such as the directories in
|
||||
# which to install scripts and modules (shared libraries).
|
||||
#
|
||||
# Also detect Lua headers and libraries. The Lua version contained in the
|
||||
# header is checked to match the Lua interpreter version exactly. When
|
||||
# searching for Lua libraries, the version number is used as a suffix.
|
||||
# This is done with the goal of supporting multiple Lua installs (5.1 and
|
||||
# 5.2 side-by-side).
|
||||
#
|
||||
# A note on compatibility with previous versions: This file has been
|
||||
# mostly rewritten for serial 18. Most developers should be able to use
|
||||
# these macros without needing to modify configure.ac. Care has been taken
|
||||
# to preserve each macro's behavior, but there are some differences:
|
||||
#
|
||||
# 1) AX_WITH_LUA is deprecated; it now expands to the exact same thing as
|
||||
# AX_PROG_LUA with no arguments.
|
||||
#
|
||||
# 2) AX_LUA_HEADERS now checks that the version number defined in lua.h
|
||||
# matches the interpreter version. AX_LUA_HEADERS_VERSION is therefore
|
||||
# unnecessary, so it is deprecated and does not expand to anything.
|
||||
#
|
||||
# 3) The configure flag --with-lua-suffix no longer exists; the user
|
||||
# should instead specify the LUA precious variable on the command line.
|
||||
# See the AX_PROG_LUA description for details.
|
||||
#
|
||||
# Please read the macro descriptions below for more information.
|
||||
#
|
||||
# This file was inspired by Andrew Dalke's and James Henstridge's
|
||||
# python.m4 and Tom Payne's, Matthieu Moy's, and Reuben Thomas's ax_lua.m4
|
||||
# (serial 17). Basically, this file is a mash-up of those two files. I
|
||||
# like to think it combines the best of the two!
|
||||
#
|
||||
# AX_PROG_LUA: Search for the Lua interpreter, and set up important Lua
|
||||
# paths. Adds precious variable LUA, which may contain the path of the Lua
|
||||
# interpreter. If LUA is blank, the user's path is searched for an
|
||||
# suitable interpreter.
|
||||
#
|
||||
# If MINIMUM-VERSION is supplied, then only Lua interpreters with a
|
||||
# version number greater or equal to MINIMUM-VERSION will be accepted. If
|
||||
# TOO-BIG- VERSION is also supplied, then only Lua interpreters with a
|
||||
# version number greater or equal to MINIMUM-VERSION and less than
|
||||
# TOO-BIG-VERSION will be accepted.
|
||||
#
|
||||
# The Lua version number, LUA_VERSION, is found from the interpreter, and
|
||||
# substituted. LUA_PLATFORM is also found, but not currently supported (no
|
||||
# standard representation).
|
||||
#
|
||||
# Finally, the macro finds four paths:
|
||||
#
|
||||
# luadir Directory to install Lua scripts.
|
||||
# pkgluadir $luadir/$PACKAGE
|
||||
# luaexecdir Directory to install Lua modules.
|
||||
# pkgluaexecdir $luaexecdir/$PACKAGE
|
||||
#
|
||||
# These paths a found based on $prefix, $exec_prefix, Lua's package.path,
|
||||
# and package.cpath. The first path of package.path beginning with $prefix
|
||||
# is selected as luadir. The first path of package.cpath beginning with
|
||||
# $exec_prefix is used as luaexecdir. This should work on all reasonable
|
||||
# Lua installations. If a path cannot be determined, a default path is
|
||||
# used. Of course, the user can override these later when invoking make.
|
||||
#
|
||||
# luadir Default: $prefix/share/lua/$LUA_VERSION
|
||||
# luaexecdir Default: $exec_prefix/lib/lua/$LUA_VERSION
|
||||
#
|
||||
# These directories can be used by Automake as install destinations. The
|
||||
# variable name minus 'dir' needs to be used as a prefix to the
|
||||
# appropriate Automake primary, e.g. lua_SCRIPS or luaexec_LIBRARIES.
|
||||
#
|
||||
# If an acceptable Lua interpreter is found, then ACTION-IF-FOUND is
|
||||
# performed, otherwise ACTION-IF-NOT-FOUND is preformed. If ACTION-IF-NOT-
|
||||
# FOUND is blank, then it will default to printing an error. To prevent
|
||||
# the default behavior, give ':' as an action.
|
||||
#
|
||||
# AX_LUA_HEADERS: Search for Lua headers. Requires that AX_PROG_LUA be
|
||||
# expanded before this macro. Adds precious variable LUA_INCLUDE, which
|
||||
# may contain Lua specific include flags, e.g. -I/usr/include/lua5.1. If
|
||||
# LUA_INCLUDE is blank, then this macro will attempt to find suitable
|
||||
# flags.
|
||||
#
|
||||
# LUA_INCLUDE can be used by Automake to compile Lua modules or
|
||||
# executables with embedded interpreters. The *_CPPFLAGS variables should
|
||||
# be used for this purpose, e.g. myprog_CPPFLAGS = $(LUA_INCLUDE).
|
||||
#
|
||||
# This macro searches for the header lua.h (and others). The search is
|
||||
# performed with a combination of CPPFLAGS, CPATH, etc, and LUA_INCLUDE.
|
||||
# If the search is unsuccessful, then some common directories are tried.
|
||||
# If the headers are then found, then LUA_INCLUDE is set accordingly.
|
||||
#
|
||||
# The paths automatically searched are:
|
||||
#
|
||||
# * /usr/include/luaX.Y
|
||||
# * /usr/include/lua/X.Y
|
||||
# * /usr/include/luaXY
|
||||
# * /usr/local/include/luaX.Y
|
||||
# * /usr/local/include/lua-X.Y
|
||||
# * /usr/local/include/lua/X.Y
|
||||
# * /usr/local/include/luaXY
|
||||
#
|
||||
# (Where X.Y is the Lua version number, e.g. 5.1.)
|
||||
#
|
||||
# The Lua version number found in the headers is always checked to match
|
||||
# the Lua interpreter's version number. Lua headers with mismatched
|
||||
# version numbers are not accepted.
|
||||
#
|
||||
# If headers are found, then ACTION-IF-FOUND is performed, otherwise
|
||||
# ACTION-IF-NOT-FOUND is performed. If ACTION-IF-NOT-FOUND is blank, then
|
||||
# it will default to printing an error. To prevent the default behavior,
|
||||
# set the action to ':'.
|
||||
#
|
||||
# AX_LUA_LIBS: Search for Lua libraries. Requires that AX_PROG_LUA be
|
||||
# expanded before this macro. Adds precious variable LUA_LIB, which may
|
||||
# contain Lua specific linker flags, e.g. -llua5.1. If LUA_LIB is blank,
|
||||
# then this macro will attempt to find suitable flags.
|
||||
#
|
||||
# LUA_LIB can be used by Automake to link Lua modules or executables with
|
||||
# embedded interpreters. The *_LIBADD and *_LDADD variables should be used
|
||||
# for this purpose, e.g. mymod_LIBADD = $(LUA_LIB).
|
||||
#
|
||||
# This macro searches for the Lua library. More technically, it searches
|
||||
# for a library containing the function lua_load. The search is performed
|
||||
# with a combination of LIBS, LIBRARY_PATH, and LUA_LIB.
|
||||
#
|
||||
# If the search determines that some linker flags are missing, then those
|
||||
# flags will be added to LUA_LIB.
|
||||
#
|
||||
# If libraries are found, then ACTION-IF-FOUND is performed, otherwise
|
||||
# ACTION-IF-NOT-FOUND is performed. If ACTION-IF-NOT-FOUND is blank, then
|
||||
# it will default to printing an error. To prevent the default behavior,
|
||||
# set the action to ':'.
|
||||
#
|
||||
# AX_LUA_READLINE: Search for readline headers and libraries. Requires the
|
||||
# AX_LIB_READLINE macro, which is provided by ax_lib_readline.m4 from the
|
||||
# Autoconf Archive.
|
||||
#
|
||||
# If a readline compatible library is found, then ACTION-IF-FOUND is
|
||||
# performed, otherwise ACTION-IF-NOT-FOUND is performed.
|
||||
#
|
||||
# LICENSE
|
||||
#
|
||||
# Copyright (c) 2014 Reuben Thomas <rrt@sc3d.org>
|
||||
# Copyright (c) 2013 Tim Perkins <tprk77@gmail.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the
|
||||
# Free Software Foundation, either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
# Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# As a special exception, the respective Autoconf Macro's copyright owner
|
||||
# gives unlimited permission to copy, distribute and modify the configure
|
||||
# scripts that are the output of Autoconf when processing the Macro. You
|
||||
# need not follow the terms of the GNU General Public License when using
|
||||
# or distributing such scripts, even though portions of the text of the
|
||||
# Macro appear in them. The GNU General Public License (GPL) does govern
|
||||
# all other use of the material that constitutes the Autoconf Macro.
|
||||
#
|
||||
# This special exception to the GPL applies to versions of the Autoconf
|
||||
# Macro released by the Autoconf Archive. When you make and distribute a
|
||||
# modified version of the Autoconf Macro, you may extend this special
|
||||
# exception to the GPL to apply to your modified version as well.
|
||||
|
||||
#serial 23
|
||||
|
||||
dnl =========================================================================
|
||||
dnl AX_PROG_LUA([MINIMUM-VERSION], [TOO-BIG-VERSION],
|
||||
dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
|
||||
dnl =========================================================================
|
||||
AC_DEFUN([AX_PROG_LUA],
|
||||
[
|
||||
dnl Make LUA a precious variable.
|
||||
AC_ARG_VAR([LUA], [The Lua interpreter, e.g. /usr/bin/lua5.1])
|
||||
|
||||
dnl Find a Lua interpreter.
|
||||
m4_define_default([_AX_LUA_INTERPRETER_LIST],
|
||||
[lua lua5.2 lua52 lua5.1 lua51 lua50])
|
||||
|
||||
m4_if([$1], [],
|
||||
[ dnl No version check is needed. Find any Lua interpreter.
|
||||
AS_IF([test "x$LUA" = 'x'],
|
||||
[AC_PATH_PROGS([LUA], [_AX_LUA_INTERPRETER_LIST], [:])])
|
||||
ax_display_LUA='lua'
|
||||
|
||||
dnl At least check if this is a Lua interpreter.
|
||||
AC_MSG_CHECKING([if $LUA is a Lua interpreter])
|
||||
_AX_LUA_CHK_IS_INTRP([$LUA],
|
||||
[AC_MSG_RESULT([yes])],
|
||||
[ AC_MSG_RESULT([no])
|
||||
AC_MSG_ERROR([not a Lua interpreter])
|
||||
])
|
||||
],
|
||||
[ dnl A version check is needed.
|
||||
AS_IF([test "x$LUA" != 'x'],
|
||||
[ dnl Check if this is a Lua interpreter.
|
||||
AC_MSG_CHECKING([if $LUA is a Lua interpreter])
|
||||
_AX_LUA_CHK_IS_INTRP([$LUA],
|
||||
[AC_MSG_RESULT([yes])],
|
||||
[ AC_MSG_RESULT([no])
|
||||
AC_MSG_ERROR([not a Lua interpreter])
|
||||
])
|
||||
dnl Check the version.
|
||||
m4_if([$2], [],
|
||||
[_ax_check_text="whether $LUA version >= $1"],
|
||||
[_ax_check_text="whether $LUA version >= $1, < $2"])
|
||||
AC_MSG_CHECKING([$_ax_check_text])
|
||||
_AX_LUA_CHK_VER([$LUA], [$1], [$2],
|
||||
[AC_MSG_RESULT([yes])],
|
||||
[ AC_MSG_RESULT([no])
|
||||
AC_MSG_ERROR([version is out of range for specified LUA])])
|
||||
ax_display_LUA=$LUA
|
||||
],
|
||||
[ dnl Try each interpreter until we find one that satisfies VERSION.
|
||||
m4_if([$2], [],
|
||||
[_ax_check_text="for a Lua interpreter with version >= $1"],
|
||||
[_ax_check_text="for a Lua interpreter with version >= $1, < $2"])
|
||||
AC_CACHE_CHECK([$_ax_check_text],
|
||||
[ax_cv_pathless_LUA],
|
||||
[ for ax_cv_pathless_LUA in _AX_LUA_INTERPRETER_LIST none; do
|
||||
test "x$ax_cv_pathless_LUA" = 'xnone' && break
|
||||
_AX_LUA_CHK_IS_INTRP([$ax_cv_pathless_LUA], [], [continue])
|
||||
_AX_LUA_CHK_VER([$ax_cv_pathless_LUA], [$1], [$2], [break])
|
||||
done
|
||||
])
|
||||
dnl Set $LUA to the absolute path of $ax_cv_pathless_LUA.
|
||||
AS_IF([test "x$ax_cv_pathless_LUA" = 'xnone'],
|
||||
[LUA=':'],
|
||||
[AC_PATH_PROG([LUA], [$ax_cv_pathless_LUA])])
|
||||
ax_display_LUA=$ax_cv_pathless_LUA
|
||||
])
|
||||
])
|
||||
|
||||
AS_IF([test "x$LUA" = 'x:'],
|
||||
[ dnl Run any user-specified action, or abort.
|
||||
m4_default([$4], [AC_MSG_ERROR([cannot find suitable Lua interpreter])])
|
||||
],
|
||||
[ dnl Query Lua for its version number.
|
||||
AC_CACHE_CHECK([for $ax_display_LUA version], [ax_cv_lua_version],
|
||||
[ ax_cv_lua_version=`$LUA -e 'print(_VERSION:match "(%d+%.%d+)")'` ])
|
||||
AS_IF([test "x$ax_cv_lua_version" = 'x'],
|
||||
[AC_MSG_ERROR([invalid Lua version number])])
|
||||
AC_SUBST([LUA_VERSION], [$ax_cv_lua_version])
|
||||
AC_SUBST([LUA_SHORT_VERSION], [`echo "$LUA_VERSION" | sed 's|\.||'`])
|
||||
|
||||
dnl The following check is not supported:
|
||||
dnl At times (like when building shared libraries) you may want to know
|
||||
dnl which OS platform Lua thinks this is.
|
||||
AC_CACHE_CHECK([for $ax_display_LUA platform], [ax_cv_lua_platform],
|
||||
[ax_cv_lua_platform=`$LUA -e "print('unknown')"`])
|
||||
AC_SUBST([LUA_PLATFORM], [$ax_cv_lua_platform])
|
||||
|
||||
dnl Use the values of $prefix and $exec_prefix for the corresponding
|
||||
dnl values of LUA_PREFIX and LUA_EXEC_PREFIX. These are made distinct
|
||||
dnl variables so they can be overridden if need be. However, the general
|
||||
dnl consensus is that you shouldn't need this ability.
|
||||
AC_SUBST([LUA_PREFIX], ['${prefix}'])
|
||||
AC_SUBST([LUA_EXEC_PREFIX], ['${exec_prefix}'])
|
||||
|
||||
dnl Lua provides no way to query the script directory, and instead
|
||||
dnl provides LUA_PATH. However, we should be able to make a safe educated
|
||||
dnl guess. If the built-in search path contains a directory which is
|
||||
dnl prefixed by $prefix, then we can store scripts there. The first
|
||||
dnl matching path will be used.
|
||||
AC_CACHE_CHECK([for $ax_display_LUA script directory],
|
||||
[ax_cv_lua_luadir],
|
||||
[ AS_IF([test "x$prefix" = 'xNONE'],
|
||||
[ax_lua_prefix=$ac_default_prefix],
|
||||
[ax_lua_prefix=$prefix])
|
||||
|
||||
dnl Initialize to the default path.
|
||||
ax_cv_lua_luadir="$LUA_PREFIX/share/lua/$LUA_VERSION"
|
||||
|
||||
dnl Try to find a path with the prefix.
|
||||
_AX_LUA_FND_PRFX_PTH([$LUA], [$ax_lua_prefix], [package.path])
|
||||
AS_IF([test "x$ax_lua_prefixed_path" != 'x'],
|
||||
[ dnl Fix the prefix.
|
||||
_ax_strip_prefix=`echo "$ax_lua_prefix" | sed 's|.|.|g'`
|
||||
ax_cv_lua_luadir=`echo "$ax_lua_prefixed_path" | \
|
||||
sed "s,^$_ax_strip_prefix,$LUA_PREFIX,"`
|
||||
])
|
||||
])
|
||||
AC_SUBST([luadir], [$ax_cv_lua_luadir])
|
||||
AC_SUBST([pkgluadir], [\${luadir}/$PACKAGE])
|
||||
|
||||
dnl Lua provides no way to query the module directory, and instead
|
||||
dnl provides LUA_PATH. However, we should be able to make a safe educated
|
||||
dnl guess. If the built-in search path contains a directory which is
|
||||
dnl prefixed by $exec_prefix, then we can store modules there. The first
|
||||
dnl matching path will be used.
|
||||
AC_CACHE_CHECK([for $ax_display_LUA module directory],
|
||||
[ax_cv_lua_luaexecdir],
|
||||
[ AS_IF([test "x$exec_prefix" = 'xNONE'],
|
||||
[ax_lua_exec_prefix=$ax_lua_prefix],
|
||||
[ax_lua_exec_prefix=$exec_prefix])
|
||||
|
||||
dnl Initialize to the default path.
|
||||
ax_cv_lua_luaexecdir="$LUA_EXEC_PREFIX/lib/lua/$LUA_VERSION"
|
||||
|
||||
dnl Try to find a path with the prefix.
|
||||
_AX_LUA_FND_PRFX_PTH([$LUA],
|
||||
[$ax_lua_exec_prefix], [package.cpathd])
|
||||
AS_IF([test "x$ax_lua_prefixed_path" != 'x'],
|
||||
[ dnl Fix the prefix.
|
||||
_ax_strip_prefix=`echo "$ax_lua_exec_prefix" | sed 's|.|.|g'`
|
||||
ax_cv_lua_luaexecdir=`echo "$ax_lua_prefixed_path" | \
|
||||
sed "s,^$_ax_strip_prefix,$LUA_EXEC_PREFIX,"`
|
||||
])
|
||||
])
|
||||
AC_SUBST([luaexecdir], [$ax_cv_lua_luaexecdir])
|
||||
AC_SUBST([pkgluaexecdir], [\${luaexecdir}/$PACKAGE])
|
||||
|
||||
dnl Run any user specified action.
|
||||
$3
|
||||
])
|
||||
])
|
||||
|
||||
dnl AX_WITH_LUA is now the same thing as AX_PROG_LUA.
|
||||
AC_DEFUN([AX_WITH_LUA],
|
||||
[
|
||||
AC_MSG_WARN([[$0 is deprecated, please use AX_PROG_LUA]])
|
||||
AX_PROG_LUA
|
||||
])
|
||||
|
||||
|
||||
dnl =========================================================================
|
||||
dnl _AX_LUA_CHK_IS_INTRP(PROG, [ACTION-IF-TRUE], [ACTION-IF-FALSE])
|
||||
dnl =========================================================================
|
||||
AC_DEFUN([_AX_LUA_CHK_IS_INTRP],
|
||||
[
|
||||
dnl Just print _VERSION because all Lua interpreters have this global.
|
||||
AS_IF([$1 -e "print('Hello ' .. _VERSION .. '!')" &>/dev/null],
|
||||
[$2], [$3])
|
||||
])
|
||||
|
||||
|
||||
dnl =========================================================================
|
||||
dnl _AX_LUA_CHK_VER(PROG, MINIMUM-VERSION, [TOO-BIG-VERSION],
|
||||
dnl [ACTION-IF-TRUE], [ACTION-IF-FALSE])
|
||||
dnl =========================================================================
|
||||
AC_DEFUN([_AX_LUA_CHK_VER],
|
||||
[
|
||||
AS_IF([$1 2>/dev/null -e '
|
||||
function norm (v) i,j=v:match "(%d+)%.(%d+)" return 100 * i + j end
|
||||
v=norm (_VERSION)
|
||||
os.exit ((v >= norm ("$2") and ("$3" == "" or v < norm ("$3"))) and 0 or 1)'],
|
||||
[$4], [$5])
|
||||
])
|
||||
|
||||
|
||||
dnl =========================================================================
|
||||
dnl _AX_LUA_FND_PRFX_PTH(PROG, PREFIX, LUA-PATH-VARIABLE)
|
||||
dnl =========================================================================
|
||||
AC_DEFUN([_AX_LUA_FND_PRFX_PTH],
|
||||
[
|
||||
dnl Invokes the Lua interpreter PROG to print the path variable
|
||||
dnl LUA-PATH-VARIABLE, usually package.path or package.cpath. Paths are
|
||||
dnl then matched against PREFIX. The first path to begin with PREFIX is set
|
||||
dnl to ax_lua_prefixed_path.
|
||||
|
||||
ax_lua_prefixed_path=''
|
||||
_ax_package_paths=`$1 -e 'print($3)' 2>/dev/null | sed 's|;|\n|g'`
|
||||
dnl Try the paths in order, looking for the prefix.
|
||||
for _ax_package_path in $_ax_package_paths; do
|
||||
dnl Copy the path, up to the use of a Lua wildcard.
|
||||
_ax_path_parts=`echo "$_ax_package_path" | sed 's|/|\n|g'`
|
||||
_ax_reassembled=''
|
||||
for _ax_path_part in $_ax_path_parts; do
|
||||
echo "$_ax_path_part" | grep '\?' >/dev/null && break
|
||||
_ax_reassembled="$_ax_reassembled/$_ax_path_part"
|
||||
done
|
||||
dnl Check the path against the prefix.
|
||||
_ax_package_path=$_ax_reassembled
|
||||
if echo "$_ax_package_path" | grep "^$2" >/dev/null; then
|
||||
dnl Found it.
|
||||
ax_lua_prefixed_path=$_ax_package_path
|
||||
break
|
||||
fi
|
||||
done
|
||||
])
|
||||
|
||||
|
||||
dnl =========================================================================
|
||||
dnl AX_LUA_HEADERS([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
|
||||
dnl =========================================================================
|
||||
AC_DEFUN([AX_LUA_HEADERS],
|
||||
[
|
||||
dnl Check for LUA_VERSION.
|
||||
AC_MSG_CHECKING([if LUA_VERSION is defined])
|
||||
AS_IF([test "x$LUA_VERSION" != 'x'],
|
||||
[AC_MSG_RESULT([yes])],
|
||||
[ AC_MSG_RESULT([no])
|
||||
AC_MSG_ERROR([cannot check Lua headers without knowing LUA_VERSION])
|
||||
])
|
||||
|
||||
dnl Make LUA_INCLUDE a precious variable.
|
||||
AC_ARG_VAR([LUA_INCLUDE], [The Lua includes, e.g. -I/usr/include/lua5.1])
|
||||
|
||||
dnl Some default directories to search.
|
||||
LUA_SHORT_VERSION=`echo "$LUA_VERSION" | sed 's|\.||'`
|
||||
m4_define_default([_AX_LUA_INCLUDE_LIST],
|
||||
[ /usr/include/lua$LUA_VERSION \
|
||||
/usr/include/lua/$LUA_VERSION \
|
||||
/usr/include/lua$LUA_SHORT_VERSION \
|
||||
/usr/local/include/lua$LUA_VERSION \
|
||||
/usr/local/include/lua-$LUA_VERSION \
|
||||
/usr/local/include/lua/$LUA_VERSION \
|
||||
/usr/local/include/lua$LUA_SHORT_VERSION \
|
||||
])
|
||||
|
||||
dnl Try to find the headers.
|
||||
_ax_lua_saved_cppflags=$CPPFLAGS
|
||||
CPPFLAGS="$CPPFLAGS $LUA_INCLUDE"
|
||||
AC_CHECK_HEADERS([lua.h lualib.h lauxlib.h luaconf.h])
|
||||
CPPFLAGS=$_ax_lua_saved_cppflags
|
||||
|
||||
dnl Try some other directories if LUA_INCLUDE was not set.
|
||||
AS_IF([test "x$LUA_INCLUDE" = 'x' &&
|
||||
test "x$ac_cv_header_lua_h" != 'xyes'],
|
||||
[ dnl Try some common include paths.
|
||||
for _ax_include_path in _AX_LUA_INCLUDE_LIST; do
|
||||
test ! -d "$_ax_include_path" && continue
|
||||
|
||||
AC_MSG_CHECKING([for Lua headers in])
|
||||
AC_MSG_RESULT([$_ax_include_path])
|
||||
|
||||
AS_UNSET([ac_cv_header_lua_h])
|
||||
AS_UNSET([ac_cv_header_lualib_h])
|
||||
AS_UNSET([ac_cv_header_lauxlib_h])
|
||||
AS_UNSET([ac_cv_header_luaconf_h])
|
||||
|
||||
_ax_lua_saved_cppflags=$CPPFLAGS
|
||||
CPPFLAGS="$CPPFLAGS -I$_ax_include_path"
|
||||
AC_CHECK_HEADERS([lua.h lualib.h lauxlib.h luaconf.h])
|
||||
CPPFLAGS=$_ax_lua_saved_cppflags
|
||||
|
||||
AS_IF([test "x$ac_cv_header_lua_h" = 'xyes'],
|
||||
[ LUA_INCLUDE="-I$_ax_include_path"
|
||||
break
|
||||
])
|
||||
done
|
||||
])
|
||||
|
||||
AS_IF([test "x$ac_cv_header_lua_h" = 'xyes' && test "x$cross_compiling" != 'xyes'],
|
||||
[ dnl Make a program to print LUA_VERSION defined in the header.
|
||||
dnl TODO This probably shouldn't be a runtime test.
|
||||
|
||||
AC_CACHE_CHECK([for Lua header version],
|
||||
[ax_cv_lua_header_version],
|
||||
[ _ax_lua_saved_cppflags=$CPPFLAGS
|
||||
CPPFLAGS="$CPPFLAGS $LUA_INCLUDE"
|
||||
AC_RUN_IFELSE(
|
||||
[ AC_LANG_SOURCE([[
|
||||
#include <lua.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
if(argc > 1) printf("%s", LUA_VERSION);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
]])
|
||||
],
|
||||
[ ax_cv_lua_header_version=`./conftest$EXEEXT p | \
|
||||
sed "s|^Lua \(.*\)|\1|" | \
|
||||
grep -o "^@<:@0-9@:>@\+\\.@<:@0-9@:>@\+"`
|
||||
],
|
||||
[ax_cv_lua_header_version='unknown'])
|
||||
CPPFLAGS=$_ax_lua_saved_cppflags
|
||||
])
|
||||
|
||||
dnl Compare this to the previously found LUA_VERSION.
|
||||
AC_MSG_CHECKING([if Lua header version matches $LUA_VERSION])
|
||||
AS_IF([test "x$ax_cv_lua_header_version" = "x$LUA_VERSION"],
|
||||
[ AC_MSG_RESULT([yes])
|
||||
ax_header_version_match='yes'
|
||||
],
|
||||
[ AC_MSG_RESULT([no])
|
||||
ax_header_version_match='no'
|
||||
])
|
||||
],
|
||||
[
|
||||
ax_header_version_match='yes'
|
||||
])
|
||||
|
||||
dnl Was LUA_INCLUDE specified?
|
||||
AS_IF([test "x$ax_header_version_match" != 'xyes' &&
|
||||
test "x$LUA_INCLUDE" != 'x'],
|
||||
[AC_MSG_ERROR([cannot find headers for specified LUA_INCLUDE])])
|
||||
|
||||
dnl Test the final result and run user code.
|
||||
AS_IF([test "x$ax_header_version_match" = 'xyes'], [$1],
|
||||
[m4_default([$2], [AC_MSG_ERROR([cannot find Lua includes])])])
|
||||
])
|
||||
|
||||
dnl AX_LUA_HEADERS_VERSION no longer exists, use AX_LUA_HEADERS.
|
||||
AC_DEFUN([AX_LUA_HEADERS_VERSION],
|
||||
[
|
||||
AC_MSG_WARN([[$0 is deprecated, please use AX_LUA_HEADERS]])
|
||||
])
|
||||
|
||||
|
||||
dnl =========================================================================
|
||||
dnl AX_LUA_LIBS([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
|
||||
dnl =========================================================================
|
||||
AC_DEFUN([AX_LUA_LIBS],
|
||||
[
|
||||
dnl TODO Should this macro also check various -L flags?
|
||||
|
||||
dnl Check for LUA_VERSION.
|
||||
AC_MSG_CHECKING([if LUA_VERSION is defined])
|
||||
AS_IF([test "x$LUA_VERSION" != 'x'],
|
||||
[AC_MSG_RESULT([yes])],
|
||||
[ AC_MSG_RESULT([no])
|
||||
AC_MSG_ERROR([cannot check Lua libs without knowing LUA_VERSION])
|
||||
])
|
||||
|
||||
dnl Make LUA_LIB a precious variable.
|
||||
AC_ARG_VAR([LUA_LIB], [The Lua library, e.g. -llua5.1])
|
||||
|
||||
AS_IF([test "x$LUA_LIB" != 'x'],
|
||||
[ dnl Check that LUA_LIBS works.
|
||||
_ax_lua_saved_libs=$LIBS
|
||||
LIBS="$LIBS $LUA_LIB"
|
||||
AC_SEARCH_LIBS([lua_load], [],
|
||||
[_ax_found_lua_libs='yes'],
|
||||
[_ax_found_lua_libs='no'])
|
||||
LIBS=$_ax_lua_saved_libs
|
||||
|
||||
dnl Check the result.
|
||||
AS_IF([test "x$_ax_found_lua_libs" != 'xyes'],
|
||||
[AC_MSG_ERROR([cannot find libs for specified LUA_LIB])])
|
||||
],
|
||||
[ dnl First search for extra libs.
|
||||
_ax_lua_extra_libs=''
|
||||
|
||||
_ax_lua_saved_libs=$LIBS
|
||||
LIBS="$LIBS $LUA_LIB"
|
||||
AC_SEARCH_LIBS([exp], [m])
|
||||
AC_SEARCH_LIBS([dlopen], [dl])
|
||||
LIBS=$_ax_lua_saved_libs
|
||||
|
||||
AS_IF([test "x$ac_cv_search_exp" != 'xno' &&
|
||||
test "x$ac_cv_search_exp" != 'xnone required'],
|
||||
[_ax_lua_extra_libs="$_ax_lua_extra_libs $ac_cv_search_exp"])
|
||||
|
||||
AS_IF([test "x$ac_cv_search_dlopen" != 'xno' &&
|
||||
test "x$ac_cv_search_dlopen" != 'xnone required'],
|
||||
[_ax_lua_extra_libs="$_ax_lua_extra_libs $ac_cv_search_dlopen"])
|
||||
|
||||
dnl Try to find the Lua libs.
|
||||
_ax_lua_saved_libs=$LIBS
|
||||
LIBS="$LIBS $LUA_LIB"
|
||||
AC_SEARCH_LIBS([lua_load],
|
||||
[ lua$LUA_VERSION \
|
||||
lua$LUA_SHORT_VERSION \
|
||||
lua-$LUA_VERSION \
|
||||
lua-$LUA_SHORT_VERSION \
|
||||
lua],
|
||||
[_ax_found_lua_libs='yes'],
|
||||
[_ax_found_lua_libs='no'],
|
||||
[$_ax_lua_extra_libs])
|
||||
LIBS=$_ax_lua_saved_libs
|
||||
|
||||
AS_IF([test "x$ac_cv_search_lua_load" != 'xno' &&
|
||||
test "x$ac_cv_search_lua_load" != 'xnone required'],
|
||||
[LUA_LIB="$ac_cv_search_lua_load $_ax_lua_extra_libs"])
|
||||
])
|
||||
|
||||
dnl Test the result and run user code.
|
||||
AS_IF([test "x$_ax_found_lua_libs" = 'xyes'], [$1],
|
||||
[m4_default([$2], [AC_MSG_ERROR([cannot find Lua libs])])])
|
||||
])
|
||||
|
||||
|
||||
dnl =========================================================================
|
||||
dnl AX_LUA_READLINE([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
|
||||
dnl =========================================================================
|
||||
AC_DEFUN([AX_LUA_READLINE],
|
||||
[
|
||||
AX_LIB_READLINE
|
||||
AS_IF([test "x$ac_cv_header_readline_readline_h" != 'x' &&
|
||||
test "x$ac_cv_header_readline_history_h" != 'x'],
|
||||
[ LUA_LIBS_CFLAGS="-DLUA_USE_READLINE $LUA_LIBS_CFLAGS"
|
||||
$1
|
||||
],
|
||||
[$2])
|
||||
])
|
@ -130,6 +130,19 @@ ucl_emitter_print_key (bool print_key, struct ucl_emitter_context *ctx,
|
||||
func->ucl_emitter_append_character (' ', 1, func->ud);
|
||||
}
|
||||
}
|
||||
else if (ctx->id == UCL_EMIT_YAML) {
|
||||
if (obj->keylen > 0 && (obj->flags & UCL_OBJECT_NEED_KEY_ESCAPE)) {
|
||||
ucl_elt_string_write_json (obj->key, obj->keylen, ctx);
|
||||
}
|
||||
else if (obj->keylen > 0) {
|
||||
func->ucl_emitter_append_len (obj->key, obj->keylen, func->ud);
|
||||
}
|
||||
else {
|
||||
func->ucl_emitter_append_len ("null", 4, func->ud);
|
||||
}
|
||||
|
||||
func->ucl_emitter_append_len (": ", 2, func->ud);
|
||||
}
|
||||
else {
|
||||
if (obj->keylen > 0) {
|
||||
ucl_elt_string_write_json (obj->key, obj->keylen, ctx);
|
||||
@ -182,7 +195,7 @@ ucl_emitter_common_end_object (struct ucl_emitter_context *ctx,
|
||||
const struct ucl_emitter_functions *func = ctx->func;
|
||||
|
||||
if (UCL_EMIT_IDENT_TOP_OBJ(ctx, obj)) {
|
||||
ctx->ident --;
|
||||
ctx->indent --;
|
||||
if (compact) {
|
||||
func->ucl_emitter_append_character ('}', 1, func->ud);
|
||||
}
|
||||
@ -191,7 +204,7 @@ ucl_emitter_common_end_object (struct ucl_emitter_context *ctx,
|
||||
/* newline is already added for this format */
|
||||
func->ucl_emitter_append_character ('\n', 1, func->ud);
|
||||
}
|
||||
ucl_add_tabs (func, ctx->ident, compact);
|
||||
ucl_add_tabs (func, ctx->indent, compact);
|
||||
func->ucl_emitter_append_character ('}', 1, func->ud);
|
||||
}
|
||||
}
|
||||
@ -210,7 +223,7 @@ ucl_emitter_common_end_array (struct ucl_emitter_context *ctx,
|
||||
{
|
||||
const struct ucl_emitter_functions *func = ctx->func;
|
||||
|
||||
ctx->ident --;
|
||||
ctx->indent --;
|
||||
if (compact) {
|
||||
func->ucl_emitter_append_character (']', 1, func->ud);
|
||||
}
|
||||
@ -219,7 +232,7 @@ ucl_emitter_common_end_array (struct ucl_emitter_context *ctx,
|
||||
/* newline is already added for this format */
|
||||
func->ucl_emitter_append_character ('\n', 1, func->ud);
|
||||
}
|
||||
ucl_add_tabs (func, ctx->ident, compact);
|
||||
ucl_add_tabs (func, ctx->indent, compact);
|
||||
func->ucl_emitter_append_character (']', 1, func->ud);
|
||||
}
|
||||
|
||||
@ -249,7 +262,7 @@ ucl_emitter_common_start_array (struct ucl_emitter_context *ctx,
|
||||
func->ucl_emitter_append_len ("[\n", 2, func->ud);
|
||||
}
|
||||
|
||||
ctx->ident ++;
|
||||
ctx->indent ++;
|
||||
|
||||
if (obj->type == UCL_ARRAY) {
|
||||
/* explicit array */
|
||||
@ -294,7 +307,7 @@ ucl_emitter_common_start_object (struct ucl_emitter_context *ctx,
|
||||
else {
|
||||
func->ucl_emitter_append_len ("{\n", 2, func->ud);
|
||||
}
|
||||
ctx->ident ++;
|
||||
ctx->indent ++;
|
||||
}
|
||||
|
||||
while ((cur = ucl_hash_iterate (obj->value.ov, &it))) {
|
||||
@ -315,7 +328,7 @@ ucl_emitter_common_start_object (struct ucl_emitter_context *ctx,
|
||||
func->ucl_emitter_append_len (",\n", 2, func->ud);
|
||||
}
|
||||
}
|
||||
ucl_add_tabs (func, ctx->ident, compact);
|
||||
ucl_add_tabs (func, ctx->indent, compact);
|
||||
ucl_emitter_common_start_array (ctx, cur, true, compact);
|
||||
ucl_emitter_common_end_array (ctx, cur, compact);
|
||||
}
|
||||
@ -342,17 +355,23 @@ ucl_emitter_common_elt (struct ucl_emitter_context *ctx,
|
||||
{
|
||||
const struct ucl_emitter_functions *func = ctx->func;
|
||||
bool flag;
|
||||
struct ucl_object_userdata *ud;
|
||||
const char *ud_out = "";
|
||||
|
||||
if (ctx->id != UCL_EMIT_CONFIG && !first) {
|
||||
if (compact) {
|
||||
func->ucl_emitter_append_character (',', 1, func->ud);
|
||||
}
|
||||
else {
|
||||
func->ucl_emitter_append_len (",\n", 2, func->ud);
|
||||
if (ctx->id == UCL_EMIT_YAML && ctx->indent == 0) {
|
||||
func->ucl_emitter_append_len ("\n", 1, func->ud);
|
||||
} else {
|
||||
func->ucl_emitter_append_len (",\n", 2, func->ud);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ucl_add_tabs (func, ctx->ident, compact);
|
||||
ucl_add_tabs (func, ctx->indent, compact);
|
||||
|
||||
switch (obj->type) {
|
||||
case UCL_INT:
|
||||
@ -379,7 +398,12 @@ ucl_emitter_common_elt (struct ucl_emitter_context *ctx,
|
||||
break;
|
||||
case UCL_STRING:
|
||||
ucl_emitter_print_key (print_key, ctx, obj, compact);
|
||||
ucl_elt_string_write_json (obj->value.sv, obj->len, ctx);
|
||||
if (ctx->id == UCL_EMIT_CONFIG && ucl_maybe_long_string (obj)) {
|
||||
ucl_elt_string_write_multiline (obj->value.sv, obj->len, ctx);
|
||||
}
|
||||
else {
|
||||
ucl_elt_string_write_json (obj->value.sv, obj->len, ctx);
|
||||
}
|
||||
ucl_emitter_finish_object (ctx, obj, compact, !print_key);
|
||||
break;
|
||||
case UCL_NULL:
|
||||
@ -396,6 +420,16 @@ ucl_emitter_common_elt (struct ucl_emitter_context *ctx,
|
||||
ucl_emitter_common_end_array (ctx, obj, compact);
|
||||
break;
|
||||
case UCL_USERDATA:
|
||||
ud = (struct ucl_object_userdata *)obj;
|
||||
ucl_emitter_print_key (print_key, ctx, obj, compact);
|
||||
if (ud->emitter) {
|
||||
ud_out = ud->emitter (obj->value.ud);
|
||||
if (ud_out == NULL) {
|
||||
ud_out = "null";
|
||||
}
|
||||
}
|
||||
ucl_elt_string_write_json (ud_out, strlen (ud_out), ctx);
|
||||
ucl_emitter_finish_object (ctx, obj, compact, !print_key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -425,10 +459,10 @@ ucl_emitter_common_elt (struct ucl_emitter_context *ctx,
|
||||
ucl_emitter_common_end_array (ctx, obj, (compact)); \
|
||||
}
|
||||
|
||||
UCL_EMIT_TYPE_IMPL(json, false);
|
||||
UCL_EMIT_TYPE_IMPL(json_compact, true);
|
||||
UCL_EMIT_TYPE_IMPL(config, false);
|
||||
UCL_EMIT_TYPE_IMPL(yaml, false);
|
||||
UCL_EMIT_TYPE_IMPL(json, false)
|
||||
UCL_EMIT_TYPE_IMPL(json_compact, true)
|
||||
UCL_EMIT_TYPE_IMPL(config, false)
|
||||
UCL_EMIT_TYPE_IMPL(yaml, false)
|
||||
|
||||
unsigned char *
|
||||
ucl_object_emit (const ucl_object_t *obj, enum ucl_emitter emit_type)
|
||||
@ -461,7 +495,7 @@ ucl_object_emit_full (const ucl_object_t *obj, enum ucl_emitter emit_type,
|
||||
if (ctx != NULL) {
|
||||
memcpy (&my_ctx, ctx, sizeof (my_ctx));
|
||||
my_ctx.func = emitter;
|
||||
my_ctx.ident = 0;
|
||||
my_ctx.indent = 0;
|
||||
my_ctx.top = obj;
|
||||
|
||||
my_ctx.ops->ucl_emitter_write_elt (&my_ctx, obj, true, false);
|
||||
|
@ -108,9 +108,8 @@ ucl_object_emit_streamline_start_container (struct ucl_emitter_context *ctx,
|
||||
st->is_array = false;
|
||||
sctx->ops->ucl_emitter_start_object (ctx, obj, print_key);
|
||||
}
|
||||
LL_PREPEND (sctx->containers, st);
|
||||
}
|
||||
|
||||
LL_PREPEND (sctx->containers, st);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -93,9 +93,7 @@ ucl_elt_string_write_json (const char *str, size_t size,
|
||||
size_t len = 0;
|
||||
const struct ucl_emitter_functions *func = ctx->func;
|
||||
|
||||
if (ctx->id != UCL_EMIT_YAML) {
|
||||
func->ucl_emitter_append_character ('"', 1, func->ud);
|
||||
}
|
||||
func->ucl_emitter_append_character ('"', 1, func->ud);
|
||||
|
||||
while (size) {
|
||||
if (ucl_test_character (*p, UCL_CHARACTER_JSON_UNSAFE)) {
|
||||
@ -137,9 +135,18 @@ ucl_elt_string_write_json (const char *str, size_t size,
|
||||
if (len > 0) {
|
||||
func->ucl_emitter_append_len (c, len, func->ud);
|
||||
}
|
||||
if (ctx->id != UCL_EMIT_YAML) {
|
||||
func->ucl_emitter_append_character ('"', 1, func->ud);
|
||||
}
|
||||
func->ucl_emitter_append_character ('"', 1, func->ud);
|
||||
}
|
||||
|
||||
void
|
||||
ucl_elt_string_write_multiline (const char *str, size_t size,
|
||||
struct ucl_emitter_context *ctx)
|
||||
{
|
||||
const struct ucl_emitter_functions *func = ctx->func;
|
||||
|
||||
func->ucl_emitter_append_len ("<<EOD\n", sizeof ("<<EOD\n") - 1, func->ud);
|
||||
func->ucl_emitter_append_len (str, size, func->ud);
|
||||
func->ucl_emitter_append_len ("\nEOD", sizeof ("\nEOD") - 1, func->ud);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -154,7 +161,7 @@ ucl_utstring_append_character (unsigned char c, size_t len, void *ud)
|
||||
utstring_append_c (buf, c);
|
||||
}
|
||||
else {
|
||||
utstring_reserve (buf, len);
|
||||
utstring_reserve (buf, len + 1);
|
||||
memset (&buf->d[buf->i], c, len);
|
||||
buf->i += len;
|
||||
buf->d[buf->i] = '\0';
|
||||
@ -267,19 +274,23 @@ ucl_fd_append_character (unsigned char c, size_t len, void *ud)
|
||||
unsigned char *buf;
|
||||
|
||||
if (len == 1) {
|
||||
write (fd, &c, 1);
|
||||
return write (fd, &c, 1);
|
||||
}
|
||||
else {
|
||||
buf = malloc (len);
|
||||
if (buf == NULL) {
|
||||
/* Fallback */
|
||||
while (len --) {
|
||||
write (fd, &c, 1);
|
||||
if (write (fd, &c, 1) == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
memset (buf, c, len);
|
||||
write (fd, buf, len);
|
||||
if (write (fd, buf, len) == -1) {
|
||||
return -1;
|
||||
}
|
||||
free (buf);
|
||||
}
|
||||
}
|
||||
@ -292,9 +303,7 @@ ucl_fd_append_len (const unsigned char *str, size_t len, void *ud)
|
||||
{
|
||||
int fd = *(int *)ud;
|
||||
|
||||
write (fd, str, len);
|
||||
|
||||
return 0;
|
||||
return write (fd, str, len);
|
||||
}
|
||||
|
||||
static int
|
||||
@ -304,9 +313,7 @@ ucl_fd_append_int (int64_t val, void *ud)
|
||||
char intbuf[64];
|
||||
|
||||
snprintf (intbuf, sizeof (intbuf), "%jd", (intmax_t)val);
|
||||
write (fd, intbuf, strlen (intbuf));
|
||||
|
||||
return 0;
|
||||
return write (fd, intbuf, strlen (intbuf));
|
||||
}
|
||||
|
||||
static int
|
||||
@ -327,9 +334,7 @@ ucl_fd_append_double (double val, void *ud)
|
||||
snprintf (nbuf, sizeof (nbuf), "%lf", val);
|
||||
}
|
||||
|
||||
write (fd, nbuf, strlen (nbuf));
|
||||
|
||||
return 0;
|
||||
return write (fd, nbuf, strlen (nbuf));
|
||||
}
|
||||
|
||||
struct ucl_emitter_functions*
|
||||
@ -464,3 +469,18 @@ ucl_object_emit_single_json (const ucl_object_t *obj)
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#define LONG_STRING_LIMIT 80
|
||||
|
||||
bool
|
||||
ucl_maybe_long_string (const ucl_object_t *obj)
|
||||
{
|
||||
if (obj->len > LONG_STRING_LIMIT || (obj->flags & UCL_OBJECT_MULTILINE)) {
|
||||
/* String is long enough, so search for newline characters in it */
|
||||
if (memchr (obj->value.sv, '\n', obj->len) != NULL) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -66,6 +66,20 @@ ucl_hash_insert (ucl_hash_t* hashlin, const ucl_object_t *obj,
|
||||
HASH_ADD_KEYPTR (hh, hashlin->buckets, key, keylen, node);
|
||||
}
|
||||
|
||||
void ucl_hash_replace (ucl_hash_t* hashlin, const ucl_object_t *old,
|
||||
const ucl_object_t *new)
|
||||
{
|
||||
ucl_hash_node_t *node;
|
||||
|
||||
HASH_FIND (hh, hashlin->buckets, old->key, old->keylen, node);
|
||||
if (node != NULL) {
|
||||
/* Direct replacement */
|
||||
node->data = new;
|
||||
node->hh.key = new->key;
|
||||
node->hh.keylen = new->keylen;
|
||||
}
|
||||
}
|
||||
|
||||
const void*
|
||||
ucl_hash_iterate (ucl_hash_t *hashlin, ucl_hash_iter_t *iter)
|
||||
{
|
||||
@ -122,5 +136,6 @@ ucl_hash_delete (ucl_hash_t* hashlin, const ucl_object_t *obj)
|
||||
|
||||
if (found) {
|
||||
HASH_DELETE (hh, hashlin->buckets, found);
|
||||
UCL_FREE (sizeof (ucl_hash_node_t), found);
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +65,12 @@ void ucl_hash_destroy (ucl_hash_t* hashlin, ucl_hash_free_func *func);
|
||||
void ucl_hash_insert (ucl_hash_t* hashlin, const ucl_object_t *obj, const char *key,
|
||||
unsigned keylen);
|
||||
|
||||
/**
|
||||
* Replace element in the hash
|
||||
*/
|
||||
void ucl_hash_replace (ucl_hash_t* hashlin, const ucl_object_t *old,
|
||||
const ucl_object_t *new);
|
||||
|
||||
/**
|
||||
* Delete an element from the the hashtable.
|
||||
*/
|
||||
|
@ -163,6 +163,7 @@ struct ucl_chunk {
|
||||
size_t remain;
|
||||
unsigned int line;
|
||||
unsigned int column;
|
||||
unsigned priority;
|
||||
struct ucl_chunk *next;
|
||||
};
|
||||
|
||||
@ -182,7 +183,7 @@ struct ucl_variable {
|
||||
char *value;
|
||||
size_t var_len;
|
||||
size_t value_len;
|
||||
struct ucl_variable *next;
|
||||
struct ucl_variable *prev, *next;
|
||||
};
|
||||
|
||||
struct ucl_parser {
|
||||
@ -192,6 +193,7 @@ struct ucl_parser {
|
||||
int flags;
|
||||
ucl_object_t *top_obj;
|
||||
ucl_object_t *cur_obj;
|
||||
char *cur_file;
|
||||
struct ucl_macro *macroes;
|
||||
struct ucl_stack *stack;
|
||||
struct ucl_chunk *chunks;
|
||||
@ -202,6 +204,12 @@ struct ucl_parser {
|
||||
UT_string *err;
|
||||
};
|
||||
|
||||
struct ucl_object_userdata {
|
||||
ucl_object_t obj;
|
||||
ucl_userdata_dtor dtor;
|
||||
ucl_userdata_emitter emitter;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unescape json string inplace
|
||||
* @param str
|
||||
@ -216,9 +224,11 @@ size_t ucl_unescape_json_string (char *str, size_t len);
|
||||
* @param err error ptr
|
||||
* @return
|
||||
*/
|
||||
bool ucl_include_handler (const unsigned char *data, size_t len, void* ud);
|
||||
bool ucl_include_handler (const unsigned char *data, size_t len,
|
||||
const ucl_object_t *args, void* ud);
|
||||
|
||||
bool ucl_try_include_handler (const unsigned char *data, size_t len, void* ud);
|
||||
bool ucl_try_include_handler (const unsigned char *data, size_t len,
|
||||
const ucl_object_t *args, void* ud);
|
||||
|
||||
/**
|
||||
* Handle includes macro
|
||||
@ -228,7 +238,8 @@ bool ucl_try_include_handler (const unsigned char *data, size_t len, void* ud);
|
||||
* @param err error ptr
|
||||
* @return
|
||||
*/
|
||||
bool ucl_includes_handler (const unsigned char *data, size_t len, void* ud);
|
||||
bool ucl_includes_handler (const unsigned char *data, size_t len,
|
||||
const ucl_object_t *args, void* ud);
|
||||
|
||||
size_t ucl_strlcpy (char *dst, const char *src, size_t siz);
|
||||
size_t ucl_strlcpy_unsafe (char *dst, const char *src, size_t siz);
|
||||
@ -264,7 +275,7 @@ ucl_create_err (UT_string **err, const char *fmt, ...)
|
||||
static inline bool
|
||||
ucl_maybe_parse_boolean (ucl_object_t *obj, const unsigned char *start, size_t len)
|
||||
{
|
||||
const unsigned char *p = start;
|
||||
const char *p = (const char *)start;
|
||||
bool ret = false, val = false;
|
||||
|
||||
if (len == 5) {
|
||||
@ -351,13 +362,22 @@ const struct ucl_emitter_context *
|
||||
ucl_emit_get_standard_context (enum ucl_emitter emit_type);
|
||||
|
||||
/**
|
||||
* Serialise string
|
||||
* Serialize string as JSON string
|
||||
* @param str string to emit
|
||||
* @param buf target buffer
|
||||
*/
|
||||
void ucl_elt_string_write_json (const char *str, size_t size,
|
||||
struct ucl_emitter_context *ctx);
|
||||
|
||||
/**
|
||||
* Write multiline string using `EOD` as string terminator
|
||||
* @param str
|
||||
* @param size
|
||||
* @param ctx
|
||||
*/
|
||||
void ucl_elt_string_write_multiline (const char *str, size_t size,
|
||||
struct ucl_emitter_context *ctx);
|
||||
|
||||
/**
|
||||
* Emit a single object to string
|
||||
* @param obj
|
||||
@ -365,4 +385,12 @@ void ucl_elt_string_write_json (const char *str, size_t size,
|
||||
*/
|
||||
unsigned char * ucl_object_emit_single_json (const ucl_object_t *obj);
|
||||
|
||||
/**
|
||||
* Check whether a specified string is long and should be likely printed in
|
||||
* multiline mode
|
||||
* @param obj
|
||||
* @return
|
||||
*/
|
||||
bool ucl_maybe_long_string (const ucl_object_t *obj);
|
||||
|
||||
#endif /* UCL_INTERNAL_H_ */
|
||||
|
434
src/ucl_parser.c
434
src/ucl_parser.c
@ -26,8 +26,8 @@
|
||||
#include "ucl_chartable.h"
|
||||
|
||||
/**
|
||||
* @file rcl_parser.c
|
||||
* The implementation of rcl parser
|
||||
* @file ucl_parser.c
|
||||
* The implementation of ucl parser
|
||||
*/
|
||||
|
||||
struct ucl_parser_saved_state {
|
||||
@ -56,20 +56,33 @@ struct ucl_parser_saved_state {
|
||||
} while (0)
|
||||
|
||||
static inline void
|
||||
ucl_set_err (struct ucl_chunk *chunk, int code, const char *str, UT_string **err)
|
||||
ucl_set_err (struct ucl_parser *parser, int code, const char *str, UT_string **err)
|
||||
{
|
||||
if (chunk->pos < chunk->end) {
|
||||
if (isgraph (*chunk->pos)) {
|
||||
ucl_create_err (err, "error on line %d at column %d: '%s', character: '%c'",
|
||||
chunk->line, chunk->column, str, *chunk->pos);
|
||||
}
|
||||
else {
|
||||
ucl_create_err (err, "error on line %d at column %d: '%s', character: '0x%02x'",
|
||||
chunk->line, chunk->column, str, (int)*chunk->pos);
|
||||
}
|
||||
const char *fmt_string, *filename;
|
||||
struct ucl_chunk *chunk = parser->chunks;
|
||||
|
||||
if (parser->cur_file) {
|
||||
filename = parser->cur_file;
|
||||
}
|
||||
else {
|
||||
ucl_create_err (err, "error at the end of chunk: %s", str);
|
||||
filename = "<unknown>";
|
||||
}
|
||||
if (chunk->pos < chunk->end) {
|
||||
if (isgraph (*chunk->pos)) {
|
||||
fmt_string = "error while parsing %s: "
|
||||
"line: %d, column: %d - '%s', character: '%c'";
|
||||
}
|
||||
else {
|
||||
fmt_string = "error while parsing %s: "
|
||||
"line: %d, column: %d - '%s', character: '0x%02x'";
|
||||
}
|
||||
ucl_create_err (err, fmt_string,
|
||||
filename, chunk->line, chunk->column,
|
||||
str, *chunk->pos);
|
||||
}
|
||||
else {
|
||||
ucl_create_err (err, "error while parsing %s: at the end of chunk: %s",
|
||||
filename, str);
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,11 +97,12 @@ ucl_skip_comments (struct ucl_parser *parser)
|
||||
struct ucl_chunk *chunk = parser->chunks;
|
||||
const unsigned char *p;
|
||||
int comments_nested = 0;
|
||||
bool quoted = false;
|
||||
|
||||
p = chunk->pos;
|
||||
|
||||
start:
|
||||
if (*p == '#') {
|
||||
if (chunk->remain > 0 && *p == '#') {
|
||||
if (parser->state != UCL_STATE_SCOMMENT &&
|
||||
parser->state != UCL_STATE_MCOMMENT) {
|
||||
while (p < chunk->end) {
|
||||
@ -100,34 +114,41 @@ ucl_skip_comments (struct ucl_parser *parser)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (*p == '/' && chunk->remain >= 2) {
|
||||
else if (chunk->remain >= 2 && *p == '/') {
|
||||
if (p[1] == '*') {
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
comments_nested ++;
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
|
||||
while (p < chunk->end) {
|
||||
if (*p == '*') {
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
if (*p == '/') {
|
||||
comments_nested --;
|
||||
if (comments_nested == 0) {
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
goto start;
|
||||
}
|
||||
}
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
if (*p == '"' && *(p - 1) != '\\') {
|
||||
quoted = !quoted;
|
||||
}
|
||||
else if (p[0] == '/' && chunk->remain >= 2 && p[1] == '*') {
|
||||
comments_nested ++;
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
continue;
|
||||
|
||||
if (!quoted) {
|
||||
if (*p == '*') {
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
if (*p == '/') {
|
||||
comments_nested --;
|
||||
if (comments_nested == 0) {
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
goto start;
|
||||
}
|
||||
}
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
}
|
||||
else if (p[0] == '/' && chunk->remain >= 2 && p[1] == '*') {
|
||||
comments_nested ++;
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
}
|
||||
if (comments_nested != 0) {
|
||||
ucl_set_err (chunk, UCL_ENESTED, "unfinished multiline comment", &parser->err);
|
||||
ucl_set_err (parser, UCL_ENESTED,
|
||||
"unfinished multiline comment", &parser->err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -492,7 +513,8 @@ ucl_copy_or_store_ptr (struct ucl_parser *parser,
|
||||
/* Copy string */
|
||||
*dst = UCL_ALLOC (in_len + 1);
|
||||
if (*dst == NULL) {
|
||||
ucl_set_err (parser->chunks, 0, "cannot allocate memory for a string", &parser->err);
|
||||
ucl_set_err (parser, 0, "cannot allocate memory for a string",
|
||||
&parser->err);
|
||||
return false;
|
||||
}
|
||||
if (need_lowercase) {
|
||||
@ -514,6 +536,10 @@ ucl_copy_or_store_ptr (struct ucl_parser *parser,
|
||||
*dst = tmp;
|
||||
ret = tret;
|
||||
}
|
||||
else {
|
||||
/* Free unexpanded value */
|
||||
UCL_FREE (in_len + 1, tmp);
|
||||
}
|
||||
}
|
||||
*dst_const = *dst;
|
||||
}
|
||||
@ -539,7 +565,7 @@ ucl_add_parser_stack (ucl_object_t *obj, struct ucl_parser *parser, bool is_arra
|
||||
|
||||
if (!is_array) {
|
||||
if (obj == NULL) {
|
||||
obj = ucl_object_typed_new (UCL_OBJECT);
|
||||
obj = ucl_object_new_full (UCL_OBJECT, parser->chunks->priority);
|
||||
}
|
||||
else {
|
||||
obj->type = UCL_OBJECT;
|
||||
@ -549,7 +575,7 @@ ucl_add_parser_stack (ucl_object_t *obj, struct ucl_parser *parser, bool is_arra
|
||||
}
|
||||
else {
|
||||
if (obj == NULL) {
|
||||
obj = ucl_object_typed_new (UCL_ARRAY);
|
||||
obj = ucl_object_new_full (UCL_ARRAY, parser->chunks->priority);
|
||||
}
|
||||
else {
|
||||
obj->type = UCL_ARRAY;
|
||||
@ -559,7 +585,9 @@ ucl_add_parser_stack (ucl_object_t *obj, struct ucl_parser *parser, bool is_arra
|
||||
|
||||
st = UCL_ALLOC (sizeof (struct ucl_stack));
|
||||
if (st == NULL) {
|
||||
ucl_set_err (parser->chunks, 0, "cannot allocate memory for an object", &parser->err);
|
||||
ucl_set_err (parser, 0, "cannot allocate memory for an object",
|
||||
&parser->err);
|
||||
ucl_object_unref (obj);
|
||||
return NULL;
|
||||
}
|
||||
st->obj = obj;
|
||||
@ -676,8 +704,7 @@ ucl_maybe_parse_number (ucl_object_t *obj,
|
||||
}
|
||||
|
||||
/* Now check endptr */
|
||||
if (endptr == NULL || ucl_lex_is_atom_end (*endptr) || *endptr == '\0' ||
|
||||
ucl_test_character (*endptr, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
|
||||
if (endptr == NULL || ucl_lex_is_atom_end (*endptr) || *endptr == '\0') {
|
||||
p = endptr;
|
||||
goto set_obj;
|
||||
}
|
||||
@ -788,8 +815,21 @@ ucl_maybe_parse_number (ucl_object_t *obj,
|
||||
goto set_obj;
|
||||
}
|
||||
break;
|
||||
case '\t':
|
||||
case ' ':
|
||||
while (p < end && ucl_test_character(*p, UCL_CHARACTER_WHITESPACE)) {
|
||||
p++;
|
||||
}
|
||||
if (ucl_lex_is_atom_end(*p))
|
||||
goto set_obj;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (endptr == end) {
|
||||
/* Just a number at the end of chunk */
|
||||
p = endptr;
|
||||
goto set_obj;
|
||||
}
|
||||
|
||||
*pos = c;
|
||||
return EINVAL;
|
||||
@ -835,7 +875,7 @@ ucl_lex_number (struct ucl_parser *parser,
|
||||
return true;
|
||||
}
|
||||
else if (ret == ERANGE) {
|
||||
ucl_set_err (chunk, ERANGE, "numeric value out of range", &parser->err);
|
||||
ucl_set_err (parser, ERANGE, "numeric value out of range", &parser->err);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -860,10 +900,12 @@ ucl_lex_json_string (struct ucl_parser *parser,
|
||||
if (c < 0x1F) {
|
||||
/* Unmasked control character */
|
||||
if (c == '\n') {
|
||||
ucl_set_err (chunk, UCL_ESYNTAX, "unexpected newline", &parser->err);
|
||||
ucl_set_err (parser, UCL_ESYNTAX, "unexpected newline",
|
||||
&parser->err);
|
||||
}
|
||||
else {
|
||||
ucl_set_err (chunk, UCL_ESYNTAX, "unexpected control character", &parser->err);
|
||||
ucl_set_err (parser, UCL_ESYNTAX, "unexpected control character",
|
||||
&parser->err);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -871,7 +913,8 @@ ucl_lex_json_string (struct ucl_parser *parser,
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
c = *p;
|
||||
if (p >= chunk->end) {
|
||||
ucl_set_err (chunk, UCL_ESYNTAX, "unfinished escape character", &parser->err);
|
||||
ucl_set_err (parser, UCL_ESYNTAX, "unfinished escape character",
|
||||
&parser->err);
|
||||
return false;
|
||||
}
|
||||
else if (ucl_test_character (c, UCL_CHARACTER_ESCAPE)) {
|
||||
@ -879,13 +922,15 @@ ucl_lex_json_string (struct ucl_parser *parser,
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
for (i = 0; i < 4 && p < chunk->end; i ++) {
|
||||
if (!isxdigit (*p)) {
|
||||
ucl_set_err (chunk, UCL_ESYNTAX, "invalid utf escape", &parser->err);
|
||||
ucl_set_err (parser, UCL_ESYNTAX, "invalid utf escape",
|
||||
&parser->err);
|
||||
return false;
|
||||
}
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
}
|
||||
if (p >= chunk->end) {
|
||||
ucl_set_err (chunk, UCL_ESYNTAX, "unfinished escape character", &parser->err);
|
||||
ucl_set_err (parser, UCL_ESYNTAX, "unfinished escape character",
|
||||
&parser->err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -910,10 +955,42 @@ ucl_lex_json_string (struct ucl_parser *parser,
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
}
|
||||
|
||||
ucl_set_err (chunk, UCL_ESYNTAX, "no quote at the end of json string", &parser->err);
|
||||
ucl_set_err (parser, UCL_ESYNTAX, "no quote at the end of json string",
|
||||
&parser->err);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
ucl_parser_append_elt (struct ucl_parser *parser, ucl_hash_t *cont,
|
||||
ucl_object_t *top,
|
||||
ucl_object_t *elt)
|
||||
{
|
||||
ucl_object_t *nobj;
|
||||
|
||||
if ((parser->flags & UCL_PARSER_NO_IMPLICIT_ARRAYS) == 0) {
|
||||
/* Implicit array */
|
||||
top->flags |= UCL_OBJECT_MULTIVALUE;
|
||||
DL_APPEND (top, elt);
|
||||
}
|
||||
else {
|
||||
if ((top->flags & UCL_OBJECT_MULTIVALUE) != 0) {
|
||||
/* Just add to the explicit array */
|
||||
DL_APPEND (top->value.av, elt);
|
||||
}
|
||||
else {
|
||||
/* Convert to an array */
|
||||
ucl_hash_delete (cont, top);
|
||||
nobj = ucl_object_typed_new (UCL_ARRAY);
|
||||
nobj->key = top->key;
|
||||
nobj->keylen = top->keylen;
|
||||
nobj->flags |= UCL_OBJECT_MULTIVALUE;
|
||||
DL_APPEND (nobj->value.av, top);
|
||||
DL_APPEND (nobj->value.av, elt);
|
||||
ucl_hash_insert (cont, nobj, nobj->key, nobj->keylen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a key in an object
|
||||
* @param parser
|
||||
@ -981,7 +1058,8 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_ke
|
||||
}
|
||||
else {
|
||||
/* Invalid identifier */
|
||||
ucl_set_err (chunk, UCL_ESYNTAX, "key must begin with a letter", &parser->err);
|
||||
ucl_set_err (parser, UCL_ESYNTAX, "key must begin with a letter",
|
||||
&parser->err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -997,7 +1075,8 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_ke
|
||||
break;
|
||||
}
|
||||
else {
|
||||
ucl_set_err (chunk, UCL_ESYNTAX, "invalid character in a key", &parser->err);
|
||||
ucl_set_err (parser, UCL_ESYNTAX, "invalid character in a key",
|
||||
&parser->err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -1015,7 +1094,7 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_ke
|
||||
}
|
||||
|
||||
if (p >= chunk->end && got_content) {
|
||||
ucl_set_err (chunk, UCL_ESYNTAX, "unfinished key", &parser->err);
|
||||
ucl_set_err (parser, UCL_ESYNTAX, "unfinished key", &parser->err);
|
||||
return false;
|
||||
}
|
||||
else if (!got_content) {
|
||||
@ -1033,7 +1112,8 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_ke
|
||||
got_eq = true;
|
||||
}
|
||||
else {
|
||||
ucl_set_err (chunk, UCL_ESYNTAX, "unexpected '=' character", &parser->err);
|
||||
ucl_set_err (parser, UCL_ESYNTAX, "unexpected '=' character",
|
||||
&parser->err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -1043,7 +1123,8 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_ke
|
||||
got_semicolon = true;
|
||||
}
|
||||
else {
|
||||
ucl_set_err (chunk, UCL_ESYNTAX, "unexpected ':' character", &parser->err);
|
||||
ucl_set_err (parser, UCL_ESYNTAX, "unexpected ':' character",
|
||||
&parser->err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -1061,7 +1142,7 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_ke
|
||||
}
|
||||
|
||||
if (p >= chunk->end && got_content) {
|
||||
ucl_set_err (chunk, UCL_ESYNTAX, "unfinished key", &parser->err);
|
||||
ucl_set_err (parser, UCL_ESYNTAX, "unfinished key", &parser->err);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1096,7 +1177,7 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_ke
|
||||
}
|
||||
|
||||
/* Create a new object */
|
||||
nobj = ucl_object_new ();
|
||||
nobj = ucl_object_new_full (UCL_NULL, parser->chunks->priority);
|
||||
keylen = ucl_copy_or_store_ptr (parser, c, &nobj->trash_stack[UCL_TRASH_KEY],
|
||||
&key, end - c, need_unescape, parser->flags & UCL_PARSER_KEY_LOWERCASE, false);
|
||||
if (keylen == -1) {
|
||||
@ -1104,7 +1185,7 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_ke
|
||||
return false;
|
||||
}
|
||||
else if (keylen == 0) {
|
||||
ucl_set_err (chunk, UCL_ESYNTAX, "empty keys are not allowed", &parser->err);
|
||||
ucl_set_err (parser, UCL_ESYNTAX, "empty keys are not allowed", &parser->err);
|
||||
ucl_object_unref (nobj);
|
||||
return false;
|
||||
}
|
||||
@ -1120,7 +1201,27 @@ ucl_parse_key (struct ucl_parser *parser, struct ucl_chunk *chunk, bool *next_ke
|
||||
parser->stack->obj->len ++;
|
||||
}
|
||||
else {
|
||||
DL_APPEND (tobj, nobj);
|
||||
/*
|
||||
* The logic here is the following:
|
||||
*
|
||||
* - if we have two objects with the same priority, then we form an
|
||||
* implicit or explicit array
|
||||
* - if a new object has bigger priority, then we overwrite an old one
|
||||
* - if a new object has lower priority, then we ignore it
|
||||
*/
|
||||
unsigned priold = ucl_object_get_priority (tobj),
|
||||
prinew = ucl_object_get_priority (nobj);
|
||||
if (priold == prinew) {
|
||||
ucl_parser_append_elt (parser, container, tobj, nobj);
|
||||
}
|
||||
else if (priold > prinew) {
|
||||
ucl_object_unref (nobj);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
ucl_hash_replace (container, tobj, nobj);
|
||||
ucl_object_unref (tobj);
|
||||
}
|
||||
}
|
||||
|
||||
if (ucl_escape) {
|
||||
@ -1197,11 +1298,6 @@ ucl_parse_string_value (struct ucl_parser *parser,
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
}
|
||||
|
||||
if (p >= chunk->end) {
|
||||
ucl_set_err (chunk, UCL_ESYNTAX, "unfinished value", &parser->err);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1219,7 +1315,7 @@ ucl_parse_multiline_string (struct ucl_parser *parser,
|
||||
int term_len, unsigned char const **beg,
|
||||
bool *var_expand)
|
||||
{
|
||||
const unsigned char *p, *c;
|
||||
const unsigned char *p, *c, *tend;
|
||||
bool newline = false;
|
||||
int len = 0;
|
||||
|
||||
@ -1232,7 +1328,13 @@ ucl_parse_multiline_string (struct ucl_parser *parser,
|
||||
if (chunk->end - p < term_len) {
|
||||
return 0;
|
||||
}
|
||||
else if (memcmp (p, term, term_len) == 0 && (p[term_len] == '\n' || p[term_len] == '\r')) {
|
||||
else if (memcmp (p, term, term_len) == 0) {
|
||||
tend = p + term_len;
|
||||
if (*tend != '\n' && *tend != ';' && *tend != ',') {
|
||||
/* Incomplete terminator */
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
continue;
|
||||
}
|
||||
len = p - c;
|
||||
chunk->remain -= term_len;
|
||||
chunk->pos = p + term_len;
|
||||
@ -1263,7 +1365,7 @@ ucl_get_value_object (struct ucl_parser *parser)
|
||||
|
||||
if (parser->stack->obj->type == UCL_ARRAY) {
|
||||
/* Object must be allocated */
|
||||
obj = ucl_object_new ();
|
||||
obj = ucl_object_new_full (UCL_NULL, parser->chunks->priority);
|
||||
t = parser->stack->obj->value.av;
|
||||
DL_APPEND (t, obj);
|
||||
parser->cur_obj = obj;
|
||||
@ -1378,7 +1480,8 @@ ucl_parse_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
|
||||
chunk->line ++;
|
||||
if ((str_len = ucl_parse_multiline_string (parser, chunk, c,
|
||||
p - c, &c, &var_expand)) == 0) {
|
||||
ucl_set_err (chunk, UCL_ESYNTAX, "unterminated multiline value", &parser->err);
|
||||
ucl_set_err (parser, UCL_ESYNTAX,
|
||||
"unterminated multiline value", &parser->err);
|
||||
return false;
|
||||
}
|
||||
obj->type = UCL_STRING;
|
||||
@ -1423,7 +1526,8 @@ ucl_parse_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
|
||||
}
|
||||
str_len = chunk->pos - c - stripped_spaces;
|
||||
if (str_len <= 0) {
|
||||
ucl_set_err (chunk, 0, "string value must not be empty", &parser->err);
|
||||
ucl_set_err (parser, 0, "string value must not be empty",
|
||||
&parser->err);
|
||||
return false;
|
||||
}
|
||||
else if (str_len == 4 && memcmp (c, "null", 4) == 0) {
|
||||
@ -1482,7 +1586,9 @@ ucl_parse_after_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
|
||||
else if (ucl_test_character (*p, UCL_CHARACTER_VALUE_END)) {
|
||||
if (*p == '}' || *p == ']') {
|
||||
if (parser->stack == NULL) {
|
||||
ucl_set_err (chunk, UCL_ESYNTAX, "end of array or object detected without corresponding start", &parser->err);
|
||||
ucl_set_err (parser, UCL_ESYNTAX,
|
||||
"end of array or object detected without corresponding start",
|
||||
&parser->err);
|
||||
return false;
|
||||
}
|
||||
if ((*p == '}' && parser->stack->obj->type == UCL_OBJECT) ||
|
||||
@ -1503,7 +1609,9 @@ ucl_parse_after_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
|
||||
}
|
||||
}
|
||||
else {
|
||||
ucl_set_err (chunk, UCL_ESYNTAX, "unexpected terminating symbol detected", &parser->err);
|
||||
ucl_set_err (parser, UCL_ESYNTAX,
|
||||
"unexpected terminating symbol detected",
|
||||
&parser->err);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1525,7 +1633,8 @@ ucl_parse_after_value (struct ucl_parser *parser, struct ucl_chunk *chunk)
|
||||
else {
|
||||
/* Anything else */
|
||||
if (!got_sep) {
|
||||
ucl_set_err (chunk, UCL_ESYNTAX, "delimiter is missing", &parser->err);
|
||||
ucl_set_err (parser, UCL_ESYNTAX, "delimiter is missing",
|
||||
&parser->err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -1612,6 +1721,120 @@ ucl_parse_macro_value (struct ucl_parser *parser,
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse macro arguments as UCL object
|
||||
* @param parser parser structure
|
||||
* @param chunk the current data chunk
|
||||
* @return
|
||||
*/
|
||||
static ucl_object_t *
|
||||
ucl_parse_macro_arguments (struct ucl_parser *parser,
|
||||
struct ucl_chunk *chunk)
|
||||
{
|
||||
ucl_object_t *res = NULL;
|
||||
struct ucl_parser *params_parser;
|
||||
int obraces = 1, ebraces = 0, state = 0;
|
||||
const unsigned char *p, *c;
|
||||
size_t args_len = 0;
|
||||
struct ucl_parser_saved_state saved;
|
||||
|
||||
saved.column = chunk->column;
|
||||
saved.line = chunk->line;
|
||||
saved.pos = chunk->pos;
|
||||
saved.remain = chunk->remain;
|
||||
p = chunk->pos;
|
||||
|
||||
if (*p != '(' || chunk->remain < 2) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Set begin and start */
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
c = p;
|
||||
|
||||
while ((p) < (chunk)->end) {
|
||||
switch (state) {
|
||||
case 0:
|
||||
/* Parse symbols and check for '(', ')' and '"' */
|
||||
if (*p == '(') {
|
||||
obraces ++;
|
||||
}
|
||||
else if (*p == ')') {
|
||||
ebraces ++;
|
||||
}
|
||||
else if (*p == '"') {
|
||||
state = 1;
|
||||
}
|
||||
/* Check pairing */
|
||||
if (obraces == ebraces) {
|
||||
state = 99;
|
||||
}
|
||||
else {
|
||||
args_len ++;
|
||||
}
|
||||
/* Check overflow */
|
||||
if (chunk->remain == 0) {
|
||||
goto restore_chunk;
|
||||
}
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
break;
|
||||
case 1:
|
||||
/* We have quote character, so skip all but quotes */
|
||||
if (*p == '"' && *(p - 1) != '\\') {
|
||||
state = 0;
|
||||
}
|
||||
if (chunk->remain == 0) {
|
||||
goto restore_chunk;
|
||||
}
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
break;
|
||||
case 99:
|
||||
/*
|
||||
* We have read the full body of arguments, so we need to parse and set
|
||||
* object from that
|
||||
*/
|
||||
params_parser = ucl_parser_new (parser->flags);
|
||||
if (!ucl_parser_add_chunk (params_parser, c, args_len)) {
|
||||
ucl_set_err (parser, UCL_ESYNTAX, "macro arguments parsing error",
|
||||
&parser->err);
|
||||
}
|
||||
else {
|
||||
res = ucl_parser_get_object (params_parser);
|
||||
}
|
||||
ucl_parser_free (params_parser);
|
||||
|
||||
return res;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
restore_chunk:
|
||||
chunk->column = saved.column;
|
||||
chunk->line = saved.line;
|
||||
chunk->pos = saved.pos;
|
||||
chunk->remain = saved.remain;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define SKIP_SPACES_COMMENTS(parser, chunk, p) do { \
|
||||
while ((p) < (chunk)->end) { \
|
||||
if (!ucl_test_character (*(p), UCL_CHARACTER_WHITESPACE_UNSAFE)) { \
|
||||
if ((chunk)->remain >= 2 && ucl_lex_is_comment ((p)[0], (p)[1])) { \
|
||||
if (!ucl_skip_comments (parser)) { \
|
||||
return false; \
|
||||
} \
|
||||
p = (chunk)->pos; \
|
||||
} \
|
||||
break; \
|
||||
} \
|
||||
ucl_chunk_skipc (chunk, p); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* Handle the main states of rcl parser
|
||||
* @param parser parser structure
|
||||
@ -1622,13 +1845,13 @@ ucl_parse_macro_value (struct ucl_parser *parser,
|
||||
static bool
|
||||
ucl_state_machine (struct ucl_parser *parser)
|
||||
{
|
||||
ucl_object_t *obj;
|
||||
ucl_object_t *obj, *macro_args;
|
||||
struct ucl_chunk *chunk = parser->chunks;
|
||||
const unsigned char *p, *c = NULL, *macro_start = NULL;
|
||||
unsigned char *macro_escaped;
|
||||
size_t macro_len = 0;
|
||||
struct ucl_macro *macro = NULL;
|
||||
bool next_key = false, end_of_object = false;
|
||||
bool next_key = false, end_of_object = false, ret;
|
||||
|
||||
if (parser->top_obj == NULL) {
|
||||
if (*chunk->pos == '[') {
|
||||
@ -1654,7 +1877,6 @@ ucl_state_machine (struct ucl_parser *parser)
|
||||
* if we got [ or { correspondingly or can just treat new data as
|
||||
* a key of newly created object
|
||||
*/
|
||||
obj = parser->cur_obj;
|
||||
if (!ucl_skip_comments (parser)) {
|
||||
parser->prev_state = parser->state;
|
||||
parser->state = UCL_STATE_ERROR;
|
||||
@ -1691,7 +1913,7 @@ ucl_state_machine (struct ucl_parser *parser)
|
||||
}
|
||||
if (parser->stack == NULL) {
|
||||
/* No objects are on stack, but we want to parse a key */
|
||||
ucl_set_err (chunk, UCL_ESYNTAX, "top object is finished but the parser "
|
||||
ucl_set_err (parser, UCL_ESYNTAX, "top object is finished but the parser "
|
||||
"expects a key", &parser->err);
|
||||
parser->prev_state = parser->state;
|
||||
parser->state = UCL_STATE_ERROR;
|
||||
@ -1757,7 +1979,8 @@ ucl_state_machine (struct ucl_parser *parser)
|
||||
p = chunk->pos;
|
||||
break;
|
||||
case UCL_STATE_MACRO_NAME:
|
||||
if (!ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
|
||||
if (!ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE) &&
|
||||
*p != '(') {
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
}
|
||||
else if (p - c > 0) {
|
||||
@ -1772,48 +1995,51 @@ ucl_state_machine (struct ucl_parser *parser)
|
||||
return false;
|
||||
}
|
||||
/* Now we need to skip all spaces */
|
||||
while (p < chunk->end) {
|
||||
if (!ucl_test_character (*p, UCL_CHARACTER_WHITESPACE_UNSAFE)) {
|
||||
if (chunk->remain >= 2 && ucl_lex_is_comment (p[0], p[1])) {
|
||||
/* Skip comment */
|
||||
if (!ucl_skip_comments (parser)) {
|
||||
return false;
|
||||
}
|
||||
p = chunk->pos;
|
||||
}
|
||||
break;
|
||||
}
|
||||
ucl_chunk_skipc (chunk, p);
|
||||
}
|
||||
SKIP_SPACES_COMMENTS(parser, chunk, p);
|
||||
parser->state = UCL_STATE_MACRO;
|
||||
}
|
||||
break;
|
||||
case UCL_STATE_MACRO:
|
||||
if (*chunk->pos == '(') {
|
||||
macro_args = ucl_parse_macro_arguments (parser, chunk);
|
||||
p = chunk->pos;
|
||||
if (macro_args) {
|
||||
SKIP_SPACES_COMMENTS(parser, chunk, p);
|
||||
}
|
||||
}
|
||||
else {
|
||||
macro_args = NULL;
|
||||
}
|
||||
if (!ucl_parse_macro_value (parser, chunk, macro,
|
||||
¯o_start, ¯o_len)) {
|
||||
parser->prev_state = parser->state;
|
||||
parser->state = UCL_STATE_ERROR;
|
||||
return false;
|
||||
}
|
||||
macro_len = ucl_expand_variable (parser, ¯o_escaped, macro_start, macro_len);
|
||||
macro_len = ucl_expand_variable (parser, ¯o_escaped,
|
||||
macro_start, macro_len);
|
||||
parser->state = parser->prev_state;
|
||||
if (macro_escaped == NULL) {
|
||||
if (!macro->handler (macro_start, macro_len, macro->ud)) {
|
||||
return false;
|
||||
}
|
||||
ret = macro->handler (macro_start, macro_len, macro_args,
|
||||
macro->ud);
|
||||
}
|
||||
else {
|
||||
if (!macro->handler (macro_escaped, macro_len, macro->ud)) {
|
||||
UCL_FREE (macro_len + 1, macro_escaped);
|
||||
return false;
|
||||
}
|
||||
ret = macro->handler (macro_escaped, macro_len, macro_args,
|
||||
macro->ud);
|
||||
UCL_FREE (macro_len + 1, macro_escaped);
|
||||
}
|
||||
p = chunk->pos;
|
||||
if (macro_args) {
|
||||
ucl_object_unref (macro_args);
|
||||
}
|
||||
if (!ret) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* TODO: add all states */
|
||||
ucl_set_err (chunk, UCL_EINTERNAL, "internal error: parser is in an unknown state", &parser->err);
|
||||
ucl_set_err (parser, UCL_EINTERNAL,
|
||||
"internal error: parser is in an unknown state", &parser->err);
|
||||
parser->state = UCL_STATE_ERROR;
|
||||
return false;
|
||||
}
|
||||
@ -1888,7 +2114,7 @@ ucl_parser_register_variable (struct ucl_parser *parser, const char *var,
|
||||
|
||||
if (new != NULL) {
|
||||
/* Remove variable */
|
||||
LL_DELETE (parser->variables, new);
|
||||
DL_DELETE (parser->variables, new);
|
||||
free (new->var);
|
||||
free (new->value);
|
||||
UCL_FREE (sizeof (struct ucl_variable), new);
|
||||
@ -1910,7 +2136,7 @@ ucl_parser_register_variable (struct ucl_parser *parser, const char *var,
|
||||
new->value = strdup (value);
|
||||
new->value_len = strlen (value);
|
||||
|
||||
LL_PREPEND (parser->variables, new);
|
||||
DL_APPEND (parser->variables, new);
|
||||
}
|
||||
else {
|
||||
free (new->value);
|
||||
@ -1929,15 +2155,19 @@ ucl_parser_set_variables_handler (struct ucl_parser *parser,
|
||||
}
|
||||
|
||||
bool
|
||||
ucl_parser_add_chunk (struct ucl_parser *parser, const unsigned char *data,
|
||||
size_t len)
|
||||
ucl_parser_add_chunk_priority (struct ucl_parser *parser, const unsigned char *data,
|
||||
size_t len, unsigned priority)
|
||||
{
|
||||
struct ucl_chunk *chunk;
|
||||
|
||||
if (data == NULL || len == 0) {
|
||||
if (data == NULL) {
|
||||
ucl_create_err (&parser->err, "invalid chunk added");
|
||||
return false;
|
||||
}
|
||||
if (len == 0) {
|
||||
parser->top_obj = ucl_object_new_full (UCL_OBJECT, priority);
|
||||
return true;
|
||||
}
|
||||
if (parser->state != UCL_STATE_ERROR) {
|
||||
chunk = UCL_ALLOC (sizeof (struct ucl_chunk));
|
||||
if (chunk == NULL) {
|
||||
@ -1950,6 +2180,7 @@ ucl_parser_add_chunk (struct ucl_parser *parser, const unsigned char *data,
|
||||
chunk->end = chunk->begin + len;
|
||||
chunk->line = 1;
|
||||
chunk->column = 0;
|
||||
chunk->priority = priority;
|
||||
LL_PREPEND (parser->chunks, chunk);
|
||||
parser->recursion ++;
|
||||
if (parser->recursion > UCL_MAX_RECURSION) {
|
||||
@ -1965,6 +2196,13 @@ ucl_parser_add_chunk (struct ucl_parser *parser, const unsigned char *data,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
ucl_parser_add_chunk (struct ucl_parser *parser, const unsigned char *data,
|
||||
size_t len)
|
||||
{
|
||||
return ucl_parser_add_chunk_priority (parser, data, len, 0);
|
||||
}
|
||||
|
||||
bool
|
||||
ucl_parser_add_string (struct ucl_parser *parser, const char *data,
|
||||
size_t len)
|
||||
|
581
src/ucl_util.c
581
src/ucl_util.c
@ -25,6 +25,8 @@
|
||||
#include "ucl_internal.h"
|
||||
#include "ucl_chartable.h"
|
||||
|
||||
#include <glob.h>
|
||||
|
||||
#ifdef HAVE_LIBGEN_H
|
||||
#include <libgen.h> /* For dirname */
|
||||
#endif
|
||||
@ -129,11 +131,6 @@ static char* ucl_realpath(const char *path, char *resolved_path) {
|
||||
#define ucl_realpath realpath
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file rcl_util.c
|
||||
* Utilities for rcl parsing
|
||||
*/
|
||||
|
||||
typedef void (*ucl_object_dtor) (ucl_object_t *obj);
|
||||
static void ucl_object_free_internal (ucl_object_t *obj, bool allow_rec,
|
||||
ucl_object_dtor dtor);
|
||||
@ -148,7 +145,19 @@ ucl_object_dtor_free (ucl_object_t *obj)
|
||||
if (obj->trash_stack[UCL_TRASH_VALUE] != NULL) {
|
||||
UCL_FREE (obj->len, obj->trash_stack[UCL_TRASH_VALUE]);
|
||||
}
|
||||
UCL_FREE (sizeof (ucl_object_t), obj);
|
||||
/* Do not free ephemeral objects */
|
||||
if ((obj->flags & UCL_OBJECT_EPHEMERAL) == 0) {
|
||||
if (obj->type != UCL_USERDATA) {
|
||||
UCL_FREE (sizeof (ucl_object_t), obj);
|
||||
}
|
||||
else {
|
||||
struct ucl_object_userdata *ud = (struct ucl_object_userdata *)obj;
|
||||
if (ud->dtor) {
|
||||
ud->dtor (obj->value.ud);
|
||||
}
|
||||
UCL_FREE (sizeof (*ud), obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -423,7 +432,11 @@ ucl_parser_free (struct ucl_parser *parser)
|
||||
}
|
||||
|
||||
if (parser->err != NULL) {
|
||||
utstring_free(parser->err);
|
||||
utstring_free (parser->err);
|
||||
}
|
||||
|
||||
if (parser->cur_file) {
|
||||
free (parser->cur_file);
|
||||
}
|
||||
|
||||
UCL_FREE (sizeof (struct ucl_parser), parser);
|
||||
@ -701,7 +714,8 @@ ucl_sig_check (const unsigned char *data, size_t datalen,
|
||||
*/
|
||||
static bool
|
||||
ucl_include_url (const unsigned char *data, size_t len,
|
||||
struct ucl_parser *parser, bool check_signature, bool must_exist)
|
||||
struct ucl_parser *parser, bool check_signature, bool must_exist,
|
||||
unsigned priority)
|
||||
{
|
||||
|
||||
bool res;
|
||||
@ -744,7 +758,7 @@ ucl_include_url (const unsigned char *data, size_t len,
|
||||
prev_state = parser->state;
|
||||
parser->state = UCL_STATE_INIT;
|
||||
|
||||
res = ucl_parser_add_chunk (parser, buf, buflen);
|
||||
res = ucl_parser_add_chunk_priority (parser, buf, buflen, priority);
|
||||
if (res == true) {
|
||||
/* Remove chunk from the stack */
|
||||
chunk = parser->chunks;
|
||||
@ -761,23 +775,30 @@ ucl_include_url (const unsigned char *data, size_t len,
|
||||
}
|
||||
|
||||
/**
|
||||
* Include a file to configuration
|
||||
* Include a single file to the parser
|
||||
* @param data
|
||||
* @param len
|
||||
* @param parser
|
||||
* @param err
|
||||
* @param check_signature
|
||||
* @param must_exist
|
||||
* @param allow_glob
|
||||
* @param priority
|
||||
* @return
|
||||
*/
|
||||
static bool
|
||||
ucl_include_file (const unsigned char *data, size_t len,
|
||||
struct ucl_parser *parser, bool check_signature, bool must_exist)
|
||||
ucl_include_file_single (const unsigned char *data, size_t len,
|
||||
struct ucl_parser *parser, bool check_signature, bool must_exist,
|
||||
unsigned priority)
|
||||
{
|
||||
bool res;
|
||||
struct ucl_chunk *chunk;
|
||||
unsigned char *buf = NULL;
|
||||
char *old_curfile;
|
||||
size_t buflen;
|
||||
char filebuf[PATH_MAX], realbuf[PATH_MAX];
|
||||
int prev_state;
|
||||
struct ucl_variable *cur_var, *tmp_var, *old_curdir = NULL,
|
||||
*old_filename = NULL;
|
||||
|
||||
snprintf (filebuf, sizeof (filebuf), "%.*s", (int)len, data);
|
||||
if (ucl_realpath (filebuf, realbuf) == NULL) {
|
||||
@ -790,6 +811,13 @@ ucl_include_file (const unsigned char *data, size_t len,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parser->cur_file && strcmp (realbuf, parser->cur_file) == 0) {
|
||||
/* We are likely including the file itself */
|
||||
ucl_create_err (&parser->err, "trying to include the file %s from itself",
|
||||
realbuf);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ucl_fetch_file (realbuf, &buf, &buflen, &parser->err, must_exist)) {
|
||||
return (!must_exist || false);
|
||||
}
|
||||
@ -818,19 +846,66 @@ ucl_include_file (const unsigned char *data, size_t len,
|
||||
#endif
|
||||
}
|
||||
|
||||
old_curfile = parser->cur_file;
|
||||
parser->cur_file = strdup (realbuf);
|
||||
|
||||
/* Store old file vars */
|
||||
DL_FOREACH_SAFE (parser->variables, cur_var, tmp_var) {
|
||||
if (strcmp (cur_var->var, "CURDIR") == 0) {
|
||||
old_curdir = cur_var;
|
||||
DL_DELETE (parser->variables, cur_var);
|
||||
}
|
||||
else if (strcmp (cur_var->var, "FILENAME") == 0) {
|
||||
old_filename = cur_var;
|
||||
DL_DELETE (parser->variables, cur_var);
|
||||
}
|
||||
}
|
||||
|
||||
ucl_parser_set_filevars (parser, realbuf, false);
|
||||
|
||||
prev_state = parser->state;
|
||||
parser->state = UCL_STATE_INIT;
|
||||
|
||||
res = ucl_parser_add_chunk (parser, buf, buflen);
|
||||
if (res == true) {
|
||||
/* Remove chunk from the stack */
|
||||
chunk = parser->chunks;
|
||||
if (chunk != NULL) {
|
||||
parser->chunks = chunk->next;
|
||||
UCL_FREE (sizeof (struct ucl_chunk), chunk);
|
||||
res = ucl_parser_add_chunk_priority (parser, buf, buflen, priority);
|
||||
if (!res && !must_exist) {
|
||||
/* Free error */
|
||||
utstring_free (parser->err);
|
||||
parser->err = NULL;
|
||||
parser->state = UCL_STATE_AFTER_VALUE;
|
||||
}
|
||||
|
||||
/* Remove chunk from the stack */
|
||||
chunk = parser->chunks;
|
||||
if (chunk != NULL) {
|
||||
parser->chunks = chunk->next;
|
||||
UCL_FREE (sizeof (struct ucl_chunk), chunk);
|
||||
parser->recursion --;
|
||||
}
|
||||
|
||||
/* Restore old file vars */
|
||||
parser->cur_file = old_curfile;
|
||||
DL_FOREACH_SAFE (parser->variables, cur_var, tmp_var) {
|
||||
if (strcmp (cur_var->var, "CURDIR") == 0 && old_curdir) {
|
||||
DL_DELETE (parser->variables, cur_var);
|
||||
free (cur_var->var);
|
||||
free (cur_var->value);
|
||||
UCL_FREE (sizeof (struct ucl_variable), cur_var);
|
||||
}
|
||||
else if (strcmp (cur_var->var, "FILENAME") == 0 && old_filename) {
|
||||
DL_DELETE (parser->variables, cur_var);
|
||||
free (cur_var->var);
|
||||
free (cur_var->value);
|
||||
UCL_FREE (sizeof (struct ucl_variable), cur_var);
|
||||
}
|
||||
}
|
||||
if (old_filename) {
|
||||
DL_APPEND (parser->variables, old_filename);
|
||||
}
|
||||
if (old_curdir) {
|
||||
DL_APPEND (parser->variables, old_curdir);
|
||||
}
|
||||
if (old_curfile) {
|
||||
free (old_curfile);
|
||||
}
|
||||
|
||||
parser->state = prev_state;
|
||||
@ -842,6 +917,138 @@ ucl_include_file (const unsigned char *data, size_t len,
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Include a file to configuration
|
||||
* @param data
|
||||
* @param len
|
||||
* @param parser
|
||||
* @param err
|
||||
* @return
|
||||
*/
|
||||
static bool
|
||||
ucl_include_file (const unsigned char *data, size_t len,
|
||||
struct ucl_parser *parser, bool check_signature, bool must_exist,
|
||||
bool allow_glob, unsigned priority)
|
||||
{
|
||||
const unsigned char *p = data, *end = data + len;
|
||||
bool need_glob = false;
|
||||
int cnt = 0;
|
||||
glob_t globbuf;
|
||||
char glob_pattern[PATH_MAX];
|
||||
size_t i;
|
||||
|
||||
if (!allow_glob) {
|
||||
return ucl_include_file_single (data, len, parser, check_signature,
|
||||
must_exist, priority);
|
||||
}
|
||||
else {
|
||||
/* Check for special symbols in a filename */
|
||||
while (p != end) {
|
||||
if (*p == '*' || *p == '?') {
|
||||
need_glob = true;
|
||||
break;
|
||||
}
|
||||
p ++;
|
||||
}
|
||||
if (need_glob) {
|
||||
memset (&globbuf, 0, sizeof (globbuf));
|
||||
ucl_strlcpy (glob_pattern, (const char *)data, sizeof (glob_pattern));
|
||||
if (glob (glob_pattern, 0, NULL, &globbuf) != 0) {
|
||||
return (!must_exist || false);
|
||||
}
|
||||
for (i = 0; i < globbuf.gl_pathc; i ++) {
|
||||
if (!ucl_include_file_single ((unsigned char *)globbuf.gl_pathv[i],
|
||||
strlen (globbuf.gl_pathv[i]), parser, check_signature,
|
||||
must_exist, priority)) {
|
||||
globfree (&globbuf);
|
||||
return false;
|
||||
}
|
||||
cnt ++;
|
||||
}
|
||||
globfree (&globbuf);
|
||||
|
||||
if (cnt == 0 && must_exist) {
|
||||
ucl_create_err (&parser->err, "cannot match any files for pattern %s",
|
||||
glob_pattern);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return ucl_include_file_single (data, len, parser, check_signature,
|
||||
must_exist, priority);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common function to handle .*include* macros
|
||||
* @param data
|
||||
* @param len
|
||||
* @param args
|
||||
* @param parser
|
||||
* @param default_try
|
||||
* @param default_sign
|
||||
* @return
|
||||
*/
|
||||
static bool
|
||||
ucl_include_common (const unsigned char *data, size_t len,
|
||||
const ucl_object_t *args, struct ucl_parser *parser,
|
||||
bool default_try,
|
||||
bool default_sign)
|
||||
{
|
||||
bool try_load, allow_glob, allow_url, need_sign;
|
||||
unsigned priority;
|
||||
const ucl_object_t *param;
|
||||
ucl_object_iter_t it = NULL;
|
||||
|
||||
/* Default values */
|
||||
try_load = default_try;
|
||||
allow_glob = false;
|
||||
allow_url = true;
|
||||
need_sign = default_sign;
|
||||
priority = 0;
|
||||
|
||||
/* Process arguments */
|
||||
if (args != NULL && args->type == UCL_OBJECT) {
|
||||
while ((param = ucl_iterate_object (args, &it, true)) != NULL) {
|
||||
if (param->type == UCL_BOOLEAN) {
|
||||
if (strcmp (param->key, "try") == 0) {
|
||||
try_load = ucl_object_toboolean (param);
|
||||
}
|
||||
else if (strcmp (param->key, "sign") == 0) {
|
||||
need_sign = ucl_object_toboolean (param);
|
||||
}
|
||||
else if (strcmp (param->key, "glob") == 0) {
|
||||
allow_glob = ucl_object_toboolean (param);
|
||||
}
|
||||
else if (strcmp (param->key, "url") == 0) {
|
||||
allow_url = ucl_object_toboolean (param);
|
||||
}
|
||||
}
|
||||
else if (param->type == UCL_INT) {
|
||||
if (strcmp (param->key, "priority") == 0) {
|
||||
priority = ucl_object_toint (param);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (*data == '/' || *data == '.') {
|
||||
/* Try to load a file */
|
||||
return ucl_include_file (data, len, parser, need_sign, !try_load,
|
||||
allow_glob, priority);
|
||||
}
|
||||
else if (allow_url) {
|
||||
/* Globbing is not used for URL's */
|
||||
return ucl_include_url (data, len, parser, need_sign, !try_load,
|
||||
priority);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle include macro
|
||||
* @param data include data
|
||||
@ -851,16 +1058,12 @@ ucl_include_file (const unsigned char *data, size_t len,
|
||||
* @return
|
||||
*/
|
||||
UCL_EXTERN bool
|
||||
ucl_include_handler (const unsigned char *data, size_t len, void* ud)
|
||||
ucl_include_handler (const unsigned char *data, size_t len,
|
||||
const ucl_object_t *args, void* ud)
|
||||
{
|
||||
struct ucl_parser *parser = ud;
|
||||
|
||||
if (*data == '/' || *data == '.') {
|
||||
/* Try to load a file */
|
||||
return ucl_include_file (data, len, parser, false, true);
|
||||
}
|
||||
|
||||
return ucl_include_url (data, len, parser, false, true);
|
||||
return ucl_include_common (data, len, args, parser, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -872,30 +1075,22 @@ ucl_include_handler (const unsigned char *data, size_t len, void* ud)
|
||||
* @return
|
||||
*/
|
||||
UCL_EXTERN bool
|
||||
ucl_includes_handler (const unsigned char *data, size_t len, void* ud)
|
||||
ucl_includes_handler (const unsigned char *data, size_t len,
|
||||
const ucl_object_t *args, void* ud)
|
||||
{
|
||||
struct ucl_parser *parser = ud;
|
||||
|
||||
if (*data == '/' || *data == '.') {
|
||||
/* Try to load a file */
|
||||
return ucl_include_file (data, len, parser, true, true);
|
||||
}
|
||||
|
||||
return ucl_include_url (data, len, parser, true, true);
|
||||
return ucl_include_common (data, len, args, parser, false, true);
|
||||
}
|
||||
|
||||
|
||||
UCL_EXTERN bool
|
||||
ucl_try_include_handler (const unsigned char *data, size_t len, void* ud)
|
||||
ucl_try_include_handler (const unsigned char *data, size_t len,
|
||||
const ucl_object_t *args, void* ud)
|
||||
{
|
||||
struct ucl_parser *parser = ud;
|
||||
|
||||
if (*data == '/' || *data == '.') {
|
||||
/* Try to load a file */
|
||||
return ucl_include_file (data, len, parser, false, false);
|
||||
}
|
||||
|
||||
return ucl_include_url (data, len, parser, false, false);
|
||||
return ucl_include_common (data, len, args, parser, true, false);
|
||||
}
|
||||
|
||||
UCL_EXTERN bool
|
||||
@ -947,6 +1142,10 @@ ucl_parser_add_file (struct ucl_parser *parser, const char *filename)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parser->cur_file) {
|
||||
free (parser->cur_file);
|
||||
}
|
||||
parser->cur_file = strdup (realbuf);
|
||||
ucl_parser_set_filevars (parser, realbuf, false);
|
||||
ret = ucl_parser_add_chunk (parser, buf, len);
|
||||
|
||||
@ -957,6 +1156,39 @@ ucl_parser_add_file (struct ucl_parser *parser, const char *filename)
|
||||
return ret;
|
||||
}
|
||||
|
||||
UCL_EXTERN bool
|
||||
ucl_parser_add_fd (struct ucl_parser *parser, int fd)
|
||||
{
|
||||
unsigned char *buf;
|
||||
size_t len;
|
||||
bool ret;
|
||||
struct stat st;
|
||||
|
||||
if (fstat (fd, &st) == -1) {
|
||||
ucl_create_err (&parser->err, "cannot stat fd %d: %s",
|
||||
fd, strerror (errno));
|
||||
return false;
|
||||
}
|
||||
if ((buf = ucl_mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
|
||||
ucl_create_err (&parser->err, "cannot mmap fd %d: %s",
|
||||
fd, strerror (errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parser->cur_file) {
|
||||
free (parser->cur_file);
|
||||
}
|
||||
parser->cur_file = NULL;
|
||||
len = st.st_size;
|
||||
ret = ucl_parser_add_chunk (parser, buf, len);
|
||||
|
||||
if (len > 0) {
|
||||
ucl_munmap (buf, len);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t
|
||||
ucl_strlcpy (char *dst, const char *src, size_t siz)
|
||||
{
|
||||
@ -1176,6 +1408,15 @@ ucl_object_insert_key_common (ucl_object_t *top, ucl_object_t *elt,
|
||||
}
|
||||
}
|
||||
|
||||
/* workaround for some use cases */
|
||||
if (elt->trash_stack[UCL_TRASH_KEY] != NULL &&
|
||||
key != (const char *)elt->trash_stack[UCL_TRASH_KEY]) {
|
||||
/* Remove copied key */
|
||||
free (elt->trash_stack[UCL_TRASH_KEY]);
|
||||
elt->trash_stack[UCL_TRASH_KEY] = NULL;
|
||||
elt->flags &= ~UCL_OBJECT_ALLOCATED_KEY;
|
||||
}
|
||||
|
||||
elt->key = key;
|
||||
elt->keylen = keylen;
|
||||
|
||||
@ -1185,9 +1426,8 @@ ucl_object_insert_key_common (ucl_object_t *top, ucl_object_t *elt,
|
||||
|
||||
found = __DECONST (ucl_object_t *, ucl_hash_search_obj (top->value.ov, elt));
|
||||
|
||||
if (!found) {
|
||||
if (found == NULL) {
|
||||
top->value.ov = ucl_hash_insert_object (top->value.ov, elt);
|
||||
DL_APPEND (found, elt);
|
||||
top->len ++;
|
||||
if (replace) {
|
||||
ret = false;
|
||||
@ -1195,11 +1435,8 @@ ucl_object_insert_key_common (ucl_object_t *top, ucl_object_t *elt,
|
||||
}
|
||||
else {
|
||||
if (replace) {
|
||||
ucl_hash_delete (top->value.ov, found);
|
||||
ucl_hash_replace (top->value.ov, found, elt);
|
||||
ucl_object_unref (found);
|
||||
top->value.ov = ucl_hash_insert_object (top->value.ov, elt);
|
||||
found = NULL;
|
||||
DL_APPEND (found, elt);
|
||||
}
|
||||
else if (merge) {
|
||||
if (found->type != UCL_OBJECT && elt->type == UCL_OBJECT) {
|
||||
@ -1310,6 +1547,40 @@ ucl_object_replace_key (ucl_object_t *top, ucl_object_t *elt,
|
||||
return ucl_object_insert_key_common (top, elt, key, keylen, copy_key, false, true);
|
||||
}
|
||||
|
||||
bool
|
||||
ucl_object_merge (ucl_object_t *top, ucl_object_t *elt, bool copy)
|
||||
{
|
||||
ucl_object_t *cur = NULL, *cp = NULL, *found = NULL;
|
||||
ucl_object_iter_t iter = NULL;
|
||||
|
||||
if (top == NULL || top->type != UCL_OBJECT || elt == NULL || elt->type != UCL_OBJECT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Mix two hashes */
|
||||
while ((cur = (ucl_object_t*)ucl_hash_iterate (elt->value.ov, &iter))) {
|
||||
if (copy) {
|
||||
cp = ucl_object_copy (cur);
|
||||
}
|
||||
else {
|
||||
cp = ucl_object_ref (cur);
|
||||
}
|
||||
found = __DECONST(ucl_object_t *, ucl_hash_search (top->value.ov, cp->key, cp->keylen));
|
||||
if (found == NULL) {
|
||||
/* The key does not exist */
|
||||
top->value.ov = ucl_hash_insert_object (top->value.ov, cp);
|
||||
top->len ++;
|
||||
}
|
||||
else {
|
||||
/* The key already exists, replace it */
|
||||
ucl_hash_replace (top->value.ov, found, cp);
|
||||
ucl_object_unref (found);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const ucl_object_t *
|
||||
ucl_object_find_keyl (const ucl_object_t *obj, const char *key, size_t klen)
|
||||
{
|
||||
@ -1372,9 +1643,6 @@ ucl_iterate_object (const ucl_object_t *obj, ucl_object_iter_t *iter, bool expan
|
||||
elt = *iter;
|
||||
if (elt == NULL) {
|
||||
elt = obj;
|
||||
if (elt == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else if (elt == obj) {
|
||||
return NULL;
|
||||
@ -1442,29 +1710,59 @@ ucl_lookup_path (const ucl_object_t *top, const char *path_in) {
|
||||
ucl_object_t *
|
||||
ucl_object_new (void)
|
||||
{
|
||||
ucl_object_t *new;
|
||||
new = malloc (sizeof (ucl_object_t));
|
||||
if (new != NULL) {
|
||||
memset (new, 0, sizeof (ucl_object_t));
|
||||
new->ref = 1;
|
||||
new->type = UCL_NULL;
|
||||
}
|
||||
return new;
|
||||
return ucl_object_typed_new (UCL_NULL);
|
||||
}
|
||||
|
||||
ucl_object_t *
|
||||
ucl_object_typed_new (ucl_type_t type)
|
||||
{
|
||||
return ucl_object_new_full (type, 0);
|
||||
}
|
||||
|
||||
ucl_object_t *
|
||||
ucl_object_new_full (ucl_type_t type, unsigned priority)
|
||||
{
|
||||
ucl_object_t *new;
|
||||
new = malloc (sizeof (ucl_object_t));
|
||||
if (new != NULL) {
|
||||
memset (new, 0, sizeof (ucl_object_t));
|
||||
new->ref = 1;
|
||||
new->type = (type <= UCL_NULL ? type : UCL_NULL);
|
||||
|
||||
if (type != UCL_USERDATA) {
|
||||
new = UCL_ALLOC (sizeof (ucl_object_t));
|
||||
if (new != NULL) {
|
||||
memset (new, 0, sizeof (ucl_object_t));
|
||||
new->ref = 1;
|
||||
new->type = (type <= UCL_NULL ? type : UCL_NULL);
|
||||
new->next = NULL;
|
||||
new->prev = new;
|
||||
ucl_object_set_priority (new, priority);
|
||||
}
|
||||
}
|
||||
else {
|
||||
new = ucl_object_new_userdata (NULL, NULL);
|
||||
ucl_object_set_priority (new, priority);
|
||||
}
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
ucl_object_t*
|
||||
ucl_object_new_userdata (ucl_userdata_dtor dtor, ucl_userdata_emitter emitter)
|
||||
{
|
||||
struct ucl_object_userdata *new;
|
||||
size_t nsize = sizeof (*new);
|
||||
|
||||
new = UCL_ALLOC (nsize);
|
||||
if (new != NULL) {
|
||||
memset (new, 0, nsize);
|
||||
new->obj.ref = 1;
|
||||
new->obj.type = UCL_USERDATA;
|
||||
new->obj.next = NULL;
|
||||
new->obj.prev = (ucl_object_t *)new;
|
||||
new->dtor = dtor;
|
||||
new->emitter = emitter;
|
||||
}
|
||||
|
||||
return (ucl_object_t *)new;
|
||||
}
|
||||
|
||||
ucl_type_t
|
||||
ucl_object_type (const ucl_object_t *obj)
|
||||
{
|
||||
@ -1576,6 +1874,30 @@ ucl_array_prepend (ucl_object_t *top, ucl_object_t *elt)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ucl_array_merge (ucl_object_t *top, ucl_object_t *elt, bool copy)
|
||||
{
|
||||
ucl_object_t *cur, *tmp, *cp;
|
||||
|
||||
if (elt == NULL || top == NULL || top->type != UCL_ARRAY || elt->type != UCL_ARRAY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DL_FOREACH_SAFE (elt->value.av, cur, tmp) {
|
||||
if (copy) {
|
||||
cp = ucl_object_copy (cur);
|
||||
}
|
||||
else {
|
||||
cp = ucl_object_ref (cur);
|
||||
}
|
||||
if (cp != NULL) {
|
||||
ucl_array_append (top, cp);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ucl_object_t *
|
||||
ucl_array_delete (ucl_object_t *top, ucl_object_t *elt)
|
||||
{
|
||||
@ -1660,6 +1982,28 @@ ucl_array_find_index (const ucl_object_t *top, unsigned int index)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ucl_object_t *
|
||||
ucl_array_replace_index (ucl_object_t *top, ucl_object_t *elt,
|
||||
unsigned int index)
|
||||
{
|
||||
ucl_object_t *cur, *tmp;
|
||||
|
||||
if (top == NULL || top->type != UCL_ARRAY || elt == NULL ||
|
||||
top->len == 0 || (index + 1) > top->len) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DL_FOREACH_SAFE (top->value.av, cur, tmp) {
|
||||
if (index == 0) {
|
||||
DL_REPLACE_ELEM (top->value.av, cur, elt);
|
||||
return cur;
|
||||
}
|
||||
--index;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ucl_object_t *
|
||||
ucl_elt_append (ucl_object_t *head, ucl_object_t *elt)
|
||||
{
|
||||
@ -1849,16 +2193,99 @@ ucl_object_ref (const ucl_object_t *obj)
|
||||
ucl_object_t *res = NULL;
|
||||
|
||||
if (obj != NULL) {
|
||||
res = __DECONST (ucl_object_t *, obj);
|
||||
if (obj->flags & UCL_OBJECT_EPHEMERAL) {
|
||||
/*
|
||||
* Use deep copy for ephemeral objects, note that its refcount
|
||||
* is NOT increased, since ephemeral objects does not need refcount
|
||||
* at all
|
||||
*/
|
||||
res = ucl_object_copy (obj);
|
||||
}
|
||||
else {
|
||||
res = __DECONST (ucl_object_t *, obj);
|
||||
#ifdef HAVE_ATOMIC_BUILTINS
|
||||
(void)__sync_add_and_fetch (&res->ref, 1);
|
||||
(void)__sync_add_and_fetch (&res->ref, 1);
|
||||
#else
|
||||
res->ref ++;
|
||||
res->ref ++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static ucl_object_t *
|
||||
ucl_object_copy_internal (const ucl_object_t *other, bool allow_array)
|
||||
{
|
||||
|
||||
ucl_object_t *new;
|
||||
ucl_object_iter_t it = NULL;
|
||||
const ucl_object_t *cur;
|
||||
|
||||
new = malloc (sizeof (*new));
|
||||
|
||||
if (new != NULL) {
|
||||
memcpy (new, other, sizeof (*new));
|
||||
if (other->flags & UCL_OBJECT_EPHEMERAL) {
|
||||
/* Copied object is always non ephemeral */
|
||||
new->flags &= ~UCL_OBJECT_EPHEMERAL;
|
||||
}
|
||||
new->ref = 1;
|
||||
/* Unlink from others */
|
||||
new->next = NULL;
|
||||
new->prev = new;
|
||||
|
||||
/* deep copy of values stored */
|
||||
if (other->trash_stack[UCL_TRASH_KEY] != NULL) {
|
||||
new->trash_stack[UCL_TRASH_KEY] =
|
||||
strdup (other->trash_stack[UCL_TRASH_KEY]);
|
||||
if (other->key == (const char *)other->trash_stack[UCL_TRASH_KEY]) {
|
||||
new->key = new->trash_stack[UCL_TRASH_KEY];
|
||||
}
|
||||
}
|
||||
if (other->trash_stack[UCL_TRASH_VALUE] != NULL) {
|
||||
new->trash_stack[UCL_TRASH_VALUE] =
|
||||
strdup (other->trash_stack[UCL_TRASH_VALUE]);
|
||||
if (new->type == UCL_STRING) {
|
||||
new->value.sv = new->trash_stack[UCL_TRASH_VALUE];
|
||||
}
|
||||
}
|
||||
|
||||
if (other->type == UCL_ARRAY || other->type == UCL_OBJECT) {
|
||||
/* reset old value */
|
||||
memset (&new->value, 0, sizeof (new->value));
|
||||
|
||||
while ((cur = ucl_iterate_object (other, &it, true)) != NULL) {
|
||||
if (other->type == UCL_ARRAY) {
|
||||
ucl_array_append (new, ucl_object_copy_internal (cur, false));
|
||||
}
|
||||
else {
|
||||
ucl_object_t *cp = ucl_object_copy_internal (cur, true);
|
||||
if (cp != NULL) {
|
||||
ucl_object_insert_key (new, cp, cp->key, cp->keylen,
|
||||
false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (allow_array && other->next != NULL) {
|
||||
LL_FOREACH (other->next, cur) {
|
||||
ucl_object_t *cp = ucl_object_copy_internal (cur, false);
|
||||
if (cp != NULL) {
|
||||
DL_APPEND (new, cp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
ucl_object_t *
|
||||
ucl_object_copy (const ucl_object_t *other)
|
||||
{
|
||||
return ucl_object_copy_internal (other, true);
|
||||
}
|
||||
|
||||
void
|
||||
ucl_object_unref (ucl_object_t *obj)
|
||||
{
|
||||
@ -1956,3 +2383,25 @@ ucl_object_array_sort (ucl_object_t *ar,
|
||||
|
||||
DL_SORT (ar->value.av, cmp);
|
||||
}
|
||||
|
||||
#define PRIOBITS 4
|
||||
|
||||
unsigned int
|
||||
ucl_object_get_priority (const ucl_object_t *obj)
|
||||
{
|
||||
if (obj == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (obj->flags >> ((sizeof (obj->flags) * NBBY) - PRIOBITS));
|
||||
}
|
||||
|
||||
void
|
||||
ucl_object_set_priority (ucl_object_t *obj,
|
||||
unsigned int priority)
|
||||
{
|
||||
if (obj != NULL) {
|
||||
priority &= (0x1 << PRIOBITS) - 1;
|
||||
obj->flags |= priority << ((sizeof (obj->flags) * NBBY) - PRIOBITS);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
EXTRA_DIST = $(TESTS) basic schema generate.res rcl_test.json.xz
|
||||
EXTRA_DIST = $(TESTS) basic schema generate.res streamline.res rcl_test.json.xz
|
||||
|
||||
TESTS = basic.test \
|
||||
generate.test \
|
||||
|
2
tests/basic/12.in
Normal file
2
tests/basic/12.in
Normal file
@ -0,0 +1,2 @@
|
||||
key1: 12 ,
|
||||
key2: 12 value
|
3
tests/basic/12.res
Normal file
3
tests/basic/12.res
Normal file
@ -0,0 +1,3 @@
|
||||
key1 = 12;
|
||||
key2 = "12 value";
|
||||
|
9
tests/basic/13.in
Normal file
9
tests/basic/13.in
Normal file
@ -0,0 +1,9 @@
|
||||
key = value_orig;
|
||||
|
||||
# test glob
|
||||
.include(glob=true) "${CURDIR}/include_dir/test*.conf"
|
||||
|
||||
.include(priority=1) "${CURDIR}/include_dir/pri1.conf"
|
||||
.include(priority=2) "${CURDIR}/include_dir/pri2.conf"
|
||||
|
||||
.include(try=true) "${CURDIR}/include_dir/invalid.conf"
|
8
tests/basic/13.res
Normal file
8
tests/basic/13.res
Normal file
@ -0,0 +1,8 @@
|
||||
key = "value_orig";
|
||||
key = "value1";
|
||||
key = "value2";
|
||||
key = "value3";
|
||||
key_pri = "priority2";
|
||||
key_trace1 = "pri1";
|
||||
key_trace2 = "pri2";
|
||||
|
@ -10,7 +10,14 @@ licenses [
|
||||
"BSD",
|
||||
]
|
||||
flatsize = 60523;
|
||||
desc = "pkgconf is a program which helps to configure compiler and linker flags for\ndevelopment frameworks. It is similar to pkg-config, but was written from\nscratch in Summer of 2011 to replace pkg-config, which now needs itself to build\nitself.\n\nWWW: https://github.com/pkgconf/pkgconf";
|
||||
desc = <<EOD
|
||||
pkgconf is a program which helps to configure compiler and linker flags for
|
||||
development frameworks. It is similar to pkg-config, but was written from
|
||||
scratch in Summer of 2011 to replace pkg-config, which now needs itself to build
|
||||
itself.
|
||||
|
||||
WWW: https://github.com/pkgconf/pkgconf
|
||||
EOD;
|
||||
categories [
|
||||
"devel",
|
||||
]
|
||||
@ -31,6 +38,17 @@ scripts {
|
||||
pre-deinstall = "cd /usr/local\nn";
|
||||
post-deinstall = "cd /usr/local\nn";
|
||||
}
|
||||
multiline-key = "test\ntest\ntest\\n\n/* comment like */\n# Some invalid endings\n EOD\nEOD \nEOF\n# Valid ending + empty string\n";
|
||||
multiline-key = <<EOD
|
||||
test
|
||||
test
|
||||
test\n
|
||||
/* comment like */
|
||||
# Some invalid endings
|
||||
EOD
|
||||
EOD
|
||||
EOF
|
||||
# Valid ending + empty string
|
||||
|
||||
EOD;
|
||||
normal-key = "<<EODnot";
|
||||
|
||||
|
25
tests/basic/comments.in
Normal file
25
tests/basic/comments.in
Normal file
@ -0,0 +1,25 @@
|
||||
# This test is intended to check various comments in ucl
|
||||
|
||||
obj {
|
||||
|
||||
key = value
|
||||
key = "/* value"
|
||||
/*
|
||||
key = value
|
||||
*/
|
||||
# Nested comments
|
||||
key = nested
|
||||
/*
|
||||
adasdasdads
|
||||
/* asdasdasd */asjdasjldaskd
|
||||
/* asdsadasd */
|
||||
/* /* /* /* /* */ */ */ */ */
|
||||
# some
|
||||
*/
|
||||
key = quotes # quoted
|
||||
# Quotes
|
||||
/*
|
||||
key = "/* value"
|
||||
key = "*/value"
|
||||
*/
|
||||
}
|
7
tests/basic/comments.res
Normal file
7
tests/basic/comments.res
Normal file
@ -0,0 +1,7 @@
|
||||
obj {
|
||||
key = "value";
|
||||
key = "/* value";
|
||||
key = "nested";
|
||||
key = "quotes";
|
||||
}
|
||||
|
1
tests/basic/include_dir/invalid.conf
Normal file
1
tests/basic/include_dir/invalid.conf
Normal file
@ -0,0 +1 @@
|
||||
@@@@ BAD UCL ~~~~
|
2
tests/basic/include_dir/pri1.conf
Normal file
2
tests/basic/include_dir/pri1.conf
Normal file
@ -0,0 +1,2 @@
|
||||
key_pri = priority1;
|
||||
key_trace1 = pri1;
|
2
tests/basic/include_dir/pri2.conf
Normal file
2
tests/basic/include_dir/pri2.conf
Normal file
@ -0,0 +1,2 @@
|
||||
key_pri = priority2;
|
||||
key_trace2 = pri2;
|
1
tests/basic/include_dir/test1.conf
Normal file
1
tests/basic/include_dir/test1.conf
Normal file
@ -0,0 +1 @@
|
||||
key = value1;
|
1
tests/basic/include_dir/test2.conf
Normal file
1
tests/basic/include_dir/test2.conf
Normal file
@ -0,0 +1 @@
|
||||
key = value2;
|
1
tests/basic/include_dir/test3.conf
Normal file
1
tests/basic/include_dir/test3.conf
Normal file
@ -0,0 +1 @@
|
||||
key = value3;
|
@ -1,3 +1,4 @@
|
||||
key0 = 0.100000;
|
||||
key1 = "test string";
|
||||
key2 = "test \\nstring";
|
||||
key3 = " test string \n";
|
||||
|
@ -90,7 +90,7 @@ main (int argc, char **argv)
|
||||
inlen = strlen (inbuf);
|
||||
test_in = malloc (inlen);
|
||||
memcpy (test_in, inbuf, inlen);
|
||||
ucl_parser_add_chunk (parser, test_in, inlen);
|
||||
ucl_parser_add_chunk (parser, (const unsigned char *)test_in, inlen);
|
||||
}
|
||||
fclose (in);
|
||||
|
||||
@ -126,7 +126,7 @@ main (int argc, char **argv)
|
||||
ucl_parser_free (parser);
|
||||
ucl_object_unref (obj);
|
||||
parser2 = ucl_parser_new (UCL_PARSER_KEY_LOWERCASE);
|
||||
ucl_parser_add_string (parser2, emitted, 0);
|
||||
ucl_parser_add_string (parser2, (const char *)emitted, 0);
|
||||
|
||||
if (ucl_parser_get_error(parser2) != NULL) {
|
||||
fprintf (out, "Error occurred: %s\n", ucl_parser_get_error(parser2));
|
||||
|
@ -54,6 +54,13 @@ main (int argc, char **argv)
|
||||
}
|
||||
|
||||
obj = ucl_object_typed_new (UCL_OBJECT);
|
||||
|
||||
/* Keys replacing */
|
||||
cur = ucl_object_fromstring_common ("value1", 0, UCL_STRING_TRIM);
|
||||
ucl_object_insert_key (obj, cur, "key0", 0, false);
|
||||
cur = ucl_object_fromdouble (0.1);
|
||||
ucl_object_replace_key (obj, cur, "key0", 0, false);
|
||||
|
||||
/* Create some strings */
|
||||
cur = ucl_object_fromstring_common (" test string ", 0, UCL_STRING_TRIM);
|
||||
ucl_object_insert_key (obj, cur, "key1", 0, false);
|
||||
|
@ -61,7 +61,7 @@ static bool
|
||||
perform_test (const ucl_object_t *schema, const ucl_object_t *obj,
|
||||
struct ucl_schema_error *err)
|
||||
{
|
||||
const const ucl_object_t *valid, *data, *description;
|
||||
const ucl_object_t *valid, *data, *description;
|
||||
bool match;
|
||||
|
||||
data = ucl_object_find_key (obj, "data");
|
||||
|
@ -53,6 +53,7 @@ ucl_obj_dump (const ucl_object_t *obj, unsigned int shift)
|
||||
if (obj->type == UCL_OBJECT) {
|
||||
printf ("%stype: UCL_OBJECT\n", pre);
|
||||
printf ("%svalue: %p\n", pre, obj->value.ov);
|
||||
it_obj = NULL;
|
||||
while ((cur = ucl_iterate_object (obj, &it_obj, true))) {
|
||||
ucl_obj_dump (cur, shift + 2);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user