From 9cd6e4c07cdf76f5f34e331c8d927be52fd02b2f Mon Sep 17 00:00:00 2001 From: PhilipDeegan Date: Sat, 8 Aug 2026 20:11:44 +0200 Subject: [PATCH 1/3] ++ --- API.md | 87 +++++++++++++++++++++++++++++++++++++++++++++ inc/mkn/kul/os.hpp | 16 +++++---- inc/mkn/kul/scm.hpp | 4 +-- 3 files changed, 97 insertions(+), 10 deletions(-) create mode 100644 API.md diff --git a/API.md b/API.md new file mode 100644 index 0000000..a44617a --- /dev/null +++ b/API.md @@ -0,0 +1,87 @@ +# mkn::kul — C++ API Reference + +**mkn::kul** (`Make Know Utility Library`) is a modern C++20 utility library providing cross-platform abstractions for threading, I/O, processes, logging, containers, and more. + +All public symbols live under the `mkn::kul` namespace (or sub-namespaces noted per section). + +--- + +## Table of Contents + +1. [Core Definitions (`defs.hpp`)](#1-core-definitions-defshpp) +2. [Exception Handling (`except.hpp`)](doc/mkn/kul/except.md) +3. [Assertions (`assert.hpp`)](doc/mkn/kul/assert.md) +4. [Type Trait Helpers (`decltype.hpp`)](#4-type-trait-helpers-decltypehpp) +5. [Span Containers (`span.hpp`)](doc/mkn/kul/span.md) +6. [Custom Allocators (`alloc.hpp`)](doc/mkn/kul/alloc.md) +7. [Vectors with Custom Allocators (`vector.hpp`)](doc/mkn/kul/vector.md) +8. [Byte Utilities (`byte.hpp`)](doc/mkn/kul/byte.md) +9. [String Utilities (`string.hpp`)](doc/mkn/kul/string.md) +10. [Wide String Utilities (`wstring.hpp`)](doc/mkn/kul/wstring.md) +11. [Floating-Point Utilities (`float.hpp`)](doc/mkn/kul/float.md) +12. [Hashing (`hash.hpp`)](doc/mkn/kul/hash.md) +13. [Hash Maps and Sets (`map.hpp`)](doc/mkn/kul/map.md) +14. [Time and Date (`time.hpp`)](doc/mkn/kul/time.md) +15. [Logging (`log.hpp`)](doc/mkn/kul/log.md) +16. [Threading (`threads.hpp`)](doc/mkn/kul/threads.md) +17. [File I/O (`io.hpp`)](doc/mkn/kul/io.md) +18. [Environment Variables (`env.hpp`)](doc/mkn/kul/env.md) +19. [Filesystem Abstractions (`os.hpp`)](doc/mkn/kul/os.md) +20. [Process Management (`proc.hpp`)](doc/mkn/kul/proc.md) +21. [Signal Handling (`signal.hpp`)](doc/mkn/kul/signal.md) +22. [Command-Line Interface (`cli.hpp`)](doc/mkn/kul/cli.md) +23. [Debugging and Stack Traces (`dbg.hpp`)](doc/mkn/kul/dbg.md) +24. [Compile-Time Iteration (`for.hpp`)](doc/mkn/kul/for.md) +25. [Tuple Utilities (`tuple.hpp`)](doc/mkn/kul/tuple.md) +26. [Aggregate Comparison (`all.hpp`)](doc/mkn/kul/all.md) +27. [Zip Iterator (`zip.hpp`)](doc/mkn/kul/zip.md) +28. [Math Utilities (`math.hpp`)](doc/mkn/kul/math.md) +29. [YAML Parsing (`yaml.hpp`)](doc/mkn/kul/yaml.md) +30. [Better Object Notation (`bon.hpp`)](doc/mkn/kul/bon.md) +31. [Source Control Management (`scm.hpp`)](doc/mkn/kul/scm.md) +32. [CPU Utilities (`cpu.hpp`)](doc/mkn/kul/cpu.md) +33. [Inter-Process Communication (`ipc.hpp`)](doc/mkn/kul/ipc.md) +34. [System Utilities (`sys.hpp`)](doc/mkn/kul/sys.md) + +--- + +## 1. Core Definitions (`defs.hpp`) + +Platform detection and utility macros. + +### Macros + +| Macro | Description | +|-------|-------------| +| `MKN_KUL_STR(x)` | Stringify `x` | +| `MKN_KUL_STR_CAT(x, y)` | Token-paste `x` and `y` | +| `MKN_KUL_PUBLISH` | Mark a symbol for DLL export / `visibility("default")` | +| `MKN_KUL_PRIVATE` | Mark a symbol as `visibility("hidden")` | +| `MKN_KUL_IS_WIN` | `1` on Windows, `0` otherwise | +| `MKN_KUL_IS_NIX` | `1` on Linux, `0` otherwise | +| `MKN_KUL_IS_BSD` | `1` on BSD, `0` otherwise | +| `MKN_KUL_DEBUG_DO(...)` | Expands to `__VA_ARGS__` in debug builds; empty in release | +| `MKN_KUL_DEBUG_DO_ELSE(...)` | Inverse of `MKN_KUL_DEBUG_DO` | + +--- + +## Environment Variables + +| Variable | Effect | +|----------|--------| +| `KLOG` | Log verbosity: `0`=off, `1`=INF, `2`=ERR, `3`=DBG, `4`=OTH, `5`=TRC | +| `MKN_KUL_GIT_CO` | Extra args passed to `git clone` during dependency fetch | + +--- + +## Platform Notes + +| Header | Windows | Linux | BSD | +|--------|---------|-------|-----| +| `cpu.hpp` | `os/win/cpu.hpp` | `os/nix/cpu.hpp` | `os/bsd/cpu.hpp` | +| `env.hpp` | `os/win/env.hpp` | `os/nixish/env.hpp` | `os/nixish/env.hpp` | +| `ipc.hpp` | `os/win/ipc.hpp` | `os/nixish/ipc.hpp` | `os/nixish/ipc.hpp` | +| `signal.hpp` | `os/win/signal.hpp` | `os/nixish/signal.hpp` | `os/nixish/signal.hpp` | +| `sys.hpp` | `os/win/sys.hpp` | `os/nixish/sys.hpp` | `os/nixish/sys.hpp` | +| `proc.hpp` | `os/win/proc.hpp` | `os/nixish/proc.hpp` + `os/nix/proc.os.hpp` | similar | +| `threads.hpp` | `os/win/threads.os.hpp` | `os/nixish/threads.os.hpp` | similar | diff --git a/inc/mkn/kul/os.hpp b/inc/mkn/kul/os.hpp index dbb3a15..db4b444 100644 --- a/inc/mkn/kul/os.hpp +++ b/inc/mkn/kul/os.hpp @@ -205,7 +205,9 @@ class File : public fs::Item { public: File() : _n(), _d() {} - File(std::string const& n, bool m = false) : _n(Dir::LOCL(n)), _d(env::CWD()) { + File(std::string const& n, bool m = false) : _n(Dir::LOCL(n)) { + // _d starts empty so is() stats _n as-given (Dir::join returns it unprefixed), + // correctly resolving both absolute and CWD-relative input before any CWD default is applied. if (is()) { try { this->_n = Dir::REAL(this->_n); @@ -213,13 +215,13 @@ class File : public fs::Item { this->_n = this->_n.substr(_d.path().size() + 1); } catch (mkn::kul::fs::Exception const& e) { } + } else if (n.find(Dir::SEP()) != std::string::npos) { + this->_d = Dir(n.substr(0, n.rfind(Dir::SEP()))); + this->_n = this->_n.substr(n.rfind(Dir::SEP()) + 1); + } else if (auto const& _N = Dir::REAL_OR_NULL(this->_n)) { + this->_d = Dir(Dir::PRNT(*_N), m); } else { - if (n.find(Dir::SEP()) != std::string::npos) { - this->_d = Dir(n.substr(0, n.rfind(Dir::SEP()))); - this->_n = this->_n.substr(n.rfind(Dir::SEP()) + 1); - } else { - if (auto const& _N = Dir::REAL_OR_NULL(this->_n)) this->_d = Dir(Dir::PRNT(*_N), m); - } + this->_d = Dir(env::CWD()); } } File(char const* n, bool m = false) : File(std::string(n), m) {} diff --git a/inc/mkn/kul/scm.hpp b/inc/mkn/kul/scm.hpp index ec74ea4..8b7bc93 100644 --- a/inc/mkn/kul/scm.hpp +++ b/inc/mkn/kul/scm.hpp @@ -75,8 +75,8 @@ class SCM { virtual std::string defaultRemoteBranch(std::string const& repo) const = 0; }; -// review https://gist.github.com/aleksey-bykov/1273f4982c317c92d532 namespace scm { + class Git : public SCM { public: std::string defaultRemoteBranch(std::string const& repo) const override { @@ -85,14 +85,12 @@ class Git : public SCM { p << "ls-remote" << "--symref" << repo << "HEAD"; try { - KLOG(TRC) << p; p.start(); } catch (mkn::kul::proc::ExitException const& e) { KEXCEPT(Exception, "SCM ERROR - Checking local branch") << p.toString(); } auto const lines = mkn::kul::String::LINES(pc.outs()); if (lines.empty()) return {}; - KLOG(TRC) << lines[0]; // "ref: refs/heads/master\tHEAD" -> "master" auto ret = mkn::kul::String::SPLIT(lines[0], "/").back(); auto const tab = ret.find('\t'); From 03e2061326992cfc035ee86d78de4eedb78daf82 Mon Sep 17 00:00:00 2001 From: PhilipDeegan Date: Sat, 8 Aug 2026 21:38:53 +0200 Subject: [PATCH 2/3] ++ --- inc/mkn/kul/os.hpp | 16 ++++++------- tst/test/os.cpp | 57 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/inc/mkn/kul/os.hpp b/inc/mkn/kul/os.hpp index db4b444..dbb3a15 100644 --- a/inc/mkn/kul/os.hpp +++ b/inc/mkn/kul/os.hpp @@ -205,9 +205,7 @@ class File : public fs::Item { public: File() : _n(), _d() {} - File(std::string const& n, bool m = false) : _n(Dir::LOCL(n)) { - // _d starts empty so is() stats _n as-given (Dir::join returns it unprefixed), - // correctly resolving both absolute and CWD-relative input before any CWD default is applied. + File(std::string const& n, bool m = false) : _n(Dir::LOCL(n)), _d(env::CWD()) { if (is()) { try { this->_n = Dir::REAL(this->_n); @@ -215,13 +213,13 @@ class File : public fs::Item { this->_n = this->_n.substr(_d.path().size() + 1); } catch (mkn::kul::fs::Exception const& e) { } - } else if (n.find(Dir::SEP()) != std::string::npos) { - this->_d = Dir(n.substr(0, n.rfind(Dir::SEP()))); - this->_n = this->_n.substr(n.rfind(Dir::SEP()) + 1); - } else if (auto const& _N = Dir::REAL_OR_NULL(this->_n)) { - this->_d = Dir(Dir::PRNT(*_N), m); } else { - this->_d = Dir(env::CWD()); + if (n.find(Dir::SEP()) != std::string::npos) { + this->_d = Dir(n.substr(0, n.rfind(Dir::SEP()))); + this->_n = this->_n.substr(n.rfind(Dir::SEP()) + 1); + } else { + if (auto const& _N = Dir::REAL_OR_NULL(this->_n)) this->_d = Dir(Dir::PRNT(*_N), m); + } } } File(char const* n, bool m = false) : File(std::string(n), m) {} diff --git a/tst/test/os.cpp b/tst/test/os.cpp index 6c8a184..c07f729 100644 --- a/tst/test/os.cpp +++ b/tst/test/os.cpp @@ -39,3 +39,60 @@ TEST(OperatingSystemTests, HasFileModifiedTimeStampSupport) { ASSERT_TRUE(TimeStampHandler::INSTANCE().is()); ASSERT_TRUE(TimeStampHandler::INSTANCE().timeStamps().modified()); } + +TEST(OperatingSystemTests, FileAbsolutePathMatchesRelativeForTopLevelFile) { + mkn::kul::File const relative("mkn.yaml"); + ASSERT_TRUE(relative.is()); + mkn::kul::File const absolute(relative.real()); + EXPECT_TRUE(absolute.is()); + EXPECT_EQ(relative.name(), absolute.name()); + EXPECT_EQ(relative.real(), absolute.real()); + EXPECT_EQ(relative.dir().real(), absolute.dir().real()); + EXPECT_EQ(relative.size(), absolute.size()); +} + +TEST(OperatingSystemTests, FileAbsolutePathMatchesRelativeForNestedFile) { + mkn::kul::File const relative("inc/mkn/kul/os.hpp"); + ASSERT_TRUE(relative.is()); + mkn::kul::File const absolute(relative.real()); + EXPECT_TRUE(absolute.is()); + EXPECT_EQ(relative.name(), absolute.name()); + EXPECT_EQ(relative.real(), absolute.real()); + EXPECT_EQ(relative.dir().real(), absolute.dir().real()); + EXPECT_EQ(relative.size(), absolute.size()); +} + +TEST(OperatingSystemTests, FileAbsolutePathResolvesRegardlessOfCWD) { + mkn::kul::File const relative("LICENSE.md"); + ASSERT_TRUE(relative.is()); + std::string const absPath = relative.real(); + + mkn::kul::os::PushDir const pd(mkn::kul::Dir("inc")); + mkn::kul::File const absolute(absPath); + EXPECT_TRUE(absolute.is()); + EXPECT_EQ(absPath, absolute.real()); +} + +TEST(OperatingSystemTests, FileAbsolutePathIsCanonicalized) { + std::string const messy = + mkn::kul::env::CWD() + mkn::kul::Dir::SEP() + mkn::kul::Dir::SEP() + "mkn.yaml"; + mkn::kul::File const f(messy); + ASSERT_TRUE(f.is()); + EXPECT_EQ("mkn.yaml", f.name()); + EXPECT_EQ(mkn::kul::Dir(mkn::kul::env::CWD()).path(), f.dir().path()); +} + +TEST(OperatingSystemTests, FileAbsoluteNonExistentPathSplitsDirAndName) { + std::string const absPath = + mkn::kul::env::CWD() + mkn::kul::Dir::SEP() + "does_not_exist_kul_os_test.txt"; + mkn::kul::File const f(absPath); + EXPECT_FALSE(f.is()); + EXPECT_EQ("does_not_exist_kul_os_test.txt", f.name()); + EXPECT_EQ(mkn::kul::env::CWD(), f.dir().path()); +} + +TEST(OperatingSystemTests, FileBareNonExistentNameDefaultsToCWD) { + mkn::kul::File const f("does_not_exist_kul_os_test.txt"); + EXPECT_FALSE(f.is()); + EXPECT_EQ(mkn::kul::Dir(mkn::kul::env::CWD()).real(), f.dir().real()); +} From 3372a43c80d1df07373492d547abb3ae7a6d9d6b Mon Sep 17 00:00:00 2001 From: PhilipDeegan Date: Sat, 8 Aug 2026 21:47:02 +0200 Subject: [PATCH 3/3] ++ --- inc/mkn/kul/os.hpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/inc/mkn/kul/os.hpp b/inc/mkn/kul/os.hpp index dbb3a15..db4b444 100644 --- a/inc/mkn/kul/os.hpp +++ b/inc/mkn/kul/os.hpp @@ -205,7 +205,9 @@ class File : public fs::Item { public: File() : _n(), _d() {} - File(std::string const& n, bool m = false) : _n(Dir::LOCL(n)), _d(env::CWD()) { + File(std::string const& n, bool m = false) : _n(Dir::LOCL(n)) { + // _d starts empty so is() stats _n as-given (Dir::join returns it unprefixed), + // correctly resolving both absolute and CWD-relative input before any CWD default is applied. if (is()) { try { this->_n = Dir::REAL(this->_n); @@ -213,13 +215,13 @@ class File : public fs::Item { this->_n = this->_n.substr(_d.path().size() + 1); } catch (mkn::kul::fs::Exception const& e) { } + } else if (n.find(Dir::SEP()) != std::string::npos) { + this->_d = Dir(n.substr(0, n.rfind(Dir::SEP()))); + this->_n = this->_n.substr(n.rfind(Dir::SEP()) + 1); + } else if (auto const& _N = Dir::REAL_OR_NULL(this->_n)) { + this->_d = Dir(Dir::PRNT(*_N), m); } else { - if (n.find(Dir::SEP()) != std::string::npos) { - this->_d = Dir(n.substr(0, n.rfind(Dir::SEP()))); - this->_n = this->_n.substr(n.rfind(Dir::SEP()) + 1); - } else { - if (auto const& _N = Dir::REAL_OR_NULL(this->_n)) this->_d = Dir(Dir::PRNT(*_N), m); - } + this->_d = Dir(env::CWD()); } } File(char const* n, bool m = false) : File(std::string(n), m) {}