[M1] add util::Error, Result<T>, format()
This commit is contained in:
@@ -12,3 +12,10 @@ All notable changes to cargoxx will be documented in this file.
|
||||
builds an empty `cargoxx` binary.
|
||||
- `.clang-format` (LLVM, 100-column) and `.gitignore`.
|
||||
- `SPEC.md`, `TECH_SPEC.md`.
|
||||
- M1 foundation in `cargoxx.util`: `ErrorCode` (numbers per `TECH_SPEC.md` §4),
|
||||
`Error`, `Result<T> = std::expected<T, Error>`, and `format(Error)` rendering
|
||||
`error[Ennnn]: ...` with optional `--> path:line:col` and `hint:` lines.
|
||||
`format` lives in `src/util/error.cpp` as a module implementation unit.
|
||||
- `Catch2 v3` wired through `flake.nix` (libc++-built override) and registered
|
||||
in CMake; `tests/util_error.cpp` covers six format cases via
|
||||
`catch_discover_tests`.
|
||||
|
||||
@@ -13,7 +13,7 @@ project(cargoxx LANGUAGES CXX VERSION 0.1.0)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_EXTENSIONS ON)
|
||||
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
@@ -29,9 +29,11 @@ if(CARGOXX_WERROR)
|
||||
add_compile_options(-Werror)
|
||||
endif()
|
||||
|
||||
# ----- cargoxx library: all module units -----
|
||||
# ----- cargoxx library: module units + implementation units -----
|
||||
add_library(cargoxx STATIC)
|
||||
target_sources(cargoxx
|
||||
PRIVATE
|
||||
src/util/error.cpp
|
||||
PUBLIC
|
||||
FILE_SET CXX_MODULES FILES
|
||||
src/lib.cppm
|
||||
@@ -50,3 +52,10 @@ target_sources(cargoxx
|
||||
add_executable(cargoxx_bin src/main.cpp)
|
||||
set_target_properties(cargoxx_bin PROPERTIES OUTPUT_NAME cargoxx)
|
||||
target_link_libraries(cargoxx_bin PRIVATE cargoxx)
|
||||
|
||||
# ----- tests -----
|
||||
option(CARGOXX_BUILD_TESTS "Build cargoxx tests" ON)
|
||||
if(CARGOXX_BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
llvmPkgs = pkgs.llvmPackages;
|
||||
libcxxPkgs = {
|
||||
catch2_3 = pkgs.catch2_3.override {
|
||||
stdenv = llvmPkgs.libcxxStdenv;
|
||||
};
|
||||
};
|
||||
in {
|
||||
devShells.default = llvmPkgs.libcxxStdenv.mkDerivation {
|
||||
name = "cargoxx-dev";
|
||||
@@ -25,6 +30,7 @@
|
||||
buildInputs = [
|
||||
pkgs.sqlite
|
||||
pkgs.reproc
|
||||
libcxxPkgs.catch2_3
|
||||
];
|
||||
env.NIX_CFLAGS_COMPILE = toString [
|
||||
"-stdlib=libc++"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import cargoxx;
|
||||
import std;
|
||||
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
std::println("Hello, cargoxx!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
29
src/util/error.cpp
Normal file
29
src/util/error.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
module cargoxx.util;
|
||||
|
||||
import std;
|
||||
|
||||
namespace cargoxx::util {
|
||||
|
||||
auto format(const Error& e) -> std::string {
|
||||
std::string out;
|
||||
out += std::format("error[E{:04}]: {}\n", static_cast<int>(e.code), e.message);
|
||||
|
||||
if (e.location) {
|
||||
if (e.line_col) {
|
||||
out += std::format(" --> {}:{}:{}\n",
|
||||
e.location->string(),
|
||||
e.line_col->first,
|
||||
e.line_col->second);
|
||||
} else {
|
||||
out += std::format(" --> {}\n", e.location->string());
|
||||
}
|
||||
}
|
||||
|
||||
if (!e.hint.empty()) {
|
||||
out += std::format(" hint: {}\n", e.hint);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace cargoxx::util
|
||||
@@ -1 +1,50 @@
|
||||
export module cargoxx.util;
|
||||
|
||||
import std;
|
||||
|
||||
export namespace cargoxx::util {
|
||||
|
||||
// Error code numbering follows TECH_SPEC.md §4.
|
||||
enum class ErrorCode {
|
||||
ManifestNotFound = 1,
|
||||
ManifestParseError,
|
||||
ManifestInvalidField,
|
||||
ManifestUnknownField,
|
||||
ManifestVersionInvalid,
|
||||
|
||||
LayoutNoTarget = 20,
|
||||
LayoutAmbiguousLib,
|
||||
LayoutInvalidName,
|
||||
|
||||
ResolutionUnknownPackage = 40,
|
||||
ResolutionNetworkError,
|
||||
ResolutionUnsatisfiable,
|
||||
ResolutionVersionNotFound,
|
||||
|
||||
LinkdbUnknownPackage = 60,
|
||||
LinkdbCorrupt,
|
||||
LinkdbComponentNotSupported,
|
||||
|
||||
ExecCommandFailed = 80,
|
||||
ExecToolNotFound,
|
||||
BuildCmakeFailed,
|
||||
BuildNixFailed,
|
||||
|
||||
Internal = 100,
|
||||
NotImplemented,
|
||||
};
|
||||
|
||||
struct Error {
|
||||
ErrorCode code;
|
||||
std::string message;
|
||||
std::string hint;
|
||||
std::optional<std::filesystem::path> location;
|
||||
std::optional<std::pair<int, int>> line_col;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using Result = std::expected<T, Error>;
|
||||
|
||||
auto format(const Error& e) -> std::string;
|
||||
|
||||
} // namespace cargoxx::util
|
||||
|
||||
10
tests/CMakeLists.txt
Normal file
10
tests/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
find_package(Catch2 3 REQUIRED CONFIG)
|
||||
include(Catch)
|
||||
|
||||
function(cargoxx_add_test name)
|
||||
add_executable(${name} ${name}.cpp)
|
||||
target_link_libraries(${name} PRIVATE cargoxx Catch2::Catch2WithMain)
|
||||
catch_discover_tests(${name})
|
||||
endfunction()
|
||||
|
||||
cargoxx_add_test(util_error)
|
||||
69
tests/util_error.cpp
Normal file
69
tests/util_error.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
import cargoxx.util;
|
||||
import std;
|
||||
|
||||
using cargoxx::util::Error;
|
||||
using cargoxx::util::ErrorCode;
|
||||
using cargoxx::util::format;
|
||||
|
||||
TEST_CASE("format renders code, message, and trailing newline", "[util][format]") {
|
||||
Error e{ErrorCode::ManifestNotFound, "manifest not found", "", std::nullopt, std::nullopt};
|
||||
REQUIRE(format(e) == "error[E0001]: manifest not found\n");
|
||||
}
|
||||
|
||||
TEST_CASE("format pads error codes to four digits", "[util][format]") {
|
||||
Error e{ErrorCode::Internal, "boom", "", std::nullopt, std::nullopt};
|
||||
REQUIRE(format(e) == "error[E0100]: boom\n");
|
||||
}
|
||||
|
||||
TEST_CASE("format includes location with line:col when present", "[util][format]") {
|
||||
Error e{
|
||||
ErrorCode::ManifestParseError,
|
||||
"syntax error",
|
||||
"",
|
||||
std::filesystem::path{"Cargoxx.toml"},
|
||||
std::pair{7, 1},
|
||||
};
|
||||
REQUIRE(format(e) == "error[E0002]: syntax error\n"
|
||||
" --> Cargoxx.toml:7:1\n");
|
||||
}
|
||||
|
||||
TEST_CASE("format includes location without line:col when only path is set", "[util][format]") {
|
||||
Error e{
|
||||
ErrorCode::LayoutNoTarget,
|
||||
"no target found",
|
||||
"",
|
||||
std::filesystem::path{"./"},
|
||||
std::nullopt,
|
||||
};
|
||||
REQUIRE(format(e) == "error[E0020]: no target found\n"
|
||||
" --> ./\n");
|
||||
}
|
||||
|
||||
TEST_CASE("format appends a hint line when hint is non-empty", "[util][format]") {
|
||||
Error e{
|
||||
ErrorCode::LayoutNoTarget,
|
||||
"no target found",
|
||||
"run `cargoxx new --lib <name>` to create a library project",
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
};
|
||||
REQUIRE(format(e) ==
|
||||
"error[E0020]: no target found\n"
|
||||
" hint: run `cargoxx new --lib <name>` to create a library project\n");
|
||||
}
|
||||
|
||||
TEST_CASE("format combines location and hint", "[util][format]") {
|
||||
Error e{
|
||||
ErrorCode::LinkdbUnknownPackage,
|
||||
"package not in link database",
|
||||
"file an issue or supply a manual recipe",
|
||||
std::filesystem::path{"Cargoxx.toml"},
|
||||
std::pair{7, 1},
|
||||
};
|
||||
REQUIRE(format(e) ==
|
||||
"error[E0060]: package not in link database\n"
|
||||
" --> Cargoxx.toml:7:1\n"
|
||||
" hint: file an issue or supply a manual recipe\n");
|
||||
}
|
||||
Reference in New Issue
Block a user