@@ -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