From e02c295dac5087fb6827ebd375d5911140a8b73a Mon Sep 17 00:00:00 2001 From: Babayaga Date: Wed, 18 Feb 2026 11:24:00 +0100 Subject: [PATCH] min boilerplate 1/2 --- .gitignore | 31 ++++- .npmignore | 4 - CMakeLists.txt | 66 +++++++++ CMakePresets.json | 36 +++++ README.md | 34 ++++- config.toml | 12 ++ package.json | 45 ------ packages/html/CMakeLists.txt | 26 ++++ packages/html/include/html/html.h | 21 +++ packages/html/src/html.cpp | 129 ++++++++++++++++++ packages/logger/CMakeLists.txt | 21 +++ packages/logger/include/logger/logger.h | 16 +++ packages/logger/src/logger.cpp | 21 +++ packages/postgres/CMakeLists.txt | 11 ++ packages/postgres/include/postgres/postgres.h | 11 ++ packages/postgres/src/postgres.cpp | 12 ++ src/.gitignore | 0 src/main.cpp | 77 +++++++++++ 18 files changed, 519 insertions(+), 54 deletions(-) delete mode 100644 .npmignore create mode 100644 CMakeLists.txt create mode 100644 CMakePresets.json create mode 100644 config.toml delete mode 100644 package.json create mode 100644 packages/html/CMakeLists.txt create mode 100644 packages/html/include/html/html.h create mode 100644 packages/html/src/html.cpp create mode 100644 packages/logger/CMakeLists.txt create mode 100644 packages/logger/include/logger/logger.h create mode 100644 packages/logger/src/logger.cpp create mode 100644 packages/postgres/CMakeLists.txt create mode 100644 packages/postgres/include/postgres/postgres.h create mode 100644 packages/postgres/src/postgres.cpp delete mode 100644 src/.gitignore create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore index cab85ca..b16286d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,29 @@ -/node_modules -/coverage -*.log +# Build output +/build/ + +# Compiled objects +*.o +*.obj +*.exe +*.out +*.app + +# CMake generated +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +Makefile + +# IDE / Editor +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS .DS_Store +Thumbs.db + +# Logs +*.log diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 4c9adda..0000000 --- a/.npmignore +++ /dev/null @@ -1,4 +0,0 @@ -./docs -./scripts -./tests -./incoming \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2efffc8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 3.20) + +project(polymech-cli + VERSION 0.1.0 + DESCRIPTION "Polymech C++ CLI" + LANGUAGES CXX C +) + +# ── C++ standard ───────────────────────────────────────────────────────────── +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# ── Dependencies ───────────────────────────────────────────────────────────── +include(FetchContent) + +FetchContent_Declare( + cli11 + GIT_REPOSITORY https://github.com/CLIUtils/CLI11.git + GIT_TAG v2.4.2 + GIT_SHALLOW TRUE +) + +FetchContent_Declare( + tomlplusplus + GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git + GIT_TAG v3.4.0 + GIT_SHALLOW TRUE +) + +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.7.1 + GIT_SHALLOW TRUE +) + +FetchContent_MakeAvailable(cli11 tomlplusplus Catch2) + +# ── Packages ───────────────────────────────────────────────────────────────── +add_subdirectory(packages/logger) +add_subdirectory(packages/html) +add_subdirectory(packages/postgres) + +# ── Sources ────────────────────────────────────────────────────────────────── +add_executable(${PROJECT_NAME} + src/main.cpp +) + +target_link_libraries(${PROJECT_NAME} PRIVATE CLI11::CLI11 tomlplusplus::tomlplusplus logger html postgres) + +# ── Compiler warnings ─────────────────────────────────────────────────────── +if(MSVC) + target_compile_options(${PROJECT_NAME} PRIVATE /W4 /permissive-) +else() + target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic) +endif() + +# ── Install ────────────────────────────────────────────────────────────────── +install(TARGETS ${PROJECT_NAME} + RUNTIME DESTINATION bin +) + +# ── Tests ──────────────────────────────────────────────────────────────────── +enable_testing() +add_subdirectory(tests) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..d295b66 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,36 @@ +{ + "version": 6, + "cmakeMinimumRequired": { + "major": 3, + "minor": 20, + "patch": 0 + }, + "configurePresets": [ + { + "name": "dev", + "displayName": "Dev (Debug)", + "binaryDir": "${sourceDir}/build/dev", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "release", + "displayName": "Release", + "binaryDir": "${sourceDir}/build/release", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + } + ], + "buildPresets": [ + { + "name": "dev", + "configurePreset": "dev" + }, + { + "name": "release", + "configurePreset": "release" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index dc27f0f..4edfddd 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,33 @@ -# osr-package-template +# polymech-cli -Package basics \ No newline at end of file +Cross-platform C++ CLI built with CMake. + +## Prerequisites + +| Tool | Version | +|------|---------| +| CMake | ≥ 3.20 | +| C++ compiler | C++17 (MSVC, GCC, or Clang) | + +## Build + +```bash +# Debug +cmake --preset dev +cmake --build --preset dev + +# Release +cmake --preset release +cmake --build --preset release +``` + +## Usage + +```bash +polymech-cli --help +polymech-cli --version +``` + +## License + +BSD-3-Clause \ No newline at end of file diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..f093f4e --- /dev/null +++ b/config.toml @@ -0,0 +1,12 @@ +[project] +name = "polymech" +version = "0.1.0" +description = "Polymech C++ CLI" + +[database] +host = "localhost" +port = 5432 +name = "polymech" + +[logging] +level = "debug" diff --git a/package.json b/package.json deleted file mode 100644 index e67de54..0000000 --- a/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@plastichub/template", - "description": "", - "version": "0.3.1", - "main": "main.js", - "typings": "index.d.ts", - "publishConfig": { - "access": "public" - }, - "bin": { - "osr-bin": "main.js" - }, - "dependencies": { - "@types/node": "^14.17.5", - "@types/yargs": "^17.0.2", - "chalk": "^2.4.1", - "convert-units": "^2.3.4", - "env-var": "^7.0.1", - "typescript": "^4.3.5", - "yargs": "^14.2.3", - "yargs-parser": "^15.0.3" - }, - "scripts": { - "test": "tsc; mocha --full-trace mocha \"spec/**/*.spec.js\"", - "test-with-coverage": "istanbul cover node_modules/.bin/_mocha -- 'spec/**/*.spec.js'", - "lint": "tslint --project=./tsconfig.json", - "build": "tsc -p .", - "dev": "tsc -p . --declaration -w", - "typings": "tsc --declaration", - "docs": "npx typedoc src/index.ts", - "dev-test-watch": "mocha-typescript-watch" - }, - "homepage": "https://git.osr-plastic.org/plastichub/lib-content", - "repository": { - "type": "git", - "url": "https://git.osr-plastic.org/plastichub/lib-content.git" - }, - "engines": { - "node": ">= 14.0.0" - }, - "license": "BSD-3-Clause", - "keywords": [ - "typescript" - ] -} diff --git a/packages/html/CMakeLists.txt b/packages/html/CMakeLists.txt new file mode 100644 index 0000000..e592b29 --- /dev/null +++ b/packages/html/CMakeLists.txt @@ -0,0 +1,26 @@ +include(FetchContent) + +FetchContent_Declare( + lexbor + GIT_REPOSITORY https://github.com/lexbor/lexbor.git + GIT_TAG v2.4.0 + GIT_SHALLOW TRUE +) + +# Build lexbor as static +set(LEXBOR_BUILD_SHARED OFF CACHE BOOL "" FORCE) +set(LEXBOR_BUILD_STATIC ON CACHE BOOL "" FORCE) + +FetchContent_MakeAvailable(lexbor) + +add_library(html STATIC + src/html.cpp +) + +target_include_directories(html + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +target_link_libraries(html + PUBLIC lexbor_static +) diff --git a/packages/html/include/html/html.h b/packages/html/include/html/html.h new file mode 100644 index 0000000..684180e --- /dev/null +++ b/packages/html/include/html/html.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +namespace html { + +/// Parsed element — tag name + text content. +struct Element { + std::string tag; + std::string text; +}; + +/// Parse an HTML string and return all elements with their text content. +std::vector parse(const std::string &html_str); + +/// Extract the text content of all elements matching a CSS selector. +std::vector select(const std::string &html_str, + const std::string &selector); + +} // namespace html diff --git a/packages/html/src/html.cpp b/packages/html/src/html.cpp new file mode 100644 index 0000000..0883059 --- /dev/null +++ b/packages/html/src/html.cpp @@ -0,0 +1,129 @@ +#include "html/html.h" + +#include +#include +#include + +namespace html { + +// ── helpers ───────────────────────────────────────────────────────────────── + +static std::string node_text(lxb_dom_node_t *node) { + size_t len = 0; + lxb_char_t *text = lxb_dom_node_text_content(node, &len); + if (!text) + return {}; + std::string result(reinterpret_cast(text), len); + lxb_dom_document_destroy_text(node->owner_document, text); + return result; +} + +static std::string tag_name(lxb_dom_element_t *el) { + size_t len = 0; + const lxb_char_t *name = lxb_dom_element_qualified_name(el, &len); + if (!name) + return {}; + return std::string(reinterpret_cast(name), len); +} + +// ── walk tree recursively ─────────────────────────────────────────────────── + +static void walk(lxb_dom_node_t *node, std::vector &out) { + if (!node) + return; + if (node->type == LXB_DOM_NODE_TYPE_ELEMENT) { + auto *el = lxb_dom_interface_element(node); + auto txt = node_text(node); + if (!txt.empty()) { + out.push_back({tag_name(el), txt}); + } + } + auto *child = node->first_child; + while (child) { + walk(child, out); + child = child->next; + } +} + +// ── public API ────────────────────────────────────────────────────────────── + +std::vector parse(const std::string &html_str) { + auto *doc = lxb_html_document_create(); + if (!doc) + return {}; + + auto status = lxb_html_document_parse( + doc, reinterpret_cast(html_str.c_str()), + html_str.size()); + + std::vector result; + if (status == LXB_STATUS_OK) { + auto *body = lxb_dom_interface_node(lxb_html_document_body_element(doc)); + walk(body, result); + } + + lxb_html_document_destroy(doc); + return result; +} + +// ── CSS selector callback ─────────────────────────────────────────────────── + +struct SelectCtx { + std::vector *out; +}; + +static lxb_status_t select_cb(lxb_dom_node_t *node, + lxb_css_selector_specificity_t spec, void *ctx) { + (void)spec; + auto *sctx = static_cast(ctx); + auto txt = node_text(node); + if (!txt.empty()) { + sctx->out->push_back(txt); + } + return LXB_STATUS_OK; +} + +std::vector select(const std::string &html_str, + const std::string &selector) { + std::vector result; + + // Parse document + auto *doc = lxb_html_document_create(); + if (!doc) + return result; + + auto status = lxb_html_document_parse( + doc, reinterpret_cast(html_str.c_str()), + html_str.size()); + if (status != LXB_STATUS_OK) { + lxb_html_document_destroy(doc); + return result; + } + + // Set up CSS parser + selectors engine + auto *css_parser = lxb_css_parser_create(); + lxb_css_parser_init(css_parser, nullptr); + + auto *selectors = lxb_selectors_create(); + lxb_selectors_init(selectors); + + auto *list = lxb_css_selectors_parse( + css_parser, reinterpret_cast(selector.c_str()), + selector.size()); + + if (list) { + SelectCtx ctx{&result}; + lxb_selectors_find( + selectors, lxb_dom_interface_node(lxb_html_document_body_element(doc)), + list, select_cb, &ctx); + lxb_css_selector_list_destroy_memory(list); + } + + lxb_selectors_destroy(selectors, true); + lxb_css_parser_destroy(css_parser, true); + lxb_html_document_destroy(doc); + + return result; +} + +} // namespace html diff --git a/packages/logger/CMakeLists.txt b/packages/logger/CMakeLists.txt new file mode 100644 index 0000000..4fb71e1 --- /dev/null +++ b/packages/logger/CMakeLists.txt @@ -0,0 +1,21 @@ +include(FetchContent) + +FetchContent_Declare( + spdlog + GIT_REPOSITORY https://github.com/gabime/spdlog.git + GIT_TAG v1.15.1 + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(spdlog) + +add_library(logger STATIC + src/logger.cpp +) + +target_include_directories(logger + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +target_link_libraries(logger + PUBLIC spdlog::spdlog +) diff --git a/packages/logger/include/logger/logger.h b/packages/logger/include/logger/logger.h new file mode 100644 index 0000000..6cfffb1 --- /dev/null +++ b/packages/logger/include/logger/logger.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace logger { + +/// Initialize the default logger (call once at startup). +void init(const std::string &app_name = "polymech"); + +/// Log at various levels. +void info(const std::string &msg); +void warn(const std::string &msg); +void error(const std::string &msg); +void debug(const std::string &msg); + +} // namespace logger diff --git a/packages/logger/src/logger.cpp b/packages/logger/src/logger.cpp new file mode 100644 index 0000000..8aa4de3 --- /dev/null +++ b/packages/logger/src/logger.cpp @@ -0,0 +1,21 @@ +#include "logger/logger.h" + +#include +#include + + +namespace logger { + +void init(const std::string &app_name) { + auto console = spdlog::stdout_color_mt(app_name); + spdlog::set_default_logger(console); + spdlog::set_level(spdlog::level::debug); + spdlog::set_pattern("[%H:%M:%S] [%^%l%$] %v"); +} + +void info(const std::string &msg) { spdlog::info(msg); } +void warn(const std::string &msg) { spdlog::warn(msg); } +void error(const std::string &msg) { spdlog::error(msg); } +void debug(const std::string &msg) { spdlog::debug(msg); } + +} // namespace logger diff --git a/packages/postgres/CMakeLists.txt b/packages/postgres/CMakeLists.txt new file mode 100644 index 0000000..db2c230 --- /dev/null +++ b/packages/postgres/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library(postgres STATIC + src/postgres.cpp +) + +target_include_directories(postgres + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +target_link_libraries(postgres + PUBLIC logger +) diff --git a/packages/postgres/include/postgres/postgres.h b/packages/postgres/include/postgres/postgres.h new file mode 100644 index 0000000..700e9f1 --- /dev/null +++ b/packages/postgres/include/postgres/postgres.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace postgres { + +/// Connect to a PostgreSQL database (stub). +/// Returns a human-readable status string. +std::string connect(const std::string &connection_string); + +} // namespace postgres diff --git a/packages/postgres/src/postgres.cpp b/packages/postgres/src/postgres.cpp new file mode 100644 index 0000000..25e59b8 --- /dev/null +++ b/packages/postgres/src/postgres.cpp @@ -0,0 +1,12 @@ +#include "postgres/postgres.h" +#include "logger/logger.h" + +namespace postgres { + +std::string connect(const std::string &connection_string) { + logger::debug("postgres::connect → " + connection_string); + // stub — no real connection + return "ok"; +} + +} // namespace postgres diff --git a/src/.gitignore b/src/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..d4cebe7 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,77 @@ +#include +#include + +#include +#include + +#include "html/html.h" +#include "logger/logger.h" +#include "postgres/postgres.h" + +#ifndef PROJECT_VERSION +#define PROJECT_VERSION "0.1.0" +#endif + +int main(int argc, char *argv[]) { + CLI::App app{"polymech-cli — Polymech C++ CLI", "polymech-cli"}; + app.set_version_flag("-v,--version", PROJECT_VERSION); + + // Subcommand: parse HTML + std::string html_input; + auto *parse_cmd = app.add_subcommand("parse", "Parse HTML and list elements"); + parse_cmd->add_option("html", html_input, "HTML string to parse")->required(); + + // Subcommand: select from HTML + std::string select_input; + std::string selector; + auto *select_cmd = + app.add_subcommand("select", "CSS-select elements from HTML"); + select_cmd->add_option("html", select_input, "HTML string")->required(); + select_cmd->add_option("selector", selector, "CSS selector")->required(); + + // Subcommand: config — read a TOML file + std::string config_path; + auto *config_cmd = + app.add_subcommand("config", "Read and display a TOML config file"); + config_cmd->add_option("file", config_path, "Path to TOML file")->required(); + + CLI11_PARSE(app, argc, argv); + + logger::init("polymech-cli"); + + if (parse_cmd->parsed()) { + auto elements = html::parse(html_input); + logger::info("Parsed " + std::to_string(elements.size()) + " elements"); + for (const auto &el : elements) { + std::cout << "<" << el.tag << "> " << el.text << "\n"; + } + return 0; + } + + if (select_cmd->parsed()) { + auto matches = html::select(select_input, selector); + logger::info("Matched " + std::to_string(matches.size()) + " elements"); + for (const auto &m : matches) { + std::cout << m << "\n"; + } + return 0; + } + + if (config_cmd->parsed()) { + try { + auto tbl = toml::parse_file(config_path); + logger::info("Loaded config: " + config_path); + std::cout << tbl << "\n"; + } catch (const toml::parse_error &err) { + logger::error("TOML parse error: " + std::string(err.what())); + return 1; + } + return 0; + } + + // Default: demo + auto status = postgres::connect("postgresql://localhost:5432/polymech"); + logger::info("polymech-cli " + std::string(PROJECT_VERSION) + + " ready (pg: " + status + ")"); + return 0; +}