[M1] add cargoxx new

This commit is contained in:
2026-05-08 11:22:34 +00:00
parent 3dc082147a
commit 361b936648
9 changed files with 12555 additions and 11 deletions

View File

@@ -1,10 +1,14 @@
export module cargoxx.cli;
import std;
import cargoxx.util;
import cargoxx.exec;
import cargoxx.manifest;
import cargoxx.lockfile;
import cargoxx.layout;
import cargoxx.linkdb;
import cargoxx.resolver;
import cargoxx.codegen;
export namespace cargoxx::cli {
auto cmd_new(const std::string& name, bool lib_only,
const std::filesystem::path& parent_dir) -> util::Result<void>;
auto run(int argc, char** argv) -> int;
} // namespace cargoxx::cli

158
src/cli/cmd_new.cpp Normal file
View File

@@ -0,0 +1,158 @@
module cargoxx.cli;
import std;
import cargoxx.util;
import cargoxx.manifest;
namespace cargoxx::cli {
namespace {
constexpr std::string_view MAIN_CPP_TEMPLATE = R"(import std;
int main() {
std::println("Hello from {}!", "@@NAME@@");
return 0;
}
)";
constexpr std::string_view LIB_CPPM_TEMPLATE = R"(export module @@MODULE@@;
import std;
export namespace @@MODULE@@ {
auto greeting() -> std::string_view {
return "Hello from @@NAME@@!";
}
}
)";
constexpr std::string_view GITIGNORE_TEMPLATE = "/build/\n";
auto is_valid_pkg_name(std::string_view s) -> bool {
if (s.empty() || 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 sanitize_id(std::string_view s) -> std::string {
std::string out{s};
std::ranges::replace(out, '-', '_');
return out;
}
auto substitute(std::string_view tmpl, std::string_view marker, std::string_view value)
-> std::string {
std::string out;
out.reserve(tmpl.size());
std::size_t pos = 0;
while (pos < tmpl.size()) {
auto next = tmpl.find(marker, pos);
if (next == std::string_view::npos) {
out.append(tmpl.substr(pos));
break;
}
out.append(tmpl.substr(pos, next - pos));
out.append(value);
pos = next + marker.size();
}
return out;
}
auto write_text(const std::filesystem::path& path, std::string_view content)
-> util::Result<void> {
std::ofstream out{path};
if (!out) {
return std::unexpected(util::Error{
util::ErrorCode::Internal,
std::format("cannot open for writing: {}", path.string()),
"", path, std::nullopt,
});
}
out << content;
if (!out) {
return std::unexpected(util::Error{
util::ErrorCode::Internal,
std::format("write failed: {}", path.string()),
"", path, std::nullopt,
});
}
return {};
}
} // namespace
auto cmd_new(const std::string& name, bool lib_only,
const std::filesystem::path& parent_dir) -> util::Result<void> {
if (!is_valid_pkg_name(name)) {
return std::unexpected(util::Error{
util::ErrorCode::LayoutInvalidName,
std::format("invalid package name '{}'", name),
"names must match [a-zA-Z_][a-zA-Z0-9_-]*",
std::nullopt, std::nullopt,
});
}
const auto root = parent_dir / name;
std::error_code ec;
if (std::filesystem::exists(root, ec)) {
return std::unexpected(util::Error{
util::ErrorCode::Internal,
std::format("destination '{}' already exists", root.string()),
"remove it or pick a different name",
root, std::nullopt,
});
}
std::filesystem::create_directories(root / "src", ec);
if (ec) {
return std::unexpected(util::Error{
util::ErrorCode::Internal,
std::format("cannot create '{}': {}", (root / "src").string(), ec.message()),
"", root / "src", std::nullopt,
});
}
manifest::Manifest m{
.package = manifest::Package{
.name = name,
.version = "0.1.0",
.edition = manifest::Edition::Cpp23,
.authors = {},
.license = std::nullopt,
},
.dependencies = {},
.build = {},
};
if (auto r = manifest::write(m, root / "Cargoxx.toml"); !r) {
return std::unexpected(r.error());
}
if (lib_only) {
auto content = substitute(LIB_CPPM_TEMPLATE, "@@MODULE@@", sanitize_id(name));
content = substitute(content, "@@NAME@@", name);
if (auto r = write_text(root / "src" / "lib.cppm", content); !r) {
return std::unexpected(r.error());
}
} else {
auto content = substitute(MAIN_CPP_TEMPLATE, "@@NAME@@", name);
if (auto r = write_text(root / "src" / "main.cpp", content); !r) {
return std::unexpected(r.error());
}
}
if (auto r = write_text(root / ".gitignore", GITIGNORE_TEMPLATE); !r) {
return std::unexpected(r.error());
}
return {};
}
} // namespace cargoxx::cli

48
src/cli/run.cpp Normal file
View File

@@ -0,0 +1,48 @@
module;
#include <CLI11.hpp>
module cargoxx.cli;
import std;
import cargoxx.util;
namespace cargoxx::cli {
auto run(int argc, char** argv) -> int {
CLI::App app{"cargoxx — a Cargo-style project tool for modern C++"};
app.require_subcommand(1);
auto* new_cmd = app.add_subcommand("new", "Create a new cargoxx project");
std::string new_name;
bool new_lib = false;
new_cmd->add_option("name", new_name, "Project name")->required();
new_cmd->add_flag("--lib", new_lib, "Create a library project");
try {
app.parse(argc, argv);
} catch (const CLI::ParseError& e) {
return app.exit(e);
}
if (*new_cmd) {
std::error_code ec;
auto cwd = std::filesystem::current_path(ec);
if (ec) {
std::cerr << std::format("error: cannot determine current working directory: {}\n",
ec.message());
return 1;
}
auto r = cmd_new(new_name, new_lib, cwd);
if (!r) {
std::cerr << util::format(r.error());
return 1;
}
std::cout << std::format(" Created `{}` project\n", new_name);
return 0;
}
return 0;
}
} // namespace cargoxx::cli