@@ -0,0 +1,89 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [trunk]
|
||||||
|
pull_request:
|
||||||
|
branches: [trunk]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-analyze:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install tools
|
||||||
|
run: |
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y build-essential clang clang-tools clang-tidy cppcheck gcc-mingw-w64-x86-64 cmake
|
||||||
|
|
||||||
|
- name: Build with GCC
|
||||||
|
run: |
|
||||||
|
make -f Makefile.posix clean
|
||||||
|
make -f Makefile.posix CC=gcc CFLAGS="-std=c99 -Wall -Wextra -Wpedantic -Werror -O2"
|
||||||
|
|
||||||
|
- name: Build with Clang
|
||||||
|
run: |
|
||||||
|
make -f Makefile.posix clean
|
||||||
|
make -f Makefile.posix CC=clang CFLAGS="-std=c99 -Wall -Wextra -Wpedantic -Werror -O2"
|
||||||
|
|
||||||
|
- name: Build with MinGW
|
||||||
|
run: |
|
||||||
|
make -f Makefile.posix clean
|
||||||
|
make -f Makefile.mingw CC=x86_64-w64-mingw32-gcc AR=x86_64-w64-mingw32-ar CFLAGS="-std=c99 -Wall -Wextra -Wpedantic -Werror -O2"
|
||||||
|
|
||||||
|
- name: Build with CMake
|
||||||
|
run: |
|
||||||
|
cmake -S . -B build
|
||||||
|
cmake --build build
|
||||||
|
|
||||||
|
- name: Linting checks
|
||||||
|
run: |
|
||||||
|
echo "Running all linting checks..."
|
||||||
|
FAILED=0
|
||||||
|
|
||||||
|
# cppcheck
|
||||||
|
echo "=== Check 1/4: cppcheck ==="
|
||||||
|
if make -f Makefile.lint cppcheck; then
|
||||||
|
echo "cppcheck: PASSED"
|
||||||
|
else
|
||||||
|
echo "cppcheck: FAILED"
|
||||||
|
FAILED=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# clang-tidy
|
||||||
|
echo "=== Check 2/4: clang-tidy ==="
|
||||||
|
if make -f Makefile.lint clang-tidy; then
|
||||||
|
echo "clang-tidy: PASSED"
|
||||||
|
else
|
||||||
|
echo "clang-tidy: FAILED"
|
||||||
|
FAILED=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# scan-build
|
||||||
|
echo "=== Check 3/4: scan-build ==="
|
||||||
|
if make -f Makefile.lint scan-build; then
|
||||||
|
echo "scan-build: PASSED"
|
||||||
|
else
|
||||||
|
echo "scan-build: FAILED"
|
||||||
|
FAILED=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# security-check
|
||||||
|
echo "=== Check 4/4: security-check ==="
|
||||||
|
if make -f Makefile.lint security-check; then
|
||||||
|
echo "security-check: PASSED"
|
||||||
|
else
|
||||||
|
echo "security-check: FAILED"
|
||||||
|
FAILED=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Final result
|
||||||
|
if [ $FAILED -ne 0 ]; then
|
||||||
|
echo "One or more linting checks failed."
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "All linting checks passed."
|
||||||
|
fi
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
# ---> C
|
||||||
|
# Prerequisites
|
||||||
|
*.d
|
||||||
|
|
||||||
|
# Object files
|
||||||
|
*.o
|
||||||
|
*.ko
|
||||||
|
*.obj
|
||||||
|
*.elf
|
||||||
|
|
||||||
|
# Linker output
|
||||||
|
*.ilk
|
||||||
|
*.map
|
||||||
|
*.exp
|
||||||
|
|
||||||
|
# Precompiled Headers
|
||||||
|
*.gch
|
||||||
|
*.pch
|
||||||
|
|
||||||
|
# Libraries
|
||||||
|
*.lib
|
||||||
|
*.a
|
||||||
|
*.la
|
||||||
|
*.lo
|
||||||
|
|
||||||
|
# Shared objects (inc. Windows DLLs)
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.so.*
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.exe
|
||||||
|
*.out
|
||||||
|
*.app
|
||||||
|
*.i*86
|
||||||
|
*.x86_64
|
||||||
|
*.hex
|
||||||
|
|
||||||
|
# Debug files
|
||||||
|
*.dSYM/
|
||||||
|
*.su
|
||||||
|
*.idb
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
# Kernel Module Compile Results
|
||||||
|
*.mod*
|
||||||
|
*.cmd
|
||||||
|
.tmp_versions/
|
||||||
|
modules.order
|
||||||
|
Module.symvers
|
||||||
|
Mkfile.old
|
||||||
|
dkms.conf
|
||||||
|
|
||||||
|
# ---> CMake
|
||||||
|
CMakeLists.txt.user
|
||||||
|
CMakeCache.txt
|
||||||
|
CMakeFiles
|
||||||
|
CMakeScripts
|
||||||
|
Testing
|
||||||
|
Makefile
|
||||||
|
cmake_install.cmake
|
||||||
|
install_manifest.txt
|
||||||
|
compile_commands.json
|
||||||
|
CTestTestfile.cmake
|
||||||
|
_deps
|
||||||
|
CMakeUserPresets.json
|
||||||
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
project(CgeConf LANGUAGES C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
set(CONF_SOURCES
|
||||||
|
Conf.c
|
||||||
|
Query.c
|
||||||
|
)
|
||||||
|
|
||||||
|
set(CONF_HEADERS
|
||||||
|
CgeConf.h
|
||||||
|
CgeConfQuery.h
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(CgeConf STATIC ${CONF_SOURCES} ${CONF_HEADERS})
|
||||||
|
|
||||||
|
target_include_directories(CgeConf PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
|
install(TARGETS CgeConf
|
||||||
|
ARCHIVE DESTINATION lib
|
||||||
|
LIBRARY DESTINATION lib
|
||||||
|
RUNTIME DESTINATION bin
|
||||||
|
)
|
||||||
|
|
||||||
|
install(FILES ${CONF_HEADERS} DESTINATION include)
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#ifndef CGE_CONF_H
|
||||||
|
#define CGE_CONF_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct CgeConfIO {
|
||||||
|
int (*read)(void *ctx, void *buffer, size_t size, size_t *actual);
|
||||||
|
void *ctx;
|
||||||
|
} CgeConfIO;
|
||||||
|
|
||||||
|
enum CgeConfEvent {
|
||||||
|
CGE_CONF_SECTION,
|
||||||
|
CGE_CONF_KEYVALUE,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void (*CgeConfCb)(int type, const char *key, const char *value,
|
||||||
|
void *user);
|
||||||
|
|
||||||
|
int CgeConfFromString(const char *str, CgeConfCb callback, void *user);
|
||||||
|
int CgeConfFromMemory(const char *buffer, size_t size, CgeConfCb callback,
|
||||||
|
void *user);
|
||||||
|
int CgeConfFromFile(const char *path, CgeConfCb callback, void *user);
|
||||||
|
int CgeConfFromCb(CgeConfIO *io, CgeConfCb callback, void *user);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* CGE_CONF_H */
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#ifndef CGE_CONF_QUERY_H
|
||||||
|
#define CGE_CONF_QUERY_H
|
||||||
|
|
||||||
|
#include "CgeConf.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct CgeConfQuery CgeConfQuery;
|
||||||
|
typedef void (*CgeConfQueryIterCb)(const char *section, const char *key,
|
||||||
|
const char *value, void *user);
|
||||||
|
|
||||||
|
|
||||||
|
CgeConfQuery *CgeConfQueryFromCb(CgeConfIO *io);
|
||||||
|
CgeConfQuery *CgeConfQueryFromFile(const char *file);
|
||||||
|
CgeConfQuery *CgeConfQueryFromMemory(const char *buffer, size_t size);
|
||||||
|
CgeConfQuery *CgeConfQueryFromString(const char *str);
|
||||||
|
void CgeConfQueryFree(CgeConfQuery *query);
|
||||||
|
const char *CgeConfQueryGet(CgeConfQuery *query, const char *section,
|
||||||
|
const char *key, const char *defaultValue);
|
||||||
|
|
||||||
|
void CgeConfQueryIter(CgeConfQuery *query, CgeConfQueryIterCb iter,
|
||||||
|
void *user);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* CGE_CONF_QUERY_H */
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
#include "CgeConf.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#define MAX_LINE 4096
|
||||||
|
|
||||||
|
typedef struct StrView {
|
||||||
|
char *data;
|
||||||
|
size_t size;
|
||||||
|
} StrView;
|
||||||
|
|
||||||
|
typedef struct MemCtx {
|
||||||
|
const char *buffer;
|
||||||
|
size_t size;
|
||||||
|
size_t pos;
|
||||||
|
} MemCtx;
|
||||||
|
|
||||||
|
static int memRead(void *ctx, void *buffer, size_t size, size_t *actual) {
|
||||||
|
MemCtx *mem = (MemCtx *)ctx;
|
||||||
|
size_t remaining = mem->size - mem->pos;
|
||||||
|
|
||||||
|
if (size > remaining)
|
||||||
|
size = remaining;
|
||||||
|
|
||||||
|
memcpy(buffer, mem->buffer + mem->pos, size);
|
||||||
|
mem->pos += size;
|
||||||
|
*actual = size;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fileRead(void *ctx, void *buffer, size_t size, size_t *actual) {
|
||||||
|
*actual = fread(buffer, 1, size, (FILE *)ctx);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void trimLeft(StrView *view) {
|
||||||
|
for (; view->size && isspace(*view->data); ++view->data, --view->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void trimRight(StrView *view) {
|
||||||
|
char *last = view->data + view->size;
|
||||||
|
for (; last > view->data && isspace(*(last - 1)); last--, view->size--);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void trim(StrView *view) {
|
||||||
|
trimLeft(view);
|
||||||
|
trimRight(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t firstIndex(StrView *view, char symbol) {
|
||||||
|
char *current, *end;
|
||||||
|
current = view->data;
|
||||||
|
end = view->data + view->size;
|
||||||
|
|
||||||
|
for (; current < end; ++current) {
|
||||||
|
if (*current == symbol)
|
||||||
|
return current - view->data;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int split(StrView *view, StrView *prefix, char delim) {
|
||||||
|
size_t position;
|
||||||
|
|
||||||
|
if ((position = firstIndex(view, delim)) == -1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
prefix->data = view->data;
|
||||||
|
prefix->size = position;
|
||||||
|
view->data += position;
|
||||||
|
view->size -= position;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parseLine(StrView *view, CgeConfCb callback, void *user) {
|
||||||
|
StrView key;
|
||||||
|
|
||||||
|
trim(view);
|
||||||
|
|
||||||
|
if (!view->size || *view->data == '#' || *view->data == ';')
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (*view->data == '[' && *(view->data + view->size - 1) == ']') {
|
||||||
|
view->size -= 2;
|
||||||
|
view->data += 1;
|
||||||
|
trim(view);
|
||||||
|
|
||||||
|
view->data[view->size] = 0;
|
||||||
|
callback(CGE_CONF_SECTION, NULL, view->data, user);
|
||||||
|
} else if (split(view, &key, '=')) {
|
||||||
|
trimRight(&key);
|
||||||
|
view->data++, view->size--;
|
||||||
|
trimLeft(view);
|
||||||
|
|
||||||
|
if (view->size > 1 && *view->data == '"' &&
|
||||||
|
*(view->data + view->size - 1) == '"') {
|
||||||
|
view->size -= 2;
|
||||||
|
view->data += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
key.data[key.size] = 0;
|
||||||
|
view->data[view->size] = 0;
|
||||||
|
callback(CGE_CONF_KEYVALUE, key.data, view->data, user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int readSymbol(CgeConfIO *io) {
|
||||||
|
unsigned char symbol;
|
||||||
|
size_t actual;
|
||||||
|
|
||||||
|
if (!io->read(io->ctx, &symbol, 1, &actual) || !actual)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return (int)symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t readLine(CgeConfIO *io, char *buffer, size_t size) {
|
||||||
|
int symbol;
|
||||||
|
size_t counter = 0;
|
||||||
|
|
||||||
|
while ((symbol = readSymbol(io)) != -1) {
|
||||||
|
if (counter && symbol == '\n')
|
||||||
|
break;
|
||||||
|
else if (symbol == '\n')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (symbol == '\r')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (counter >= size - 1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
buffer[counter++] = symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer[counter] = 0;
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CgeConfFromString(const char *str, CgeConfCb callback, void *user) {
|
||||||
|
return CgeConfFromMemory(str, strlen(str), callback, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
int CgeConfFromMemory(const char *buffer, size_t size, CgeConfCb callback,
|
||||||
|
void *user) {
|
||||||
|
CgeConfIO io;
|
||||||
|
MemCtx mem;
|
||||||
|
|
||||||
|
mem.buffer = buffer;
|
||||||
|
mem.size = size;
|
||||||
|
mem.pos = 0;
|
||||||
|
|
||||||
|
io.read = memRead;
|
||||||
|
io.ctx = &mem;
|
||||||
|
|
||||||
|
return CgeConfFromCb(&io, callback, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
int CgeConfFromFile(const char *path, CgeConfCb callback, void *user) {
|
||||||
|
CgeConfIO io;
|
||||||
|
FILE *file;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
file = fopen(path, "rb");
|
||||||
|
if (!file)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
io.read = fileRead;
|
||||||
|
io.ctx = file;
|
||||||
|
|
||||||
|
ret = CgeConfFromCb(&io, callback, user);
|
||||||
|
fclose(file);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CgeConfFromCb(CgeConfIO *io, CgeConfCb callback, void *user) {
|
||||||
|
char buffer[MAX_LINE];
|
||||||
|
StrView view;
|
||||||
|
|
||||||
|
while ((view.size = readLine(io, buffer, MAX_LINE))) {
|
||||||
|
if (view.size == -1)
|
||||||
|
return 0;
|
||||||
|
view.data = buffer;
|
||||||
|
parseLine(&view, callback, user);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
Copyright (C) 2026 by blankhex me@blankhex.com
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
PERFORMANCE OF THIS SOFTWARE.
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Makefile.lint - Individual linting targets
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: cppcheck clang-tidy scan-build security-check
|
||||||
|
@echo "All linting scheduled"
|
||||||
|
|
||||||
|
.PHONY: cppcheck
|
||||||
|
cppcheck:
|
||||||
|
@echo "Running cppcheck..."
|
||||||
|
@cppcheck --enable=warning,performance,portability --std=c99 --quiet -Iinclude -I. .
|
||||||
|
|
||||||
|
.PHONY: clang-tidy
|
||||||
|
clang-tidy:
|
||||||
|
@echo "Running clang-tidy..."
|
||||||
|
@for file in $$(find . -name "*.c" -type f -not -path "*/win32/*"); do \
|
||||||
|
clang-tidy "$$file" -- -I. -Iinclude -std=c99; \
|
||||||
|
done
|
||||||
|
|
||||||
|
.PHONY: scan-build
|
||||||
|
scan-build:
|
||||||
|
@echo "Running scan-build..."
|
||||||
|
@scan-build --status-bugs make -f Makefile.posix clean all CC=clang
|
||||||
|
|
||||||
|
.PHONY: security-check
|
||||||
|
security-check:
|
||||||
|
@echo "Scanning for unsafe C functions..."
|
||||||
|
@grep -rnE '\b(strcpy|strcat|sprintf|gets|scanf|sscanf|realpath|mktemp|tempnam|tmpnam|getwd|getlogin)\b' \
|
||||||
|
. --include="*.c" --include="*.h" | grep -v "^Binary file" && \
|
||||||
|
exit 1 || exit 0
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# MinGW Makefile for CgeConf
|
||||||
|
|
||||||
|
CC = gcc
|
||||||
|
AR = ar
|
||||||
|
CFLAGS = -std=c99 -O2 -Wall -Wextra
|
||||||
|
ARFLAGS = rcs
|
||||||
|
TARGET = libCgeConf.a
|
||||||
|
|
||||||
|
SOURCES = Conf.c Query.c
|
||||||
|
OBJECTS = $(SOURCES:.c=.o)
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
|
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
$(TARGET): $(OBJECTS)
|
||||||
|
$(AR) $(ARFLAGS) $@ $^
|
||||||
|
|
||||||
|
%.o: %.c CgeConf.h CgeConfQuery.h
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
del $(OBJECTS) $(TARGET) 2>nul || exit 0
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# POSIX Makefile for CgeConf
|
||||||
|
|
||||||
|
CC = gcc
|
||||||
|
AR = ar
|
||||||
|
CFLAGS = -std=c99 -O2 -Wall -Wextra -fPIC
|
||||||
|
ARFLAGS = rcs
|
||||||
|
TARGET = libCgeConf.a
|
||||||
|
|
||||||
|
SOURCES = Conf.c Query.c
|
||||||
|
OBJECTS = $(SOURCES:.c=.o)
|
||||||
|
|
||||||
|
.PHONY: all clean install
|
||||||
|
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
$(TARGET): $(OBJECTS)
|
||||||
|
$(AR) $(ARFLAGS) $@ $^
|
||||||
|
|
||||||
|
%.o: %.c CgeConf.h CgeConfQuery.h
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(OBJECTS) $(TARGET)
|
||||||
|
|
||||||
|
install: $(TARGET)
|
||||||
|
cp $(TARGET) /usr/local/lib/
|
||||||
|
cp CgeConf.h /usr/local/include/
|
||||||
|
cp CgeConfQuery.h /usr/local/include/
|
||||||
|
ldconfig || echo "Run ldconfig manually if needed"
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
rm -f /usr/local/lib/libCgeConf.a
|
||||||
|
rm -f /usr/local/include/CgeConf.h
|
||||||
|
rm -f /usr/local/include/CgeConfQuery.h
|
||||||
|
ldconfig || true
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Makefile.win32 for MSVC (NMake) — CgeConf
|
||||||
|
# Usage: Open "x86 Native Tools Command Prompt", then:
|
||||||
|
# nmake -f Makefile.win32
|
||||||
|
|
||||||
|
CC = cl
|
||||||
|
LIB = lib
|
||||||
|
CFLAGS = /c /nologo /W3 /O2
|
||||||
|
LIBFLAGS = /nologo
|
||||||
|
TARGET = CgeConf.lib
|
||||||
|
|
||||||
|
SOURCES = Conf.c Query.c
|
||||||
|
OBJECTS = $(SOURCES:.c=.obj)
|
||||||
|
|
||||||
|
$(TARGET): $(OBJECTS)
|
||||||
|
$(LIB) $(LIBFLAGS) /OUT:$(TARGET) $(OBJECTS)
|
||||||
|
|
||||||
|
{.}.c{}.obj:
|
||||||
|
$(CC) $(CFLAGS) /Fo$@ $<
|
||||||
|
|
||||||
|
clean:
|
||||||
|
del $(OBJECTS) $(TARGET) 2>nul
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
#include "CgeConfQuery.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define INIT_EXP 4
|
||||||
|
#define INIT_CAPACITY ((size_t)1 << INIT_EXP)
|
||||||
|
#define MAX_SECTION 4096
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *section;
|
||||||
|
char *key;
|
||||||
|
char *value;
|
||||||
|
} Entry;
|
||||||
|
|
||||||
|
struct CgeConfQuery {
|
||||||
|
Entry *entryData;
|
||||||
|
size_t size;
|
||||||
|
size_t capacity;
|
||||||
|
Entry **slotData;
|
||||||
|
char currentSection[MAX_SECTION];
|
||||||
|
int exp;
|
||||||
|
};
|
||||||
|
|
||||||
|
static char *stringDup(const char *str) {
|
||||||
|
size_t length;
|
||||||
|
char *dest;
|
||||||
|
|
||||||
|
length = strlen(str);
|
||||||
|
if ((dest = malloc(length + 1)))
|
||||||
|
memcpy(dest, str, length + 1);
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t hashKey(const char *section, const char *key) {
|
||||||
|
size_t h = 5381;
|
||||||
|
const char *p;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
for (p = section; (c = *p); ++p)
|
||||||
|
h = ((h << 5) + h) + (unsigned char)c;
|
||||||
|
h = ((h << 5) + h);
|
||||||
|
for (p = key; (c = *p); ++p)
|
||||||
|
h = ((h << 5) + h) + (unsigned char)c;
|
||||||
|
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t lookup(size_t hash, int exp, size_t index) {
|
||||||
|
size_t mask = (1 << exp) - 1;
|
||||||
|
size_t step = (hash >> (sizeof(hash) * 8 - exp)) | 1;
|
||||||
|
return (index + step) & mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int reserveSpace(CgeConfQuery *query, size_t need) {
|
||||||
|
CgeConfQuery tmp;
|
||||||
|
size_t i, hash, index;
|
||||||
|
|
||||||
|
if (need <= (query->capacity - (query->capacity >> 2)))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
tmp.exp = query->exp ? query->exp + 1 : INIT_EXP;
|
||||||
|
tmp.capacity = query->capacity ? query->capacity * 2 : INIT_CAPACITY;
|
||||||
|
tmp.size = 0;
|
||||||
|
tmp.entryData = malloc(tmp.capacity * sizeof(*tmp.entryData));
|
||||||
|
tmp.slotData = malloc(tmp.capacity * sizeof(*tmp.slotData));
|
||||||
|
if (!tmp.entryData || !tmp.slotData) {
|
||||||
|
free(tmp.entryData);
|
||||||
|
free(tmp.slotData);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
memset(tmp.slotData, 0, sizeof(*tmp.slotData) * tmp.capacity);
|
||||||
|
|
||||||
|
for (i = 0; i < query->size; ++i) {
|
||||||
|
Entry *entry = &query->entryData[i];
|
||||||
|
|
||||||
|
hash = hashKey(entry->section, entry->key);
|
||||||
|
index = lookup(hash, tmp.exp, 0);
|
||||||
|
while (tmp.slotData[index] != NULL)
|
||||||
|
index = lookup(hash, tmp.exp, index);
|
||||||
|
|
||||||
|
tmp.entryData[tmp.size] = *entry;
|
||||||
|
tmp.slotData[index] = tmp.entryData + tmp.size++;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(query->entryData);
|
||||||
|
free(query->slotData);
|
||||||
|
query->entryData = tmp.entryData;
|
||||||
|
query->slotData = tmp.slotData;
|
||||||
|
query->capacity = tmp.capacity;
|
||||||
|
query->exp = tmp.exp;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int insert(CgeConfQuery *query, const char *section, const char *key,
|
||||||
|
const char *value) {
|
||||||
|
size_t hash, index;
|
||||||
|
char *newValue;
|
||||||
|
Entry *entry;
|
||||||
|
|
||||||
|
if (!reserveSpace(query, query->size + 1))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
hash = hashKey(section, key);
|
||||||
|
index = lookup(hash, query->exp, 0);
|
||||||
|
|
||||||
|
while ((entry = query->slotData[index])) {
|
||||||
|
if (strcmp(entry->section, section) == 0 &&
|
||||||
|
strcmp(entry->key, key) == 0) {
|
||||||
|
newValue = stringDup(value);
|
||||||
|
if (!newValue)
|
||||||
|
return 0;
|
||||||
|
free(entry->value);
|
||||||
|
entry->value = newValue;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
index = lookup(hash, query->exp, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = &query->entryData[query->size];
|
||||||
|
entry->section = stringDup(section);
|
||||||
|
entry->key = stringDup(key);
|
||||||
|
entry->value = stringDup(value);
|
||||||
|
if (!entry->section || !entry->key || !entry->value) {
|
||||||
|
free(entry->section);
|
||||||
|
free(entry->key);
|
||||||
|
free(entry->value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
query->size++;
|
||||||
|
query->slotData[index] = entry;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void populateCb(int type, const char *key, const char *value,
|
||||||
|
void *user) {
|
||||||
|
CgeConfQuery *query = (CgeConfQuery *)user;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
if (type == CGE_CONF_SECTION) {
|
||||||
|
len = strlen(value);
|
||||||
|
if (len >= sizeof(query->currentSection))
|
||||||
|
len = sizeof(query->currentSection) - 1;
|
||||||
|
memcpy(query->currentSection, value, len);
|
||||||
|
query->currentSection[len] = 0;
|
||||||
|
} else if (type == CGE_CONF_KEYVALUE) {
|
||||||
|
insert(query, query->currentSection, key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static CgeConfQuery *queryNew(void) {
|
||||||
|
CgeConfQuery *query;
|
||||||
|
|
||||||
|
query = (CgeConfQuery *)malloc(sizeof(CgeConfQuery));
|
||||||
|
if (!query)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
memset(query, 0, sizeof(*query));
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
CgeConfQuery *CgeConfQueryFromCb(CgeConfIO *io) {
|
||||||
|
CgeConfQuery *query;
|
||||||
|
|
||||||
|
query = queryNew();
|
||||||
|
if (!query)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (!CgeConfFromCb(io, populateCb, query)) {
|
||||||
|
CgeConfQueryFree(query);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
CgeConfQuery *CgeConfQueryFromFile(const char *file) {
|
||||||
|
CgeConfQuery *query;
|
||||||
|
|
||||||
|
query = queryNew();
|
||||||
|
if (!query)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (!CgeConfFromFile(file, populateCb, query)) {
|
||||||
|
CgeConfQueryFree(query);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
CgeConfQuery *CgeConfQueryFromMemory(const char *buffer, size_t size) {
|
||||||
|
CgeConfQuery *query;
|
||||||
|
|
||||||
|
query = queryNew();
|
||||||
|
if (!query)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (!CgeConfFromMemory(buffer, size, populateCb, query)) {
|
||||||
|
CgeConfQueryFree(query);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
CgeConfQuery *CgeConfQueryFromString(const char *str) {
|
||||||
|
return CgeConfQueryFromMemory(str, strlen(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *CgeConfQueryGet(CgeConfQuery *query, const char *section,
|
||||||
|
const char *key, const char *defaultValue) {
|
||||||
|
size_t hash, index, i;
|
||||||
|
Entry *entry;
|
||||||
|
|
||||||
|
if (!query || !key)
|
||||||
|
return defaultValue;
|
||||||
|
if (!section)
|
||||||
|
section = "";
|
||||||
|
|
||||||
|
hash = hashKey(section, key);
|
||||||
|
index = lookup(hash, query->exp, 0);
|
||||||
|
|
||||||
|
for (i = 0; i < query->capacity; ++i) {
|
||||||
|
entry = query->slotData[index];
|
||||||
|
if (!entry)
|
||||||
|
break;
|
||||||
|
if (strcmp(entry->section, section) == 0 &&
|
||||||
|
strcmp(entry->key, key) == 0)
|
||||||
|
return entry->value;
|
||||||
|
index = lookup(hash, query->exp, index);
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CgeConfQueryFree(CgeConfQuery *query) {
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (!query)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (i = 0; i < query->size; ++i) {
|
||||||
|
free(query->entryData[i].section);
|
||||||
|
free(query->entryData[i].key);
|
||||||
|
free(query->entryData[i].value);
|
||||||
|
}
|
||||||
|
free(query->entryData);
|
||||||
|
free(query->slotData);
|
||||||
|
free(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CgeConfQueryIter(CgeConfQuery *query, CgeConfQueryIterCb iter,
|
||||||
|
void *user) {
|
||||||
|
Entry *entry, *end;
|
||||||
|
|
||||||
|
entry = query->entryData;
|
||||||
|
end = query->entryData + query->size;
|
||||||
|
for (; entry != end; ++entry)
|
||||||
|
iter(entry->section, entry->key, entry->value, user);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user