min boilerplate 1/2
This commit is contained in:
parent
9c73cec7a3
commit
e02c295dac
31
.gitignore
vendored
31
.gitignore
vendored
@ -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
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
./docs
|
||||
./scripts
|
||||
./tests
|
||||
./incoming
|
||||
66
CMakeLists.txt
Normal file
66
CMakeLists.txt
Normal file
@ -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)
|
||||
36
CMakePresets.json
Normal file
36
CMakePresets.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
34
README.md
34
README.md
@ -1,3 +1,33 @@
|
||||
# osr-package-template
|
||||
# polymech-cli
|
||||
|
||||
Package basics
|
||||
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
|
||||
12
config.toml
Normal file
12
config.toml
Normal file
@ -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"
|
||||
45
package.json
45
package.json
@ -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"
|
||||
]
|
||||
}
|
||||
26
packages/html/CMakeLists.txt
Normal file
26
packages/html/CMakeLists.txt
Normal file
@ -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
|
||||
)
|
||||
21
packages/html/include/html/html.h
Normal file
21
packages/html/include/html/html.h
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<Element> parse(const std::string &html_str);
|
||||
|
||||
/// Extract the text content of all elements matching a CSS selector.
|
||||
std::vector<std::string> select(const std::string &html_str,
|
||||
const std::string &selector);
|
||||
|
||||
} // namespace html
|
||||
129
packages/html/src/html.cpp
Normal file
129
packages/html/src/html.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
#include "html/html.h"
|
||||
|
||||
#include <lexbor/css/css.h>
|
||||
#include <lexbor/html/html.h>
|
||||
#include <lexbor/selectors/selectors.h>
|
||||
|
||||
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<const char *>(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<const char *>(name), len);
|
||||
}
|
||||
|
||||
// ── walk tree recursively ───────────────────────────────────────────────────
|
||||
|
||||
static void walk(lxb_dom_node_t *node, std::vector<Element> &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<Element> parse(const std::string &html_str) {
|
||||
auto *doc = lxb_html_document_create();
|
||||
if (!doc)
|
||||
return {};
|
||||
|
||||
auto status = lxb_html_document_parse(
|
||||
doc, reinterpret_cast<const lxb_char_t *>(html_str.c_str()),
|
||||
html_str.size());
|
||||
|
||||
std::vector<Element> 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<std::string> *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<SelectCtx *>(ctx);
|
||||
auto txt = node_text(node);
|
||||
if (!txt.empty()) {
|
||||
sctx->out->push_back(txt);
|
||||
}
|
||||
return LXB_STATUS_OK;
|
||||
}
|
||||
|
||||
std::vector<std::string> select(const std::string &html_str,
|
||||
const std::string &selector) {
|
||||
std::vector<std::string> result;
|
||||
|
||||
// Parse document
|
||||
auto *doc = lxb_html_document_create();
|
||||
if (!doc)
|
||||
return result;
|
||||
|
||||
auto status = lxb_html_document_parse(
|
||||
doc, reinterpret_cast<const lxb_char_t *>(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<const lxb_char_t *>(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
|
||||
21
packages/logger/CMakeLists.txt
Normal file
21
packages/logger/CMakeLists.txt
Normal file
@ -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
|
||||
)
|
||||
16
packages/logger/include/logger/logger.h
Normal file
16
packages/logger/include/logger/logger.h
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
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
|
||||
21
packages/logger/src/logger.cpp
Normal file
21
packages/logger/src/logger.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#include "logger/logger.h"
|
||||
|
||||
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
|
||||
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
|
||||
11
packages/postgres/CMakeLists.txt
Normal file
11
packages/postgres/CMakeLists.txt
Normal file
@ -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
|
||||
)
|
||||
11
packages/postgres/include/postgres/postgres.h
Normal file
11
packages/postgres/include/postgres/postgres.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace postgres {
|
||||
|
||||
/// Connect to a PostgreSQL database (stub).
|
||||
/// Returns a human-readable status string.
|
||||
std::string connect(const std::string &connection_string);
|
||||
|
||||
} // namespace postgres
|
||||
12
packages/postgres/src/postgres.cpp
Normal file
12
packages/postgres/src/postgres.cpp
Normal file
@ -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
|
||||
0
src/.gitignore
vendored
0
src/.gitignore
vendored
77
src/main.cpp
Normal file
77
src/main.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include <CLI/CLI.hpp>
|
||||
#include <toml++/toml.hpp>
|
||||
|
||||
#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;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user