commit 64e40987b0338dbe534b6f41e31ecb1419721b1f Author: Mikhail Romanko Date: Mon Aug 17 15:06:08 2026 +0300 Initial commit diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..01e9e41 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -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 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f765426 --- /dev/null +++ b/.gitignore @@ -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 + diff --git a/Args.c b/Args.c new file mode 100644 index 0000000..7cb052d --- /dev/null +++ b/Args.c @@ -0,0 +1,134 @@ +#include "CgeArgs.h" +#include +#include +#include + +static void parseArg(CgeArgs *arg, const char *value) { + if (value == NULL && arg->type != CGE_ARGS_BOOL) + return; + + switch (arg->type) { + case CGE_ARGS_STRING: + *(const char**)arg->data = value; + break; + + case CGE_ARGS_FLOAT: + *(float*)arg->data = strtod(value, NULL); + break; + + case CGE_ARGS_DOUBLE: + *(double*)arg->data = strtod(value, NULL); + break; + + case CGE_ARGS_INT: + *(int*)arg->data = atoi(value); + break; + + case CGE_ARGS_BOOL: + *(int*)arg->data = 1; + break; + } +} + +static CgeArgs *findShort(CgeArgs *args, size_t size, char symbol) { + size_t i; + + for (i = 0; i < size; i++) { + if (args[i].shortName == symbol) + return args + i; + } + + return NULL; +} + +static CgeArgs *findLong(CgeArgs *args, size_t size, char *value) { + char *equalSign; + size_t i; + + equalSign = strchr(value, '='); + for (i = 0; i < size; i++) { + if (!args[i].longName) + continue; + + if (equalSign) { + if (!strncmp(args[i].longName, value, strlen(args[i].longName))) + return args + i; + } else { + if (!strcmp(args[i].longName, value)) + return args + i; + } + } + + return NULL; +} + +int CgeArgsParse(CgeArgs *args, size_t size, int argc, char **argv) { + int i, ogArgc = argc; + + for (i = 1; i < argc;) { + int isFlag = 0; + + if (!strcmp(argv[i], "--")) + return i + 1; + + if (argv[i][0] == '-' && argv[i][1] != '-') { + int j; + + for (j = 1; argv[i][j]; j++) { + CgeArgs *arg; + + arg = findShort(args, size, argv[i][j]); + if (!arg) { + fprintf(stderr, "Unknown argument: %c\n", argv[i][j]); + return -1; + } + isFlag = 1; + + if (arg->type != CGE_ARGS_BOOL) { + if (argv[i][j + 1]) + parseArg(arg, argv[i] + j + 1); + else + parseArg(arg, (i + 1 < argc) ? argv[++i] : NULL); + break; + } else { + parseArg(arg, NULL); + } + } + } else if (argv[i][0] == '-' && argv[i][1] == '-') { + CgeArgs *arg; + + arg = findLong(args, size, argv[i] + 2); + if (!arg) { + fprintf(stderr, "Unknown argument: %s\n", argv[i] + 2); + return -1; + } + isFlag = 1; + + if (arg->type != CGE_ARGS_BOOL) { + char *equalSign; + + equalSign = strchr(argv[i] + 2, '='); + if (equalSign) + parseArg(arg, equalSign + 1); + else + parseArg(arg, (i + 1 < argc) ? argv[++i] : NULL); + } else { + parseArg(arg, NULL); + } + } + + if (!isFlag) { + char *arg = argv[i]; + int j; + + for (j = i; j < ogArgc - 1; j++) + argv[j] = argv[j + 1]; + argv[ogArgc - 1] = arg; + argc--; + } else { + i++; + } + } + + return argc; +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..bd5a2e9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.10) +project(CgeArgs LANGUAGES C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) + +set(SOURCES + Args.c +) + +set(HEADERS + CgeArgs.h +) + +add_library(CgeArgs STATIC ${SOURCES} ${HEADERS}) + +target_include_directories(CgeArgs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +install(TARGETS CgeArgs + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) + +install(FILES ${HEADERS} DESTINATION include) diff --git a/CgeArgs.h b/CgeArgs.h new file mode 100644 index 0000000..ead736a --- /dev/null +++ b/CgeArgs.h @@ -0,0 +1,23 @@ +#ifndef CGE_ARGS_H +#define CGE_ARGS_H + +#include + +enum CgeArgsType { + CGE_ARGS_STRING, + CGE_ARGS_INT, + CGE_ARGS_FLOAT, + CGE_ARGS_DOUBLE, + CGE_ARGS_BOOL, +}; + +typedef struct CgeArgs { + char shortName; + const char *longName; + int type; + void *data; +} CgeArgs; + +int CgeArgsParse(CgeArgs *args, size_t size, int argc, char **argv); + +#endif /* CGE_ARGS_H */ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3d44ffd --- /dev/null +++ b/LICENSE @@ -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. diff --git a/Makefile.lint b/Makefile.lint new file mode 100644 index 0000000..cac9110 --- /dev/null +++ b/Makefile.lint @@ -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 \ No newline at end of file diff --git a/Makefile.mingw b/Makefile.mingw new file mode 100644 index 0000000..4b30e1f --- /dev/null +++ b/Makefile.mingw @@ -0,0 +1,23 @@ +# MinGW Makefile for CgeArgs + +CC = gcc +AR = ar +CFLAGS = -std=c99 -O2 -Wall -Wextra +ARFLAGS = rcs +TARGET = libCgeArgs.a + +SOURCES = Args.c +OBJECTS = $(SOURCES:.c=.o) + +.PHONY: all clean + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(AR) $(ARFLAGS) $@ $^ + +%.o: %.c CgeArgs.h + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + del $(OBJECTS) $(TARGET) 2>nul || exit 0 diff --git a/Makefile.posix b/Makefile.posix new file mode 100644 index 0000000..724ba7c --- /dev/null +++ b/Makefile.posix @@ -0,0 +1,33 @@ +# POSIX Makefile for CgeArgs + +CC = gcc +AR = ar +CFLAGS = -std=c99 -O2 -Wall -Wextra -fPIC +ARFLAGS = rcs +TARGET = libCgeArgs.a + +SOURCES = Args.c +OBJECTS = $(SOURCES:.c=.o) + +.PHONY: all clean install + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(AR) $(ARFLAGS) $@ $^ + +%.o: %.c CgeArgs.h + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f $(OBJECTS) $(TARGET) + +install: $(TARGET) + cp $(TARGET) /usr/local/lib/ + cp CgeArgs.h /usr/local/include/ + ldconfig || echo "Run ldconfig manually if needed" + +uninstall: + rm -f /usr/local/lib/libCgeArgs.a + rm -f /usr/local/include/CgeArgs.h + ldconfig || true diff --git a/Makefile.win32 b/Makefile.win32 new file mode 100644 index 0000000..c90bc26 --- /dev/null +++ b/Makefile.win32 @@ -0,0 +1,23 @@ +# Makefile.win32 for MSVC (NMake) +# Usage: Open "x86 Native Tools Command Prompt", then: +# nmake -f Makefile.win32 + +CC = cl +LIB = lib +CFLAGS = /c /nologo /W3 /O2 +LIBFLAGS = /nologo +TARGET = CgeArgs.lib + +SOURCES = Args.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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..289d208 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# CgeArgs - CLI argument parsing + +TODO + +## Usage Example + +```c +#include "CgeArgs.h" +#include + +int main(int argc, char **argv) { + int boolOption = 0, intOption = 0; + const char *strOption = "default"; + float floatOption = 0; + double doubleOption = 0; + int i; + + CgeArgs args[] = { + {'a', "a", CGE_ARGS_BOOL, &boolOption}, + {'b', "b", CGE_ARGS_INT, &intOption}, + {'c', "c", CGE_ARGS_STRING, &strOption}, + {'d', "d", CGE_ARGS_FLOAT, &floatOption}, + {'e', "e", CGE_ARGS_DOUBLE, &doubleOption}, + }; + + i = CgeArgsParse(args, sizeof(args) / sizeof(CgeArgs), argc, argv); + if (i != -1) { + printf("bool: %d\n", boolOption); + printf("int: %d\n", intOption); + printf("str: %s\n", strOption); + printf("float: %f\n", floatOption); + printf("double: %lf\n", doubleOption); + + for (; i < argc; i++) { + printf("free arg: %s\n", argv[i]); + } + } else { + printf("unknown arg\n"); + } + return 0; +} +``` + +## License + +0BSD - a permissive license with no attribution required.