[M1] add manifest::parse
This commit is contained in:
@@ -19,3 +19,9 @@ All notable changes to cargoxx will be documented in this file.
|
|||||||
- `Catch2 v3` wired through `flake.nix` (libc++-built override) and registered
|
- `Catch2 v3` wired through `flake.nix` (libc++-built override) and registered
|
||||||
in CMake; `tests/util_error.cpp` covers six format cases via
|
in CMake; `tests/util_error.cpp` covers six format cases via
|
||||||
`catch_discover_tests`.
|
`catch_discover_tests`.
|
||||||
|
- `cargoxx.manifest` public types (`Package`, `Dependency`, `BuildSettings`,
|
||||||
|
`Edition`, `Manifest`) and `parse(path)` returning `Result<Manifest>`,
|
||||||
|
backed by toml++ vendored as `third_party/toml.hpp`. Unknown keys in
|
||||||
|
`[package]` / `[build]` and unknown top-level keys are rejected; reserved
|
||||||
|
fields (`description`, `repository`, `[dev-dependencies]`, `[features]`,
|
||||||
|
`[workspace]`) are accepted. `tests/manifest_parse.cpp` covers 17 cases.
|
||||||
|
|||||||
@@ -31,9 +31,11 @@ endif()
|
|||||||
|
|
||||||
# ----- cargoxx library: module units + implementation units -----
|
# ----- cargoxx library: module units + implementation units -----
|
||||||
add_library(cargoxx STATIC)
|
add_library(cargoxx STATIC)
|
||||||
|
target_include_directories(cargoxx SYSTEM PRIVATE third_party)
|
||||||
target_sources(cargoxx
|
target_sources(cargoxx
|
||||||
PRIVATE
|
PRIVATE
|
||||||
src/util/error.cpp
|
src/util/error.cpp
|
||||||
|
src/manifest/parser.cpp
|
||||||
PUBLIC
|
PUBLIC
|
||||||
FILE_SET CXX_MODULES FILES
|
FILE_SET CXX_MODULES FILES
|
||||||
src/lib.cppm
|
src/lib.cppm
|
||||||
|
|||||||
@@ -1,3 +1,37 @@
|
|||||||
export module cargoxx.manifest;
|
export module cargoxx.manifest;
|
||||||
|
|
||||||
|
import std;
|
||||||
import cargoxx.util;
|
import cargoxx.util;
|
||||||
|
|
||||||
|
export namespace cargoxx::manifest {
|
||||||
|
|
||||||
|
struct Dependency {
|
||||||
|
std::string name;
|
||||||
|
std::string version_spec;
|
||||||
|
std::vector<std::string> components;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BuildSettings {
|
||||||
|
bool warnings_as_errors = false;
|
||||||
|
std::vector<std::string> sanitizers;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Edition { Cpp20, Cpp23, Cpp26 };
|
||||||
|
|
||||||
|
struct Package {
|
||||||
|
std::string name;
|
||||||
|
std::string version;
|
||||||
|
Edition edition = Edition::Cpp23;
|
||||||
|
std::vector<std::string> authors;
|
||||||
|
std::optional<std::string> license;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Manifest {
|
||||||
|
Package package;
|
||||||
|
std::vector<Dependency> dependencies;
|
||||||
|
BuildSettings build;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto parse(const std::filesystem::path& path) -> util::Result<Manifest>;
|
||||||
|
|
||||||
|
} // namespace cargoxx::manifest
|
||||||
|
|||||||
285
src/manifest/parser.cpp
Normal file
285
src/manifest/parser.cpp
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <toml.hpp>
|
||||||
|
|
||||||
|
module cargoxx.manifest;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
import cargoxx.util;
|
||||||
|
|
||||||
|
namespace cargoxx::manifest {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using util::Error;
|
||||||
|
using util::ErrorCode;
|
||||||
|
|
||||||
|
auto err(ErrorCode code, std::string msg, std::filesystem::path path,
|
||||||
|
std::optional<std::pair<int, int>> line_col = std::nullopt) -> Error {
|
||||||
|
return Error{code, std::move(msg), "", std::move(path), line_col};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto is_valid_name(std::string_view s) -> bool {
|
||||||
|
if (s.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (std::isdigit(static_cast<unsigned char>(s[0]))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (char c : s) {
|
||||||
|
bool ok = std::isalnum(static_cast<unsigned char>(c)) || c == '_' || c == '-';
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parse_edition(std::string_view s) -> std::optional<Edition> {
|
||||||
|
if (s == "cpp20") {
|
||||||
|
return Edition::Cpp20;
|
||||||
|
}
|
||||||
|
if (s == "cpp23") {
|
||||||
|
return Edition::Cpp23;
|
||||||
|
}
|
||||||
|
if (s == "cpp26") {
|
||||||
|
return Edition::Cpp26;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto source_pos(const toml::node& n) -> std::pair<int, int> {
|
||||||
|
auto src = n.source();
|
||||||
|
return {static_cast<int>(src.begin.line), static_cast<int>(src.begin.column)};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto extract_string_array(const toml::array& arr, std::string_view field,
|
||||||
|
const std::filesystem::path& path)
|
||||||
|
-> util::Result<std::vector<std::string>> {
|
||||||
|
std::vector<std::string> out;
|
||||||
|
out.reserve(arr.size());
|
||||||
|
for (const auto& el : arr) {
|
||||||
|
if (auto s = el.value<std::string>()) {
|
||||||
|
out.push_back(*s);
|
||||||
|
} else {
|
||||||
|
return std::unexpected(err(
|
||||||
|
ErrorCode::ManifestInvalidField,
|
||||||
|
std::format("'{}' must be an array of strings", field),
|
||||||
|
path, source_pos(el)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr std::array PACKAGE_KNOWN_KEYS = {
|
||||||
|
"name", "version", "edition", "authors", "license", "description", "repository",
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr std::array BUILD_KNOWN_KEYS = {
|
||||||
|
"warnings_as_errors", "sanitizers",
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr std::array TOPLEVEL_KNOWN_KEYS = {
|
||||||
|
"package", "dependencies", "build",
|
||||||
|
// reserved (accepted, ignored for v0.1)
|
||||||
|
"dev-dependencies", "features", "workspace",
|
||||||
|
};
|
||||||
|
|
||||||
|
auto parse_package(const toml::table& tbl, const std::filesystem::path& path)
|
||||||
|
-> util::Result<Package> {
|
||||||
|
Package pkg;
|
||||||
|
|
||||||
|
for (const auto& [key, value] : tbl) {
|
||||||
|
std::string k{key.str()};
|
||||||
|
if (std::ranges::find(PACKAGE_KNOWN_KEYS, k) == PACKAGE_KNOWN_KEYS.end()) {
|
||||||
|
return std::unexpected(err(ErrorCode::ManifestUnknownField,
|
||||||
|
std::format("unknown key '[package].{}'", k), path,
|
||||||
|
source_pos(value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto name = tbl["name"].value<std::string>()) {
|
||||||
|
if (!is_valid_name(*name)) {
|
||||||
|
return std::unexpected(err(ErrorCode::LayoutInvalidName,
|
||||||
|
std::format("invalid package name '{}'", *name), path,
|
||||||
|
source_pos(*tbl.get("name"))));
|
||||||
|
}
|
||||||
|
pkg.name = *name;
|
||||||
|
} else {
|
||||||
|
return std::unexpected(
|
||||||
|
err(ErrorCode::ManifestInvalidField, "[package].name is required", path));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto version = tbl["version"].value<std::string>()) {
|
||||||
|
pkg.version = *version;
|
||||||
|
} else {
|
||||||
|
return std::unexpected(
|
||||||
|
err(ErrorCode::ManifestInvalidField, "[package].version is required", path));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto edition = tbl["edition"].value<std::string>()) {
|
||||||
|
auto parsed = parse_edition(*edition);
|
||||||
|
if (!parsed) {
|
||||||
|
return std::unexpected(err(
|
||||||
|
ErrorCode::ManifestInvalidField,
|
||||||
|
std::format("invalid edition '{}': expected cpp20, cpp23, or cpp26", *edition),
|
||||||
|
path, source_pos(*tbl.get("edition"))));
|
||||||
|
}
|
||||||
|
pkg.edition = *parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto* authors = tbl["authors"].as_array()) {
|
||||||
|
auto r = extract_string_array(*authors, "[package].authors", path);
|
||||||
|
if (!r) {
|
||||||
|
return std::unexpected(r.error());
|
||||||
|
}
|
||||||
|
pkg.authors = std::move(*r);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto license = tbl["license"].value<std::string>()) {
|
||||||
|
pkg.license = *license;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkg;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parse_dependency(std::string name, const toml::node& value,
|
||||||
|
const std::filesystem::path& path) -> util::Result<Dependency> {
|
||||||
|
Dependency dep;
|
||||||
|
dep.name = std::move(name);
|
||||||
|
|
||||||
|
if (auto v = value.value<std::string>()) {
|
||||||
|
dep.version_spec = *v;
|
||||||
|
return dep;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto* tbl = value.as_table()) {
|
||||||
|
if (auto v = (*tbl)["version"].value<std::string>()) {
|
||||||
|
dep.version_spec = *v;
|
||||||
|
} else {
|
||||||
|
return std::unexpected(err(
|
||||||
|
ErrorCode::ManifestInvalidField,
|
||||||
|
std::format("dependency '{}' table must have a 'version' string", dep.name),
|
||||||
|
path, source_pos(value)));
|
||||||
|
}
|
||||||
|
if (const auto* comps = (*tbl)["components"].as_array()) {
|
||||||
|
auto r = extract_string_array(
|
||||||
|
*comps, std::format("dependencies.{}.components", dep.name), path);
|
||||||
|
if (!r) {
|
||||||
|
return std::unexpected(r.error());
|
||||||
|
}
|
||||||
|
dep.components = std::move(*r);
|
||||||
|
}
|
||||||
|
return dep;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::unexpected(err(
|
||||||
|
ErrorCode::ManifestInvalidField,
|
||||||
|
std::format("dependency '{}' must be a version string or a table", dep.name),
|
||||||
|
path, source_pos(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parse_dependencies(const toml::table& tbl, const std::filesystem::path& path)
|
||||||
|
-> util::Result<std::vector<Dependency>> {
|
||||||
|
std::vector<Dependency> out;
|
||||||
|
out.reserve(tbl.size());
|
||||||
|
for (const auto& [key, value] : tbl) {
|
||||||
|
auto d = parse_dependency(std::string{key.str()}, value, path);
|
||||||
|
if (!d) {
|
||||||
|
return std::unexpected(d.error());
|
||||||
|
}
|
||||||
|
out.push_back(std::move(*d));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parse_build(const toml::table& tbl, const std::filesystem::path& path)
|
||||||
|
-> util::Result<BuildSettings> {
|
||||||
|
BuildSettings b;
|
||||||
|
|
||||||
|
for (const auto& [key, value] : tbl) {
|
||||||
|
std::string k{key.str()};
|
||||||
|
if (std::ranges::find(BUILD_KNOWN_KEYS, k) == BUILD_KNOWN_KEYS.end()) {
|
||||||
|
return std::unexpected(err(ErrorCode::ManifestUnknownField,
|
||||||
|
std::format("unknown key '[build].{}'", k), path,
|
||||||
|
source_pos(value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto v = tbl["warnings_as_errors"].value<bool>()) {
|
||||||
|
b.warnings_as_errors = *v;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto* sans = tbl["sanitizers"].as_array()) {
|
||||||
|
auto r = extract_string_array(*sans, "[build].sanitizers", path);
|
||||||
|
if (!r) {
|
||||||
|
return std::unexpected(r.error());
|
||||||
|
}
|
||||||
|
b.sanitizers = std::move(*r);
|
||||||
|
}
|
||||||
|
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
auto parse(const std::filesystem::path& path) -> util::Result<Manifest> {
|
||||||
|
std::error_code ec;
|
||||||
|
if (!std::filesystem::exists(path, ec) || ec) {
|
||||||
|
return std::unexpected(err(ErrorCode::ManifestNotFound,
|
||||||
|
std::format("manifest not found: {}", path.string()), path));
|
||||||
|
}
|
||||||
|
|
||||||
|
toml::table root;
|
||||||
|
try {
|
||||||
|
root = toml::parse_file(path.string());
|
||||||
|
} catch (const toml::parse_error& e) {
|
||||||
|
auto src = e.source();
|
||||||
|
return std::unexpected(err(ErrorCode::ManifestParseError,
|
||||||
|
std::format("toml parse error: {}", e.description()), path,
|
||||||
|
std::pair{static_cast<int>(src.begin.line),
|
||||||
|
static_cast<int>(src.begin.column)}));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [key, value] : root) {
|
||||||
|
std::string k{key.str()};
|
||||||
|
if (std::ranges::find(TOPLEVEL_KNOWN_KEYS, k) == TOPLEVEL_KNOWN_KEYS.end()) {
|
||||||
|
return std::unexpected(err(ErrorCode::ManifestUnknownField,
|
||||||
|
std::format("unknown top-level key '{}'", k), path,
|
||||||
|
source_pos(value)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Manifest m;
|
||||||
|
|
||||||
|
const auto* pkg_tbl = root["package"].as_table();
|
||||||
|
if (!pkg_tbl) {
|
||||||
|
return std::unexpected(
|
||||||
|
err(ErrorCode::ManifestInvalidField, "[package] table is required", path));
|
||||||
|
}
|
||||||
|
auto pkg = parse_package(*pkg_tbl, path);
|
||||||
|
if (!pkg) {
|
||||||
|
return std::unexpected(pkg.error());
|
||||||
|
}
|
||||||
|
m.package = std::move(*pkg);
|
||||||
|
|
||||||
|
if (const auto* deps_tbl = root["dependencies"].as_table()) {
|
||||||
|
auto deps = parse_dependencies(*deps_tbl, path);
|
||||||
|
if (!deps) {
|
||||||
|
return std::unexpected(deps.error());
|
||||||
|
}
|
||||||
|
m.dependencies = std::move(*deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto* build_tbl = root["build"].as_table()) {
|
||||||
|
auto build = parse_build(*build_tbl, path);
|
||||||
|
if (!build) {
|
||||||
|
return std::unexpected(build.error());
|
||||||
|
}
|
||||||
|
m.build = std::move(*build);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cargoxx::manifest
|
||||||
@@ -8,3 +8,4 @@ function(cargoxx_add_test name)
|
|||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
cargoxx_add_test(util_error)
|
cargoxx_add_test(util_error)
|
||||||
|
cargoxx_add_test(manifest_parse)
|
||||||
|
|||||||
245
tests/manifest_parse.cpp
Normal file
245
tests/manifest_parse.cpp
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
import cargoxx.manifest;
|
||||||
|
import cargoxx.util;
|
||||||
|
import std;
|
||||||
|
|
||||||
|
using cargoxx::manifest::Edition;
|
||||||
|
using cargoxx::manifest::parse;
|
||||||
|
using cargoxx::util::ErrorCode;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
auto write_manifest(std::string_view body) -> std::filesystem::path {
|
||||||
|
auto dir = std::filesystem::temp_directory_path() /
|
||||||
|
std::format("cargoxx-manifest-test-{}", std::random_device{}());
|
||||||
|
std::filesystem::create_directories(dir);
|
||||||
|
auto path = dir / "Cargoxx.toml";
|
||||||
|
std::ofstream{path} << body;
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("parse rejects missing file", "[manifest][parse]") {
|
||||||
|
auto r = parse("/nonexistent/Cargoxx.toml");
|
||||||
|
REQUIRE_FALSE(r.has_value());
|
||||||
|
REQUIRE(r.error().code == ErrorCode::ManifestNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parse rejects malformed toml", "[manifest][parse]") {
|
||||||
|
auto p = write_manifest("[package\nname = ");
|
||||||
|
auto r = parse(p);
|
||||||
|
REQUIRE_FALSE(r.has_value());
|
||||||
|
REQUIRE(r.error().code == ErrorCode::ManifestParseError);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parse accepts a minimal manifest", "[manifest][parse]") {
|
||||||
|
auto p = write_manifest(R"(
|
||||||
|
[package]
|
||||||
|
name = "foo"
|
||||||
|
version = "0.1.0"
|
||||||
|
)");
|
||||||
|
auto r = parse(p);
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
REQUIRE(r->package.name == "foo");
|
||||||
|
REQUIRE(r->package.version == "0.1.0");
|
||||||
|
REQUIRE(r->package.edition == Edition::Cpp23);
|
||||||
|
REQUIRE(r->dependencies.empty());
|
||||||
|
REQUIRE_FALSE(r->build.warnings_as_errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parse handles all edition values", "[manifest][parse]") {
|
||||||
|
for (auto [s, e] : std::vector<std::pair<std::string, Edition>>{
|
||||||
|
{"cpp20", Edition::Cpp20}, {"cpp23", Edition::Cpp23}, {"cpp26", Edition::Cpp26}}) {
|
||||||
|
auto p = write_manifest(std::format(R"(
|
||||||
|
[package]
|
||||||
|
name = "foo"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "{}"
|
||||||
|
)",
|
||||||
|
s));
|
||||||
|
auto r = parse(p);
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
REQUIRE(r->package.edition == e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parse rejects unknown edition", "[manifest][parse]") {
|
||||||
|
auto p = write_manifest(R"(
|
||||||
|
[package]
|
||||||
|
name = "foo"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "cpp99"
|
||||||
|
)");
|
||||||
|
auto r = parse(p);
|
||||||
|
REQUIRE_FALSE(r.has_value());
|
||||||
|
REQUIRE(r.error().code == ErrorCode::ManifestInvalidField);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parse extracts authors and license", "[manifest][parse]") {
|
||||||
|
auto p = write_manifest(R"(
|
||||||
|
[package]
|
||||||
|
name = "foo"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Ada", "Grace"]
|
||||||
|
license = "MIT"
|
||||||
|
)");
|
||||||
|
auto r = parse(p);
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
REQUIRE(r->package.authors == std::vector<std::string>{"Ada", "Grace"});
|
||||||
|
REQUIRE(r->package.license == "MIT");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parse accepts string-form dependency", "[manifest][parse]") {
|
||||||
|
auto p = write_manifest(R"(
|
||||||
|
[package]
|
||||||
|
name = "foo"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
fmt = "10.2"
|
||||||
|
)");
|
||||||
|
auto r = parse(p);
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
REQUIRE(r->dependencies.size() == 1);
|
||||||
|
REQUIRE(r->dependencies[0].name == "fmt");
|
||||||
|
REQUIRE(r->dependencies[0].version_spec == "10.2");
|
||||||
|
REQUIRE(r->dependencies[0].components.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parse accepts table-form dependency with components", "[manifest][parse]") {
|
||||||
|
auto p = write_manifest(R"(
|
||||||
|
[package]
|
||||||
|
name = "foo"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
boost = { version = "1.84", components = ["filesystem", "system"] }
|
||||||
|
)");
|
||||||
|
auto r = parse(p);
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
REQUIRE(r->dependencies.size() == 1);
|
||||||
|
REQUIRE(r->dependencies[0].name == "boost");
|
||||||
|
REQUIRE(r->dependencies[0].version_spec == "1.84");
|
||||||
|
REQUIRE(r->dependencies[0].components == std::vector<std::string>{"filesystem", "system"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parse rejects dependency value of wrong type", "[manifest][parse]") {
|
||||||
|
auto p = write_manifest(R"(
|
||||||
|
[package]
|
||||||
|
name = "foo"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
weird = 42
|
||||||
|
)");
|
||||||
|
auto r = parse(p);
|
||||||
|
REQUIRE_FALSE(r.has_value());
|
||||||
|
REQUIRE(r.error().code == ErrorCode::ManifestInvalidField);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parse extracts build settings", "[manifest][parse]") {
|
||||||
|
auto p = write_manifest(R"(
|
||||||
|
[package]
|
||||||
|
name = "foo"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
warnings_as_errors = true
|
||||||
|
sanitizers = ["address", "undefined"]
|
||||||
|
)");
|
||||||
|
auto r = parse(p);
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
REQUIRE(r->build.warnings_as_errors);
|
||||||
|
REQUIRE(r->build.sanitizers == std::vector<std::string>{"address", "undefined"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parse rejects unknown [package] key", "[manifest][parse]") {
|
||||||
|
auto p = write_manifest(R"(
|
||||||
|
[package]
|
||||||
|
name = "foo"
|
||||||
|
version = "0.1.0"
|
||||||
|
flavor = "spicy"
|
||||||
|
)");
|
||||||
|
auto r = parse(p);
|
||||||
|
REQUIRE_FALSE(r.has_value());
|
||||||
|
REQUIRE(r.error().code == ErrorCode::ManifestUnknownField);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parse rejects unknown [build] key", "[manifest][parse]") {
|
||||||
|
auto p = write_manifest(R"(
|
||||||
|
[package]
|
||||||
|
name = "foo"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
optimize = "max"
|
||||||
|
)");
|
||||||
|
auto r = parse(p);
|
||||||
|
REQUIRE_FALSE(r.has_value());
|
||||||
|
REQUIRE(r.error().code == ErrorCode::ManifestUnknownField);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parse accepts reserved [package] fields", "[manifest][parse]") {
|
||||||
|
auto p = write_manifest(R"(
|
||||||
|
[package]
|
||||||
|
name = "foo"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "demo"
|
||||||
|
repository = "https://example.com/foo"
|
||||||
|
)");
|
||||||
|
auto r = parse(p);
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parse accepts reserved top-level tables", "[manifest][parse]") {
|
||||||
|
auto p = write_manifest(R"(
|
||||||
|
[package]
|
||||||
|
name = "foo"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
gtest = "1.14"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["a"]
|
||||||
|
)");
|
||||||
|
auto r = parse(p);
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parse rejects invalid name with spaces", "[manifest][parse]") {
|
||||||
|
auto p = write_manifest(R"(
|
||||||
|
[package]
|
||||||
|
name = "foo bar"
|
||||||
|
version = "0.1.0"
|
||||||
|
)");
|
||||||
|
auto r = parse(p);
|
||||||
|
REQUIRE_FALSE(r.has_value());
|
||||||
|
REQUIRE(r.error().code == ErrorCode::LayoutInvalidName);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parse rejects name starting with digit", "[manifest][parse]") {
|
||||||
|
auto p = write_manifest(R"(
|
||||||
|
[package]
|
||||||
|
name = "1foo"
|
||||||
|
version = "0.1.0"
|
||||||
|
)");
|
||||||
|
auto r = parse(p);
|
||||||
|
REQUIRE_FALSE(r.has_value());
|
||||||
|
REQUIRE(r.error().code == ErrorCode::LayoutInvalidName);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("parse rejects missing [package].name", "[manifest][parse]") {
|
||||||
|
auto p = write_manifest(R"(
|
||||||
|
[package]
|
||||||
|
version = "0.1.0"
|
||||||
|
)");
|
||||||
|
auto r = parse(p);
|
||||||
|
REQUIRE_FALSE(r.has_value());
|
||||||
|
REQUIRE(r.error().code == ErrorCode::ManifestInvalidField);
|
||||||
|
}
|
||||||
17890
third_party/toml.hpp
vendored
Normal file
17890
third_party/toml.hpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user