diff --git a/Makefile b/Makefile index 537f79a..bd9f73d 100644 --- a/Makefile +++ b/Makefile @@ -41,10 +41,10 @@ clean: # Test targets test: build - ./build/debug/test/scrap_test + ctest --test-dir build/debug --output-on-failure test-verbose: build - ./build/debug/test/scrap_test -v high + ctest --test-dir build/debug --output-on-failure --verbose # Code quality targets format: diff --git a/README.md b/README.md index 3504bef..c06926b 100644 --- a/README.md +++ b/README.md @@ -126,12 +126,13 @@ scrap update scrap is in early alpha development (v0.0.1). Currently implemented: ✅ **Core Features** -- Project creation (`scrap new`) -- Template system with variable substitution -- Basic command structure (build, run, clean) -- Configuration file parsing (`scrap.toml`) +- CLI command framework (help, version, command discovery) 🚧 **In Progress** +- Project creation (`scrap new`) - currently a placeholder command +- Template system with variable substitution +- Basic command structure (build, run, clean) - currently placeholder commands +- Configuration file parsing (`scrap.toml`) - Git-based template repository integration - Build system implementation - Toolchain management @@ -226,13 +227,13 @@ cmake --build build/debug --parallel # Run tests cmake --build build/debug --target test -# Or run tests directly -./build/debug/test/scrap_test +# Or run tests directly with ctest +ctest --test-dir build/debug --output-on-failure ``` ### Testing -The project uses Catch2 v3.7.1 for unit testing. Tests are automatically built when `BUILD_TESTS=ON`. +The project uses GoogleTest for unit testing, run through ctest. Tests are automatically built when `BUILD_TESTS=ON`. ```bash # Build and run all tests @@ -241,8 +242,9 @@ cmake --build build/debug --target test # Run tests with verbose output ctest --test-dir build/debug --output-on-failure --verbose -# Run specific test executable -./build/debug/test/scrap_test +# Run a specific test executable directly +./build/debug/test/scrap_gtest +./build/debug/test/scrap_gtest_cli11 # Release build testing cmake -S . -B build/release -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON @@ -250,14 +252,13 @@ cmake --build build/release --target test ``` **Test Structure:** -- `test/unit/` - Unit tests for individual components -- `test/helpers/` - Test utilities (TestPresenter, FileSystemHelper) -- `test/fixtures/` - Test data and mock templates +- `test/unit/command/` - Unit tests for the command layer ## 📊 Roadmap ### Phase 1: Foundation (Current) -- ✅ Command structure and template system +- ✅ Command structure (CLI framework) +- 🚧 Template system - 🚧 Configuration management - 🚧 Basic build system diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8bcda2f..b7c47c7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,91 +6,31 @@ configure_file( @ONLY ) -add_executable(${PROJECT_NAME} ${SCRAP_SOURCES}) +add_executable(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp) target_sources(${PROJECT_NAME} PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp # Generated version source ${CMAKE_CURRENT_BINARY_DIR}/shared/constants/version.cpp - # New command architecture + # Command architecture ${CMAKE_CURRENT_SOURCE_DIR}/command/Application.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/command/BuiltinCommandResolver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/command/CommandCatalog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/command/CommandHandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/command/CommandResolver.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/command/ParserAdapter.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/command/HelpRenderer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/command/VersionRenderer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/command/DefaultHelpRenderer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/command/DefaultVersionRenderer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/command/BuiltinCommandResolver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/command/ExternalCommandResolver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/command/ExternalMetadataProvider.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/command/HelpRenderer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/command/ParserAdapter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/command/ProjectCommandResolver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/command/ScriptsReader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/command/StubScriptsReader.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/command/VersionRenderer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/command/driver/CLI11ParserAdapter.cpp - # Legacy command components (to be removed in main.cpp migration) - ${CMAKE_CURRENT_SOURCE_DIR}/shared/command/Application.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/shared/command/Operation.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/shared/command/CompositeOperation.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/shared/command/CommandDispatcher.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/shared/command/ApplicationCommandHandler.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/shared/command/HelpCommand.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/shared/command/CommandOptions.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/shared/command/ParsedOptions.cpp - # CLI driver implementations - ${CMAKE_CURRENT_SOURCE_DIR}/shared/command/driver/CLI11Parser.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/shared/command/driver/CLI11CommandDispatcher.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/shared/command/driver/PresenterFormatter.cpp - # Presentation components - ${CMAKE_CURRENT_SOURCE_DIR}/shared/presentation/driver/ConsolePresenter.cpp - # Repository components - ${CMAKE_CURRENT_SOURCE_DIR}/repository/model/Repository.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/repository/RepositoryFactory.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/repository/driver/GitDriver.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/repository/driver/LibGitRepository.cpp - # Toolchain components - ${CMAKE_CURRENT_SOURCE_DIR}/toolchain/ToolchainModule.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/toolchain/model/Toolchain.cpp - # ${CMAKE_CURRENT_SOURCE_DIR}/toolchain/service/ToolchainService.cpp # Old implementation - ${CMAKE_CURRENT_SOURCE_DIR}/toolchain/service/MockToolchainService.cpp - # ${CMAKE_CURRENT_SOURCE_DIR}/toolchain/driver/ToolchainRepository.cpp # Old implementation - ${CMAKE_CURRENT_SOURCE_DIR}/toolchain/command/ToolchainOperation.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/toolchain/command/ListOperation.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/toolchain/command/InstallOperation.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/toolchain/command/SelectOperation.cpp - # Project components - ${CMAKE_CURRENT_SOURCE_DIR}/project/ProjectModule.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/project/model/Project.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/project/model/ProjectError.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/project/service/MockProjectService.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/project/command/NewOperation.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/project/command/BuildOperation.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/project/command/RunOperation.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/project/command/CleanOperation.cpp - # Configuration components - ${CMAKE_CURRENT_SOURCE_DIR}/configuration/ConfigurationModule.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/configuration/model/ConfigurationSource.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/configuration/model/Configuration.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/configuration/model/ProjectConfiguration.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/configuration/model/ConfigurationError.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/configuration/model/ToolchainReference.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/configuration/service/ConfigurationService.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/configuration/service/DefaultConfigurationService.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/configuration/service/ConfigurationServiceError.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/configuration/driver/TomlDriver.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/configuration/driver/TomlPlusPlusDriver.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/configuration/driver/TomlDriverError.cpp - # Template components - ${CMAKE_CURRENT_SOURCE_DIR}/template/TemplateModule.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/template/model/Template.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/template/model/TemplateError.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/template/service/TemplateService.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/template/service/TemplateServiceError.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/template/service/TemplateProcessor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/template/command/TemplateOperation.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/template/command/ListOperation.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/template/command/UpdateOperation.cpp + # Composition root helpers + ${CMAKE_CURRENT_SOURCE_DIR}/command/NullMetadataProvider.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/command/RuntimeEnvironmentFactory.cpp ) target_compile_options(${PROJECT_NAME} PUBLIC @@ -112,14 +52,8 @@ set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "scrap" ) -find_package(Dross REQUIRED) -find_package(Git2 REQUIRED) find_package(CLI11 REQUIRED) -find_package(TomlPlusPlus REQUIRED) target_link_libraries(${PROJECT_NAME} PRIVATE - dross - libgit2package CLI11::CLI11 - tomlplusplus::tomlplusplus ) diff --git a/src/command/Application.cpp b/src/command/Application.cpp index dfe6f16..6878b28 100644 --- a/src/command/Application.cpp +++ b/src/command/Application.cpp @@ -70,6 +70,11 @@ auto Application::run(std::span argv, const RuntimeEnvironmen return 1; } auto handler = entry->createHandler(invocation.options); + if (handler == nullptr) { + std::cerr << "Command '" << invocation.commandPath << "' is not available yet.\n"; + std::cerr << "Run 'scrap --help' for usage information.\n"; + return 1; + } const InvocationContext ctx{invocation.options, &env, &catalog}; return handler->execute(ctx); } diff --git a/src/command/ExternalCommandResolver.cpp b/src/command/ExternalCommandResolver.cpp index 6683020..3d457f0 100644 --- a/src/command/ExternalCommandResolver.cpp +++ b/src/command/ExternalCommandResolver.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -26,14 +27,18 @@ constexpr std::string_view ExternalPrefix = "scrap-"; */ auto isScrapExecutable(const std::filesystem::directory_entry& entry) -> bool { - if (! entry.is_regular_file()) { + std::error_code ec; + if (! entry.is_regular_file(ec) || ec) { return false; } auto filename = entry.path().filename().string(); if (! filename.starts_with(ExternalPrefix)) { return false; } - auto status = std::filesystem::status(entry.path()); + auto status = std::filesystem::status(entry.path(), ec); + if (ec) { + return false; + } return (status.permissions() & std::filesystem::perms::owner_exec) != std::filesystem::perms::none; } @@ -95,12 +100,13 @@ auto ExternalCommandResolver::resolve(const RuntimeEnvironment& env) -> std::vec std::vector entries; for (const auto& searchPath : env.searchPaths) { - if (! std::filesystem::is_directory(searchPath)) { + std::error_code ec; + if (! std::filesystem::is_directory(searchPath, ec) || ec) { continue; } - for (const auto& dirEntry : std::filesystem::directory_iterator(searchPath)) { - if (isScrapExecutable(dirEntry)) { - entries.push_back(buildEntry(dirEntry.path(), metadataProvider_.get())); + for (std::filesystem::directory_iterator it(searchPath, ec), end; ! ec && it != end; it.increment(ec)) { + if (isScrapExecutable(*it)) { + entries.push_back(buildEntry(it->path(), metadataProvider_.get())); } } } diff --git a/src/command/NullMetadataProvider.cpp b/src/command/NullMetadataProvider.cpp new file mode 100644 index 0000000..503d7ef --- /dev/null +++ b/src/command/NullMetadataProvider.cpp @@ -0,0 +1,20 @@ +#include "command/NullMetadataProvider.h" + +#include "command/ExternalMetadataProvider.h" + +#include +#include + +namespace scrap::Command { + +/** + * Always return an error indicating the metadata protocol is not yet implemented. + */ +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) — virtual override +auto NullMetadataProvider::fetch([[maybe_unused]] const std::filesystem::path& executable) + -> std::expected +{ + return std::unexpected(std::string{"external metadata protocol not yet implemented"}); +} + +} // namespace scrap::Command diff --git a/src/command/NullMetadataProvider.h b/src/command/NullMetadataProvider.h new file mode 100644 index 0000000..826beed --- /dev/null +++ b/src/command/NullMetadataProvider.h @@ -0,0 +1,31 @@ +#pragma once + +#include "command/ExternalMetadataProvider.h" + +#include +#include +#include + +namespace scrap::Command { + +/** + * @brief Metadata provider stub used until the external metadata protocol lands. + * + * External commands are still discovered by ExternalCommandResolver via + * filesystem scanning; this provider simply reports that fetching rich + * metadata (name/description/options) is not yet supported. The real + * --scrap-metadata protocol is implemented in a later phase. + */ +class NullMetadataProvider final : public ExternalMetadataProvider { +public: + /** + * @brief Always report metadata fetching as unimplemented. + * + * @param executable Path to the scrap-* executable (unused). + * @return An error describing that the protocol is not yet implemented. + */ + [[nodiscard]] auto + fetch(const std::filesystem::path& executable) -> std::expected override; +}; + +} // namespace scrap::Command diff --git a/src/command/RuntimeEnvironmentFactory.cpp b/src/command/RuntimeEnvironmentFactory.cpp new file mode 100644 index 0000000..20eb6d8 --- /dev/null +++ b/src/command/RuntimeEnvironmentFactory.cpp @@ -0,0 +1,42 @@ +#include "command/RuntimeEnvironmentFactory.h" + +#include "command/RuntimeEnvironment.h" + +#include +#include +#include + +namespace scrap::Command { + +/** + * Build a RuntimeEnvironment from the given cwd, SCRAP_HOME, and PATH values. + */ +auto makeRuntimeEnvironment(const std::filesystem::path& cwd, + const std::string& scrapHome, + const std::string& pathEnv) -> RuntimeEnvironment +{ + RuntimeEnvironment env; + env.projectRoot = cwd; + + if (! scrapHome.empty()) { + env.searchPaths.emplace_back(std::filesystem::path(scrapHome) / "bin"); + } + + std::size_t start = 0; + while (start <= pathEnv.size()) { + auto separator = pathEnv.find(':', start); + auto segment = + (separator == std::string::npos) ? pathEnv.substr(start) : pathEnv.substr(start, separator - start); + if (! segment.empty()) { + env.searchPaths.emplace_back(segment); + } + if (separator == std::string::npos) { + break; + } + start = separator + 1; + } + + return env; +} + +} // namespace scrap::Command diff --git a/src/command/RuntimeEnvironmentFactory.h b/src/command/RuntimeEnvironmentFactory.h new file mode 100644 index 0000000..cacc502 --- /dev/null +++ b/src/command/RuntimeEnvironmentFactory.h @@ -0,0 +1,29 @@ +#pragma once + +#include "command/RuntimeEnvironment.h" + +#include +#include + +namespace scrap::Command { + +/** + * @brief Build a RuntimeEnvironment from raw process inputs. + * + * Pure function: projectRoot and searchPaths are derived solely from the + * arguments, without reading environment variables or touching the + * filesystem. Callers (e.g. main()) are responsible for reading SCRAP_HOME + * and PATH and for resolving the current working directory. + * + * @param cwd Current working directory, used as projectRoot. + * @param scrapHome Value of SCRAP_HOME, or empty if unset. When non-empty, + * "/bin" is prepended to searchPaths. + * @param pathEnv Value of PATH, colon-separated. Empty segments (from + * leading, trailing, or doubled colons) are skipped. + * @return Constructed RuntimeEnvironment. + */ +[[nodiscard]] auto makeRuntimeEnvironment(const std::filesystem::path& cwd, + const std::string& scrapHome, + const std::string& pathEnv) -> RuntimeEnvironment; + +} // namespace scrap::Command diff --git a/src/configuration/ConfigurationModule.cpp b/src/configuration/ConfigurationModule.cpp deleted file mode 100644 index 6a5ff3e..0000000 --- a/src/configuration/ConfigurationModule.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "ConfigurationModule.h" -#include "driver/TomlPlusPlusDriver.h" -#include "service/DefaultConfigurationService.h" - -namespace scrap::Configuration { - -std::shared_ptr ConfigurationModule::createConfigurationService() -{ - // Create default TOML driver - auto tomlDriver = std::make_shared(); - - // Create configuration service with the driver - return std::make_shared(std::move(tomlDriver)); -} - -std::shared_ptr -ConfigurationModule::createConfigurationService(std::shared_ptr tomlDriver) -{ - return std::make_shared(std::move(tomlDriver)); -} - -} // namespace scrap::Configuration diff --git a/src/configuration/ConfigurationModule.h b/src/configuration/ConfigurationModule.h deleted file mode 100644 index b97d988..0000000 --- a/src/configuration/ConfigurationModule.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include "service/ConfigurationService.h" -#include - -namespace scrap::Configuration { - -namespace Driver { -class TomlDriver; -} - -/** - * @brief Configuration module for dependency injection and setup - * - * This module provides factory methods and dependency injection - * for configuration-related services following DI principles. - */ -class ConfigurationModule { -public: - /** - * @brief Create default configuration service - * @return Configured ConfigurationService instance - */ - static std::shared_ptr createConfigurationService(); - - /** - * @brief Create configuration service with custom TOML driver - * @param tomlDriver Custom TOML driver implementation - * @return Configured ConfigurationService instance - */ - static std::shared_ptr - createConfigurationService(std::shared_ptr tomlDriver); -}; - -} // namespace scrap::Configuration diff --git a/src/configuration/driver/TomlDriver.cpp b/src/configuration/driver/TomlDriver.cpp deleted file mode 100644 index a2d5a5f..0000000 --- a/src/configuration/driver/TomlDriver.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "TomlDriver.h" - -namespace scrap::Configuration::Driver { - -// Constructor -TomlDriver::TomlDriver() = default; - -// Destructor -TomlDriver::~TomlDriver() = default; - -} // namespace scrap::Configuration::Driver \ No newline at end of file diff --git a/src/configuration/driver/TomlDriver.h b/src/configuration/driver/TomlDriver.h deleted file mode 100644 index 4778079..0000000 --- a/src/configuration/driver/TomlDriver.h +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once - -#include "configuration/model/ProjectConfiguration.h" -#include -#include -#include -#include -#include -#include - -namespace scrap::Configuration::Driver { - -/** - * @brief Abstract interface for TOML file operations - * - * This driver abstracts TOML parsing and serialization operations - * following Clean Architecture principles. - */ -class TomlDriver { -public: - // Constructor and destructor - TomlDriver(); - virtual ~TomlDriver(); - - // Deleted copy/move operations (interface should not be copied/moved) - TomlDriver(const TomlDriver&) = delete; - TomlDriver& operator=(const TomlDriver&) = delete; - TomlDriver(TomlDriver&&) = delete; - TomlDriver& operator=(TomlDriver&&) = delete; - - /** - * @brief Load project configuration from TOML file - * @param filePath Path to scrap.toml file - * @return Parsed configuration, nullopt if file doesn't exist, or error if parsing fails - */ - [[nodiscard]] virtual std::expected, dross::error> - loadProjectConfiguration(const std::filesystem::path& filePath) noexcept = 0; - - /** - * @brief Save project configuration to TOML file - * @param filePath Path to scrap.toml file - * @param config Configuration to save - * @return void on success, error on failure - */ - [[nodiscard]] virtual std::expected - saveProjectConfiguration(const std::filesystem::path& filePath, - const Configuration::Model::ProjectConfiguration& config) noexcept = 0; - - /** - * @brief Load simple key-value pairs from TOML file - * @param filePath Path to TOML file - * @return Map of key-value pairs, nullopt if file doesn't exist, or error if parsing fails - */ - [[nodiscard]] virtual std::expected>, dross::error> - loadKeyValues(const std::filesystem::path& filePath) noexcept = 0; - - /** - * @brief Save key-value pairs to TOML file - * @param filePath Path to TOML file - * @param keyValues Map of key-value pairs to save - * @return void on success, error on failure - */ - [[nodiscard]] virtual std::expected - saveKeyValues(const std::filesystem::path& filePath, - const std::map& keyValues) noexcept = 0; - - /** - * @brief Check if TOML file exists and is readable - * @param filePath Path to TOML file - * @return True if file exists and is readable - */ - [[nodiscard]] virtual bool exists(const std::filesystem::path& filePath) = 0; - - /** - * @brief Validate TOML syntax without full parsing - * @param filePath Path to TOML file - * @return Empty string if valid, error message if invalid - */ - virtual std::string validateSyntax(const std::filesystem::path& filePath) = 0; -}; - -} // namespace scrap::Configuration::Driver diff --git a/src/configuration/driver/TomlDriverError.cpp b/src/configuration/driver/TomlDriverError.cpp deleted file mode 100644 index a03e98d..0000000 --- a/src/configuration/driver/TomlDriverError.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "TomlDriverError.h" -#include - -std::error_code make_error_code(scrap::Configuration::Driver::TomlDriverError e) noexcept -{ - struct TomlDriverErrorCategory : std::error_category { - [[nodiscard]] const char* name() const noexcept override - { - return "TomlDriver"; - } - - [[nodiscard]] std::string message(int ev) const override - { - switch (static_cast(ev)) { - case scrap::Configuration::Driver::TomlDriverError::ParseError: - return "TOML parse error"; - case scrap::Configuration::Driver::TomlDriverError::FileOpenError: - return "Cannot open file for writing"; - case scrap::Configuration::Driver::TomlDriverError::FileWriteError: - return "Error writing to file"; - default: - return "Unknown TomlDriver error"; - } - } - }; - - static const TomlDriverErrorCategory ErrorCategory{}; - return {static_cast(e), ErrorCategory}; -} diff --git a/src/configuration/driver/TomlDriverError.h b/src/configuration/driver/TomlDriverError.h deleted file mode 100644 index f14977b..0000000 --- a/src/configuration/driver/TomlDriverError.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -#include - -namespace scrap::Configuration::Driver { - -/// Error codes for TOML driver operations -enum class TomlDriverError : std::uint8_t { - ParseError = 1, ///< TOML parsing failed - FileOpenError, ///< Cannot open file for writing - FileWriteError ///< Error writing to file -}; - -} // namespace scrap::Configuration::Driver - -// ADL-discoverable make_error_code (must be in global scope) -[[nodiscard]] std::error_code make_error_code(scrap::Configuration::Driver::TomlDriverError e) noexcept; - -// Enable automatic conversion to std::error_code -namespace std { -template <> struct is_error_code_enum : true_type { }; -} // namespace std diff --git a/src/configuration/driver/TomlPlusPlusDriver.cpp b/src/configuration/driver/TomlPlusPlusDriver.cpp deleted file mode 100644 index face837..0000000 --- a/src/configuration/driver/TomlPlusPlusDriver.cpp +++ /dev/null @@ -1,460 +0,0 @@ -#include "TomlPlusPlusDriver.h" -#include "TomlDriverError.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace scrap::Configuration::Driver { - -namespace { - -// Helper functions for parsing TOML sections -void parsePackageSection(const toml::table& config, Configuration::Model::ProjectConfiguration& result) -{ - const auto* package = config["package"].as_table(); - if (package == nullptr) { - return; - } - - if (const auto* name = package->get("name")) { - result.name = std::string(name->value_or("")); - } - if (const auto* version = package->get("version")) { - result.version = std::string(version->value_or("0.1.0")); - } - if (const auto* type = package->get("type")) { - const auto typeStr = std::string(type->value_or("app")); - auto typeResult = Configuration::Model::parseProjectType(typeStr); - if (typeResult) { - result.type = *typeResult; - } - // If parsing fails, keep the default value - } - if (const auto* std = package->get("std")) { - result.cppStandard = std::string(std->value_or("23")); - } - if (const auto* toolchain = package->get("toolchain")) { - const auto toolchainStr = std::string(toolchain->value_or("")); - if (! toolchainStr.empty()) { - const auto parseResult = Configuration::Model::ToolchainReference::parse(toolchainStr); - if (parseResult.has_value()) { - result.toolchain = parseResult.value(); - } - } - } -} - -void parseCxxFlagsArray(const toml::node* cxxFlags, Configuration::Model::ProjectConfiguration& result) -{ - const auto* flagsArray = cxxFlags->as_array(); - if (flagsArray == nullptr) { - return; - } - - for (const auto& flag : *flagsArray) { - const auto flagStr = flag.value(); - if (flagStr.has_value()) { - result.cxxFlags.push_back(*flagStr); - } - } -} - -void parseLinkFlagsArray(const toml::node* linkFlags, Configuration::Model::ProjectConfiguration& result) -{ - const auto* flagsArray = linkFlags->as_array(); - if (flagsArray == nullptr) { - return; - } - - for (const auto& flag : *flagsArray) { - const auto flagStr = flag.value(); - if (flagStr.has_value()) { - result.linkFlags.push_back(*flagStr); - } - } -} - -void parseDefinesArray(const toml::node* defines, Configuration::Model::ProjectConfiguration& result) -{ - const auto* definesArray = defines->as_array(); - if (definesArray == nullptr) { - return; - } - - std::string defineList; - for (const auto& define : *definesArray) { - const auto defineStr = define.value(); - if (defineStr.has_value()) { - if (! defineList.empty()) { - defineList += ","; - } - defineList += *defineStr; - } - } - if (! defineList.empty()) { - result.buildOptions["defines"] = defineList; - } -} - -void parseBuildSection(const toml::table& config, Configuration::Model::ProjectConfiguration& result) -{ - const auto* build = config["build"].as_table(); - if (build == nullptr) { - return; - } - - if (const auto* system = build->get("system")) { - const auto systemStr = std::string(system->value_or("native")); - auto systemResult = Configuration::Model::parseBuildSystem(systemStr); - if (systemResult) { - result.buildSystem = *systemResult; - } - // If parsing fails, keep the default value - } - - if (const auto* cxxFlags = build->get("cxx_flags")) { - parseCxxFlagsArray(cxxFlags, result); - } - - if (const auto* linkFlags = build->get("link_flags")) { - parseLinkFlagsArray(linkFlags, result); - } - - if (const auto* defines = build->get("defines")) { - parseDefinesArray(defines, result); - } -} - -void parseDependenciesSection(const toml::table& config, Configuration::Model::ProjectConfiguration& result) -{ - const auto* dependencies = config["dependencies"].as_table(); - if (dependencies == nullptr) { - return; - } - - for (const auto& [key, value] : *dependencies) { - const auto versionStr = value.value(); - if (versionStr.has_value()) { - result.dependencies[std::string(key.str())] = *versionStr; - } - } -} - -void parseTestSection(const toml::table& config, Configuration::Model::ProjectConfiguration& result) -{ - const auto* test = config["test"].as_table(); - if (test == nullptr) { - return; - } - - if (const auto* framework = test->get("framework")) { - result.testFramework = std::string(framework->value_or("")); - } -} - -// Helper functions for serializing TOML sections -toml::table createPackageSection(const Configuration::Model::ProjectConfiguration& config) -{ - toml::table package; - package.insert("name", config.name); - package.insert("version", config.version); - package.insert("type", Configuration::Model::toString(config.type)); - package.insert("std", config.cppStandard); - - if (config.toolchain.has_value()) { - package.insert("toolchain", config.toolchain->toString()); - } - - return package; -} - -void addCxxFlagsToTable(const std::vector& cxxFlags, toml::table& build) -{ - if (! cxxFlags.empty()) { - toml::array cxxFlagsArray; - for (const auto& flag : cxxFlags) { - cxxFlagsArray.push_back(flag); - } - build.insert("cxx_flags", std::move(cxxFlagsArray)); - } -} - -void addLinkFlagsToTable(const std::vector& linkFlags, toml::table& build) -{ - if (! linkFlags.empty()) { - toml::array linkFlagsArray; - for (const auto& flag : linkFlags) { - linkFlagsArray.push_back(flag); - } - build.insert("link_flags", std::move(linkFlagsArray)); - } -} - -void addDefinesToTable(const std::map& buildOptions, toml::table& build) -{ - const auto definesIt = buildOptions.find("defines"); - if (definesIt != buildOptions.end() && ! definesIt->second.empty()) { - toml::array definesArray; - std::stringstream ss(definesIt->second); - std::string define; - while (std::getline(ss, define, ',')) { - if (! define.empty()) { - definesArray.push_back(define); - } - } - if (! definesArray.empty()) { - build.insert("defines", std::move(definesArray)); - } - } -} - -toml::table createBuildSection(const Configuration::Model::ProjectConfiguration& config) -{ - toml::table build; - build.insert("system", Configuration::Model::toString(config.buildSystem)); - - addCxxFlagsToTable(config.cxxFlags, build); - addLinkFlagsToTable(config.linkFlags, build); - addDefinesToTable(config.buildOptions, build); - - return build; -} - -toml::table createDependenciesSection(const Configuration::Model::ProjectConfiguration& config) -{ - toml::table dependencies; - for (const auto& [name, version] : config.dependencies) { - dependencies.insert(name, version); - } - return dependencies; -} - -toml::table createTestSection(const Configuration::Model::ProjectConfiguration& config) -{ - toml::table test; - if (! config.testFramework.empty()) { - test.insert("framework", config.testFramework); - } - return test; -} - -void flattenToml(const toml::node& node, const std::string& prefix, std::map& result) -{ - // Use an iterative approach instead of recursion to avoid misc-no-recursion warning - std::stack, std::string>> nodeStack; - nodeStack.emplace(std::cref(node), prefix); - - while (! nodeStack.empty()) { - const auto [currentNode, currentPrefix] = nodeStack.top(); - nodeStack.pop(); - - if (const auto* table = currentNode.get().as_table()) { - for (const auto& [key, value] : *table) { - const std::string newKey = - currentPrefix.empty() ? std::string(key.str()) : currentPrefix + "." + std::string(key.str()); - nodeStack.emplace(std::cref(value), newKey); - } - } else if (const auto stringValue = currentNode.get().value()) { - result[currentPrefix] = *stringValue; - } else if (const auto intValue = currentNode.get().value()) { - result[currentPrefix] = std::to_string(*intValue); - } else if (const auto doubleValue = currentNode.get().value()) { - result[currentPrefix] = std::to_string(*doubleValue); - } else if (const auto boolValue = currentNode.get().value()) { - result[currentPrefix] = (*boolValue) ? "true" : "false"; - } else { - // Fallback for unknown types - result[currentPrefix] = "[unknown type]"; - } - } -} - -} // anonymous namespace - -class TomlPlusPlusDriver::Impl { -public: - static std::expected, dross::error> - loadProjectConfiguration(const std::filesystem::path& filePath) noexcept - { - if (! std::filesystem::exists(filePath)) { - return std::optional{}; - } - - try { - const auto config = toml::parse_file(filePath.string()); - return parseProjectConfiguration(config); - } catch (const toml::parse_error&) { - auto errorCode = make_error_code(TomlDriverError::ParseError); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - } - - static std::expected - saveProjectConfiguration(const std::filesystem::path& filePath, - const Configuration::Model::ProjectConfiguration& config) noexcept - { - const auto tomlTable = serializeProjectConfiguration(config); - - std::ofstream file(filePath); - if (! file.is_open()) { - auto errorCode = make_error_code(TomlDriverError::FileOpenError); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - - file << tomlTable; - - if (! file.good()) { - auto errorCode = make_error_code(TomlDriverError::FileWriteError); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - - return {}; - } - - static std::expected>, dross::error> - loadKeyValues(const std::filesystem::path& filePath) noexcept - { - if (! std::filesystem::exists(filePath)) { - return std::optional>{}; - } - - try { - const auto config = toml::parse_file(filePath.string()); - std::map result; - - // Flatten TOML structure to key-value pairs - flattenToml(config, "", result); - - return result; - } catch (const toml::parse_error&) { - auto errorCode = make_error_code(TomlDriverError::ParseError); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - } - - static std::expected saveKeyValues(const std::filesystem::path& filePath, - const std::map& keyValues) noexcept - { - toml::table tomlTable; - - for (const auto& [key, value] : keyValues) { - tomlTable.insert(key, value); - } - - std::ofstream file(filePath); - if (! file.is_open()) { - auto errorCode = make_error_code(TomlDriverError::FileOpenError); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - - file << tomlTable; - - if (! file.good()) { - auto errorCode = make_error_code(TomlDriverError::FileWriteError); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - - return {}; - } - - static bool exists(const std::filesystem::path& filePath) - { - return std::filesystem::exists(filePath) && std::filesystem::is_regular_file(filePath); - } - - static std::string validateSyntax(const std::filesystem::path& filePath) - { - if (! std::filesystem::exists(filePath)) { - return "File does not exist"; - } - - try { - [[maybe_unused]] auto result = toml::parse_file(filePath.string()); - return ""; // Valid - } catch (const toml::parse_error& e) { - return {e.what()}; - } - } - -private: - static Configuration::Model::ProjectConfiguration parseProjectConfiguration(const toml::table& config) - { - Configuration::Model::ProjectConfiguration result; - - parsePackageSection(config, result); - parseBuildSection(config, result); - parseDependenciesSection(config, result); - parseTestSection(config, result); - - return result; - } - - static toml::table serializeProjectConfiguration(const Configuration::Model::ProjectConfiguration& config) - { - toml::table result; - - result.insert("package", createPackageSection(config)); - result.insert("build", createBuildSection(config)); - - if (! config.dependencies.empty()) { - result.insert("dependencies", createDependenciesSection(config)); - } - - if (! config.testFramework.empty()) { - result.insert("test", createTestSection(config)); - } - - return result; - } -}; - -// TomlPlusPlusDriver implementation - -TomlPlusPlusDriver::TomlPlusPlusDriver() = default; - -TomlPlusPlusDriver::~TomlPlusPlusDriver() = default; - -std::expected, dross::error> -TomlPlusPlusDriver::loadProjectConfiguration(const std::filesystem::path& filePath) noexcept -{ - return Impl::loadProjectConfiguration(filePath); -} - -std::expected -TomlPlusPlusDriver::saveProjectConfiguration(const std::filesystem::path& filePath, - const Configuration::Model::ProjectConfiguration& config) noexcept -{ - return Impl::saveProjectConfiguration(filePath, config); -} - -std::expected>, dross::error> -TomlPlusPlusDriver::loadKeyValues(const std::filesystem::path& filePath) noexcept -{ - return Impl::loadKeyValues(filePath); -} - -std::expected -TomlPlusPlusDriver::saveKeyValues(const std::filesystem::path& filePath, - const std::map& keyValues) noexcept -{ - return Impl::saveKeyValues(filePath, keyValues); -} - -bool TomlPlusPlusDriver::exists(const std::filesystem::path& filePath) -{ - return Impl::exists(filePath); -} - -std::string TomlPlusPlusDriver::validateSyntax(const std::filesystem::path& filePath) -{ - return Impl::validateSyntax(filePath); -} - -} // namespace scrap::Configuration::Driver diff --git a/src/configuration/driver/TomlPlusPlusDriver.h b/src/configuration/driver/TomlPlusPlusDriver.h deleted file mode 100644 index 5ec5d2a..0000000 --- a/src/configuration/driver/TomlPlusPlusDriver.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include "TomlDriver.h" - -namespace scrap::Configuration::Driver { - -/** - * @brief TOML driver implementation using toml++ library - * - * Concrete implementation of TomlDriver using the toml++ library - * for parsing and serializing TOML files. - */ -class TomlPlusPlusDriver : public TomlDriver { -public: - TomlPlusPlusDriver(); - ~TomlPlusPlusDriver() override; - - // Non-copyable, non-movable (base class disallows it) - TomlPlusPlusDriver(const TomlPlusPlusDriver&) = delete; - TomlPlusPlusDriver& operator=(const TomlPlusPlusDriver&) = delete; - TomlPlusPlusDriver(TomlPlusPlusDriver&&) = delete; - TomlPlusPlusDriver& operator=(TomlPlusPlusDriver&&) = delete; - - [[nodiscard]] std::expected, dross::error> - loadProjectConfiguration(const std::filesystem::path& filePath) noexcept override; - - [[nodiscard]] std::expected - saveProjectConfiguration(const std::filesystem::path& filePath, - const Configuration::Model::ProjectConfiguration& config) noexcept override; - - [[nodiscard]] std::expected>, dross::error> - loadKeyValues(const std::filesystem::path& filePath) noexcept override; - - [[nodiscard]] std::expected - saveKeyValues(const std::filesystem::path& filePath, - const std::map& keyValues) noexcept override; - - [[nodiscard]] bool exists(const std::filesystem::path& filePath) override; - - std::string validateSyntax(const std::filesystem::path& filePath) override; - -private: - class Impl; -}; - -} // namespace scrap::Configuration::Driver diff --git a/src/configuration/model/Configuration.cpp b/src/configuration/model/Configuration.cpp deleted file mode 100644 index 3d7623f..0000000 --- a/src/configuration/model/Configuration.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include "Configuration.h" -#include - -namespace scrap::Configuration::Model { - -// Configuration class methods - -const ConfigurationValue& Configuration::toolchain() const noexcept -{ - return toolchain_; -} - -const std::optional& Configuration::projectConfig() const noexcept -{ - return projectConfig_; -} - -void Configuration::setToolchain(ToolchainReference toolchain, ConfigurationSource source) -{ - if (! toolchain_.hasValue() || hasHigherPrecedence(source, toolchain_.source())) { - toolchain_ = ConfigurationValue(std::move(toolchain), source); - } -} - -void Configuration::setProjectConfig(ProjectConfiguration config) -{ - projectConfig_ = std::move(config); - - // If project config specifies a toolchain, apply it - if (projectConfig_->toolchain) { - setToolchain(*projectConfig_->toolchain, ConfigurationSource::ProjectConfig); - } -} - -bool Configuration::isComplete() const noexcept -{ - return toolchain_.hasValue(); -} - -void Configuration::applyDefaults() -{ - if (! toolchain_.hasValue()) { - toolchain_ = ConfigurationValue(ToolchainReference::createSystemDefault(), - ConfigurationSource::SystemDefault); - } -} - -std::string Configuration::summary() const -{ - std::ostringstream oss; - - oss << "Configuration Summary:\n"; - oss << " Toolchain: " << toolchain_.value().toString(); - oss << " (from " << toString(toolchain_.source()) << ")\n"; - - if (projectConfig_) { - oss << " Project: " << projectConfig_->name << " v" << projectConfig_->version << "\n"; - oss << " Type: " << toString(projectConfig_->type) << "\n"; - oss << " Build System: " << toString(projectConfig_->buildSystem) << "\n"; - } else { - oss << " Project: No scrap.toml found\n"; - } - - return oss.str(); -} - -} // namespace scrap::Configuration::Model diff --git a/src/configuration/model/Configuration.h b/src/configuration/model/Configuration.h deleted file mode 100644 index e2ec5d6..0000000 --- a/src/configuration/model/Configuration.h +++ /dev/null @@ -1,113 +0,0 @@ -#pragma once - -#include "ConfigurationSource.h" -#include "ProjectConfiguration.h" -#include "ToolchainReference.h" -#include -#include - -namespace scrap::Configuration::Model { - -/** - * @brief Configuration value with its source - * - * Template class that holds a configuration value along with - * information about where it came from. - */ -template class ConfigurationValue { -public: - ConfigurationValue() = default; - - ConfigurationValue(T value, ConfigurationSource source); - - [[nodiscard]] const T& value() const noexcept; - [[nodiscard]] ConfigurationSource source() const noexcept; - - [[nodiscard]] bool hasValue() const noexcept; - - // Allow implicit conversion to T for convenience - explicit operator const T&() const noexcept; - -private: - T value_{}; - ConfigurationSource source_ = ConfigurationSource::SystemDefault; -}; - -// Template implementation (must be in header for templates) -template -ConfigurationValue::ConfigurationValue(T value, ConfigurationSource source) - : value_(std::move(value)), source_(source) -{ -} - -template const T& ConfigurationValue::value() const noexcept -{ - return value_; -} - -template ConfigurationSource ConfigurationValue::source() const noexcept -{ - return source_; -} - -template bool ConfigurationValue::hasValue() const noexcept -{ - return source_ != ConfigurationSource::SystemDefault || ! value_.toString().empty(); -} - -template ConfigurationValue::operator const T&() const noexcept -{ - return value_; -} - -/** - * @brief Resolved configuration combining all sources - * - * This class represents the final configuration after applying - * precedence rules across all configuration sources. - */ -class Configuration { -public: - Configuration() = default; - - /** - * @brief Get resolved toolchain reference - */ - [[nodiscard]] const ConfigurationValue& toolchain() const noexcept; - - /** - * @brief Get project configuration (from scrap.toml) - */ - [[nodiscard]] const std::optional& projectConfig() const noexcept; - - /** - * @brief Set toolchain from specific source - */ - void setToolchain(ToolchainReference toolchain, ConfigurationSource source); - - /** - * @brief Set project configuration - */ - void setProjectConfig(ProjectConfiguration config); - - /** - * @brief Check if configuration is complete - */ - [[nodiscard]] bool isComplete() const noexcept; - - /** - * @brief Apply system default if no toolchain specified - */ - void applyDefaults(); - - /** - * @brief Get configuration summary for debugging - */ - [[nodiscard]] std::string summary() const; - -private: - ConfigurationValue toolchain_; - std::optional projectConfig_; -}; - -} // namespace scrap::Configuration::Model diff --git a/src/configuration/model/ConfigurationError.cpp b/src/configuration/model/ConfigurationError.cpp deleted file mode 100644 index bbf97ab..0000000 --- a/src/configuration/model/ConfigurationError.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "ConfigurationError.h" -#include - -// Error code creation function (global scope for ADL) - -std::error_code make_error_code(scrap::Configuration::Model::ProjectConfigurationError e) noexcept -{ - struct ProjectConfigurationErrorCategory : std::error_category { - [[nodiscard]] const char* name() const noexcept override - { - return "ProjectConfiguration"; - } - - [[nodiscard]] std::string message(int ev) const override - { - switch (static_cast(ev)) { - case scrap::Configuration::Model::ProjectConfigurationError::InvalidProjectType: - return "Invalid project type"; - case scrap::Configuration::Model::ProjectConfigurationError::InvalidBuildSystem: - return "Invalid build system"; - case scrap::Configuration::Model::ProjectConfigurationError::EmptyProjectName: - return "Project name cannot be empty"; - case scrap::Configuration::Model::ProjectConfigurationError::EmptyProjectVersion: - return "Project version cannot be empty"; - case scrap::Configuration::Model::ProjectConfigurationError::UnsupportedCppStandard: - return "Unsupported C++ standard (must be 17, 20, or 23)"; - default: - return "Unknown ProjectConfiguration error"; - } - } - }; - - static const ProjectConfigurationErrorCategory ErrorCategory{}; - return {static_cast(e), ErrorCategory}; -} diff --git a/src/configuration/model/ConfigurationError.h b/src/configuration/model/ConfigurationError.h deleted file mode 100644 index 2135dc9..0000000 --- a/src/configuration/model/ConfigurationError.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace scrap::Configuration::Model { - -/** - * @brief Error codes for ProjectConfiguration validation - */ -enum class ProjectConfigurationError : std::uint8_t { - InvalidProjectType, ///< Invalid project type string - InvalidBuildSystem, ///< Invalid build system string - EmptyProjectName, ///< Project name cannot be empty - EmptyProjectVersion, ///< Project version cannot be empty - UnsupportedCppStandard ///< C++ standard not supported (must be 17, 20, or 23) -}; - -} // namespace scrap::Configuration::Model - -// Error code creation function declaration (must be in global scope for ADL) -// NOLINTNEXTLINE(readability-identifier-naming) - C++ standard requires this exact name for ADL -std::error_code make_error_code(scrap::Configuration::Model::ProjectConfigurationError e) noexcept; - -// C++ standard requires specializing std::is_error_code_enum for custom error enums -namespace std { - -template <> struct is_error_code_enum : true_type { }; - -} // namespace std diff --git a/src/configuration/model/ConfigurationSource.cpp b/src/configuration/model/ConfigurationSource.cpp deleted file mode 100644 index 83c8b33..0000000 --- a/src/configuration/model/ConfigurationSource.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "ConfigurationSource.h" - -#include - -namespace scrap::Configuration::Model { - -std::string toString(ConfigurationSource source) noexcept -{ - switch (source) { - case ConfigurationSource::CommandLine: - return "command-line"; - case ConfigurationSource::ProjectConfig: - return "project configuration"; - case ConfigurationSource::RepositoryMarker: - return "repository marker"; - case ConfigurationSource::Environment: - return "environment variable"; - case ConfigurationSource::SystemDefault: - return "system default"; - } - return "unknown"; -} - -} // namespace scrap::Configuration::Model diff --git a/src/configuration/model/ConfigurationSource.h b/src/configuration/model/ConfigurationSource.h deleted file mode 100644 index d20ad4c..0000000 --- a/src/configuration/model/ConfigurationSource.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include -#include - -namespace scrap::Configuration::Model { - -/** - * @brief Source of configuration settings - * - * Represents the different sources where configuration can come from, - * ordered by precedence (highest to lowest priority). - */ -enum class ConfigurationSource : std::uint8_t { - CommandLine, // --toolchain=... CLI flag - ProjectConfig, // scrap.toml file - RepositoryMarker, // .scrap-toolchain file - Environment, // SCRAP_TOOLCHAIN env var - SystemDefault // System toolchain (not managed by scrap) -}; - -/** - * @brief Convert source to human-readable string - * @param source Configuration source to convert - * @return String representation of the source - */ -std::string toString(ConfigurationSource source) noexcept; - -/** - * @brief Compare sources by precedence - * @param left First configuration source - * @param right Second configuration source - * @return true if left has higher precedence than right - */ -constexpr bool hasHigherPrecedence(ConfigurationSource left, ConfigurationSource right) noexcept -{ - return static_cast(left) < static_cast(right); -} - -} // namespace scrap::Configuration::Model diff --git a/src/configuration/model/ProjectConfiguration.cpp b/src/configuration/model/ProjectConfiguration.cpp deleted file mode 100644 index e29b854..0000000 --- a/src/configuration/model/ProjectConfiguration.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include "ProjectConfiguration.h" -#include - -namespace scrap::Configuration::Model { - -std::string toString(ProjectType type) -{ - switch (type) { - case ProjectType::Application: - return "app"; - case ProjectType::Library: - return "lib"; - } - return "unknown"; -} - -std::expected parseProjectType(const std::string& str) noexcept -{ - if (str == "app" || str == "application") { - return ProjectType::Application; - } - if (str == "lib" || str == "library") { - return ProjectType::Library; - } - - auto errorCode = make_error_code(ProjectConfigurationError::InvalidProjectType); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); -} - -std::string toString(BuildSystem system) -{ - switch (system) { - case BuildSystem::Native: - return "native"; - case BuildSystem::CMake: - return "cmake"; - case BuildSystem::Meson: - return "meson"; - case BuildSystem::Bazel: - return "bazel"; - } - return "unknown"; -} - -std::expected parseBuildSystem(const std::string& str) noexcept -{ - if (str == "native" || str == "scrap") { - return BuildSystem::Native; - } - if (str == "cmake") { - return BuildSystem::CMake; - } - if (str == "meson") { - return BuildSystem::Meson; - } - if (str == "bazel") { - return BuildSystem::Bazel; - } - - auto errorCode = make_error_code(ProjectConfigurationError::InvalidBuildSystem); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); -} - -ProjectConfiguration::ProjectConfiguration() = default; - -ProjectConfiguration ProjectConfiguration::createDefault(const std::string& projectName, ProjectType projectType) -{ - ProjectConfiguration config; - config.name = projectName; - config.type = projectType; - return config; -} - -std::expected ProjectConfiguration::validate() const noexcept -{ - if (name.empty()) { - auto errorCode = make_error_code(ProjectConfigurationError::EmptyProjectName); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - if (version.empty()) { - auto errorCode = make_error_code(ProjectConfigurationError::EmptyProjectVersion); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - if (cppStandard != "17" && cppStandard != "20" && cppStandard != "23") { - auto errorCode = make_error_code(ProjectConfigurationError::UnsupportedCppStandard); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - - return {}; -} - -bool ProjectConfiguration::isApplication() const noexcept -{ - return type == ProjectType::Application; -} - -bool ProjectConfiguration::isLibrary() const noexcept -{ - return type == ProjectType::Library; -} - -bool ProjectConfiguration::isNativeBuild() const noexcept -{ - return buildSystem == BuildSystem::Native; -} - -bool ProjectConfiguration::isWrapperMode() const noexcept -{ - return buildSystem != BuildSystem::Native; -} - -} // namespace scrap::Configuration::Model diff --git a/src/configuration/model/ProjectConfiguration.h b/src/configuration/model/ProjectConfiguration.h deleted file mode 100644 index 858f270..0000000 --- a/src/configuration/model/ProjectConfiguration.h +++ /dev/null @@ -1,106 +0,0 @@ -#pragma once - -#include "ConfigurationError.h" -#include "ToolchainReference.h" -#include -#include -#include -#include -#include -#include -#include - -namespace scrap::Configuration::Model { - -/** - * @brief Project type enumeration - */ -enum class ProjectType : std::uint8_t { - Application, - Library -}; - -[[nodiscard]] std::string toString(ProjectType type); -[[nodiscard]] std::expected parseProjectType(const std::string& str) noexcept; - -/** - * @brief Build system enumeration - */ -enum class BuildSystem : std::uint8_t { - Native, // scrap's native build system - CMake, // Wrapper mode for CMake - Meson, // Wrapper mode for Meson - Bazel // Wrapper mode for Bazel -}; - -[[nodiscard]] std::string toString(BuildSystem system); -[[nodiscard]] std::expected parseBuildSystem(const std::string& str) noexcept; - -/** - * @brief Configuration loaded from scrap.toml - * - * Represents the parsed content of a project's scrap.toml file - * following the schema defined in CLAUDE.md. - */ -class ProjectConfiguration { -public: - ProjectConfiguration(); - - // Project metadata - std::string name; - std::string version{"0.1.0"}; - ProjectType type{ProjectType::Application}; - std::string cppStandard{"23"}; - - // Build configuration - BuildSystem buildSystem{BuildSystem::Native}; - std::optional toolchain; - - // Build options - std::vector cxxFlags; - std::vector linkFlags; - std::map buildOptions; - - // Dependencies - std::map dependencies; - std::map devDependencies; - - // Test configuration - std::string testFramework{"scrap"}; // Default to built-in framework - std::vector testPatterns{"*_test.cpp", "test_*.cpp"}; - - // Tool configurations - std::map toolOptions; - - /** - * @brief Create default configuration - */ - [[nodiscard]] static ProjectConfiguration createDefault(const std::string& projectName, ProjectType projectType); - - /** - * @brief Validate configuration - */ - [[nodiscard]] std::expected validate() const noexcept; - - /** - * @brief Check if this is an application project - */ - [[nodiscard]] bool isApplication() const noexcept; - - /** - * @brief Check if this is a library project - */ - [[nodiscard]] bool isLibrary() const noexcept; - - /** - * @brief Check if using native build system - */ - [[nodiscard]] bool isNativeBuild() const noexcept; - - /** - * @brief Check if using wrapper mode - */ - [[nodiscard]] bool isWrapperMode() const noexcept; -}; - -} // namespace scrap::Configuration::Model diff --git a/src/configuration/model/ToolchainReference.cpp b/src/configuration/model/ToolchainReference.cpp deleted file mode 100644 index a445bf4..0000000 --- a/src/configuration/model/ToolchainReference.cpp +++ /dev/null @@ -1,184 +0,0 @@ -#include "ToolchainReference.h" -#include -#include -#include -#include -#include -#include -#include - -// Error code creation function (global scope for ADL) -std::error_code make_error_code(scrap::Configuration::Model::ToolchainReferenceError e) noexcept -{ - struct ToolchainReferenceErrorCategory : std::error_category { - [[nodiscard]] const char* name() const noexcept override - { - return "ToolchainReference"; - } - - [[nodiscard]] std::string message(int ev) const override - { - switch (static_cast(ev)) { - case scrap::Configuration::Model::ToolchainReferenceError::EmptyName: - return "Toolchain name cannot be empty"; - case scrap::Configuration::Model::ToolchainReferenceError::EmptySpecification: - return "Toolchain specification cannot be empty"; - case scrap::Configuration::Model::ToolchainReferenceError::EmptyVersion: - return "Version cannot be empty after '@'"; - case scrap::Configuration::Model::ToolchainReferenceError::SystemDefaultHasNoName: - return "System default toolchain has no name"; - default: - return "Unknown ToolchainReference error"; - } - } - }; - - static const ToolchainReferenceErrorCategory ErrorCategory{}; - return {static_cast(e), ErrorCategory}; -} - -namespace scrap::Configuration::Model { - -// PIMPL Implementation -class ToolchainReference::Internal { -public: - bool isSystemDefault = false; - std::string name; - std::optional version; - - // Default constructor for system default - Internal() - : isSystemDefault(true) - { - } - - // Constructor for managed toolchain - Internal(std::string toolchainName, std::optional toolchainVersion) - : name(std::move(toolchainName)), version(std::move(toolchainVersion)) - { - } -}; - -ToolchainReference::ToolchainReference() - : impl_(std::make_unique()) -{ -} - -ToolchainReference::ToolchainReference(const ToolchainReference& other) - : impl_(std::make_unique(*other.impl_)) -{ -} - -ToolchainReference& ToolchainReference::operator=(const ToolchainReference& other) -{ - if (this != &other) { - *impl_ = *other.impl_; - } - return *this; -} - -ToolchainReference::ToolchainReference(ToolchainReference&& other) noexcept = default; - -ToolchainReference& ToolchainReference::operator=(ToolchainReference&& other) noexcept = default; - -ToolchainReference::~ToolchainReference() = default; - -ToolchainReference ToolchainReference::createSystemDefault() -{ - ToolchainReference result; - result.impl_ = std::make_unique(); - return result; -} - -std::expected -ToolchainReference::createManaged(const std::string& name, const std::optional& version) noexcept -{ - if (name.empty()) { - auto errorCode = make_error_code(ToolchainReferenceError::EmptyName); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - ToolchainReference result; - result.impl_ = std::make_unique(name, version); - return result; -} - -std::expected ToolchainReference::parse(const std::string& spec) noexcept -{ - if (spec.empty()) { - auto errorCode = make_error_code(ToolchainReferenceError::EmptySpecification); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - - if (spec == "system" || spec == "system-default") { - return createSystemDefault(); - } - - auto atPos = spec.find('@'); - if (atPos == std::string::npos) { - // No version specified - auto result = createManaged(spec); - if (! result) { - return std::unexpected(result.error()); - } - return result.value(); - } - - const std::string name = spec.substr(0, atPos); - std::string version = spec.substr(atPos + 1); - - if (version.empty()) { - auto errorCode = make_error_code(ToolchainReferenceError::EmptyVersion); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - - auto result = createManaged(name, version); - if (! result) { - return std::unexpected(result.error()); - } - return result.value(); -} - -bool ToolchainReference::operator==(const ToolchainReference& other) const -{ - return impl_->isSystemDefault == other.impl_->isSystemDefault && impl_->name == other.impl_->name && - impl_->version == other.impl_->version; -} - -bool ToolchainReference::operator!=(const ToolchainReference& other) const -{ - return ! (*this == other); -} - -bool ToolchainReference::isSystemDefault() const noexcept -{ - return impl_->isSystemDefault; -} - -std::expected ToolchainReference::name() const noexcept -{ - if (impl_->isSystemDefault) { - auto errorCode = make_error_code(ToolchainReferenceError::SystemDefaultHasNoName); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - return impl_->name; -} - -const std::optional& ToolchainReference::version() const noexcept -{ - return impl_->version; -} - -std::string ToolchainReference::toString() const -{ - if (impl_->isSystemDefault) { - return "system"; - } - - std::string result = impl_->name; - if (impl_->version) { - result += "@" + *impl_->version; - } - return result; -} - -} // namespace scrap::Configuration::Model diff --git a/src/configuration/model/ToolchainReference.h b/src/configuration/model/ToolchainReference.h deleted file mode 100644 index e7a28fb..0000000 --- a/src/configuration/model/ToolchainReference.h +++ /dev/null @@ -1,125 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace scrap::Configuration::Model { - -/** - * @brief Error codes for ToolchainReference operations - */ -enum class ToolchainReferenceError : std::uint8_t { - EmptyName, ///< Toolchain name cannot be empty - EmptySpecification, ///< Toolchain specification cannot be empty - EmptyVersion, ///< Version cannot be empty after '@' - SystemDefaultHasNoName ///< System default toolchain has no name -}; - -} // namespace scrap::Configuration::Model - -// Error code creation function declaration (must be in global scope for ADL) -// NOLINTNEXTLINE(readability-identifier-naming) - C++ standard requires this exact name for ADL -std::error_code make_error_code(scrap::Configuration::Model::ToolchainReferenceError e) noexcept; - -// C++ standard requires specializing std::is_error_code_enum for custom error enums -namespace std { -template <> struct is_error_code_enum : true_type { }; -} // namespace std - -namespace scrap::Configuration::Model { - -/** - * @brief Value Object representing a toolchain reference - * - * Can represent either: - * - A managed toolchain (name + optional version) - * - System default toolchain (not managed by scrap) - */ -class ToolchainReference { -public: - /** - * @brief Default constructor creates system default toolchain reference - */ - ToolchainReference(); - - /** - * @brief Copy constructor - */ - ToolchainReference(const ToolchainReference& other); - - /** - * @brief Copy assignment operator - */ - ToolchainReference& operator=(const ToolchainReference& other); - - /** - * @brief Move constructor - */ - ToolchainReference(ToolchainReference&& other) noexcept; - - /** - * @brief Move assignment operator - */ - ToolchainReference& operator=(ToolchainReference&& other) noexcept; - - /** - * @brief Destructor - */ - ~ToolchainReference(); - - /** - * @brief Create system default toolchain reference - */ - [[nodiscard]] static ToolchainReference createSystemDefault(); - - /** - * @brief Create managed toolchain reference - * @param name Toolchain name (e.g., "llvm", "gcc") - * @param version Optional version (e.g., "18.0.0") - */ - [[nodiscard]] static std::expected - createManaged(const std::string& name, const std::optional& version = std::nullopt) noexcept; - - /** - * @brief Parse toolchain reference from string - * @param spec Specification string (e.g., "llvm@18.0.0", "gcc", "system") - */ - [[nodiscard]] static std::expected parse(const std::string& spec) noexcept; - - // Value Object - equality comparison - [[nodiscard]] bool operator==(const ToolchainReference& other) const; - [[nodiscard]] bool operator!=(const ToolchainReference& other) const; - - /** - * @brief Check if this represents system default toolchain - */ - [[nodiscard]] bool isSystemDefault() const noexcept; - - /** - * @brief Get toolchain name - * @return Toolchain name or error if system default - */ - [[nodiscard]] std::expected name() const noexcept; - - /** - * @brief Get toolchain version (nullopt if no version specified or system default) - */ - [[nodiscard]] const std::optional& version() const noexcept; - - /** - * @brief Convert to string representation - */ - [[nodiscard]] std::string toString() const; - -private: - // PIMPL forward declaration - class Internal; - std::unique_ptr impl_; -}; - -} // namespace scrap::Configuration::Model diff --git a/src/configuration/service/ConfigurationService.cpp b/src/configuration/service/ConfigurationService.cpp deleted file mode 100644 index 4deea2d..0000000 --- a/src/configuration/service/ConfigurationService.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "ConfigurationService.h" - -namespace scrap::Configuration::Service { - -ConfigurationService::ConfigurationService() = default; - -ConfigurationService::~ConfigurationService() = default; - -} // namespace scrap::Configuration::Service \ No newline at end of file diff --git a/src/configuration/service/ConfigurationService.h b/src/configuration/service/ConfigurationService.h deleted file mode 100644 index d385c45..0000000 --- a/src/configuration/service/ConfigurationService.h +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once - -#include "configuration/model/Configuration.h" -#include "configuration/model/ProjectConfiguration.h" -#include "configuration/model/ToolchainReference.h" -#include -#include -#include -#include - -namespace scrap::Configuration::Service { - -/** - * @brief Service interface for configuration management - * - * This service coordinates configuration loading from multiple sources - * and applies precedence rules following Clean Architecture principles. - */ -class ConfigurationService { -public: - ConfigurationService(); - virtual ~ConfigurationService(); - - // Non-copyable, non-movable (abstract base class) - ConfigurationService(const ConfigurationService&) = delete; - ConfigurationService& operator=(const ConfigurationService&) = delete; - ConfigurationService(ConfigurationService&&) = delete; - ConfigurationService& operator=(ConfigurationService&&) = delete; - - /** - * @brief Load complete configuration for current context - * @param workingDirectory Directory to search for configuration files - * @param cliToolchain Optional toolchain override from command line - * @return Resolved configuration combining all sources - */ - virtual Configuration::Model::Configuration - loadConfiguration(const std::filesystem::path& workingDirectory, - const std::optional& cliToolchain = std::nullopt) = 0; - - /** - * @brief Load project configuration from scrap.toml - * @param projectPath Path to project directory - * @return Project configuration if scrap.toml exists - */ - virtual std::optional - loadProjectConfiguration(const std::filesystem::path& projectPath) = 0; - - /** - * @brief Save project configuration to scrap.toml - * @param projectPath Path to project directory - * @param config Configuration to save - */ - virtual void saveProjectConfiguration(const std::filesystem::path& projectPath, - const Configuration::Model::ProjectConfiguration& config) = 0; - - /** - * @brief Create default scrap.toml for new project - * @param projectPath Path to project directory - * @param projectName Name of the project - * @param projectType Type of project (app/lib) - * @param toolchain Optional toolchain to specify - */ - virtual void createDefaultConfiguration( - const std::filesystem::path& projectPath, - const std::string& projectName, - Configuration::Model::ProjectType projectType, - const std::optional& toolchain = std::nullopt) = 0; - - /** - * @brief Set toolchain for project - * @param projectPath Path to project directory - * @param toolchain Toolchain to set - * @return void on success, error on failure - */ - [[nodiscard]] virtual std::expected - setProjectToolchain(const std::filesystem::path& projectPath, - const Configuration::Model::ToolchainReference& toolchain) noexcept = 0; - - /** - * @brief Set repository-wide toolchain marker - * @param repositoryRoot Root of the repository - * @param toolchain Toolchain to set - * @return void on success, error on failure - */ - [[nodiscard]] virtual std::expected - setRepositoryToolchain(const std::filesystem::path& repositoryRoot, - const Configuration::Model::ToolchainReference& toolchain) noexcept = 0; - - /** - * @brief Validate configuration - * @param config Configuration to validate - * @return Vector of validation error messages (empty if valid) - */ - virtual std::vector validateConfiguration(const Configuration::Model::Configuration& config) = 0; -}; - -} // namespace scrap::Configuration::Service diff --git a/src/configuration/service/ConfigurationServiceError.cpp b/src/configuration/service/ConfigurationServiceError.cpp deleted file mode 100644 index 9633a89..0000000 --- a/src/configuration/service/ConfigurationServiceError.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "ConfigurationServiceError.h" -#include - -std::error_code make_error_code(scrap::Configuration::Service::ConfigurationServiceError e) noexcept -{ - struct ConfigurationServiceErrorCategory : std::error_category { - [[nodiscard]] const char* name() const noexcept override - { - return "ConfigurationService"; - } - - [[nodiscard]] std::string message(int ev) const override - { - switch (static_cast(ev)) { - case scrap::Configuration::Service::ConfigurationServiceError::ProjectConfigNotFound: - return "No scrap.toml found in project directory"; - case scrap::Configuration::Service::ConfigurationServiceError::RepositoryMarkerCreateFailed: - return "Cannot create repository toolchain marker"; - case scrap::Configuration::Service::ConfigurationServiceError::RepositoryMarkerWriteFailed: - return "Error writing repository toolchain marker"; - default: - return "Unknown ConfigurationService error"; - } - } - }; - - static const ConfigurationServiceErrorCategory ErrorCategory{}; - return {static_cast(e), ErrorCategory}; -} diff --git a/src/configuration/service/ConfigurationServiceError.h b/src/configuration/service/ConfigurationServiceError.h deleted file mode 100644 index 41dfd37..0000000 --- a/src/configuration/service/ConfigurationServiceError.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -#include - -namespace scrap::Configuration::Service { - -/// Error codes for configuration service operations -enum class ConfigurationServiceError : std::uint8_t { - ProjectConfigNotFound = 1, ///< No scrap.toml found in project directory - RepositoryMarkerCreateFailed, ///< Cannot create repository toolchain marker file - RepositoryMarkerWriteFailed ///< Error writing repository toolchain marker file -}; - -} // namespace scrap::Configuration::Service - -// ADL-discoverable make_error_code (must be in global scope) -[[nodiscard]] std::error_code make_error_code(scrap::Configuration::Service::ConfigurationServiceError e) noexcept; - -// Enable automatic conversion to std::error_code -namespace std { -template <> struct is_error_code_enum : true_type { }; -} // namespace std diff --git a/src/configuration/service/DefaultConfigurationService.cpp b/src/configuration/service/DefaultConfigurationService.cpp deleted file mode 100644 index f33f983..0000000 --- a/src/configuration/service/DefaultConfigurationService.cpp +++ /dev/null @@ -1,265 +0,0 @@ -#include "DefaultConfigurationService.h" -#include "ConfigurationServiceError.h" -#include -#include -#include -#include -#include - -namespace scrap::Configuration::Service { - -DefaultConfigurationService::DefaultConfigurationService(std::shared_ptr tomlDriver) - : tomlDriver_(std::move(tomlDriver)) -{ -} - -DefaultConfigurationService::~DefaultConfigurationService() = default; - -Configuration::Model::Configuration DefaultConfigurationService::loadConfiguration( - const std::filesystem::path& workingDirectory, - const std::optional& cliToolchain) -{ - Configuration::Model::Configuration config; - - // 1. Command-line toolchain (highest priority) - if (cliToolchain.has_value()) { - config.setToolchain(*cliToolchain, Configuration::Model::ConfigurationSource::CommandLine); - } - - // 2. Project configuration (scrap.toml) - const auto projectRoot = findProjectRoot(workingDirectory); - if (projectRoot.has_value()) { - const auto projectConfig = loadProjectConfiguration(*projectRoot); - if (projectConfig.has_value()) { - config.setProjectConfig(*projectConfig); - } - } - - // 3. Repository marker (.scrap-toolchain) - const auto repoRoot = findRepositoryRoot(workingDirectory); - if (repoRoot.has_value()) { - const auto repoToolchain = loadRepositoryToolchain(*repoRoot); - if (repoToolchain.has_value()) { - config.setToolchain(*repoToolchain, Configuration::Model::ConfigurationSource::RepositoryMarker); - } - } - - // 4. Environment variable (SCRAP_TOOLCHAIN) - const auto envToolchain = loadEnvironmentToolchain(); - if (envToolchain.has_value()) { - config.setToolchain(*envToolchain, Configuration::Model::ConfigurationSource::Environment); - } - - // 5. Apply system default if no toolchain specified - config.applyDefaults(); - - return config; -} - -std::optional -DefaultConfigurationService::loadProjectConfiguration(const std::filesystem::path& projectPath) -{ - const auto configPath = projectPath / "scrap.toml"; - auto result = tomlDriver_->loadProjectConfiguration(configPath); - - // TODO(Phase 5): Propagate error to caller via std::expected - // Currently silently converts all errors (parse errors, file access errors) to nullopt - // This loses error information but maintains API compatibility with current interface - // Callers cannot distinguish between "file not found" and "malformed TOML" - if (! result.has_value()) { - return std::nullopt; - } - - return *result; -} - -void DefaultConfigurationService::saveProjectConfiguration(const std::filesystem::path& projectPath, - const Configuration::Model::ProjectConfiguration& config) -{ - const auto configPath = projectPath / "scrap.toml"; - - // Ensure directory exists - std::filesystem::create_directories(projectPath); - - auto result = tomlDriver_->saveProjectConfiguration(configPath, config); - // TODO(Phase 5): Change this method to return std::expected - // Currently silently ignores errors (file open failures, write failures) - // This maintains API compatibility but loses error information - [[maybe_unused]] auto _ = result; -} - -void DefaultConfigurationService::createDefaultConfiguration( - const std::filesystem::path& projectPath, - const std::string& projectName, - Configuration::Model::ProjectType projectType, - const std::optional& toolchain) -{ - auto config = Configuration::Model::ProjectConfiguration::createDefault(projectName, projectType); - - if (toolchain.has_value()) { - config.toolchain = *toolchain; - } - - saveProjectConfiguration(projectPath, config); -} - -std::expected -DefaultConfigurationService::setProjectToolchain(const std::filesystem::path& projectPath, - const Configuration::Model::ToolchainReference& toolchain) noexcept -{ - // Load existing configuration or create default - auto config = loadProjectConfiguration(projectPath); - if (! config.has_value()) { - auto errorCode = make_error_code(ConfigurationServiceError::ProjectConfigNotFound); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - - // Update toolchain - config->toolchain = toolchain; - - // Save updated configuration - saveProjectConfiguration(projectPath, *config); - - return {}; -} - -std::expected -DefaultConfigurationService::setRepositoryToolchain(const std::filesystem::path& repositoryRoot, - const Configuration::Model::ToolchainReference& toolchain) noexcept -{ - const auto markerPath = repositoryRoot / ".scrap-toolchain"; - - // Write toolchain specification to marker file - std::ofstream file(markerPath); - if (! file.is_open()) { - auto errorCode = make_error_code(ConfigurationServiceError::RepositoryMarkerCreateFailed); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - - file << toolchain.toString() << std::endl; - - if (! file.good()) { - auto errorCode = make_error_code(ConfigurationServiceError::RepositoryMarkerWriteFailed); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - - return {}; -} - -std::vector -DefaultConfigurationService::validateConfiguration(const Configuration::Model::Configuration& config) -{ - std::vector errors; - - // Validate toolchain - if (! config.toolchain().hasValue()) { - errors.emplace_back("No toolchain specified"); - } - - // Validate project configuration - const auto& projectConfig = config.projectConfig(); - if (projectConfig.has_value()) { - auto validationResult = projectConfig->validate(); - if (! validationResult) { - errors.emplace_back("Project configuration error: " + std::string(validationResult.error().message())); - } - } - - return errors; -} - -std::optional -DefaultConfigurationService::loadRepositoryToolchain(const std::filesystem::path& repositoryRoot) -{ - const auto markerPath = repositoryRoot / ".scrap-toolchain"; - - if (! std::filesystem::exists(markerPath)) { - return std::nullopt; - } - - std::ifstream file(markerPath); - if (! file.is_open()) { - return std::nullopt; - } - - std::string line; - if (std::getline(file, line)) { - // Trim whitespace - line.erase(line.begin(), std::ranges::find_if(line, [](unsigned char ch) { - return ! std::isspace(ch); - })); - line.erase(std::ranges::find_if(line | std::views::reverse, - [](unsigned char ch) { - return ! std::isspace(ch); - }) - .base(), - line.end()); - - if (! line.empty()) { - auto result = Configuration::Model::ToolchainReference::parse(line); - if (result.has_value()) { - return result.value(); - } - // Invalid format, ignore - return std::nullopt; - } - } - - return std::nullopt; -} - -std::optional DefaultConfigurationService::loadEnvironmentToolchain() -{ - const auto envValue = environmentVariable("SCRAP_TOOLCHAIN"); - if (! envValue.has_value() || envValue->empty()) { - return std::nullopt; - } - - const auto result = Configuration::Model::ToolchainReference::parse(*envValue); - if (result.has_value()) { - return result.value(); - } - // Invalid format, ignore - return std::nullopt; -} - -std::optional -DefaultConfigurationService::findRepositoryRoot(const std::filesystem::path& startPath) -{ - auto currentPath = std::filesystem::canonical(startPath); - - while (currentPath != currentPath.root_path()) { - if (std::filesystem::exists(currentPath / ".git")) { - return currentPath; - } - currentPath = currentPath.parent_path(); - } - - return std::nullopt; -} - -std::optional -DefaultConfigurationService::findProjectRoot(const std::filesystem::path& startPath) -{ - auto currentPath = std::filesystem::canonical(startPath); - - while (currentPath != currentPath.root_path()) { - if (std::filesystem::exists(currentPath / "scrap.toml")) { - return currentPath; - } - currentPath = currentPath.parent_path(); - } - - return std::nullopt; -} - -std::optional DefaultConfigurationService::environmentVariable(const std::string& name) -{ - const char* value = std::getenv(name.c_str()); - if (value != nullptr) { - return std::string(value); - } - return std::nullopt; -} - -} // namespace scrap::Configuration::Service diff --git a/src/configuration/service/DefaultConfigurationService.h b/src/configuration/service/DefaultConfigurationService.h deleted file mode 100644 index 2cc029b..0000000 --- a/src/configuration/service/DefaultConfigurationService.h +++ /dev/null @@ -1,90 +0,0 @@ -#pragma once - -#include "ConfigurationService.h" -#include "configuration/driver/TomlDriver.h" -#include - -namespace scrap::Configuration::Service { - -/** - * @brief Default implementation of ConfigurationService - * - * This service coordinates configuration loading from multiple sources: - * 1. Command-line arguments (highest priority) - * 2. Project configuration (scrap.toml) - * 3. Repository marker (.scrap-toolchain) - * 4. Environment variables (SCRAP_TOOLCHAIN) - * 5. System default (lowest priority) - */ -class DefaultConfigurationService : public ConfigurationService { -public: - /** - * @brief Constructor with TOML driver dependency injection - * @param tomlDriver Driver for TOML file operations - */ - explicit DefaultConfigurationService(std::shared_ptr tomlDriver); - ~DefaultConfigurationService() override; - - // Non-copyable, non-movable (follows base class) - DefaultConfigurationService(const DefaultConfigurationService&) = delete; - DefaultConfigurationService& operator=(const DefaultConfigurationService&) = delete; - DefaultConfigurationService(DefaultConfigurationService&&) = delete; - DefaultConfigurationService& operator=(DefaultConfigurationService&&) = delete; - - Configuration::Model::Configuration loadConfiguration( - const std::filesystem::path& workingDirectory, - const std::optional& cliToolchain = std::nullopt) override; - - std::optional - loadProjectConfiguration(const std::filesystem::path& projectPath) override; - - void saveProjectConfiguration(const std::filesystem::path& projectPath, - const Configuration::Model::ProjectConfiguration& config) override; - - void createDefaultConfiguration( - const std::filesystem::path& projectPath, - const std::string& projectName, - Configuration::Model::ProjectType projectType, - const std::optional& toolchain = std::nullopt) override; - - [[nodiscard]] std::expected - setProjectToolchain(const std::filesystem::path& projectPath, - const Configuration::Model::ToolchainReference& toolchain) noexcept override; - - [[nodiscard]] std::expected - setRepositoryToolchain(const std::filesystem::path& repositoryRoot, - const Configuration::Model::ToolchainReference& toolchain) noexcept override; - - std::vector validateConfiguration(const Configuration::Model::Configuration& config) override; - -private: - std::shared_ptr tomlDriver_; - - /** - * @brief Load toolchain from repository marker file - */ - static std::optional - loadRepositoryToolchain(const std::filesystem::path& repositoryRoot); - - /** - * @brief Load toolchain from environment variable - */ - static std::optional loadEnvironmentToolchain(); - - /** - * @brief Find repository root from given directory - */ - static std::optional findRepositoryRoot(const std::filesystem::path& startPath); - - /** - * @brief Find project root (directory containing scrap.toml) - */ - static std::optional findProjectRoot(const std::filesystem::path& startPath); - - /** - * @brief Get environment variable value - */ - static std::optional environmentVariable(const std::string& name); -}; - -} // namespace scrap::Configuration::Service diff --git a/src/main.cpp b/src/main.cpp index ea0d11d..0988d3b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,15 +1,62 @@ -#include "shared/command/Application.h" +#include "command/Application.h" +#include "command/BuiltinCommandResolver.h" +#include "command/DefaultHelpRenderer.h" +#include "command/DefaultVersionRenderer.h" +#include "command/ExternalCommandResolver.h" +#include "command/HelpRenderer.h" +#include "command/NullMetadataProvider.h" +#include "command/ProjectCommandResolver.h" +#include "command/RuntimeEnvironment.h" +#include "command/RuntimeEnvironmentFactory.h" +#include "command/StubScriptsReader.h" +#include "command/VersionRenderer.h" +#include "command/driver/CLI11ParserAdapter.h" + +#include +#include #include +#include #include +#include #include +#include +#include + +using namespace scrap::Command; -int main(const int argc, const char* const argv[]) +/** + * Composition root: wires the CLI parser, renderers, and command resolvers + * together and dispatches to Application::run(). + */ +int main(int argc, char* argv[]) { try { - scrap::Application app; - return app.run(std::span{argv, static_cast(argc)}); + const char* scrapHomeEnv = std::getenv("SCRAP_HOME"); + const char* pathEnv = std::getenv("PATH"); + + std::error_code ec; + auto cwd = std::filesystem::current_path(ec); + if (ec) { + cwd = "."; + } + + auto env = makeRuntimeEnvironment(cwd, + scrapHomeEnv != nullptr ? std::string{scrapHomeEnv} : std::string{}, + pathEnv != nullptr ? std::string{pathEnv} : std::string{}); + + auto helpRenderer = std::make_unique(); + auto versionRenderer = std::make_unique(); + HelpRenderer& helpRef = *helpRenderer; + VersionRenderer& versionRef = *versionRenderer; + + Application app(std::make_unique(), std::move(helpRenderer), std::move(versionRenderer)); + app.addResolver(std::make_unique(helpRef, versionRef)); + app.addResolver(std::make_unique(std::make_unique())); + app.addResolver(std::make_unique(std::make_unique())); + + return app.run(std::span{argv, static_cast(argc)}, env); } catch (const std::exception& e) { - std::cerr << "Fatal error: " << e.what() << std::endl; + std::cerr << "Fatal error: " << e.what() << "\n"; return 1; } } diff --git a/src/project/ProjectModule.cpp b/src/project/ProjectModule.cpp deleted file mode 100644 index 0035d61..0000000 --- a/src/project/ProjectModule.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "ProjectModule.h" -#include "project/command/BuildOperation.h" -#include "project/command/CleanOperation.h" -#include "project/command/NewOperation.h" -#include "project/command/RunOperation.h" -#include "project/service/ProjectService.h" -#include "shared/command/CommandOptions.h" -#include - -namespace scrap::project { - -void ProjectModule::registerCommands(CommandDispatcher& dispatcher, - std::shared_ptr /* parser */, - std::shared_ptr presenter) -{ - // Create Mock service for now - auto service = std::make_shared(nullptr, presenter); - - // Create individual operations (with default template service) - auto newOp = std::make_shared(service); - newOp->setPresenter(presenter); - - auto buildOp = std::make_shared(service); - buildOp->setPresenter(presenter); - - auto runOp = std::make_shared(service); - runOp->setPresenter(presenter); - - auto cleanOp = std::make_shared(service); - cleanOp->setPresenter(presenter); - - // Register operations - dispatcher.registerOperation("new", newOp); - dispatcher.registerOperation("build", buildOp); - dispatcher.registerOperation("run", runOp); - dispatcher.registerOperation("clean", cleanOp); - - // Note: Command options will be configured later in ApplicationCommandHandler - // after the command structure is set up -} - -std::vector> ProjectModule::availableCommands() -{ - return {{"new", "Create a new C++ project"}, - {"build", "Compile the current project"}, - {"run", "Run the current project executable"}, - {"clean", "Remove build artifacts and cached files"}}; -} - -} // namespace scrap::project diff --git a/src/project/ProjectModule.h b/src/project/ProjectModule.h deleted file mode 100644 index 7e79abe..0000000 --- a/src/project/ProjectModule.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include "shared/command/CLIParser.h" -#include "shared/command/CommandDispatcher.h" -#include "shared/presentation/Presenter.h" -#include -#include -#include - -namespace scrap::project { - -/** - * @brief Module for project management commands - * - * This module provides commands for creating, building, running, and managing - * C++ projects using the scrap build system. - */ -class ProjectModule { -public: - /** - * @brief Register project commands with the dispatcher - * @param dispatcher Command dispatcher to register with - * @param parser CLI parser for argument processing - * @param presenter Output presenter for user feedback - */ - static void registerCommands(CommandDispatcher& dispatcher, - std::shared_ptr parser, - std::shared_ptr presenter); - - /** - * @brief Get list of available commands - * @return Vector of command name and description pairs - */ - static std::vector> availableCommands(); -}; - -} // namespace scrap::project diff --git a/src/project/command/BuildOperation.cpp b/src/project/command/BuildOperation.cpp deleted file mode 100644 index d773907..0000000 --- a/src/project/command/BuildOperation.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include "BuildOperation.h" -#include "project/model/Project.h" -#include "project/service/ProjectService.h" -#include "shared/command/CommandOptions.h" -#include "shared/presentation/Presenter.h" -#include -#include -#include - -namespace scrap::project::command { - -// Namespace alias for cleaner code -namespace Model = scrap::Project::Model; - -BuildOperation::BuildOperation(std::shared_ptr service) - : service_(service) -{ -} - -void BuildOperation::execute(const std::vector& args) -{ - auto output = presenter(); - if (! output) { - return; - } - - // Load current project - auto project = service_->loadProject(); - if (! project) { - output->displayError("No project found in current directory"); - output->displayInfo("Run 'scrap new ' to create a new project"); - return; - } - - // Parse build options - auto optionsResult = Model::BuildOptions::parse(args); - if (! optionsResult) { - output->displayError("Invalid build options: " + std::string(optionsResult.error().message())); - return; - } - const auto& options = *optionsResult; - - // Display build start (cargo-style) - if (options.clean) { - output->displayInfo(" Cleaning previous build..."); - service_->clean(*project); - } - - // Display resolving dependencies - if (! project->dependencies().empty()) { - output->displayInfo(" Resolving dependencies..."); - for (const auto& dep : project->dependencies()) { - output->displaySuccess(" ✓ " + dep.name() + " " + dep.version() + " (cached)"); - } - } - - // Start build process - std::stringstream ss; - ss << " Compiling " << project->name().toString() << " v" << project->version().toString(); - if (project->path()) { - ss << " (" << project->path()->string() << ")"; - } - output->displayInfo(ss.str()); - - // Show progress for verbose mode - if (options.verbose) { - output->displayInfo(" C++ Standard: " + project->buildConfig().cppStandard()); - output->displayInfo(" Build Mode: " + Model::buildModeToString(options.mode)); - if (options.mode == Model::BuildMode::Release) { - output->displayInfo(" Optimization: O3"); - } - } - - // Simulate build progress - output->startProgress("Building", 100); - for (int i = 0; i <= 100; i += 20) { - output->updateProgress(i); - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - } - output->finishProgress(); - - // Execute build - auto result = service_->build(*project, options); - - if (result.isSuccess()) { - // Display success message - ss.str(""); - ss << " Finished " << Model::buildModeToString(options.mode); - if (options.mode == Model::BuildMode::Debug) { - ss << " [unoptimized + debuginfo]"; - } else if (options.mode == Model::BuildMode::Release) { - ss << " [optimized]"; - } - ss << " target(s) in " << std::fixed << std::setprecision(2) << result.duration.count() / 1000.0 << "s"; - output->displayInfo(ss.str()); - - // Display artifacts - for (const auto& artifact : result.artifacts) { - output->displaySuccess(" Created " + artifact.string()); - } - } else { - output->displayError("Build failed: " + result.message); - for (const auto& error : result.errors) { - output->displayError(" " + error); - } - } -} - -CommandOptions BuildOperation::describeOptions() const -{ - return CommandOptions() - .addFlag("release", "Build in release mode (optimized)") - .addFlag("debug", "Build in debug mode [default]") - .addFlag("verbose", "v", "Use verbose output") - .addFlag("clean", "Clean before building") - .addOption(CommandOption("target", "Build only the specified target", OptionType::String)) - .addOption(CommandOption("j", "Number of parallel jobs", OptionType::Integer)); -} - -void BuildOperation::displayHelp() const -{ - // This method is deprecated and will be removed - // Help is now generated automatically from describeOptions() -} - -} // namespace scrap::project::command diff --git a/src/project/command/BuildOperation.h b/src/project/command/BuildOperation.h deleted file mode 100644 index 5250775..0000000 --- a/src/project/command/BuildOperation.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include "shared/command/Operation.h" -#include - -namespace scrap::project { - -namespace service { -class ProjectService; -} - -namespace command { - -/** - * @brief Build operation for compiling projects - * - * This class handles the "scrap build" command following - * Clean Architecture principles with proper separation of concerns. - */ -class BuildOperation : public Operation { -public: - explicit BuildOperation(std::shared_ptr service); - ~BuildOperation() override = default; - - // Non-copyable - BuildOperation(const BuildOperation&) = delete; - BuildOperation& operator=(const BuildOperation&) = delete; - - // Movable - BuildOperation(BuildOperation&&) = default; - BuildOperation& operator=(BuildOperation&&) = default; - - void execute(const std::vector& args) override; - CommandOptions describeOptions() const override; - -private: - std::shared_ptr service_; - - void displayHelp() const; -}; - -} // namespace command -} // namespace scrap::project diff --git a/src/project/command/CleanOperation.cpp b/src/project/command/CleanOperation.cpp deleted file mode 100644 index af24672..0000000 --- a/src/project/command/CleanOperation.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include "CleanOperation.h" -#include "project/service/ProjectService.h" -#include "shared/command/CommandOptions.h" -#include "shared/presentation/Presenter.h" - -namespace scrap::project::command { - -CleanOperation::CleanOperation(std::shared_ptr service) - : service_(service) -{ -} - -void CleanOperation::execute(const std::vector& args) -{ - auto output = presenter(); - if (! output) { - return; - } - - // Note: Help is now handled by CLI11, no need to check for --help here - - // Load current project - auto project = service_->loadProject(); - if (! project) { - output->displayError("No project found in current directory"); - output->displayInfo("Run 'scrap new ' to create a new project"); - return; - } - - // Parse clean options - bool deep = false; - for (const auto& arg : args) { - if (arg == "--deep") { - deep = true; - } - } - - // Perform cleaning - service_->clean(*project); - - if (deep) { - // Simulate deep clean output - output->displayInfo(" Removed build/"); - output->displayInfo(" Removed .scrap/"); - output->displayInfo(" Removed compile_commands.json"); - output->displayInfo(" Removed .cache/"); - output->displayInfo(" Cleaned 312 files, 125.8 MB freed"); - output->displaySuccess(" Workspace restored to pristine state"); - } else { - // Simulate regular clean output - output->displayInfo(" Removed .scrap/cache/"); - output->displayInfo(" Cleaned 156 files, 45.2 MB freed"); - } -} - -CommandOptions CleanOperation::describeOptions() const -{ - return CommandOptions().addFlag("deep", "Remove all generated files including caches"); -} - -void CleanOperation::displayHelp() const -{ - // This method is deprecated and will be removed - // Help is now generated automatically from describeOptions() -} - -} // namespace scrap::project::command diff --git a/src/project/command/CleanOperation.h b/src/project/command/CleanOperation.h deleted file mode 100644 index 7a32939..0000000 --- a/src/project/command/CleanOperation.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include "shared/command/Operation.h" -#include - -namespace scrap::project { - -namespace service { -class ProjectService; -} - -namespace command { - -/** - * @brief Clean operation for removing build artifacts - * - * This class handles the "scrap clean" command following - * Clean Architecture principles with proper separation of concerns. - */ -class CleanOperation : public Operation { -public: - explicit CleanOperation(std::shared_ptr service); - ~CleanOperation() override = default; - - // Non-copyable - CleanOperation(const CleanOperation&) = delete; - CleanOperation& operator=(const CleanOperation&) = delete; - - // Movable - CleanOperation(CleanOperation&&) = default; - CleanOperation& operator=(CleanOperation&&) = default; - - void execute(const std::vector& args) override; - CommandOptions describeOptions() const override; - -private: - std::shared_ptr service_; - - void displayHelp() const; -}; - -} // namespace command -} // namespace scrap::project diff --git a/src/project/command/NewOperation.cpp b/src/project/command/NewOperation.cpp deleted file mode 100644 index 8085bcd..0000000 --- a/src/project/command/NewOperation.cpp +++ /dev/null @@ -1,183 +0,0 @@ -#include "NewOperation.h" -#include "project/model/Project.h" -#include "project/service/ProjectService.h" -#include "shared/command/CommandOptions.h" -#include "shared/command/ParsedOptions.h" -#include "shared/presentation/Presenter.h" -#include "template/TemplateModule.h" -#include "template/service/TemplateService.h" -#include -#include - -namespace scrap::project::command { - -// Namespace alias for cleaner code -namespace Model = scrap::Project::Model; - -NewOperation::NewOperation(std::shared_ptr service, - std::shared_ptr templateService) - : service_(service), templateService_(templateService) -{ - - // Create default template service if not provided - if (! templateService_) { - templateService_ = template_system::TemplateModule::createTemplateService(); - } -} - -void NewOperation::execute(const std::vector& args) -{ - auto output = presenter(); - if (! output) { - return; - } - - if (args.empty()) { - // No arguments provided - this should be handled by CLI11 which will - // show help when required arguments are missing - // Let the normal parsing flow handle this - } - - // Parse project specification - auto specResult = Model::ProjectSpecification::parse(args); - if (! specResult) { - output->displayError("Invalid specification: " + std::string(specResult.error().message())); - return; - } - const auto& spec = *specResult; - - // Create the project - auto project = service_->createNew(spec); - - // Display creation result (cargo-style) - std::stringstream ss; - ss << " Created " << Model::projectTypeToString(project.type()) << " `" << project.name().toString() - << "` project"; - output->displaySuccess(ss.str()); - - // Display generated files - output->displayInfo(" Generated the following files:"); - output->displayInfo(" " + project.name().toString() + "/"); - output->displayInfo(" ├── scrap.toml"); - output->displayInfo(" ├── src/"); - output->displayInfo(" │ └── main.cpp"); - - if (project.isLibrary()) { - output->displayInfo(" ├── include/"); - output->displayInfo(" │ └── " + project.name().toString() + "/"); - output->displayInfo(" │ └── " + project.name().toString() + ".h"); - } - - output->displayInfo(" └── tests/"); - output->displayInfo(" └── main_test.cpp"); - - // Display template information if used - if (spec.templateName) { - output->displayInfo(""); - ss.str(""); - ss << " Created project from template '" << *spec.templateName << "'"; - output->displayInfo(ss.str()); - } - - // Display dependencies if any were added - if (! spec.initialDependencies.empty()) { - output->displayInfo(" Installing template dependencies..."); - for (const auto& dep : spec.initialDependencies) { - output->displaySuccess(" ✓ " + dep + " (latest)"); - } - } -} - -CommandOptions NewOperation::describeOptions() const -{ - return CommandOptions() - .addPositional("project-name", "Name of the new project") - .addOption(CommandOption("type", "Project type (app, lib)", OptionType::String) - .withDefault("app") - .withChoices({"app", "lib"})) - .addOption(CommandOption("template", "Use project template", OptionType::String)) - .addOption(CommandOption("path", "Target directory", OptionType::String)) - .addOption(CommandOption("std", "C++ standard version (17, 20, 23)", OptionType::String) - .withDefault("23") - .withChoices({"17", "20", "23"})); -} - -void NewOperation::execute(const ParsedOptions& options) -{ - auto output = presenter(); - if (! output) { - return; - } - - // Get project name from positional argument - auto projectName = options.string("project-name"); - if (! projectName || projectName->empty()) { - // No project name provided - CLI11 should handle this - output->displayError("Error: Missing required argument: "); - output->displayInfo("Run 'scrap new --help' for usage information."); - return; - } - - // Build project specification from parsed options - Model::ProjectSpecification spec; - spec.name = *projectName; - - // Parse project type - auto typeStr = options.string("type").value_or("app"); - if (typeStr == "lib" || typeStr == "library") { - spec.type = Model::ProjectType::Library; - } else { - spec.type = Model::ProjectType::Application; - } - - // Set optional parameters - spec.templateName = options.string("template"); - spec.cppStandard = options.string("std").value_or("23"); - - if (auto path = options.string("path")) { - spec.targetPath = std::filesystem::path(*path); - } - - // Create the project - auto project = service_->createNew(spec); - - // Display creation result (cargo-style) - std::stringstream ss; - ss << " Created " << Model::projectTypeToString(project.type()) << " `" << project.name().toString() - << "` project"; - output->displaySuccess(ss.str()); - - // Display generated files - output->displayInfo(" Generated the following files:"); - output->displayInfo(" " + project.name().toString() + "/"); - output->displayInfo(" ├── scrap.toml"); - output->displayInfo(" ├── src/"); - output->displayInfo(" │ └── main.cpp"); - - if (project.isLibrary()) { - output->displayInfo(" ├── include/"); - output->displayInfo(" │ └── " + project.name().toString() + "/"); - output->displayInfo(" │ └── " + project.name().toString() + ".h"); - } - - output->displayInfo(" └── tests/"); - output->displayInfo(" └── main_test.cpp"); - - // Display template information if used - if (spec.templateName) { - output->displayInfo(""); - ss.str(""); - ss << " Created project from template '" << *spec.templateName << "'"; - output->displayInfo(ss.str()); - } - - // Display dependencies if any were added - if (! spec.initialDependencies.empty()) { - output->displayInfo(" Installing template dependencies..."); - for (const auto& dep : spec.initialDependencies) { - output->displaySuccess(" ✓ " + dep + " (latest)"); - } - } -} - -} // namespace scrap::project::command diff --git a/src/project/command/NewOperation.h b/src/project/command/NewOperation.h deleted file mode 100644 index c6a889d..0000000 --- a/src/project/command/NewOperation.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include "shared/command/Operation.h" -#include - -namespace scrap::project { - -namespace service { -class ProjectService; -} - -} // namespace scrap::project - -namespace scrap::template_system::service { -class TemplateService; -} - -namespace scrap::project { - -namespace command { - -/** - * @brief New operation for creating new projects - * - * This class handles the "scrap new" command following - * Clean Architecture principles with proper separation of concerns. - */ -class NewOperation : public Operation { -public: - explicit NewOperation(std::shared_ptr service, - std::shared_ptr templateService = nullptr); - ~NewOperation() override = default; - - // Non-copyable - NewOperation(const NewOperation&) = delete; - NewOperation& operator=(const NewOperation&) = delete; - - // Movable - NewOperation(NewOperation&&) = default; - NewOperation& operator=(NewOperation&&) = default; - - void execute(const std::vector& args) override; - CommandOptions describeOptions() const override; - void execute(const ParsedOptions& options) override; - -private: - std::shared_ptr service_; - std::shared_ptr templateService_; -}; - -} // namespace command -} // namespace scrap::project diff --git a/src/project/command/RunOperation.cpp b/src/project/command/RunOperation.cpp deleted file mode 100644 index 426a11b..0000000 --- a/src/project/command/RunOperation.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "RunOperation.h" -#include "project/model/Project.h" -#include "project/service/ProjectService.h" -#include "shared/command/CommandOptions.h" -#include "shared/presentation/Presenter.h" -#include - -namespace scrap::project::command { - -// Namespace alias for cleaner code -namespace Model = scrap::Project::Model; - -RunOperation::RunOperation(std::shared_ptr service) - : service_(service) -{ -} - -void RunOperation::execute(const std::vector& args) -{ - auto output = presenter(); - if (! output) { - return; - } - - // Note: Help is now handled by CLI11, no need to check for --help here - - // Load current project - auto project = service_->loadProject(); - if (! project) { - output->displayError("No project found in current directory"); - output->displayInfo("Run 'scrap new ' to create a new project"); - return; - } - - if (! project->isApplication()) { - output->displayError("Cannot run library project"); - output->displayInfo("Libraries cannot be executed directly"); - return; - } - - // Parse run options - auto optionsResult = Model::RunOptions::parse(args); - if (! optionsResult) { - output->displayError("Invalid run options: " + std::string(optionsResult.error().message())); - return; - } - const auto& options = *optionsResult; - - // Check if build is needed (always build in mock implementation) - auto buildOptions = Model::BuildOptions(); - buildOptions.mode = Model::BuildMode::Debug; - - std::stringstream ss; - ss << " Compiling " << project->name().toString() << " v" << project->version().toString(); - if (project->path()) { - ss << " (" << project->path()->string() << ")"; - } - output->displayInfo(ss.str()); - - // Build the project first - auto buildResult = service_->build(*project, buildOptions); - if (! buildResult.isSuccess()) { - output->displayError("Build failed, cannot run"); - return; - } - - // Display build completion - ss.str(""); - ss << " Finished dev [unoptimized + debuginfo] target(s) in " << std::fixed << std::setprecision(2) - << buildResult.duration.count() / 1000.0 << "s"; - output->displayInfo(ss.str()); - - // Display run command - ss.str(""); - ss << " Running `" << project->name().toString(); - for (const auto& arg : options.arguments) { - ss << " " << arg; - } - ss << "`"; - output->displayInfo(ss.str()); - - // Execute the project - service_->run(*project, options); -} - -CommandOptions RunOperation::describeOptions() const -{ - return CommandOptions().addOption(CommandOption("working-dir", "Set working directory", OptionType::String)); - // Note: Arguments after -- are handled specially by CLI11's allow_extras() -} - -void RunOperation::displayHelp() const -{ - // This method is deprecated and will be removed - // Help is now generated automatically from describeOptions() -} - -} // namespace scrap::project::command diff --git a/src/project/command/RunOperation.h b/src/project/command/RunOperation.h deleted file mode 100644 index 1e194d2..0000000 --- a/src/project/command/RunOperation.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include "shared/command/Operation.h" -#include - -namespace scrap::project { - -namespace service { -class ProjectService; -} - -namespace command { - -/** - * @brief Run operation for executing built projects - * - * This class handles the "scrap run" command following - * Clean Architecture principles with proper separation of concerns. - */ -class RunOperation : public Operation { -public: - explicit RunOperation(std::shared_ptr service); - ~RunOperation() override = default; - - // Non-copyable - RunOperation(const RunOperation&) = delete; - RunOperation& operator=(const RunOperation&) = delete; - - // Movable - RunOperation(RunOperation&&) = default; - RunOperation& operator=(RunOperation&&) = default; - - void execute(const std::vector& args) override; - CommandOptions describeOptions() const override; - -private: - std::shared_ptr service_; - - void displayHelp() const; -}; - -} // namespace command -} // namespace scrap::project diff --git a/src/project/model/Project.cpp b/src/project/model/Project.cpp deleted file mode 100644 index 233dcb9..0000000 --- a/src/project/model/Project.cpp +++ /dev/null @@ -1,516 +0,0 @@ -#include "Project.h" -#include "ProjectError.h" -#include -#include -#include -#include -#include -#include - -namespace scrap::Project::Model { - -// ProjectName implementation -ProjectName::ProjectName(std::string value) - : value_(std::move(value)) -{ -} - -std::expected ProjectName::create(const std::string& value) noexcept -{ - if (value.empty()) { - auto errorCode = make_error_code(ProjectNameError::Empty); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - - // Check for valid C++ identifier pattern - static const std::regex validName("^[a-zA-Z_][a-zA-Z0-9_]*$"); - if (! std::regex_match(value, validName)) { - auto errorCode = make_error_code(ProjectNameError::InvalidIdentifier); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - - // Check for reserved keywords - static const std::vector reservedKeywords = {"class", - "struct", - "namespace", - "template", - "typename", - "const", - "static", - "int", - "char", - "bool", - "void", - "return", - "if", - "else", - "for", - "while"}; - - if (std::find(reservedKeywords.begin(), reservedKeywords.end(), value) != reservedKeywords.end()) { - auto errorCode = make_error_code(ProjectNameError::ReservedKeyword); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - - return ProjectName{value}; -} - -const std::string& ProjectName::value() const -{ - return value_; -} - -std::string ProjectName::toString() const -{ - return value_; -} - -bool ProjectName::operator==(const ProjectName& other) const -{ - return value_ == other.value_; -} - -// Version implementation -Version::Version(int major, int minor, int patch) - : major_(major), minor_(minor), patch_(patch) -{ -} - -Version Version::createDefault() noexcept -{ - return Version{0, 1, 0}; -} - -std::expected Version::create(int major, int minor, int patch) noexcept -{ - if (major < 0 || minor < 0 || patch < 0) { - auto errorCode = make_error_code(VersionError::NegativeComponent); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - - return Version{major, minor, patch}; -} - -std::expected Version::parse(const std::string& versionStr) noexcept -{ - static const std::regex versionPattern(R"(^(\d+)\.(\d+)\.(\d+)$)"); - std::smatch match; - - if (! std::regex_match(versionStr, match, versionPattern)) { - auto errorCode = make_error_code(VersionError::InvalidFormat); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - - // Parse version components - // Note: std::stoi could theoretically throw, but the regex has validated - // that we have valid digits, so we wrap in try-catch for noexcept guarantee - try { - const int major = std::stoi(match[1]); - const int minor = std::stoi(match[2]); - const int patch = std::stoi(match[3]); - return Version{major, minor, patch}; - } catch (...) { - // This should never happen due to regex validation, but handle for noexcept safety - auto errorCode = make_error_code(VersionError::InvalidFormat); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } -} - -std::string Version::toString() const -{ - return std::to_string(major_) + "." + std::to_string(minor_) + "." + std::to_string(patch_); -} - -bool Version::operator==(const Version& other) const -{ - return major_ == other.major_ && minor_ == other.minor_ && patch_ == other.patch_; -} - -int Version::major() const -{ - return major_; -} - -int Version::minor() const -{ - return minor_; -} - -int Version::patch() const -{ - return patch_; -} - -// Dependency implementation -Dependency::Dependency(std::string name, std::string version, std::vector features) - : name_(std::move(name)), version_(std::move(version)), features_(std::move(features)) -{ -} - -std::expected Dependency::create(const std::string& name, - const std::string& version, - const std::vector& features) noexcept -{ - if (name.empty()) { - auto errorCode = make_error_code(DependencyError::EmptyName); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - if (version.empty()) { - auto errorCode = make_error_code(DependencyError::EmptyVersion); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - - return Dependency{name, version, features}; -} - -const std::string& Dependency::name() const -{ - return name_; -} - -const std::string& Dependency::version() const -{ - return version_; -} - -const std::vector& Dependency::features() const -{ - return features_; -} - -std::string Dependency::toString() const -{ - std::stringstream ss; - ss << name_ << "@" << version_; - if (! features_.empty()) { - ss << " ["; - for (size_t i = 0; i < features_.size(); ++i) { - if (i > 0) - ss << ", "; - ss << features_[i]; - } - ss << "]"; - } - return ss.str(); -} - -// BuildConfiguration implementation -BuildConfiguration::BuildConfiguration() - : mode_(BuildMode::Debug), cppStandard_("23") -{ -} - -// BuildConfiguration implementation -BuildMode BuildConfiguration::mode() const -{ - return mode_; -} - -const std::string& BuildConfiguration::cppStandard() const -{ - return cppStandard_; -} - -const std::vector& BuildConfiguration::compilerFlags() const -{ - return compilerFlags_; -} - -const std::vector& BuildConfiguration::linkerFlags() const -{ - return linkerFlags_; -} - -const std::map& BuildConfiguration::definitions() const -{ - return definitions_; -} - -void BuildConfiguration::setMode(BuildMode mode) -{ - mode_ = mode; -} - -void BuildConfiguration::setCppStandard(const std::string& standard) -{ - cppStandard_ = standard; -} - -void BuildConfiguration::addCompilerFlag(const std::string& flag) -{ - compilerFlags_.push_back(flag); -} - -void BuildConfiguration::addLinkerFlag(const std::string& flag) -{ - linkerFlags_.push_back(flag); -} - -void BuildConfiguration::addDefinition(const std::string& key, const std::string& value) -{ - definitions_[key] = value; -} - -// BuildResult implementation -BuildResult BuildResult::success(const std::string& message, - std::chrono::milliseconds duration, - const std::vector& artifacts) -{ - BuildResult result; - result.status = Status::Success; - result.message = message; - result.duration = duration; - result.artifacts = artifacts; - return result; -} - -BuildResult BuildResult::failed(const std::string& message, const std::vector& errors) -{ - BuildResult result; - result.status = Status::Failed; - result.message = message; - result.errors = errors; - return result; -} - -bool BuildResult::isSuccess() const -{ - return status == Status::Success; -} - -// Project implementation -Project::Project(const ProjectName& name, ProjectType type, const Version& version) - : name_(name), type_(type), version_(version) -{ -} - -const ProjectName& Project::name() const -{ - return name_; -} - -ProjectType Project::type() const -{ - return type_; -} - -const Version& Project::version() const -{ - return version_; -} - -const std::optional& Project::path() const -{ - return path_; -} - -const BuildConfiguration& Project::buildConfig() const -{ - return buildConfig_; -} - -const std::vector& Project::dependencies() const -{ - return dependencies_; -} - -const std::optional& Project::toolchainRequirement() const -{ - return toolchainRequirement_; -} - -void Project::setPath(const std::filesystem::path& path) -{ - path_ = path; -} - -void Project::setBuildConfig(const BuildConfiguration& config) -{ - buildConfig_ = config; -} - -void Project::addDependency(const Dependency& dependency) -{ - dependencies_.push_back(dependency); -} - -void Project::setToolchainRequirement(const std::string& requirement) -{ - toolchainRequirement_ = requirement; -} - -bool Project::isApplication() const -{ - return type_ == ProjectType::Application; -} - -bool Project::isLibrary() const -{ - return type_ == ProjectType::Library; -} - -std::string Project::fullName() const -{ - return name_.toString() + " v" + version_.toString(); -} - -std::filesystem::path Project::buildDirectory(BuildMode mode) const -{ - if (! path_) { - return "build"; - } - - std::string modeStr = buildModeToString(mode); - std::transform(modeStr.begin(), modeStr.end(), modeStr.begin(), ::tolower); - - return *path_ / "build" / modeStr; -} - -// ProjectSpecification implementation -std::expected -ProjectSpecification::parse(const std::vector& args) noexcept -{ - ProjectSpecification spec; - - if (args.empty()) { - auto errorCode = make_error_code(ProjectSpecificationError::MissingProjectName); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - - spec.name = args[0]; - spec.type = ProjectType::Application; // default - - // Parse additional arguments - for (size_t i = 1; i < args.size(); ++i) { - const auto& arg = args[i]; - - if (arg == "--type=app" || arg == "--type=application") { - spec.type = ProjectType::Application; - } else if (arg == "--type=lib" || arg == "--type=library") { - spec.type = ProjectType::Library; - } else if (arg.starts_with("--template=")) { - spec.templateName = arg.substr(11); - } else if (arg.starts_with("--path=")) { - spec.targetPath = std::filesystem::path(arg.substr(7)); - } else if (arg.starts_with("--std=")) { - spec.cppStandard = arg.substr(6); - } else if (! arg.starts_with("--")) { - // Treat as initial dependency - spec.initialDependencies.push_back(arg); - } - } - - return spec; -} - -// BuildOptions implementation -std::expected BuildOptions::parse(const std::vector& args) noexcept -{ - BuildOptions options; - - for (const auto& arg : args) { - if (arg == "--release") { - options.mode = BuildMode::Release; - } else if (arg == "--debug") { - options.mode = BuildMode::Debug; - } else if (arg == "--verbose" || arg == "-v") { - options.verbose = true; - } else if (arg == "--clean") { - options.clean = true; - } else if (arg.starts_with("--target=")) { - options.target = arg.substr(9); - } else if (arg.starts_with("-j") && arg.length() > 2) { - try { - options.parallelJobs = std::stoi(arg.substr(2)); - } catch (const std::exception&) { - auto errorCode = make_error_code(BuildOptionsError::InvalidParallelJobs); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - } - } - - return options; -} - -// RunOptions implementation -std::expected RunOptions::parse(const std::vector& args) noexcept -{ - RunOptions options; - - bool foundSeparator = false; - for (const auto& arg : args) { - if (arg == "--") { - foundSeparator = true; - continue; - } - - if (foundSeparator) { - options.arguments.push_back(arg); - } else if (arg.starts_with("--working-dir=")) { - options.workingDirectory = std::filesystem::path(arg.substr(14)); - } - } - - return options; -} - -// Helper functions -std::string projectTypeToString(ProjectType type) -{ - switch (type) { - case ProjectType::Application: - return "application"; - case ProjectType::Library: - return "library"; - default: - return "unknown"; - } -} - -ProjectType stringToProjectType(const std::string& str) -{ - std::string lower = str; - std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); - - if (lower == "app" || lower == "application" || lower == "exe") { - return ProjectType::Application; - } else if (lower == "lib" || lower == "library") { - return ProjectType::Library; - } - return ProjectType::Unknown; -} - -std::string buildModeToString(BuildMode mode) -{ - switch (mode) { - case BuildMode::Debug: - return "Debug"; - case BuildMode::Release: - return "Release"; - case BuildMode::RelWithDebInfo: - return "RelWithDebInfo"; - case BuildMode::MinSizeRel: - return "MinSizeRel"; - default: - return "Debug"; - } -} - -BuildMode stringToBuildMode(const std::string& str) -{ - std::string lower = str; - std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); - - if (lower == "release" || lower == "rel") { - return BuildMode::Release; - } else if (lower == "debug" || lower == "dbg") { - return BuildMode::Debug; - } else if (lower == "relwithdebinfo" || lower == "relwithdbg") { - return BuildMode::RelWithDebInfo; - } else if (lower == "minsizerel" || lower == "minsize") { - return BuildMode::MinSizeRel; - } - return BuildMode::Debug; -} - -} // namespace scrap::Project::Model diff --git a/src/project/model/Project.h b/src/project/model/Project.h deleted file mode 100644 index 821aacd..0000000 --- a/src/project/model/Project.h +++ /dev/null @@ -1,264 +0,0 @@ -#pragma once - -#include "ProjectError.h" -#include -#include -#include -#include -#include -#include -#include - -namespace scrap::Project::Model { - -/** - * @brief Value object for project name - */ -class ProjectName { -public: - /** - * @brief Create ProjectName with validation - * @param value Project name to validate - * @return ProjectName if valid, error otherwise - */ - [[nodiscard]] static std::expected create(const std::string& value) noexcept; - - const std::string& value() const; - std::string toString() const; - - bool operator==(const ProjectName& other) const; - -private: - std::string value_; - - // Private constructor - use create() factory method - explicit ProjectName(std::string value); -}; - -/** - * @brief Enumeration for project types - */ -enum class ProjectType { - Application, // Executable application - Library, // Static/shared library - Unknown -}; - -/** - * @brief Enumeration for build modes - */ -enum class BuildMode { - Debug, - Release, - RelWithDebInfo, - MinSizeRel -}; - -/** - * @brief Value object for version - */ -class Version { -public: - /** - * @brief Create default version (0.1.0) - */ - [[nodiscard]] static Version createDefault() noexcept; - - /** - * @brief Create Version with validation - * @param major Major version number - * @param minor Minor version number - * @param patch Patch version number - * @return Version if valid, error otherwise - */ - [[nodiscard]] static std::expected create(int major, int minor, int patch) noexcept; - - /** - * @brief Parse Version from string (X.Y.Z format) - * @param versionStr Version string to parse - * @return Version if valid, error otherwise - */ - [[nodiscard]] static std::expected parse(const std::string& versionStr) noexcept; - - int major() const; - int minor() const; - int patch() const; - - std::string toString() const; - bool operator==(const Version& other) const; - -private: - int major_, minor_, patch_; - - // Private constructor - use create() or parse() factory methods - Version(int major, int minor, int patch); -}; - -/** - * @brief Value object for dependency specification - */ -class Dependency { -public: - /** - * @brief Create Dependency with validation - * @param name Dependency name - * @param version Dependency version - * @param features Optional feature flags - * @return Dependency if valid, error otherwise - */ - [[nodiscard]] static std::expected - create(const std::string& name, const std::string& version, const std::vector& features = {}) noexcept; - - const std::string& name() const; - const std::string& version() const; - const std::vector& features() const; - - std::string toString() const; - -private: - std::string name_; - std::string version_; - std::vector features_; - - // Private constructor - use create() factory method - Dependency(std::string name, std::string version, std::vector features); -}; - -/** - * @brief Build configuration settings - */ -class BuildConfiguration { -public: - BuildConfiguration(); - - // Getters - BuildMode mode() const; - const std::string& cppStandard() const; - const std::vector& compilerFlags() const; - const std::vector& linkerFlags() const; - const std::map& definitions() const; - - // Setters - void setMode(BuildMode mode); - void setCppStandard(const std::string& standard); - void addCompilerFlag(const std::string& flag); - void addLinkerFlag(const std::string& flag); - void addDefinition(const std::string& key, const std::string& value); - -private: - BuildMode mode_; - std::string cppStandard_; - std::vector compilerFlags_; - std::vector linkerFlags_; - std::map definitions_; -}; - -/** - * @brief Build result information - */ -struct BuildResult { - enum class Status { - Success, - Failed, - Cancelled - }; - - Status status; - std::string message; - std::chrono::milliseconds duration; - std::vector artifacts; - std::vector warnings; - std::vector errors; - - static BuildResult success(const std::string& message = "", - std::chrono::milliseconds duration = {}, - const std::vector& artifacts = {}); - - static BuildResult failed(const std::string& message, const std::vector& errors = {}); - - bool isSuccess() const; -}; - -/** - * @brief Main project entity - */ -class Project { -public: - Project(const ProjectName& name, ProjectType type, const Version& version = Version::createDefault()); - - // Getters - const ProjectName& name() const; - ProjectType type() const; - const Version& version() const; - const std::optional& path() const; - const BuildConfiguration& buildConfig() const; - const std::vector& dependencies() const; - const std::optional& toolchainRequirement() const; - - // Setters - void setPath(const std::filesystem::path& path); - void setBuildConfig(const BuildConfiguration& config); - void addDependency(const Dependency& dependency); - void setToolchainRequirement(const std::string& requirement); - - // Business logic - bool isApplication() const; - bool isLibrary() const; - std::string fullName() const; - std::filesystem::path buildDirectory(BuildMode mode) const; - -private: - ProjectName name_; - ProjectType type_; - Version version_; - std::optional path_; - BuildConfiguration buildConfig_; - std::vector dependencies_; - std::optional toolchainRequirement_; -}; - -/** - * @brief Project creation specification - */ -struct ProjectSpecification { - std::string name; - ProjectType type; - std::optional templateName; - std::optional targetPath; - std::optional cppStandard; - std::vector initialDependencies; - - [[nodiscard]] static std::expected - parse(const std::vector& args) noexcept; -}; - -/** - * @brief Build options for build command - */ -struct BuildOptions { - BuildMode mode = BuildMode::Debug; - bool verbose = false; - bool clean = false; - std::optional target; - int parallelJobs = 0; // 0 = auto-detect - - [[nodiscard]] static std::expected parse(const std::vector& args) noexcept; -}; - -/** - * @brief Run options for run command - */ -struct RunOptions { - std::vector arguments; - std::optional workingDirectory; - - [[nodiscard]] static std::expected parse(const std::vector& args) noexcept; -}; - -// Helper functions -std::string projectTypeToString(ProjectType type); -ProjectType stringToProjectType(const std::string& str); -std::string buildModeToString(BuildMode mode); -BuildMode stringToBuildMode(const std::string& str); - -} // namespace scrap::Project::Model diff --git a/src/project/model/ProjectError.cpp b/src/project/model/ProjectError.cpp deleted file mode 100644 index 52a91c2..0000000 --- a/src/project/model/ProjectError.cpp +++ /dev/null @@ -1,127 +0,0 @@ -#include "ProjectError.h" -#include - -// Error code creation functions (global scope for ADL) - -std::error_code make_error_code(scrap::Project::Model::ProjectNameError e) noexcept -{ - struct ProjectNameErrorCategory : std::error_category { - [[nodiscard]] const char* name() const noexcept override - { - return "ProjectName"; - } - - [[nodiscard]] std::string message(int ev) const override - { - switch (static_cast(ev)) { - case scrap::Project::Model::ProjectNameError::Empty: - return "Project name cannot be empty"; - case scrap::Project::Model::ProjectNameError::InvalidIdentifier: - return "Project name must be a valid C++ identifier"; - case scrap::Project::Model::ProjectNameError::ReservedKeyword: - return "Project name cannot be a C++ reserved keyword"; - default: - return "Unknown ProjectName error"; - } - } - }; - - static const ProjectNameErrorCategory ErrorCategory{}; - return {static_cast(e), ErrorCategory}; -} - -std::error_code make_error_code(scrap::Project::Model::VersionError e) noexcept -{ - struct VersionErrorCategory : std::error_category { - [[nodiscard]] const char* name() const noexcept override - { - return "Version"; - } - - [[nodiscard]] std::string message(int ev) const override - { - switch (static_cast(ev)) { - case scrap::Project::Model::VersionError::NegativeComponent: - return "Version components cannot be negative"; - case scrap::Project::Model::VersionError::InvalidFormat: - return "Invalid version format, expected X.Y.Z"; - default: - return "Unknown Version error"; - } - } - }; - - static const VersionErrorCategory ErrorCategory{}; - return {static_cast(e), ErrorCategory}; -} - -std::error_code make_error_code(scrap::Project::Model::DependencyError e) noexcept -{ - struct DependencyErrorCategory : std::error_category { - [[nodiscard]] const char* name() const noexcept override - { - return "Dependency"; - } - - [[nodiscard]] std::string message(int ev) const override - { - switch (static_cast(ev)) { - case scrap::Project::Model::DependencyError::EmptyName: - return "Dependency name cannot be empty"; - case scrap::Project::Model::DependencyError::EmptyVersion: - return "Dependency version cannot be empty"; - default: - return "Unknown Dependency error"; - } - } - }; - - static const DependencyErrorCategory ErrorCategory{}; - return {static_cast(e), ErrorCategory}; -} - -std::error_code make_error_code(scrap::Project::Model::ProjectSpecificationError e) noexcept -{ - struct ProjectSpecificationErrorCategory : std::error_category { - [[nodiscard]] const char* name() const noexcept override - { - return "ProjectSpecification"; - } - - [[nodiscard]] std::string message(int ev) const override - { - switch (static_cast(ev)) { - case scrap::Project::Model::ProjectSpecificationError::MissingProjectName: - return "Project name is required"; - default: - return "Unknown ProjectSpecification error"; - } - } - }; - - static const ProjectSpecificationErrorCategory ErrorCategory{}; - return {static_cast(e), ErrorCategory}; -} - -std::error_code make_error_code(scrap::Project::Model::BuildOptionsError e) noexcept -{ - struct BuildOptionsErrorCategory : std::error_category { - [[nodiscard]] const char* name() const noexcept override - { - return "BuildOptions"; - } - - [[nodiscard]] std::string message(int ev) const override - { - switch (static_cast(ev)) { - case scrap::Project::Model::BuildOptionsError::InvalidParallelJobs: - return "Invalid parallel jobs value, expected a positive integer"; - default: - return "Unknown BuildOptions error"; - } - } - }; - - static const BuildOptionsErrorCategory ErrorCategory{}; - return {static_cast(e), ErrorCategory}; -} diff --git a/src/project/model/ProjectError.h b/src/project/model/ProjectError.h deleted file mode 100644 index 8bdb0aa..0000000 --- a/src/project/model/ProjectError.h +++ /dev/null @@ -1,79 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace scrap::Project::Model { - -/** - * @brief Error codes for ProjectName validation - */ -enum class ProjectNameError : std::uint8_t { - Empty, ///< Project name cannot be empty - InvalidIdentifier, ///< Project name must be a valid C++ identifier - ReservedKeyword ///< Project name cannot be a C++ reserved keyword -}; - -/** - * @brief Error codes for Version validation - */ -enum class VersionError : std::uint8_t { - NegativeComponent, ///< Version components cannot be negative - InvalidFormat ///< Version string must match X.Y.Z format -}; - -/** - * @brief Error codes for Dependency validation - */ -enum class DependencyError : std::uint8_t { - EmptyName, ///< Dependency name cannot be empty - EmptyVersion ///< Dependency version cannot be empty -}; - -/** - * @brief Error codes for ProjectSpecification parsing - */ -enum class ProjectSpecificationError : std::uint8_t { - MissingProjectName ///< Project name is required -}; - -/** - * @brief Error codes for BuildOptions parsing - */ -enum class BuildOptionsError : std::uint8_t { - InvalidParallelJobs ///< Parallel jobs value must be a valid integer -}; - -} // namespace scrap::Project::Model - -// Error code creation function declarations (must be in global scope for ADL) -// NOLINTNEXTLINE(readability-identifier-naming) - C++ standard requires this exact name for ADL -std::error_code make_error_code(scrap::Project::Model::ProjectNameError e) noexcept; - -// NOLINTNEXTLINE(readability-identifier-naming) - C++ standard requires this exact name for ADL -std::error_code make_error_code(scrap::Project::Model::VersionError e) noexcept; - -// NOLINTNEXTLINE(readability-identifier-naming) - C++ standard requires this exact name for ADL -std::error_code make_error_code(scrap::Project::Model::DependencyError e) noexcept; - -// NOLINTNEXTLINE(readability-identifier-naming) - C++ standard requires this exact name for ADL -std::error_code make_error_code(scrap::Project::Model::ProjectSpecificationError e) noexcept; - -// NOLINTNEXTLINE(readability-identifier-naming) - C++ standard requires this exact name for ADL -std::error_code make_error_code(scrap::Project::Model::BuildOptionsError e) noexcept; - -// C++ standard requires specializing std::is_error_code_enum for custom error enums -namespace std { - -template <> struct is_error_code_enum : true_type { }; - -template <> struct is_error_code_enum : true_type { }; - -template <> struct is_error_code_enum : true_type { }; - -template <> struct is_error_code_enum : true_type { }; - -template <> struct is_error_code_enum : true_type { }; - -} // namespace std diff --git a/src/project/service/MockProjectService.cpp b/src/project/service/MockProjectService.cpp deleted file mode 100644 index 16bd01b..0000000 --- a/src/project/service/MockProjectService.cpp +++ /dev/null @@ -1,331 +0,0 @@ -#include "ProjectService.h" -#include "shared/presentation/driver/ConsolePresenter.h" -#include "template/TemplateModule.h" -#include "template/service/TemplateService.h" -#include -#include -#include - -namespace scrap::project::service { - -using namespace Model; - -MockProjectService::MockProjectService() -{ - presenter_ = std::make_shared(); - templateService_ = template_system::TemplateModule::createTemplateService(presenter_); -} - -MockProjectService::MockProjectService(std::shared_ptr templateService, - std::shared_ptr presenter) - : templateService_(templateService), presenter_(presenter) -{ - if (! presenter_) { - presenter_ = std::make_shared(); - } - if (! templateService_) { - templateService_ = template_system::TemplateModule::createTemplateService(presenter_); - } -} - -Model::Project MockProjectService::createNew(const Model::ProjectSpecification& spec) -{ - // Validate specification - if (spec.name.empty()) { - throw std::runtime_error("Project name cannot be empty"); - } - - // Create project entity - auto project = - Model::Project(Model::ProjectName::create(spec.name).value(), spec.type, Model::Version::createDefault()); - - // Set optional configurations - if (spec.cppStandard) { - auto config = project.buildConfig(); - config.setCppStandard(*spec.cppStandard); - project.setBuildConfig(config); - } - - // Add initial dependencies - for (const auto& depName : spec.initialDependencies) { - project.addDependency(Model::Dependency::create(depName, "latest").value()); - } - - // Set project path - auto targetPath = spec.targetPath.value_or(std::filesystem::current_path() / spec.name); - project.setPath(targetPath); - - // Use template if specified, otherwise use recommended template - if (spec.templateName) { - createProjectFromTemplate(spec, targetPath); - } else { - // Try to find default template for project type - auto recommendedTemplate = - templateService_->recommendedTemplate(spec.type == ProjectType::Application ? "app" : "lib"); - - if (recommendedTemplate) { - // Use recommended template - auto modifiedSpec = spec; - modifiedSpec.templateName = *recommendedTemplate; - createProjectFromTemplate(modifiedSpec, targetPath); - } else { - // No template available - throw std::runtime_error("No template available for project type. Please ensure templates are installed."); - } - } - - return project; -} - -std::optional MockProjectService::loadProject(const std::optional& path) -{ - - auto projectPath = path.value_or(std::filesystem::current_path()); - auto configPath = projectPath / "scrap.toml"; - - // Check if scrap.toml exists - if (! std::filesystem::exists(configPath)) { - return std::nullopt; - } - - // For mock implementation, create a simple project - // In real implementation, this would parse scrap.toml - auto project = Model::Project(Model::ProjectName::create("example").value(), - Model::ProjectType::Application, - Model::Version::createDefault()); - project.setPath(projectPath); - - // Add some mock dependencies - project.addDependency(Model::Dependency::create("fmt", "10.2.1").value()); - project.addDependency(Model::Dependency::create("spdlog", "1.13.0").value()); - - return project; -} - -void MockProjectService::saveProject(const Model::Project& project) -{ - if (! project.path()) { - throw std::runtime_error("Project path not set"); - } - - generateConfigFile(project, *project.path()); -} - -Model::BuildResult MockProjectService::build(const Model::Project& project, const Model::BuildOptions& options) -{ - // Simulate build process - auto startTime = std::chrono::steady_clock::now(); - - if (options.verbose) { - presenter_->displayDebug("Building project: " + project.fullName()); - presenter_->displayDebug("Build mode: " + buildModeToString(options.mode)); - presenter_->displayDebug("C++ Standard: " + project.buildConfig().cppStandard()); - } - - // Simulate compilation time - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - auto endTime = std::chrono::steady_clock::now(); - auto duration = std::chrono::duration_cast(endTime - startTime); - - // Generate mock artifacts - std::vector artifacts; - if (project.path()) { - auto buildDir = project.buildDirectory(options.mode); - auto artifactName = project.name().toString(); - if (project.isApplication()) { - artifacts.push_back(buildDir / artifactName); - } else { - artifacts.push_back(buildDir / ("lib" + artifactName + ".a")); - } - } - - return Model::BuildResult::success("Build completed successfully", duration, artifacts); -} - -void MockProjectService::run(const Model::Project& project, const Model::RunOptions& options) -{ - if (! project.isApplication()) { - throw std::runtime_error("Cannot run library project"); - } - - // In mock implementation, just simulate execution - std::string command = "Running `" + project.name().toString(); - for (const auto& arg : options.arguments) { - command += " " + arg; - } - command += "`"; - presenter_->displayInfo(" " + command); - - // Simulate some output - presenter_->displayInfo("Hello, World from " + project.name().toString() + "!"); - presenter_->displayInfo("Application finished with exit code 0"); -} - -void MockProjectService::clean(const Model::Project& project) -{ - if (! project.path()) { - return; - } - - // Simulate cleaning - auto buildPath = *project.path() / "build"; - - // In real implementation, this would actually remove files - // For mock, we just simulate the output - presenter_->displayInfo(" Removed " + buildPath.string()); - presenter_->displayInfo(" Cleaned build artifacts"); -} - -Model::Project MockProjectService::addDependency(const Model::Project& project, const Model::Dependency& dependency) -{ - auto modifiedProject = project; - modifiedProject.addDependency(dependency); - return modifiedProject; -} - -void MockProjectService::createProjectStructure(const Model::Project& project, const std::filesystem::path& basePath) -{ - // Create directory structure - std::filesystem::create_directories(basePath); - std::filesystem::create_directories(basePath / "src"); - std::filesystem::create_directories(basePath / "include" / project.name().toString()); - std::filesystem::create_directories(basePath / "tests"); - - if (! project.isApplication()) { - std::filesystem::create_directories(basePath / "examples"); - } -} - -void MockProjectService::generateSourceFiles(const Model::Project& project, const std::filesystem::path& projectPath) -{ - // Generate main source file - auto mainFile = projectPath / "src" / "main.cpp"; - std::ofstream main(mainFile); - - if (project.isApplication()) { - main << "#include \n\n"; - main << "int main() {\n"; - main << " std::cout << \"Hello, World from " << project.name().toString() << "!\" << std::endl;\n"; - main << " return 0;\n"; - main << "}\n"; - } else { - main << "#include \"" << project.name().toString() << "/" << project.name().toString() << ".h\"\n\n"; - main << "namespace " << project.name().toString() << " {\n\n"; - main << "void hello() {\n"; - main << " // Implementation goes here\n"; - main << "}\n\n"; - main << "} // namespace " << project.name().toString() << "\n"; - } - - // Generate header file for library - if (project.isLibrary()) { - auto headerFile = projectPath / "include" / project.name().toString() / (project.name().toString() + ".h"); - std::ofstream header(headerFile); - - header << "#pragma once\n\n"; - header << "namespace " << project.name().toString() << " {\n\n"; - header << "/**\n"; - header << " * @brief Example function\n"; - header << " */\n"; - header << "void hello();\n\n"; - header << "} // namespace " << project.name().toString() << "\n"; - } - - // Generate test file - auto testFile = projectPath / "tests" / "main_test.cpp"; - std::ofstream test(testFile); - - test << "#include \n"; - if (project.isLibrary()) { - test << "#include \"" << project.name().toString() << "/" << project.name().toString() << ".h\"\n"; - } - test << "\n"; - test << "int main() {\n"; - test << " // Add your tests here\n"; - test << " return 0;\n"; - test << "}\n"; -} - -void MockProjectService::generateConfigFile(const Model::Project& project, const std::filesystem::path& projectPath) -{ - auto configFile = projectPath / "scrap.toml"; - std::ofstream config(configFile); - - config << "[package]\n"; - config << "name = \"" << project.name().toString() << "\"\n"; - config << "version = \"" << project.version().toString() << "\"\n"; - config << "type = \"" << projectTypeToString(project.type()) << "\"\n"; - config << "\n"; - - config << "[build]\n"; - config << "std = \"" << project.buildConfig().cppStandard() << "\"\n"; - - if (project.toolchainRequirement()) { - config << "toolchain = \"" << *project.toolchainRequirement() << "\"\n"; - } - - config << "\n"; - - if (! project.dependencies().empty()) { - config << "[dependencies]\n"; - for (const auto& dep : project.dependencies()) { - config << dep.name() << " = \"" << dep.version() << "\"\n"; - } - } -} - -void MockProjectService::createProjectFromTemplate(const Model::ProjectSpecification& spec, - const std::filesystem::path& targetPath) -{ - try { - // Load template - std::optional tmpl; - - // Check if it's a local path - if (spec.templateName->starts_with("/") || spec.templateName->starts_with("./") || - spec.templateName->starts_with("../")) { - tmpl = templateService_->loadTemplateFromPath(*spec.templateName); - } else { - tmpl = templateService_->loadTemplate(*spec.templateName); - } - - if (! tmpl) { - throw std::runtime_error("Template not found: " + *spec.templateName); - } - - // Collect template variables - auto variables = templateService_->collectTemplateVariables(*tmpl, spec.name); - - // Override with specification values - variables.set("name", spec.name); - variables.set("version", "0.1.0"); - - if (spec.cppStandard) { - variables.set("std", *spec.cppStandard); - } else { - variables.set("std", "23"); - } - - // Process template - auto result = templateService_->processTemplate(*tmpl, targetPath, variables); - if (! result) { - throw std::runtime_error(result.error()); - } - - presenter_->displaySuccess(" Created project from template '" + *spec.templateName + "'"); - - } catch (const std::exception& e) { - presenter_->displayWarning("Failed to use template '" + *spec.templateName + "': " + e.what()); - presenter_->displayWarning("Falling back to default project generation."); - - // Fall back to hardcoded generation - auto fallbackProject = - Model::Project(Model::ProjectName::create(spec.name).value(), spec.type, Model::Version::createDefault()); - createProjectStructure(fallbackProject, targetPath); - generateSourceFiles(fallbackProject, targetPath); - generateConfigFile(fallbackProject, targetPath); - } -} - -} // namespace scrap::project::service diff --git a/src/project/service/ProjectService.h b/src/project/service/ProjectService.h deleted file mode 100644 index 6a3d391..0000000 --- a/src/project/service/ProjectService.h +++ /dev/null @@ -1,117 +0,0 @@ -#pragma once - -#include "project/model/Project.h" -#include -#include - -namespace scrap { -class Presenter; -} - -namespace scrap::template_system::service { -class TemplateService; -} - -namespace scrap::project::service { - -// Namespace alias for cleaner code -namespace Model = scrap::Project::Model; - -/** - * @brief Service interface for project management operations - * - * This interface defines the business operations available for project - * management, following Clean Architecture principles. - */ -class ProjectService { -public: - virtual ~ProjectService() = default; - - // Project lifecycle operations - /** - * @brief Create a new project from specification - * @param spec Project creation specification - * @return Created project - * @throws std::runtime_error if creation fails - */ - virtual Model::Project createNew(const Model::ProjectSpecification& spec) = 0; - - /** - * @brief Load project from current directory or specified path - * @param path Optional path to project directory - * @return Project if found, nullopt otherwise - */ - virtual std::optional - loadProject(const std::optional& path = std::nullopt) = 0; - - /** - * @brief Save project configuration to disk - * @param project Project to save - * @throws std::runtime_error if save fails - */ - virtual void saveProject(const Model::Project& project) = 0; - - // Build operations - /** - * @brief Build the project - * @param project Project to build - * @param options Build options - * @return Build result - */ - virtual Model::BuildResult build(const Model::Project& project, const Model::BuildOptions& options) = 0; - - /** - * @brief Run the built executable - * @param project Project to run - * @param options Run options - * @throws std::runtime_error if run fails - */ - virtual void run(const Model::Project& project, const Model::RunOptions& options) = 0; - - /** - * @brief Clean build artifacts - * @param project Project to clean - */ - virtual void clean(const Model::Project& project) = 0; - - // Dependency management - /** - * @brief Add dependency to project - * @param project Project to modify - * @param dependency Dependency to add - * @return Modified project - */ - virtual Model::Project addDependency(const Model::Project& project, const Model::Dependency& dependency) = 0; -}; - -/** - * @brief Mock implementation of ProjectService for testing - */ -class MockProjectService : public ProjectService { -public: - MockProjectService(); - explicit MockProjectService(std::shared_ptr templateService = nullptr, - std::shared_ptr presenter = nullptr); - ~MockProjectService() override = default; - - Model::Project createNew(const Model::ProjectSpecification& spec) override; - std::optional loadProject(const std::optional& path = std::nullopt) override; - void saveProject(const Model::Project& project) override; - - Model::BuildResult build(const Model::Project& project, const Model::BuildOptions& options) override; - void run(const Model::Project& project, const Model::RunOptions& options) override; - void clean(const Model::Project& project) override; - - Model::Project addDependency(const Model::Project& project, const Model::Dependency& dependency) override; - -private: - std::shared_ptr templateService_; - std::shared_ptr presenter_; - - void createProjectStructure(const Model::Project& project, const std::filesystem::path& basePath); - void generateSourceFiles(const Model::Project& project, const std::filesystem::path& projectPath); - void generateConfigFile(const Model::Project& project, const std::filesystem::path& projectPath); - void createProjectFromTemplate(const Model::ProjectSpecification& spec, const std::filesystem::path& targetPath); -}; - -} // namespace scrap::project::service diff --git a/src/repository/RepositoryFactory.cpp b/src/repository/RepositoryFactory.cpp deleted file mode 100644 index d8d501c..0000000 --- a/src/repository/RepositoryFactory.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "repository/RepositoryFactory.h" -#include "repository/driver/GitDriver.h" - -namespace scrap::repository { - -std::unique_ptr RepositoryFactory::createGitRepository(const std::filesystem::path& path) -{ - auto driver = std::make_shared(); - return std::make_unique(driver, path); -} - -} // namespace scrap::repository diff --git a/src/repository/RepositoryFactory.h b/src/repository/RepositoryFactory.h deleted file mode 100644 index 0fa1552..0000000 --- a/src/repository/RepositoryFactory.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include "repository/model/Repository.h" -#include -#include - -namespace scrap::repository { - -/** - * @brief Factory for creating Repository instances with appropriate drivers - * - * This factory encapsulates the infrastructure dependency (GitDriver) - * and provides a clean interface for creating repositories without - * violating the Dependency Inversion Principle. - */ -class RepositoryFactory { -public: - /** - * @brief Create a Git repository with GitDriver - * @param path Repository directory path - * @return Unique pointer to Repository - */ - static std::unique_ptr createGitRepository(const std::filesystem::path& path); -}; - -} // namespace scrap::repository diff --git a/src/repository/driver/Driver.h b/src/repository/driver/Driver.h deleted file mode 100644 index ea8a81e..0000000 --- a/src/repository/driver/Driver.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace scrap::repository { - -class Driver { -public: - virtual ~Driver() = default; - virtual std::expected clone(const std::string& url, const std::filesystem::path& path) = 0; - virtual std::expected update(const std::filesystem::path& path) = 0; -}; - -} // namespace scrap::repository diff --git a/src/repository/driver/GitDriver.cpp b/src/repository/driver/GitDriver.cpp deleted file mode 100644 index 8698e7e..0000000 --- a/src/repository/driver/GitDriver.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "repository/driver/GitDriver.h" -#include "repository/driver/LibGitRepository.h" - -#include - -namespace scrap::repository { - -class GitDriver::Internal { -public: - Internal() - { - git_libgit2_init(); - } - ~Internal() - { - git_libgit2_shutdown(); - } - - std::unique_ptr repository; -}; - -GitDriver::GitDriver() - : impl_(std::make_unique()) -{ -} - -GitDriver::~GitDriver() -{ -} - -std::expected GitDriver::clone(const std::string& url, const std::filesystem::path& path) -{ - try { - auto repo = std::make_unique(url, path); - // Check if the repository was created successfully - // This is a simplified check - in practice we'd need better error handling from libgit - impl_->repository = std::move(repo); - return {}; - } catch (const std::exception& e) { - return std::unexpected("Failed to clone repository: " + std::string(e.what())); - } -} - -std::expected GitDriver::update(const std::filesystem::path& path) -{ - try { - if (! impl_->repository) { - impl_->repository = std::make_unique(path); - } - - auto result = impl_->repository->update("origin", "main"); - if (! result) { - return std::unexpected("Failed to update repository: " + result.error().message()); - } - - return {}; - } catch (const std::exception& e) { - return std::unexpected("Failed to update repository: " + std::string(e.what())); - } -} - -} // namespace scrap::repository diff --git a/src/repository/driver/GitDriver.h b/src/repository/driver/GitDriver.h deleted file mode 100644 index e67615b..0000000 --- a/src/repository/driver/GitDriver.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "repository/driver/Driver.h" - -#include - -namespace scrap::repository { - -class GitDriver : public Driver { -public: - GitDriver(); - ~GitDriver(); - - std::expected clone(const std::string& url, const std::filesystem::path& path) override; - std::expected update(const std::filesystem::path& path) override; - -private: - class Internal; - std::unique_ptr impl_; -}; - -} // namespace scrap::repository diff --git a/src/repository/driver/LibGitRepository.cpp b/src/repository/driver/LibGitRepository.cpp deleted file mode 100644 index 0ab9bc5..0000000 --- a/src/repository/driver/LibGitRepository.cpp +++ /dev/null @@ -1,418 +0,0 @@ -#include "repository/driver/LibGitRepository.h" - -#include -#include -#include -#include - -enum class GitError { - RemoteLookupFailed = 1, - FetchFailed, - CommitLookupFailed, - ReferenceLookupFailed, - AnnotatedCommitCreationFailed, - CloneOptionsFailed, - CloneFailed, - RepositoryOpenFailed, - MergeAnalysisFailed, - MergeOptionsFailed, - MergeFailed, - CheckoutOptionsFailed, - CheckoutFailed, - SetHeadFailed, - SetReferenceTargetFailed -}; - -namespace std { -template <> struct is_error_code_enum : true_type { }; -} // namespace std - -class GitErrorCategory : public std::error_category { -public: - const char* name() const noexcept override - { - return "git"; - } - - std::string message(int ev) const override - { - switch (static_cast(ev)) { - case GitError::RemoteLookupFailed: - return "Failed to lookup remote"; - case GitError::FetchFailed: - return "Failed to fetch remote"; - case GitError::CommitLookupFailed: - return "Failed to lookup commit"; - case GitError::ReferenceLookupFailed: - return "Failed to lookup reference"; - case GitError::AnnotatedCommitCreationFailed: - return "Failed to create annotated commit"; - case GitError::CloneOptionsFailed: - return "Failed to initialize clone options"; - case GitError::CloneFailed: - return "Failed to clone repository"; - case GitError::RepositoryOpenFailed: - return "Failed to open repository"; - case GitError::MergeAnalysisFailed: - return "Failed to analyze merge"; - case GitError::MergeOptionsFailed: - return "Failed to initialize merge options"; - case GitError::MergeFailed: - return "Failed to merge"; - case GitError::CheckoutOptionsFailed: - return "Failed to initialize checkout options"; - case GitError::CheckoutFailed: - return "Failed to checkout head"; - case GitError::SetHeadFailed: - return "Failed to set repository head"; - case GitError::SetReferenceTargetFailed: - return "Failed to set reference target"; - default: - return "Unknown git error"; - } - } -}; - -const GitErrorCategory& gitErrorCategory() -{ - static GitErrorCategory instance; - return instance; -} - -std::error_code make_error_code(GitError e) -{ - return {static_cast(e), gitErrorCategory()}; -} - -namespace scrap::repository::libgit { - -/** - * @brief RAII wrapper for git_commit - */ -class Commit { -public: - explicit Commit(git_commit* commit) - : commit_(commit) - { - } - - ~Commit() - { - if (commit_) { - git_commit_free(commit_); - } - } - - Commit(const Commit&) = delete; - Commit& operator=(const Commit&) = delete; - - Commit(Commit&& other) noexcept - : commit_(other.commit_) - { - other.commit_ = nullptr; - } - - Commit& operator=(Commit&& other) noexcept - { - if (this != &other) { - if (commit_) { - git_commit_free(commit_); - } - commit_ = other.commit_; - other.commit_ = nullptr; - } - return *this; - } - - const git_oid* oid() const - { - return git_commit_id(commit_); - } - - git_commit* get() const - { - return commit_; - } - -private: - git_commit* commit_ = nullptr; -}; - -/** - * @brief RAII wrapper for git_reference - */ -class Reference { -public: - explicit Reference(git_reference* reference) - : reference_(reference) - { - } - - ~Reference() - { - if (reference_) { - git_reference_free(reference_); - } - } - - Reference(const Reference&) = delete; - Reference& operator=(const Reference&) = delete; - - Reference(Reference&& other) noexcept - : reference_(other.reference_) - { - other.reference_ = nullptr; - } - - Reference& operator=(Reference&& other) noexcept - { - if (this != &other) { - if (reference_) { - git_reference_free(reference_); - } - reference_ = other.reference_; - other.reference_ = nullptr; - } - return *this; - } - - const git_oid* oid() const - { - return git_reference_target(reference_); - } - - const char* name() const - { - return git_reference_name(reference_); - } - - std::expected setTarget(const Commit& c, const std::string& message) - { - if (git_reference_set_target(&reference_, reference_, c.oid(), message.c_str()) != GIT_OK) { - return std::unexpected(make_error_code(GitError::SetReferenceTargetFailed)); - } - return {}; - } - - git_reference* get() const - { - return reference_; - } - -private: - git_reference* reference_ = nullptr; -}; - -/** - * @brief The internal class for the repository class. - */ -class Repository::Internal { -public: - // -- - Internal(const std::filesystem::path& path) - { - if (git_repository_open(&repository, path.c_str()) != GIT_OK) { - repository = nullptr; // Ensure it's null on failure - } - } - // -- - Internal(const std::string& url, const std::filesystem::path& path) - { - git_clone_options options; - if (git_clone_options_init(&options, GIT_CLONE_OPTIONS_VERSION) != GIT_OK) { - repository = nullptr; - return; - } - - if (git_clone(&repository, url.c_str(), path.c_str(), &options) != GIT_OK) { - repository = nullptr; - } - } - - bool isValid() const - { - return repository != nullptr; - } - - std::error_code lastError() const - { - // For now, return a generic error. In a more sophisticated implementation, - // we could capture the specific libgit2 error - return make_error_code(repository ? GitError::RemoteLookupFailed : GitError::RepositoryOpenFailed); - } - // -- - ~Internal() - { - if (repository) { - git_repository_free(repository); - } - } - - // -- - std::expected update(const std::string& r = "origin", const std::string& b = "main") - { - auto fetchResult = fetch(r); - if (! fetchResult) { - return std::unexpected(fetchResult.error()); - } - - auto refResult = createReference("refs/remotes/" + r + "/" + b); - if (! refResult) { - return std::unexpected(refResult.error()); - } - - auto commitResult = createCommit(refResult->oid()); - if (! commitResult) { - return std::unexpected(commitResult.error()); - } - - auto localRefResult = createReference("refs/heads/" + b); - if (! localRefResult) { - return std::unexpected(localRefResult.error()); - } - - return merge(*commitResult, *localRefResult); - } - - // -- - std::expected fetch(const std::string& r = "origin") - { - git_remote* remote = nullptr; - if (git_remote_lookup(&remote, repository, r.c_str()) != GIT_OK) { - return std::unexpected(make_error_code(GitError::RemoteLookupFailed)); - } - - auto cleanup = [remote]() { - git_remote_free(remote); - }; - - if (git_remote_fetch(remote, nullptr, nullptr, nullptr) != GIT_OK) { - cleanup(); - return std::unexpected(make_error_code(GitError::FetchFailed)); - } - - cleanup(); - return {}; - } - - std::expected createReference(const std::string& name) - { - git_reference* reference = nullptr; - if (git_reference_lookup(&reference, repository, name.c_str()) != GIT_OK) { - return std::unexpected(make_error_code(GitError::ReferenceLookupFailed)); - } - return Reference{reference}; - } - - std::expected createCommit(const git_oid* oid) - { - git_commit* commit = nullptr; - if (git_commit_lookup(&commit, repository, oid) != GIT_OK) { - return std::unexpected(make_error_code(GitError::CommitLookupFailed)); - } - return Commit{commit}; - } - - // -- - std::expected merge(Commit& co, Reference& ref) - { - git_merge_analysis_t analysis; - git_merge_preference_t preference; - - git_annotated_commit* annotation = nullptr; - if (git_annotated_commit_from_ref(&annotation, repository, ref.get()) != GIT_OK) { - return std::unexpected(make_error_code(GitError::AnnotatedCommitCreationFailed)); - } - - auto cleanup = [annotation]() { - git_annotated_commit_free(annotation); - }; - const git_annotated_commit* annotations[] = {annotation}; - - if (git_merge_analysis(&analysis, &preference, repository, annotations, 1) != GIT_OK) { - cleanup(); - return std::unexpected(make_error_code(GitError::MergeAnalysisFailed)); - } - - if (analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) { - cleanup(); - return {}; - } else if (analysis & GIT_MERGE_ANALYSIS_FASTFORWARD) { - auto setTargetResult = ref.setTarget(co, "Fast-forward"); - if (! setTargetResult) { - cleanup(); - return std::unexpected(setTargetResult.error()); - } - - auto setHeadResult = setHeadToRef(ref); - if (! setHeadResult) { - cleanup(); - return std::unexpected(setHeadResult.error()); - } - - auto checkoutResult = checkoutHead(); - cleanup(); - return checkoutResult; - } else if (analysis & GIT_MERGE_ANALYSIS_NORMAL) { - git_merge_options options; - if (git_merge_init_options(&options, GIT_MERGE_OPTIONS_VERSION) != GIT_OK) { - cleanup(); - return std::unexpected(make_error_code(GitError::MergeOptionsFailed)); - } - - if (git_merge(repository, annotations, 1, &options, nullptr) != GIT_OK) { - cleanup(); - return std::unexpected(make_error_code(GitError::MergeFailed)); - } - } - - cleanup(); - return {}; - } - - // -- - std::expected setHeadToRef(const Reference& l) - { - if (git_repository_set_head(repository, l.name()) != GIT_OK) { - return std::unexpected(make_error_code(GitError::SetHeadFailed)); - } - return {}; - } - - // -- - std::expected checkoutHead() - { - git_checkout_options options; - if (git_checkout_options_init(&options, GIT_CHECKOUT_OPTIONS_VERSION) != GIT_OK) { - return std::unexpected(make_error_code(GitError::CheckoutOptionsFailed)); - } - - if (git_checkout_head(repository, &options) != GIT_OK) { - return std::unexpected(make_error_code(GitError::CheckoutFailed)); - } - - return {}; - } - - // -- - git_repository* repository = nullptr; -}; - -Repository::Repository(const std::filesystem::path& p) - : impl_(std::make_unique(p)) -{ -} - -Repository::Repository(const std::string& url, const std::filesystem::path& p) - : impl_(std::make_unique(url, p)) -{ -} - -Repository::~Repository() = default; - -std::expected Repository::update(const std::string& remote, const std::string& branch) -{ - return impl_->update(remote, branch); -} - -} // namespace scrap::repository::libgit diff --git a/src/repository/driver/LibGitRepository.h b/src/repository/driver/LibGitRepository.h deleted file mode 100644 index 40b637a..0000000 --- a/src/repository/driver/LibGitRepository.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace scrap::repository::libgit { - -class Repository { -public: - Repository(const std::filesystem::path& path); - Repository(const std::string& url, const std::filesystem::path& path); - ~Repository(); - - std::expected update(const std::string& remote, const std::string& branch); - -private: - class Internal; - std::unique_ptr impl_; -}; - -} // namespace scrap::repository::libgit diff --git a/src/repository/model/Repository.cpp b/src/repository/model/Repository.cpp deleted file mode 100644 index 870f55f..0000000 --- a/src/repository/model/Repository.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "repository/model/Repository.h" -#include "repository/driver/Driver.h" - -namespace scrap { - -Repository::Repository(std::shared_ptr driver, const std::filesystem::path& path) - : driver_(std::move(driver)), directory_(path) -{ -} - -Repository::Repository(const Repository& r) - : driver_(r.driver_), directory_(r.directory_) -{ -} - -Repository::~Repository() = default; - -void Repository::clone(const std::string& url) -{ - driver_->clone(url, directory_); -} - -void Repository::update() -{ - driver_->update(directory_); -} - -} // namespace scrap diff --git a/src/repository/model/Repository.h b/src/repository/model/Repository.h deleted file mode 100644 index c0863bb..0000000 --- a/src/repository/model/Repository.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace scrap { - -namespace repository { -class Driver; -} - -class Repository { -public: - enum class Type { - Git, - }; - - Repository(std::shared_ptr driver, const std::filesystem::path& directory); - Repository(const Repository&); - ~Repository(); - - void clone(const std::string& url); - void update(); - -private: - std::shared_ptr driver_; - std::filesystem::path directory_; -}; - -} // namespace scrap diff --git a/src/shared/command/Application.cpp b/src/shared/command/Application.cpp deleted file mode 100644 index 4cc263c..0000000 --- a/src/shared/command/Application.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "shared/command/Application.h" -#include "shared/command/ApplicationCommandHandler.h" -#include - -namespace scrap { - -Application::Application() - : commandHandler_(ApplicationCommandHandlerFactory::create()) -{ -} - -Application::~Application() = default; - -Application::Application(Application&&) noexcept = default; - -Application& Application::operator=(Application&&) noexcept = default; - -int Application::run(std::span args) -{ - commandHandler_->configureCommands(); - return commandHandler_->execute(args); -} - -} // namespace scrap diff --git a/src/shared/command/Application.h b/src/shared/command/Application.h deleted file mode 100644 index 48072f5..0000000 --- a/src/shared/command/Application.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include "shared/command/ApplicationCommandHandler.h" -#include -#include - -namespace scrap { - -/** - * @brief Main application entry point following Clean Architecture - * - * This class represents the main application orchestrator that - * coordinates CLI parsing and command execution without exposing - * implementation details. - */ -class Application { -public: - Application(); - ~Application(); - - // Non-copyable due to unique_ptr member - Application(const Application&) = delete; - Application& operator=(const Application&) = delete; - - // Movable - Application(Application&&) noexcept; - Application& operator=(Application&&) noexcept; - - /** - * @brief Run the application - * @param args Command line arguments as a span - * @return Exit code - */ - int run(std::span args); - -private: - std::unique_ptr commandHandler_; - - void registerOperations(); -}; - -} // namespace scrap diff --git a/src/shared/command/ApplicationCommandHandler.cpp b/src/shared/command/ApplicationCommandHandler.cpp deleted file mode 100644 index 35e1594..0000000 --- a/src/shared/command/ApplicationCommandHandler.cpp +++ /dev/null @@ -1,202 +0,0 @@ -#include "shared/command/ApplicationCommandHandler.h" -#include "project/ProjectModule.h" -#include "project/command/BuildOperation.h" -#include "project/command/CleanOperation.h" -#include "project/command/NewOperation.h" -#include "project/command/RunOperation.h" -#include "project/service/ProjectService.h" -#include "shared/command/CommandOptions.h" -#include "shared/command/Operation.h" -#include "shared/command/driver/CLI11CommandDispatcher.h" -#include "shared/command/driver/CLI11Parser.h" -#include "shared/presentation/driver/ConsolePresenter.h" -#include "template/TemplateModule.h" -#include "template/command/ListOperation.h" -#include "template/command/UpdateOperation.h" -#include "toolchain/ToolchainModule.h" -#include "toolchain/command/InstallOperation.h" -#include "toolchain/command/ListOperation.h" -#include "toolchain/command/SelectOperation.h" -#include "toolchain/service/ToolchainService.h" -#include -#include - -namespace scrap { - -ApplicationCommandHandler::ApplicationCommandHandler(std::unique_ptr parser, - std::unique_ptr dispatcher, - std::shared_ptr presenter) - : parser_(std::move(parser)), dispatcher_(std::move(dispatcher)), presenter_(presenter) -{ - // Set presenter on parser if it's a CLI11Parser - if (auto* cli11Parser = dynamic_cast(parser_.get())) { - cli11Parser->setPresenter(presenter_); - } -} - -ApplicationCommandHandler::~ApplicationCommandHandler() = default; - -ApplicationCommandHandler::ApplicationCommandHandler(ApplicationCommandHandler&&) noexcept = default; - -ApplicationCommandHandler& ApplicationCommandHandler::operator=(ApplicationCommandHandler&&) noexcept = default; - -int ApplicationCommandHandler::execute(std::span args) -{ - try { - // Parse command line arguments - CommandRequest request = parser_->parse(args); - - // Dispatch to appropriate command - CommandResult result = dispatcher_->dispatch(request); - - // Handle result - switch (result.status()) { - case CommandResult::Status::Success: - if (! result.message().empty()) { - std::cout << result.message() << std::endl; - } - return 0; - - case CommandResult::Status::Failure: - std::cerr << "Error: " << result.message() << std::endl; - return 1; - - case CommandResult::Status::InvalidCommand: - std::cerr << result.message() << std::endl; - return 1; - } - - } catch (const CLI::ParseError& e) { - // Handle CLI11 errors properly - note this is a simplified version - // In the actual CLI11 integration, we would need access to the CLI::App to properly handle this - std::cerr << "Usage error: " << e.what() << std::endl; - return 2; - } catch (const std::exception& e) { - std::cerr << "Fatal error: " << e.what() << std::endl; - return 1; - } - - return 0; -} - -void ApplicationCommandHandler::registerRootOperation(const std::string& commandName, - std::shared_ptr operation) -{ - dispatcher_->registerOperation(commandName, operation); -} - -void ApplicationCommandHandler::configureCommands() -{ - registerDomainModules(); - setupCommandStructure(); -} - -void ApplicationCommandHandler::registerDomainModules() -{ - // Register toolchain domain module - toolchain::ToolchainModule::registerCommands( - *dispatcher_, std::shared_ptr(parser_.get(), [](CLIParser*) {}), presenter_); - - // Register project domain module - project::ProjectModule::registerCommands( - *dispatcher_, std::shared_ptr(parser_.get(), [](CLIParser*) {}), presenter_); - - // Register template domain module - template_system::TemplateModule::registerCommands( - *dispatcher_, std::shared_ptr(parser_.get(), [](CLIParser*) {}), presenter_); - - // TODO: Register other domain modules as they are implemented - // package::PackageModule::registerCommands(*dispatcher_, parser_, presenter_); -} - -void ApplicationCommandHandler::setupCommandStructure() -{ - // Get available commands from domain modules - auto toolchainCommands = toolchain::ToolchainModule::availableCommands(); - auto projectCommands = project::ProjectModule::availableCommands(); - auto templateCommands = template_system::TemplateModule::availableCommands(); - - // Merge commands - std::vector> allCommands; - allCommands.insert(allCommands.end(), toolchainCommands.begin(), toolchainCommands.end()); - allCommands.insert(allCommands.end(), projectCommands.begin(), projectCommands.end()); - allCommands.insert(allCommands.end(), templateCommands.begin(), templateCommands.end()); - - // Configure root commands - parser_->configureCommands(allCommands); - - // Configure subcommands - auto toolchainSubcommands = toolchain::ToolchainModule::availableSubcommands(); - parser_->configureSubcommands("toolchain", toolchainSubcommands); - - auto templateSubcommands = template_system::TemplateModule::availableSubcommands(); - parser_->configureSubcommands("template", templateSubcommands); - - // Now configure command options after commands are set up - configureCommandOptions(); -} - -void ApplicationCommandHandler::configureCommandOptions() -{ - // Configure options for all project commands - auto projectService = std::make_shared(nullptr, presenter_); - - // Project commands - auto newOp = std::make_shared(projectService); - parser_->configureCommandOptions("new", newOp->describeOptions()); - - auto buildOp = std::make_shared(projectService); - parser_->configureCommandOptions("build", buildOp->describeOptions()); - - auto runOp = std::make_shared(projectService); - parser_->configureCommandOptions("run", runOp->describeOptions()); - - auto cleanOp = std::make_shared(projectService); - parser_->configureCommandOptions("clean", cleanOp->describeOptions()); - - // Configure options for toolchain subcommands - auto toolchainService = std::make_shared(); - - auto listOp = std::make_shared(toolchainService); - parser_->configureCommandOptions("toolchain.list", listOp->describeOptions()); - - auto installOp = std::make_shared(toolchainService); - parser_->configureCommandOptions("toolchain.install", installOp->describeOptions()); - - auto selectOp = std::make_shared(toolchainService); - parser_->configureCommandOptions("toolchain.select", selectOp->describeOptions()); - - // Configure options for template subcommands - auto templateService = template_system::TemplateModule::createTemplateService(presenter_); - - auto templateListOp = std::make_shared(templateService); - parser_->configureCommandOptions("template.list", templateListOp->describeOptions()); - - auto templateUpdateOp = std::make_shared(templateService); - parser_->configureCommandOptions("template.update", templateUpdateOp->describeOptions()); -} - -// ApplicationCommandHandlerFactory implementation -std::unique_ptr ApplicationCommandHandlerFactory::create() -{ - auto parserFactory = std::make_unique(); - auto parser = parserFactory->createParser("scrap", "Modern C++ development tool"); - auto dispatcher = std::make_unique(); - auto presenterFactory = std::make_unique(); - auto presenter = presenterFactory->createPresenter(); - - return std::make_unique(std::move(parser), std::move(dispatcher), std::move(presenter)); -} - -std::unique_ptr -ApplicationCommandHandlerFactory::create(std::unique_ptr parserFactory, - std::unique_ptr dispatcher) -{ - auto parser = parserFactory->createParser("scrap", "Modern C++ development tool"); - auto presenterFactory = std::make_unique(); - auto presenter = presenterFactory->createPresenter(); - - return std::make_unique(std::move(parser), std::move(dispatcher), std::move(presenter)); -} - -} // namespace scrap diff --git a/src/shared/command/ApplicationCommandHandler.h b/src/shared/command/ApplicationCommandHandler.h deleted file mode 100644 index a8f3d35..0000000 --- a/src/shared/command/ApplicationCommandHandler.h +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once - -#include "shared/command/CLIParser.h" -#include "shared/command/CommandDispatcher.h" -#include "shared/presentation/Presenter.h" -#include -#include -#include - -namespace scrap { - -class Operation; - -/** - * @brief Application-level command handler orchestrating CLI parsing and command dispatch - * - * This class represents the Application Layer in Clean Architecture, - * orchestrating the interaction between CLI parsing and command execution - * without depending on implementation details. - */ -class ApplicationCommandHandler { -public: - ApplicationCommandHandler(std::unique_ptr parser, - std::unique_ptr dispatcher, - std::shared_ptr presenter); - ~ApplicationCommandHandler(); - - // Non-copyable due to unique_ptr members - ApplicationCommandHandler(const ApplicationCommandHandler&) = delete; - ApplicationCommandHandler& operator=(const ApplicationCommandHandler&) = delete; - - // Movable - ApplicationCommandHandler(ApplicationCommandHandler&&) noexcept; - ApplicationCommandHandler& operator=(ApplicationCommandHandler&&) noexcept; - - /** - * @brief Execute command from command line arguments - * @param args Command line arguments as a span - * @return Exit code (0 for success, non-zero for failure) - */ - int execute(std::span args); - - /** - * @brief Register a root-level operation - * @param commandName Command name - * @param operation Operation to execute - */ - void registerRootOperation(const std::string& commandName, std::shared_ptr operation); - - /** - * @brief Configure command structure for CLI help generation - */ - void configureCommands(); - -private: - std::unique_ptr parser_; - std::unique_ptr dispatcher_; - std::shared_ptr presenter_; - - void setupCommandStructure(); - void registerDomainModules(); - void configureCommandOptions(); -}; - -/** - * @brief Factory for creating application command handlers - */ -class ApplicationCommandHandlerFactory { -public: - /** - * @brief Create application command handler with default implementations - * @return Unique pointer to application command handler - */ - static std::unique_ptr create(); - - /** - * @brief Create application command handler with specific implementations - * @param parserFactory Parser factory - * @param dispatcher Command dispatcher - * @return Unique pointer to application command handler - */ - static std::unique_ptr create(std::unique_ptr parserFactory, - std::unique_ptr dispatcher); -}; - -} // namespace scrap diff --git a/src/shared/command/CLIParser.h b/src/shared/command/CLIParser.h deleted file mode 100644 index be15a1c..0000000 --- a/src/shared/command/CLIParser.h +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once - -#include "shared/command/CommandDispatcher.h" -#include -#include -#include -#include - -namespace scrap { - -// Forward declaration -class CommandOptions; - -/** - * @brief Pure interface for CLI parsing - * - * This interface abstracts CLI parsing implementation details, - * allowing different CLI libraries to be used without affecting - * the domain layer. - */ -class CLIParser { -public: - virtual ~CLIParser() = default; - - /** - * @brief Parse command line arguments into a command request - * @param args Command line arguments as a span - * @return Parsed command request - */ - virtual CommandRequest parse(std::span args) = 0; - - /** - * @brief Configure the parser with available commands - * @param commands List of available command names and descriptions - */ - virtual void configureCommands(const std::vector>& commands) = 0; - - /** - * @brief Add subcommand configuration - * @param parentCommand Parent command name - * @param subcommands List of subcommand names and descriptions - */ - virtual void configureSubcommands(const std::string& parentCommand, - const std::vector>& subcommands) = 0; - - /** - * @brief Get help text for a specific command - * @param commandPath Command path (e.g., "toolchain" or "toolchain.list") - * @return Help text string - */ - virtual std::string helpText(const std::string& commandPath = "") = 0; - - /** - * @brief Configure command options from metadata - * @param command Command name - * @param options Command options metadata - */ - virtual void configureCommandOptions(const std::string& command, const CommandOptions& options) = 0; -}; - -/** - * @brief Factory for creating CLI parsers - */ -class CLIParserFactory { -public: - virtual ~CLIParserFactory() = default; - - /** - * @brief Create a CLI parser instance - * @param appName Application name - * @param appDescription Application description - * @return Unique pointer to CLI parser - */ - virtual std::unique_ptr createParser(const std::string& appName, const std::string& appDescription) = 0; -}; - -} // namespace scrap diff --git a/src/shared/command/CommandDispatcher.cpp b/src/shared/command/CommandDispatcher.cpp deleted file mode 100644 index 0aa0607..0000000 --- a/src/shared/command/CommandDispatcher.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "shared/command/CommandDispatcher.h" -#include "shared/command/ParsedOptions.h" - -namespace scrap { - -// CommandRequest implementation -CommandRequest::CommandRequest(const std::string& command, - const std::vector& arguments, - const std::vector& subcommands) - : command_(command), arguments_(arguments), subcommands_(subcommands) -{ -} - -CommandRequest::CommandRequest(const std::string& command, - const ParsedOptions& options, - const std::vector& subcommands) - : command_(command), subcommands_(subcommands), options_(std::make_unique(options)) -{ -} - -const std::string& CommandRequest::command() const -{ - return command_; -} - -const std::vector& CommandRequest::arguments() const -{ - return arguments_; -} - -const std::vector& CommandRequest::subcommands() const -{ - return subcommands_; -} - -const ParsedOptions& CommandRequest::options() const -{ - static ParsedOptions emptyOptions; - return options_ ? *options_ : emptyOptions; -} - -bool CommandRequest::hasOptions() const -{ - return options_ != nullptr; -} - -bool CommandRequest::hasSubcommand() const -{ - return ! subcommands_.empty(); -} - -CommandRequest CommandRequest::createSubcommandRequest() const -{ - if (subcommands_.empty()) { - return CommandRequest("", std::vector{}); - } - - std::vector remainingSubcommands(subcommands_.begin() + 1, subcommands_.end()); - if (hasOptions()) { - return CommandRequest(subcommands_[0], options(), remainingSubcommands); - } else { - return CommandRequest(subcommands_[0], arguments_, remainingSubcommands); - } -} - -// CommandResult implementation -CommandResult::CommandResult(Status status, const std::string& message) - : status_(status), message_(message) -{ -} - -CommandResult::Status CommandResult::status() const -{ - return status_; -} - -const std::string& CommandResult::message() const -{ - return message_; -} - -CommandResult CommandResult::success(const std::string& message) -{ - return CommandResult(Status::Success, message); -} - -CommandResult CommandResult::failure(const std::string& message) -{ - return CommandResult(Status::Failure, message); -} - -CommandResult CommandResult::invalidCommand(const std::string& command) -{ - return CommandResult(Status::InvalidCommand, "Invalid command: " + command); -} - -} // namespace scrap diff --git a/src/shared/command/CommandDispatcher.h b/src/shared/command/CommandDispatcher.h deleted file mode 100644 index e59785a..0000000 --- a/src/shared/command/CommandDispatcher.h +++ /dev/null @@ -1,98 +0,0 @@ -#pragma once - -#include "shared/command/ParsedOptions.h" -#include -#include -#include - -namespace scrap { - -// Forward declarations -class Operation; -class CommandRequest; -class CommandResult; - -/** - * @brief Pure interface for command dispatching - * - * This interface defines the contract for command dispatching without - * exposing any implementation details. It follows the Dependency Inversion - * Principle by depending only on abstractions. - */ -class CommandDispatcher { -public: - virtual ~CommandDispatcher() = default; - - /** - * @brief Dispatch command based on request - * @param request Command request containing parsed arguments and options - * @return Command execution result - */ - virtual CommandResult dispatch(const CommandRequest& request) = 0; - - /** - * @brief Register an operation for a command name - * @param commandName Name of the command - * @param operation Operation to execute for this command - */ - virtual void registerOperation(const std::string& commandName, std::shared_ptr operation) = 0; -}; - -/** - * @brief Command request encapsulating parsed command line input - * - * This class represents a parsed command request without exposing - * the underlying CLI parsing implementation. - */ -class CommandRequest { -public: - CommandRequest(const std::string& command, - const std::vector& arguments, - const std::vector& subcommands = {}); - - CommandRequest(const std::string& command, - const ParsedOptions& options, - const std::vector& subcommands = {}); - - const std::string& command() const; - const std::vector& arguments() const; - const std::vector& subcommands() const; - const ParsedOptions& options() const; - bool hasOptions() const; - - bool hasSubcommand() const; - CommandRequest createSubcommandRequest() const; - -private: - std::string command_; - std::vector arguments_; - std::vector subcommands_; - std::unique_ptr options_; -}; - -/** - * @brief Result of command execution - */ -class CommandResult { -public: - enum class Status { - Success, - Failure, - InvalidCommand - }; - - CommandResult(Status status, const std::string& message = ""); - - Status status() const; - const std::string& message() const; - - static CommandResult success(const std::string& message = ""); - static CommandResult failure(const std::string& message); - static CommandResult invalidCommand(const std::string& command); - -private: - Status status_; - std::string message_; -}; - -} // namespace scrap diff --git a/src/shared/command/CommandOptions.cpp b/src/shared/command/CommandOptions.cpp deleted file mode 100644 index af4c35b..0000000 --- a/src/shared/command/CommandOptions.cpp +++ /dev/null @@ -1,184 +0,0 @@ -#include "CommandOptions.h" - -namespace scrap { - -// CommandOption::Impl -class CommandOption::Impl { -public: - std::string name_; - std::string description_; - OptionType type_; - std::optional defaultValue_; - bool required_ = false; - std::vector choices_; - std::string shortName_; - - Impl(const std::string& name, const std::string& description, OptionType type) - : name_(name), description_(description), type_(type) - { - } -}; - -// CommandOption implementation -CommandOption::CommandOption(const std::string& name, const std::string& description, OptionType type) - : impl_(std::make_unique(name, description, type)) -{ -} - -CommandOption::~CommandOption() = default; - -CommandOption::CommandOption(const CommandOption& other) - : impl_(std::make_unique(*other.impl_)) -{ -} - -CommandOption& CommandOption::operator=(const CommandOption& other) -{ - if (this != &other) { - impl_ = std::make_unique(*other.impl_); - } - return *this; -} - -CommandOption::CommandOption(CommandOption&& other) noexcept = default; - -CommandOption& CommandOption::operator=(CommandOption&& other) noexcept = default; - -const std::string& CommandOption::name() const -{ - return impl_->name_; -} - -const std::string& CommandOption::description() const -{ - return impl_->description_; -} - -OptionType CommandOption::type() const -{ - return impl_->type_; -} - -const std::optional& CommandOption::defaultValue() const -{ - return impl_->defaultValue_; -} - -bool CommandOption::required() const -{ - return impl_->required_; -} - -const std::vector& CommandOption::choices() const -{ - return impl_->choices_; -} - -const std::string& CommandOption::shortName() const -{ - return impl_->shortName_; -} - -CommandOption& CommandOption::withDefault(const std::string& value) -{ - impl_->defaultValue_ = value; - return *this; -} - -CommandOption& CommandOption::withRequired(bool required) -{ - impl_->required_ = required; - return *this; -} - -CommandOption& CommandOption::withChoices(const std::vector& choices) -{ - impl_->choices_ = choices; - return *this; -} - -CommandOption& CommandOption::withShortName(const std::string& shortName) -{ - impl_->shortName_ = shortName; - return *this; -} - -// CommandOptions::Impl -class CommandOptions::Impl { -public: - std::vector positionals_; - std::vector options_; - std::vector flags_; -}; - -// CommandOptions implementation -CommandOptions::CommandOptions() - : impl_(std::make_unique()) -{ -} - -CommandOptions::~CommandOptions() = default; - -CommandOptions::CommandOptions(const CommandOptions& other) - : impl_(std::make_unique(*other.impl_)) -{ -} - -CommandOptions& CommandOptions::operator=(const CommandOptions& other) -{ - if (this != &other) { - impl_ = std::make_unique(*other.impl_); - } - return *this; -} - -CommandOptions::CommandOptions(CommandOptions&& other) noexcept = default; - -CommandOptions& CommandOptions::operator=(CommandOptions&& other) noexcept = default; - -CommandOptions& CommandOptions::addPositional(const std::string& name, const std::string& description) -{ - impl_->positionals_.emplace_back(name, description, OptionType::String); - return *this; -} - -CommandOptions& CommandOptions::addOption(const CommandOption& option) -{ - impl_->options_.push_back(option); - return *this; -} - -CommandOptions& CommandOptions::addFlag(const std::string& name, const std::string& description) -{ - impl_->flags_.emplace_back(name, description, OptionType::Flag); - return *this; -} - -CommandOptions& -CommandOptions::addFlag(const std::string& name, const std::string& shortName, const std::string& description) -{ - impl_->flags_.emplace_back(name, description, OptionType::Flag).withShortName(shortName); - return *this; -} - -const std::vector& CommandOptions::positionals() const -{ - return impl_->positionals_; -} - -const std::vector& CommandOptions::options() const -{ - return impl_->options_; -} - -const std::vector& CommandOptions::flags() const -{ - return impl_->flags_; -} - -bool CommandOptions::hasOptions() const -{ - return ! impl_->positionals_.empty() || ! impl_->options_.empty() || ! impl_->flags_.empty(); -} - -} // namespace scrap diff --git a/src/shared/command/CommandOptions.h b/src/shared/command/CommandOptions.h deleted file mode 100644 index 500e593..0000000 --- a/src/shared/command/CommandOptions.h +++ /dev/null @@ -1,99 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace scrap { - -/** - * @brief Enumeration for command option types - */ -enum class OptionType { - String, - Integer, - Boolean, - Flag -}; - -/** - * @brief Value object representing a single command option - * - * This class follows the builder pattern to allow fluent configuration - * of option properties while maintaining immutability after construction. - * Uses PIMPL pattern to ensure ABI safety. - */ -class CommandOption { -public: - CommandOption(const std::string& name, const std::string& description, OptionType type = OptionType::String); - ~CommandOption(); - - // Copy constructor and assignment - CommandOption(const CommandOption& other); - CommandOption& operator=(const CommandOption& other); - - // Move constructor and assignment - CommandOption(CommandOption&& other) noexcept; - CommandOption& operator=(CommandOption&& other) noexcept; - - // Accessors (no 'get' prefix per coding standards) - const std::string& name() const; - const std::string& description() const; - OptionType type() const; - const std::optional& defaultValue() const; - bool required() const; - const std::vector& choices() const; - const std::string& shortName() const; - - // Builder methods for fluent configuration - CommandOption& withDefault(const std::string& value); - CommandOption& withRequired(bool required = true); - CommandOption& withChoices(const std::vector& choices); - CommandOption& withShortName(const std::string& shortName); - -private: - class Impl; - std::unique_ptr impl_; -}; - -/** - * @brief Aggregate containing all command options - * - * This class provides a fluent interface for building command option - * specifications without coupling to any specific CLI library. - * Uses PIMPL pattern to ensure ABI safety. - */ -class CommandOptions { -public: - CommandOptions(); - ~CommandOptions(); - - // Copy constructor and assignment - CommandOptions(const CommandOptions& other); - CommandOptions& operator=(const CommandOptions& other); - - // Move constructor and assignment - CommandOptions(CommandOptions&& other) noexcept; - CommandOptions& operator=(CommandOptions&& other) noexcept; - - // Builder methods for adding different option types - CommandOptions& addPositional(const std::string& name, const std::string& description); - CommandOptions& addOption(const CommandOption& option); - CommandOptions& addFlag(const std::string& name, const std::string& description); - CommandOptions& addFlag(const std::string& name, const std::string& shortName, const std::string& description); - - // Accessors for configured options - const std::vector& positionals() const; - const std::vector& options() const; - const std::vector& flags() const; - - // Check if options are defined - bool hasOptions() const; - -private: - class Impl; - std::unique_ptr impl_; -}; - -} // namespace scrap diff --git a/src/shared/command/CompositeOperation.cpp b/src/shared/command/CompositeOperation.cpp deleted file mode 100644 index aea5092..0000000 --- a/src/shared/command/CompositeOperation.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "CompositeOperation.h" -#include "shared/presentation/Presenter.h" -#include -#include - -namespace scrap { - -CompositeOperation::CompositeOperation() = default; - -CompositeOperation::~CompositeOperation() = default; - -void CompositeOperation::addSubOperation(const std::string& name, std::shared_ptr operation) -{ - if (operation) { - subOperations_[name] = operation; - // Propagate presenter if already set - if (auto output = presenter()) { - operation->setPresenter(output); - } - } -} - -void CompositeOperation::removeSubOperation(const std::string& name) -{ - subOperations_.erase(name); -} - -bool CompositeOperation::hasSubOperation(const std::string& name) const -{ - return subOperations_.find(name) != subOperations_.end(); -} - -std::vector CompositeOperation::subOperationNames() const -{ - std::vector names; - names.reserve(subOperations_.size()); - for (const auto& [name, _] : subOperations_) { - names.push_back(name); - } - return names; -} - -std::shared_ptr CompositeOperation::subOperation(const std::string& name) const -{ - auto it = subOperations_.find(name); - return (it != subOperations_.end()) ? it->second : nullptr; -} - -void CompositeOperation::execute(const std::vector& args) -{ - if (args.empty() || args[0] == "help" || args[0] == "--help") { - displayHelp(); - return; - } - - const std::string& subcommand = args[0]; - auto operation = subOperation(subcommand); - - if (! operation) { - auto output = presenter(); - if (output) { - std::stringstream ss; - ss << "Unknown subcommand: '" << subcommand << "'"; - output->displayError(ss.str()); - output->displayInfo("Run with 'help' to see available subcommands"); - } - return; - } - - // Create new args without the subcommand - std::vector subArgs(args.begin() + 1, args.end()); - operation->execute(subArgs); -} - -void CompositeOperation::setPresenter(std::shared_ptr presenter) -{ - Operation::setPresenter(presenter); - // Propagate to all sub-operations - for (auto& [_, operation] : subOperations_) { - if (operation) { - operation->setPresenter(presenter); - } - } -} - -void CompositeOperation::displayHelp() const -{ - auto output = presenter(); - if (! output) { - return; - } - - output->displayInfo("Available subcommands:"); - auto names = subOperationNames(); - std::sort(names.begin(), names.end()); - - for (const auto& name : names) { - std::stringstream ss; - ss << " " << name; - output->displayInfo(ss.str()); - } -} - -} // namespace scrap diff --git a/src/shared/command/CompositeOperation.h b/src/shared/command/CompositeOperation.h deleted file mode 100644 index 3d550d9..0000000 --- a/src/shared/command/CompositeOperation.h +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once - -#include "Operation.h" -#include -#include -#include -#include - -namespace scrap { - -/** - * @brief Composite operation for handling subcommands - * - * This class implements the Composite pattern for operations that - * have subcommands. It manages a collection of sub-operations and - * dispatches execution to the appropriate one based on the arguments. - */ -class CompositeOperation : public Operation { -public: - CompositeOperation(); - virtual ~CompositeOperation(); - - /** - * @brief Add a sub-operation with the given name - * @param name Name of the subcommand - * @param operation Operation to execute for this subcommand - */ - void addSubOperation(const std::string& name, std::shared_ptr operation); - - /** - * @brief Remove a sub-operation - * @param name Name of the subcommand to remove - */ - void removeSubOperation(const std::string& name); - - /** - * @brief Check if a subcommand exists - * @param name Name of the subcommand - * @return true if the subcommand exists - */ - bool hasSubOperation(const std::string& name) const; - - /** - * @brief Get all subcommand names - * @return Vector of subcommand names - */ - std::vector subOperationNames() const; - - /** - * @brief Execute the appropriate sub-operation based on arguments - * @param args Command line arguments (first should be subcommand name) - */ - void execute(const std::vector& args) override; - - /** - * @brief Set presenter for this operation and all sub-operations - * @param presenter Presenter instance - */ - void setPresenter(std::shared_ptr presenter) override; - -protected: - /** - * @brief Get a sub-operation by name - * @param name Name of the subcommand - * @return Shared pointer to the operation, or nullptr if not found - */ - std::shared_ptr subOperation(const std::string& name) const; - - /** - * @brief Display help for available subcommands - */ - virtual void displayHelp() const; - -private: - std::map> subOperations_; -}; - -} // namespace scrap diff --git a/src/shared/command/HelpCommand.cpp b/src/shared/command/HelpCommand.cpp deleted file mode 100644 index 4c72be0..0000000 --- a/src/shared/command/HelpCommand.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "shared/command/HelpCommand.h" -#include "shared/presentation/Presenter.h" - -namespace scrap { - -HelpCommand::HelpCommand(std::shared_ptr parser, const std::string& commandPath) - : parser_(parser), commandPath_(commandPath) -{ -} - -HelpCommand::~HelpCommand() = default; - -void HelpCommand::execute(const std::vector& /*args*/) -{ - auto output = presenter(); - if (! output) { - return; // No presenter available - } - - if (parser_) { - std::string helpText = parser_->helpText(commandPath_); - output->displayInfo(helpText); - } else { - output->displayError("Help system not available"); - } -} - -} // namespace scrap diff --git a/src/shared/command/HelpCommand.h b/src/shared/command/HelpCommand.h deleted file mode 100644 index 6557bee..0000000 --- a/src/shared/command/HelpCommand.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include "shared/command/CLIParser.h" -#include "shared/command/Operation.h" -#include - -namespace scrap { - -/** - * @brief Generic help command that uses CLI parser's help functionality - * - * This command displays help information for any command path using - * the CLI parser's built-in help system. - */ -class HelpCommand : public Operation { -public: - explicit HelpCommand(std::shared_ptr parser, const std::string& commandPath = ""); - ~HelpCommand() override; - - void execute(const std::vector& args) override; - -private: - std::shared_ptr parser_; - std::string commandPath_; -}; - -} // namespace scrap diff --git a/src/shared/command/Operation.cpp b/src/shared/command/Operation.cpp deleted file mode 100644 index 774fff3..0000000 --- a/src/shared/command/Operation.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "shared/command/Operation.h" -#include "shared/command/CommandOptions.h" -#include "shared/command/ParsedOptions.h" -#include "shared/presentation/Presenter.h" -#include -#include -#include -#include - -namespace scrap { - -Operation::Operation() = default; - -Operation::Operation(const Operation&) = default; - -Operation& Operation::operator=(const Operation&) = default; - -Operation::Operation(Operation&&) noexcept = default; - -Operation& Operation::operator=(Operation&&) noexcept = default; - -Operation::~Operation() = default; - -void Operation::setPresenter(std::shared_ptr presenter) -{ - presenter_ = std::move(presenter); -} - -std::shared_ptr Operation::presenter() const -{ - return presenter_; -} - -void Operation::execute(const std::vector& /*args*/) -{ - // Default implementation does nothing -} - -CommandOptions Operation::describeOptions() const -{ - // Default implementation returns empty options - return {}; -} - -void Operation::execute(const ParsedOptions& options) -{ - // Default implementation converts to legacy format for backward compatibility - // Operations that override describeOptions() should also override this method - const auto& args = options.positionalArgs(); - execute(args); -} - -} // namespace scrap diff --git a/src/shared/command/Operation.h b/src/shared/command/Operation.h deleted file mode 100644 index 33f097e..0000000 --- a/src/shared/command/Operation.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace scrap { - -// Forward declarations -class Presenter; -class CommandOptions; -class ParsedOptions; - -/** - * @brief Base class for all executable operations - * - * This class provides the interface for domain operations in the Clean Architecture. - * It has been simplified to remove dependencies on infrastructure details. - */ -class Operation { -public: - Operation(); - Operation(const Operation&); - Operation& operator=(const Operation&); - Operation(Operation&&) noexcept; - Operation& operator=(Operation&&) noexcept; - virtual ~Operation(); - - /** - * @brief Set the presenter for output handling - * @param presenter Presenter instance for output formatting - */ - virtual void setPresenter(std::shared_ptr presenter); - - /** - * @brief Execute the operation with provided arguments - * @param args Command line arguments - */ - virtual void execute(const std::vector& args); - - /** - * @brief Describe command options for CLI configuration - * @return CommandOptions describing this operation's CLI options - */ - [[nodiscard]] virtual CommandOptions describeOptions() const; - - /** - * @brief Execute the operation with parsed options - * @param options Parsed command-line options - * - * This method provides a type-safe alternative to string-based argument parsing. - * The default implementation calls the legacy execute() method for backward compatibility. - */ - virtual void execute(const ParsedOptions& options); - -protected: - /** - * @brief Get the current presenter instance - * @return Shared pointer to presenter - */ - [[nodiscard]] std::shared_ptr presenter() const; - -private: - std::shared_ptr presenter_; -}; - -} // namespace scrap diff --git a/src/shared/command/ParsedOptions.cpp b/src/shared/command/ParsedOptions.cpp deleted file mode 100644 index 426b44c..0000000 --- a/src/shared/command/ParsedOptions.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include "ParsedOptions.h" -#include - -namespace scrap { - -// ParsedOptions::Impl -class ParsedOptions::Impl { -public: - std::unordered_map values_; - std::vector positionalArgs_; -}; - -// ParsedOptions implementation -ParsedOptions::ParsedOptions() - : impl_(std::make_unique()) -{ -} - -ParsedOptions::~ParsedOptions() = default; - -ParsedOptions::ParsedOptions(const ParsedOptions& other) - : impl_(std::make_unique(*other.impl_)) -{ -} - -ParsedOptions& ParsedOptions::operator=(const ParsedOptions& other) -{ - if (this != &other) { - impl_ = std::make_unique(*other.impl_); - } - return *this; -} - -ParsedOptions::ParsedOptions(ParsedOptions&& other) noexcept = default; - -ParsedOptions& ParsedOptions::operator=(ParsedOptions&& other) noexcept = default; - -void ParsedOptions::set(const std::string& key, const Value& value) -{ - impl_->values_[key] = value; -} - -std::optional ParsedOptions::string(const std::string& key) const -{ - auto it = impl_->values_.find(key); - if (it != impl_->values_.end()) { - if (const auto* str = std::get_if(&it->second)) { - return *str; - } - } - return std::nullopt; -} - -std::optional ParsedOptions::integer(const std::string& key) const -{ - auto it = impl_->values_.find(key); - if (it != impl_->values_.end()) { - if (const auto* intVal = std::get_if(&it->second)) { - return *intVal; - } - } - return std::nullopt; -} - -bool ParsedOptions::flag(const std::string& key) const -{ - auto it = impl_->values_.find(key); - if (it != impl_->values_.end()) { - if (const auto* boolVal = std::get_if(&it->second)) { - return *boolVal; - } - } - return false; -} - -std::vector ParsedOptions::stringList(const std::string& key) const -{ - auto it = impl_->values_.find(key); - if (it != impl_->values_.end()) { - if (const auto* vec = std::get_if>(&it->second)) { - return *vec; - } - } - return {}; -} - -bool ParsedOptions::has(const std::string& key) const -{ - return impl_->values_.find(key) != impl_->values_.end(); -} - -const std::vector& ParsedOptions::positionalArgs() const -{ - return impl_->positionalArgs_; -} - -void ParsedOptions::setPositionalArgs(const std::vector& args) -{ - impl_->positionalArgs_ = args; -} - -} // namespace scrap diff --git a/src/shared/command/ParsedOptions.h b/src/shared/command/ParsedOptions.h deleted file mode 100644 index 28712b8..0000000 --- a/src/shared/command/ParsedOptions.h +++ /dev/null @@ -1,92 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace scrap { - -/** - * @brief Container for parsed command-line options - * - * This class provides type-safe access to parsed command-line options - * without exposing the underlying CLI library implementation. - * Uses PIMPL pattern to ensure ABI safety. - */ -class ParsedOptions { -public: - using Value = std::variant>; - - ParsedOptions(); - ~ParsedOptions(); - - // Copy constructor and assignment - ParsedOptions(const ParsedOptions& other); - ParsedOptions& operator=(const ParsedOptions& other); - - // Move constructor and assignment - ParsedOptions(ParsedOptions&& other) noexcept; - ParsedOptions& operator=(ParsedOptions&& other) noexcept; - - /** - * @brief Set a parsed option value - * @param key Option name - * @param value Parsed value - */ - void set(const std::string& key, const Value& value); - - /** - * @brief Get a string option value - * @param key Option name - * @return Optional containing the value if present and is a string - */ - std::optional string(const std::string& key) const; - - /** - * @brief Get an integer option value - * @param key Option name - * @return Optional containing the value if present and is an integer - */ - std::optional integer(const std::string& key) const; - - /** - * @brief Check if a flag is set - * @param key Flag name - * @return True if flag is present and set - */ - bool flag(const std::string& key) const; - - /** - * @brief Get a list of string values (for repeated options) - * @param key Option name - * @return Vector of strings, empty if not present - */ - std::vector stringList(const std::string& key) const; - - /** - * @brief Check if an option is present - * @param key Option name - * @return True if option was provided - */ - bool has(const std::string& key) const; - - /** - * @brief Get all remaining positional arguments - * @return Vector of positional arguments - */ - const std::vector& positionalArgs() const; - - /** - * @brief Set positional arguments - * @param args Positional arguments - */ - void setPositionalArgs(const std::vector& args); - -private: - class Impl; - std::unique_ptr impl_; -}; - -} // namespace scrap diff --git a/src/shared/command/driver/CLI11CommandDispatcher.cpp b/src/shared/command/driver/CLI11CommandDispatcher.cpp deleted file mode 100644 index 722d212..0000000 --- a/src/shared/command/driver/CLI11CommandDispatcher.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include "shared/command/driver/CLI11CommandDispatcher.h" -#include "shared/command/Operation.h" -#include "shared/command/ParsedOptions.h" -#include -#include - -namespace scrap { - -/** - * @brief Private implementation class for CLI11CommandDispatcher - */ -class CLI11CommandDispatcher::Impl { -public: - std::map> operations_; - - CommandResult executeOperation(const std::string& command, - const std::vector& args, - const ParsedOptions* options = nullptr) - { - // Empty command should not reach here anymore since CLI11 requires a subcommand - // If it does, it's an error condition - if (command.empty()) { - return CommandResult::invalidCommand("No command specified"); - } - - auto it = operations_.find(command); - if (it == operations_.end()) { - return CommandResult::invalidCommand(command); - } - - try { - // Use parsed options if available, otherwise fall back to string args - if (options) { - it->second->execute(*options); - } else { - it->second->execute(args); - } - return CommandResult::success(); - } catch (const std::exception& e) { - return CommandResult::failure(e.what()); - } - } - - CommandResult dispatchRecursive(const CommandRequest& request) - { - const auto& command = request.command(); - - if (request.hasSubcommand()) { - // This is a parent command with subcommands - // Build full command path for nested dispatch - std::string fullCommand = command; - for (const auto& subcommand : request.subcommands()) { - fullCommand += "." + subcommand; - } - if (request.hasOptions()) { - const ParsedOptions& options = request.options(); - return executeOperation(fullCommand, {}, &options); - } else { - return executeOperation(fullCommand, request.arguments()); - } - } else { - // Leaf command, execute directly - if (request.hasOptions()) { - const ParsedOptions& options = request.options(); - return executeOperation(command, {}, &options); - } else { - return executeOperation(command, request.arguments()); - } - } - } -}; - -// CLI11CommandDispatcher implementation -CLI11CommandDispatcher::CLI11CommandDispatcher() - : impl_(std::make_unique()) -{ -} - -CLI11CommandDispatcher::~CLI11CommandDispatcher() = default; - -CLI11CommandDispatcher::CLI11CommandDispatcher(CLI11CommandDispatcher&&) noexcept = default; - -CLI11CommandDispatcher& CLI11CommandDispatcher::operator=(CLI11CommandDispatcher&&) noexcept = default; - -CommandResult CLI11CommandDispatcher::dispatch(const CommandRequest& request) -{ - return impl_->dispatchRecursive(request); -} - -void CLI11CommandDispatcher::registerOperation(const std::string& commandName, std::shared_ptr operation) -{ - impl_->operations_[commandName] = operation; -} - -} // namespace scrap diff --git a/src/shared/command/driver/CLI11CommandDispatcher.h b/src/shared/command/driver/CLI11CommandDispatcher.h deleted file mode 100644 index 0ef91c6..0000000 --- a/src/shared/command/driver/CLI11CommandDispatcher.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include "shared/command/CommandDispatcher.h" -#include -#include - -namespace scrap { - -class Operation; - -/** - * @brief CLI11-based command dispatcher implementation - * - * This class provides a concrete implementation of CommandDispatcher - * using CLI11 library. Implementation details are hidden using PIMPL pattern. - */ -class CLI11CommandDispatcher : public CommandDispatcher { -public: - CLI11CommandDispatcher(); - ~CLI11CommandDispatcher() override; - - // Non-copyable due to PIMPL - CLI11CommandDispatcher(const CLI11CommandDispatcher&) = delete; - CLI11CommandDispatcher& operator=(const CLI11CommandDispatcher&) = delete; - - // Movable - CLI11CommandDispatcher(CLI11CommandDispatcher&&) noexcept; - CLI11CommandDispatcher& operator=(CLI11CommandDispatcher&&) noexcept; - - CommandResult dispatch(const CommandRequest& request) override; - void registerOperation(const std::string& commandName, std::shared_ptr operation) override; - -private: - class Impl; - std::unique_ptr impl_; -}; - -} // namespace scrap diff --git a/src/shared/command/driver/CLI11Parser.cpp b/src/shared/command/driver/CLI11Parser.cpp deleted file mode 100644 index 62221a1..0000000 --- a/src/shared/command/driver/CLI11Parser.cpp +++ /dev/null @@ -1,359 +0,0 @@ -#include "shared/command/driver/CLI11Parser.h" -#include "shared/command/CommandOptions.h" -#include "shared/command/ParsedOptions.h" -#include "shared/command/driver/PresenterFormatter.h" -#include "shared/constants/version.h" -#include "shared/presentation/Presenter.h" -#include -#include -#include -#include - -namespace scrap { - -/** - * @brief Private implementation class for CLI11Parser - */ -class CLI11Parser::Impl { -public: - CLI::App app_; - std::map subcommands_; - std::unordered_map parsedOptions_; - - // Storage for parsed values - must persist through parsing - struct OptionStorage { - std::unordered_map strings; - std::unordered_map integers; - std::unordered_map flags; - }; - std::unordered_map> storageMap_; - - Impl(const std::string& appName, const std::string& appDescription) - : app_(appDescription, appName) - { - app_.set_version_flag("--version", version()); - - // Require at least one subcommand and show help when missing - app_.require_subcommand(1); - app_.failure_message(CLI::FailureMessage::help); - } - - void setPresenterInternal(std::shared_ptr presenter) - { - if (presenter) { - auto formatter = std::make_shared(presenter); - app_.formatter(formatter); - - // Also set formatter for all existing subcommands - for (auto& [name, subcommand] : subcommands_) { - subcommand->formatter(formatter); - } - } - } - - CommandRequest parseInternal(std::span args) - { - const int argc = static_cast(args.size()); - const char* const* argv = args.data(); - - // Check for help requests before parsing to ensure our configured options are shown - if (argc >= 3 && std::string(argv[2]) == "--help") { - std::string commandName = argv[1]; - auto it = subcommands_.find(commandName); - if (it != subcommands_.end()) { - std::cout << it->second->help() << std::endl; - std::exit(0); - } - } - - try { - app_.parse(argc, argv); - } catch (const CLI::ParseError& e) { - // Special handling for missing subcommand - show help without error message - if (dynamic_cast(&e) && argc == 1) { - // No arguments provided, just show help - std::cout << app_.help() << std::endl; - std::exit(0); - } - - // Handle help requests and other CLI11 errors - // For help requests, CLI11 sets the exit code to 0 - // For errors, it sets non-zero exit codes - int exitCode = app_.exit(e); - - // If it's a help request (exit code 0), we exit successfully - if (exitCode == 0) { - std::exit(0); - } else { - // For other errors, exit with error code - std::exit(exitCode); - } - } - - // Find which command was parsed - std::vector commandPath; - std::vector arguments; - - // Check if no subcommands are available yet (empty app) - if (subcommands_.empty()) { - // No subcommands configured, this is a root command - auto remaining = app_.remaining(); - arguments.assign(remaining.begin(), remaining.end()); - return CommandRequest("", arguments); - } - - // Find parsed subcommand path - CLI::App* current = &app_; - std::string currentCommand; - while (! current->get_subcommands().empty()) { - bool foundParsed = false; - for (auto* sub : current->get_subcommands()) { - if (sub->parsed()) { - commandPath.push_back(sub->get_name()); - if (currentCommand.empty()) { - currentCommand = sub->get_name(); - } else { - currentCommand += "." + sub->get_name(); - } - current = sub; - foundParsed = true; - break; - } - } - if (! foundParsed) - break; - } - - // Get remaining arguments from the last parsed command - auto remaining = current->remaining(); - arguments.assign(remaining.begin(), remaining.end()); - - if (commandPath.empty()) { - return CommandRequest("", arguments); - } - - // Create hierarchical command request - std::string mainCommand = commandPath[0]; - std::vector subcommandPath(commandPath.begin() + 1, commandPath.end()); - - // Check if we have parsed options for this command - auto optIt = parsedOptions_.find(mainCommand); - if (optIt != parsedOptions_.end()) { - // Copy parsed values from storage to ParsedOptions - copyStorageToOptions(mainCommand); - - // Return request with parsed options - return CommandRequest(mainCommand, optIt->second, subcommandPath); - } else { - // Return legacy request with string arguments - return CommandRequest(mainCommand, arguments, subcommandPath); - } - } - - std::string helpTextInternal(const std::string& commandPath) - { - if (commandPath.empty()) { - return app_.help(); - } - - auto it = subcommands_.find(commandPath); - if (it != subcommands_.end()) { - return it->second->help(); - } - - // If not found in flat map, try hierarchical path - size_t dotPos = commandPath.find('.'); - if (dotPos != std::string::npos) { - std::string parentCmd = commandPath.substr(0, dotPos); - auto parentIt = subcommands_.find(parentCmd); - if (parentIt != subcommands_.end()) { - return parentIt->second->help(); - } - } - - return "Command not found: " + commandPath; - } - - void configureCommandOptionsInternal(const std::string& command, const CommandOptions& options) - { - auto it = subcommands_.find(command); - if (it == subcommands_.end()) { - return; - } - - CLI::App* app = it->second; - ParsedOptions& parsed = parsedOptions_[command]; - - // Create storage for this command if it doesn't exist - if (storageMap_.find(command) == storageMap_.end()) { - storageMap_[command] = std::make_unique(); - } - auto& storage = *storageMap_[command]; - - // Configure positional arguments - for (const auto& pos : options.positionals()) { - std::string storageName = pos.name(); - storage.strings[storageName] = ""; - auto* opt = app->add_option(storageName, storage.strings[storageName], pos.description()); - if (pos.required()) { - opt->required(); - } - // CLI11 automatically parses into the storage variable - // We'll copy from storage to parsed after parsing is complete - } - - // Configure options - for (const auto& option : options.options()) { - std::string optName = "--" + option.name(); - std::string storageName = option.name(); - - if (option.type() == OptionType::String) { - storage.strings[storageName] = ""; - auto* opt = app->add_option(optName, storage.strings[storageName], option.description()); - - if (option.defaultValue()) { - opt->default_val(*option.defaultValue()); - storage.strings[storageName] = *option.defaultValue(); - // Set default in parsed options immediately - parsed.set(storageName, *option.defaultValue()); - } - - if (! option.choices().empty()) { - opt->check(CLI::IsMember(option.choices())); - } - - // CLI11 automatically parses into the storage variable - } else if (option.type() == OptionType::Integer) { - storage.integers[storageName] = 0; - auto* opt = app->add_option(optName, storage.integers[storageName], option.description()); - - if (option.defaultValue()) { - opt->default_val(*option.defaultValue()); - storage.integers[storageName] = std::stoi(*option.defaultValue()); - // Set default in parsed options immediately - parsed.set(storageName, storage.integers[storageName]); - } - - // CLI11 automatically parses into the storage variable - } - } - - // Configure flags - for (const auto& flag : options.flags()) { - std::string flagName = "--" + flag.name(); - std::string storageName = flag.name(); - storage.flags[storageName] = false; - - // Add flag option - app->add_flag(flagName, storage.flags[storageName], flag.description()); - - // CLI11 doesn't support dynamic short flag names in this version - // Short flags would need to be configured with add_flag("-h,--help", ...) - // For now we skip short names - - // CLI11 automatically parses into the storage variable - } - - // Don't allow extras if options are configured - app->allow_extras(false); - } - - void copyStorageToOptions(const std::string& command) - { - auto storageIt = storageMap_.find(command); - auto optionsIt = parsedOptions_.find(command); - - if (storageIt != storageMap_.end() && optionsIt != parsedOptions_.end()) { - auto& storage = *storageIt->second; - auto& options = optionsIt->second; - - // Copy string values - for (const auto& [key, value] : storage.strings) { - if (! value.empty()) { - options.set(key, value); - } - } - - // Copy integer values - for (const auto& [key, value] : storage.integers) { - options.set(key, value); - } - - // Copy flag values - for (const auto& [key, value] : storage.flags) { - options.set(key, value); - } - } - } -}; - -// CLI11Parser implementation -CLI11Parser::CLI11Parser(const std::string& appName, const std::string& appDescription) - : impl_(std::make_unique(appName, appDescription)) -{ -} - -CLI11Parser::~CLI11Parser() = default; - -CLI11Parser::CLI11Parser(CLI11Parser&&) noexcept = default; - -CLI11Parser& CLI11Parser::operator=(CLI11Parser&&) noexcept = default; - -CommandRequest CLI11Parser::parse(std::span args) -{ - return impl_->parseInternal(args); -} - -void CLI11Parser::configureCommands(const std::vector>& commands) -{ - for (const auto& [name, description] : commands) { - auto* sub = impl_->app_.add_subcommand(name, description); - - // Configure specific commands to allow additional arguments - if (name == "new" || name == "build" || name == "run" || name == "clean") { - sub->allow_extras(); - } - - impl_->subcommands_[name] = sub; - } -} - -void CLI11Parser::configureSubcommands(const std::string& parentCommand, - const std::vector>& subcommands) -{ - auto it = impl_->subcommands_.find(parentCommand); - if (it == impl_->subcommands_.end()) { - throw std::runtime_error("Parent command not found: " + parentCommand); - } - - CLI::App* parent = it->second; - for (const auto& [name, description] : subcommands) { - auto* sub = parent->add_subcommand(name, description); - std::string fullName = parentCommand + "." + name; - impl_->subcommands_[fullName] = sub; - } -} - -std::string CLI11Parser::helpText(const std::string& commandPath) -{ - return impl_->helpTextInternal(commandPath); -} - -void CLI11Parser::configureCommandOptions(const std::string& command, const CommandOptions& options) -{ - impl_->configureCommandOptionsInternal(command, options); -} - -void CLI11Parser::setPresenter(std::shared_ptr presenter) -{ - impl_->setPresenterInternal(presenter); -} - -// CLI11ParserFactory implementation -std::unique_ptr CLI11ParserFactory::createParser(const std::string& appName, - const std::string& appDescription) -{ - return std::make_unique(appName, appDescription); -} - -} // namespace scrap diff --git a/src/shared/command/driver/CLI11Parser.h b/src/shared/command/driver/CLI11Parser.h deleted file mode 100644 index 2f5a1c7..0000000 --- a/src/shared/command/driver/CLI11Parser.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include "shared/command/CLIParser.h" -#include - -namespace scrap { - -// Forward declaration -class Presenter; - -/** - * @brief CLI11-based parser implementation - * - * This class provides a concrete implementation of CLIParser using CLI11. - * Implementation details are completely hidden using PIMPL pattern. - */ -class CLI11Parser : public CLIParser { -public: - CLI11Parser(const std::string& appName, const std::string& appDescription); - ~CLI11Parser() override; - - // Non-copyable due to PIMPL - CLI11Parser(const CLI11Parser&) = delete; - CLI11Parser& operator=(const CLI11Parser&) = delete; - - // Movable - CLI11Parser(CLI11Parser&&) noexcept; - CLI11Parser& operator=(CLI11Parser&&) noexcept; - - CommandRequest parse(std::span args) override; - void configureCommands(const std::vector>& commands) override; - void configureSubcommands(const std::string& parentCommand, - const std::vector>& subcommands) override; - std::string helpText(const std::string& commandPath = "") override; - void configureCommandOptions(const std::string& command, const CommandOptions& options) override; - - /** - * @brief Set the presenter for custom help formatting - * @param presenter The presenter to use for help output - */ - void setPresenter(std::shared_ptr presenter); - -private: - class Impl; - std::unique_ptr impl_; -}; - -/** - * @brief Factory for creating CLI11 parsers - */ -class CLI11ParserFactory : public CLIParserFactory { -public: - std::unique_ptr createParser(const std::string& appName, const std::string& appDescription) override; -}; - -} // namespace scrap diff --git a/src/shared/command/driver/PresenterFormatter.cpp b/src/shared/command/driver/PresenterFormatter.cpp deleted file mode 100644 index ca02347..0000000 --- a/src/shared/command/driver/PresenterFormatter.cpp +++ /dev/null @@ -1,234 +0,0 @@ -#include "PresenterFormatter.h" -#include "shared/presentation/Presenter.h" -#include -#include -#include - -namespace scrap { - -PresenterFormatter::PresenterFormatter(std::shared_ptr presenter) - : CLI::Formatter(), presenter_(presenter) -{ - // Set reasonable column widths for beautiful output - column_width(30); - right_column_width(50); -} - -std::string PresenterFormatter::make_help(const CLI::App* app, std::string name, CLI::AppFormatMode mode) const -{ - if (! presenter_) { - // Fallback to default formatter if no presenter - return CLI::Formatter::make_help(app, name, mode); - } - - std::stringstream out; - - // Description - if (! app->get_description().empty()) { - out << app->get_description() << "\n"; - out << "\n"; - } - - // Usage - out << formatUsage(app, name); - - // Positionals - std::string positionals = formatPositionals(app); - if (! positionals.empty()) { - out << "\n"; - out << "Arguments:\n"; - out << positionals; - } - - // Options - std::string options = formatOptions(app); - if (! options.empty()) { - out << "\n"; - out << "Options:\n"; - out << options; - } - - // Subcommands - if (mode != CLI::AppFormatMode::Sub) { - std::string subcommands = formatSubcommands(app); - if (! subcommands.empty()) { - out << "\n"; - out << "Commands:\n"; - out << subcommands; - } - } - - // Footer - if (! app->get_footer().empty()) { - out << "\n"; - out << app->get_footer() << "\n"; - } - - return out.str(); -} - -std::string PresenterFormatter::formatUsage(const CLI::App* app, const std::string& name) const -{ - std::stringstream out; - out << "Usage: "; - - // Build the proper command path - std::string commandPath = "scrap"; - if (! name.empty()) { - // Use provided name but ensure it starts with "scrap" - if (name.find("scrap") != 0) { - commandPath = "scrap " + name; - } else { - commandPath = name; - } - } else { - // Build command path - always include "scrap" prefix - if (! app->get_name().empty()) { - commandPath += " " + app->get_name(); - } - } - - out << commandPath; - - // Add positionals placeholder - for (const auto* opt : app->get_options()) { - if (opt->get_positional()) { - out << " <" << opt->get_name(true, false) << ">"; - } - } - - // Add options placeholder - out << " [options]"; - - // Add subcommand placeholder if has subcommands - if (! app->get_subcommands({}).empty()) { - out << " [command]"; - } - - out << "\n"; - return out.str(); -} - -std::string PresenterFormatter::formatPositionals(const CLI::App* app) const -{ - std::stringstream out; - - for (const auto* opt : app->get_options()) { - if (! opt->get_positional()) { - continue; - } - - // Format: " description" - std::string name = " <" + opt->get_name(true, false) + ">"; - out << std::setw(static_cast(column_width_)) << std::left << name; - - std::string desc = opt->get_description(); - if (! desc.empty()) { - out << desc; - } - - out << "\n"; - } - - return out.str(); -} - -std::string PresenterFormatter::formatOptions(const CLI::App* app) const -{ - std::stringstream out; - - for (const auto* opt : app->get_options()) { - if (opt->get_positional() || opt->get_group() == "HIDDEN") { - continue; - } - - // Build option string with short and long forms - std::stringstream optStr; - optStr << " "; - - // Get all option names - auto names = opt->get_name(false, true); - - // Add short options first - bool first = true; - if (! opt->get_snames().empty()) { - for (const auto& sname : opt->get_snames()) { - if (! first) - optStr << ", "; - optStr << "-" << sname; - first = false; - } - } - - // Add long options - if (! opt->get_lnames().empty()) { - for (const auto& lname : opt->get_lnames()) { - if (! first) - optStr << ", "; - optStr << "--" << lname; - first = false; - } - } - - // Add value placeholder for non-flag options - if (opt->get_expected() > 0) { - // Get type name or use generic placeholder - std::string typeName = "value"; - if (! opt->get_lnames().empty()) { - typeName = opt->get_lnames()[0]; - } else if (! opt->get_snames().empty()) { - typeName = opt->get_snames()[0]; - } - optStr << "=<" << typeName << ">"; - } - - // Format with proper alignment - std::string optString = optStr.str(); - out << std::setw(static_cast(column_width_)) << std::left << optString; - - // Add description - std::string desc = opt->get_description(); - - // Add default value if present - if (! opt->get_default_str().empty()) { - desc += " [default: " + opt->get_default_str() + "]"; - } - - // Note: Choices are already shown in default_str for validators - - if (! desc.empty()) { - out << desc; - } - - out << "\n"; - } - - return out.str(); -} - -std::string PresenterFormatter::formatSubcommands(const CLI::App* app) const -{ - std::stringstream out; - - auto subcommands = app->get_subcommands({}); - for (const auto* sub : subcommands) { - if (sub->get_name().empty() || sub->get_group() == "HIDDEN") { - continue; - } - - // Format: " name description" - std::string name = " " + sub->get_name(); - out << std::setw(static_cast(column_width_)) << std::left << name; - - std::string desc = sub->get_description(); - if (! desc.empty()) { - out << desc; - } - - out << "\n"; - } - - return out.str(); -} - -} // namespace scrap diff --git a/src/shared/command/driver/PresenterFormatter.h b/src/shared/command/driver/PresenterFormatter.h deleted file mode 100644 index 9c59c08..0000000 --- a/src/shared/command/driver/PresenterFormatter.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace scrap { - -// Forward declaration -class Presenter; - -/** - * @brief Custom CLI11 formatter that outputs through Presenter - * - * This formatter bridges CLI11's help generation with our Presenter abstraction, - * ensuring consistent and beautiful output across the application. - */ -class PresenterFormatter : public CLI::Formatter { -public: - /** - * @brief Construct a new PresenterFormatter - * @param presenter The presenter to use for output - */ - explicit PresenterFormatter(std::shared_ptr presenter); - - /** - * @brief Generate help text for the application - * - * This overrides CLI11's default help generation to provide - * a more beautiful, Presenter-based output. - */ - std::string make_help(const CLI::App* app, std::string name, CLI::AppFormatMode mode) const override; - -private: - std::shared_ptr presenter_; - - /** - * @brief Format the usage line - */ - std::string formatUsage(const CLI::App* app, const std::string& name) const; - - /** - * @brief Format positional arguments section - */ - std::string formatPositionals(const CLI::App* app) const; - - /** - * @brief Format options section - */ - std::string formatOptions(const CLI::App* app) const; - - /** - * @brief Format subcommands section - */ - std::string formatSubcommands(const CLI::App* app) const; -}; - -} // namespace scrap diff --git a/src/shared/presentation/Presenter.h b/src/shared/presentation/Presenter.h deleted file mode 100644 index 5d4ff54..0000000 --- a/src/shared/presentation/Presenter.h +++ /dev/null @@ -1,111 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace scrap { - -// Forward declarations -struct Table; -struct Tree; - -/** - * @brief Output format options - */ -enum class OutputFormat { - Plain, - Json, - Xml, - Table -}; - -/** - * @brief Verbosity level for output - */ -enum class VerbosityLevel { - Quiet = 0, - Normal = 1, - Verbose = 2, - Debug = 3 -}; - -/** - * @brief Progress indicator style - */ -enum class ProgressStyle { - None, - Simple, - Rich -}; - -/** - * @brief Abstract interface for output presentation - * - * This interface separates business logic from output formatting, - * allowing different output formats (console, JSON, XML) without - * affecting the domain layer. - */ -class Presenter { -public: - virtual ~Presenter() = default; - - // Configuration - virtual void setOutputFormat(OutputFormat format) = 0; - virtual void setVerbosityLevel(VerbosityLevel level) = 0; - virtual void setColorEnabled(bool enabled) = 0; - virtual void setProgressStyle(ProgressStyle style) = 0; - - // Basic output methods - virtual void displayInfo(const std::string& message) = 0; - virtual void displaySuccess(const std::string& message) = 0; - virtual void displayWarning(const std::string& message) = 0; - virtual void displayError(const std::string& message) = 0; - virtual void displayDebug(const std::string& message) = 0; - - // Progress indicators - virtual void startProgress(const std::string& task, size_t total) = 0; - virtual void updateProgress(size_t current) = 0; - virtual void updateProgress(size_t current, const std::string& currentItem) = 0; - virtual void finishProgress() = 0; - - // Structured output - virtual void displayTable(const Table& table) = 0; - virtual void displayTree(const Tree& tree) = 0; - virtual void displayList(const std::string& title, const std::vector& items) = 0; -}; - -/** - * @brief Table structure for tabular output - */ -struct Table { - std::vector headers; - std::vector> rows; -}; - -/** - * @brief Tree structure for hierarchical output - */ -struct Tree { - struct Node { - std::string label; - std::vector> children; - }; - std::unique_ptr root; -}; - -/** - * @brief Factory for creating presenters - */ -class PresenterFactory { -public: - virtual ~PresenterFactory() = default; - - /** - * @brief Create a presenter instance - * @return Unique pointer to presenter - */ - virtual std::unique_ptr createPresenter() = 0; -}; - -} // namespace scrap diff --git a/src/shared/presentation/driver/ConsolePresenter.cpp b/src/shared/presentation/driver/ConsolePresenter.cpp deleted file mode 100644 index 493534a..0000000 --- a/src/shared/presentation/driver/ConsolePresenter.cpp +++ /dev/null @@ -1,303 +0,0 @@ -#include "shared/presentation/driver/ConsolePresenter.h" -#include -#include - -namespace scrap { - -/** - * @brief Private implementation class for ConsolePresenter - */ -class ConsolePresenter::Impl { -public: - Impl() - : outputFormat_(OutputFormat::Plain), - verbosity_(VerbosityLevel::Normal), - useColor_(true), - progressStyle_(ProgressStyle::Simple), - progressTotal_(0), - progressCurrent_(0) - { - } - - // Configuration - OutputFormat outputFormat_; - VerbosityLevel verbosity_; - bool useColor_; - ProgressStyle progressStyle_; - - // Progress tracking - std::string progressTask_; - size_t progressTotal_; - size_t progressCurrent_; - - void showInfoInternal(const std::string& message, const std::string& prefix = "") - { - if (useColor_) { - std::cout << "\033[32m" << prefix << "\033[0m" << message << std::endl; - } else { - std::cout << prefix << message << std::endl; - } - } - - void showSuccessInternal(const std::string& message) - { - if (useColor_) { - std::cout << "\033[32m✓\033[0m " << message << std::endl; - } else { - std::cout << "success: " << message << std::endl; - } - } - - void showWarningInternal(const std::string& message) - { - if (useColor_) { - std::cerr << "\033[33m⚠\033[0m " << message << std::endl; - } else { - std::cerr << "warning: " << message << std::endl; - } - } - - void showErrorInternal(const std::string& message) - { - if (useColor_) { - std::cerr << "\033[31m✗\033[0m " << message << std::endl; - } else { - std::cerr << "error: " << message << std::endl; - } - } - - void showDebugInternal(const std::string& message) - { - if (verbosity_ >= VerbosityLevel::Debug) { - if (useColor_) { - std::cout << "\033[90m[DEBUG] " << message << "\033[0m" << std::endl; - } else { - std::cout << "[DEBUG] " << message << std::endl; - } - } - } - - void showProgressInternal(size_t current) - { - if (progressStyle_ == ProgressStyle::None) { - return; - } - - if (progressStyle_ == ProgressStyle::Simple) { - if (progressTotal_ > 0) { - double percentage = (static_cast(current) / progressTotal_) * 100.0; - std::cout << "\r" << std::fixed << std::setprecision(1) << percentage << "%" << std::flush; - } - } - } - - void showListInternal(const std::string& title, const std::vector& items) - { - if (! title.empty()) { - std::cout << title << ":" << std::endl; - } - - for (const auto& item : items) { - std::cout << " " << item << std::endl; - } - - if (items.empty()) { - std::cout << " (none)" << std::endl; - } - } - - void showTableInternal(const Table& table) - { - // Simple table implementation - if (table.headers.empty() && table.rows.empty()) { - return; - } - - // Calculate column widths - std::vector colWidths; - if (! table.headers.empty()) { - colWidths.resize(table.headers.size()); - for (size_t i = 0; i < table.headers.size(); ++i) { - colWidths[i] = table.headers[i].length(); - } - } - - for (const auto& row : table.rows) { - if (colWidths.size() < row.size()) { - colWidths.resize(row.size()); - } - for (size_t i = 0; i < row.size(); ++i) { - colWidths[i] = std::max(colWidths[i], row[i].length()); - } - } - - // Print headers - if (! table.headers.empty()) { - for (size_t i = 0; i < table.headers.size(); ++i) { - if (i > 0) - std::cout << " | "; - std::cout << std::left << std::setw(colWidths[i]) << table.headers[i]; - } - std::cout << std::endl; - - // Print separator - for (size_t i = 0; i < table.headers.size(); ++i) { - if (i > 0) - std::cout << "-|-"; - std::cout << std::string(colWidths[i], '-'); - } - std::cout << std::endl; - } - - // Print rows - for (const auto& row : table.rows) { - for (size_t i = 0; i < row.size(); ++i) { - if (i > 0) - std::cout << " | "; - std::cout << std::left << std::setw(colWidths[i]) << row[i]; - } - std::cout << std::endl; - } - } - - void showTreeInternal(const Tree& tree) - { - if (tree.root) { - showTreeNodeInternal(tree.root.get(), ""); - } - } - -private: - void showTreeNodeInternal(const Tree::Node* node, const std::string& prefix) - { - if (! node) - return; - - std::cout << prefix << node->label << std::endl; - - for (size_t i = 0; i < node->children.size(); ++i) { - bool isLast = (i == node->children.size() - 1); - std::string childPrefix = prefix + (isLast ? "└── " : "├── "); - std::string nextPrefix = prefix + (isLast ? " " : "│ "); - - std::cout << childPrefix << node->children[i]->label << std::endl; - showTreeNodeInternal(node->children[i].get(), nextPrefix); - } - } -}; - -// ConsolePresenter implementation -ConsolePresenter::ConsolePresenter() - : impl_(std::make_unique()) -{ -} - -ConsolePresenter::~ConsolePresenter() = default; - -ConsolePresenter::ConsolePresenter(ConsolePresenter&&) noexcept = default; - -ConsolePresenter& ConsolePresenter::operator=(ConsolePresenter&&) noexcept = default; - -// Configuration methods -void ConsolePresenter::setOutputFormat(OutputFormat format) -{ - impl_->outputFormat_ = format; -} - -void ConsolePresenter::setVerbosityLevel(VerbosityLevel level) -{ - impl_->verbosity_ = level; -} - -void ConsolePresenter::setColorEnabled(bool enabled) -{ - impl_->useColor_ = enabled; -} - -void ConsolePresenter::setProgressStyle(ProgressStyle style) -{ - impl_->progressStyle_ = style; -} - -// Output methods -void ConsolePresenter::displayInfo(const std::string& message) -{ - impl_->showInfoInternal(message); -} - -void ConsolePresenter::displaySuccess(const std::string& message) -{ - impl_->showSuccessInternal(message); -} - -void ConsolePresenter::displayWarning(const std::string& message) -{ - impl_->showWarningInternal(message); -} - -void ConsolePresenter::displayError(const std::string& message) -{ - impl_->showErrorInternal(message); -} - -void ConsolePresenter::displayDebug(const std::string& message) -{ - impl_->showDebugInternal(message); -} - -// Progress methods -void ConsolePresenter::startProgress(const std::string& task, size_t total) -{ - impl_->progressTask_ = task; - impl_->progressTotal_ = total; - impl_->progressCurrent_ = 0; - - if (impl_->progressStyle_ != ProgressStyle::None) { - std::cout << task << "..." << std::flush; - } -} - -void ConsolePresenter::updateProgress(size_t current) -{ - impl_->progressCurrent_ = current; - impl_->showProgressInternal(current); -} - -void ConsolePresenter::updateProgress(size_t current, [[maybe_unused]] const std::string& currentItem) -{ - updateProgress(current); -} - -void ConsolePresenter::finishProgress() -{ - if (impl_->progressStyle_ != ProgressStyle::None) { - std::cout << " done" << std::endl; - } - impl_->progressTask_.clear(); - impl_->progressTotal_ = 0; - impl_->progressCurrent_ = 0; -} - -// Structured output methods -void ConsolePresenter::displayTable(const Table& table) -{ - impl_->showTableInternal(table); -} - -void ConsolePresenter::displayTree(const Tree& tree) -{ - impl_->showTreeInternal(tree); -} - -void ConsolePresenter::displayList(const std::string& title, const std::vector& items) -{ - impl_->showListInternal(title, items); -} - -// ConsolePresenterFactory implementation -std::unique_ptr ConsolePresenterFactory::createPresenter() -{ - return std::make_unique(); -} - -} // namespace scrap diff --git a/src/shared/presentation/driver/ConsolePresenter.h b/src/shared/presentation/driver/ConsolePresenter.h deleted file mode 100644 index 435bd35..0000000 --- a/src/shared/presentation/driver/ConsolePresenter.h +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include "shared/presentation/Presenter.h" -#include - -namespace scrap { - -/** - * @brief Console-based presenter implementation - * - * This class provides a concrete implementation of Presenter for console output. - * Implementation details are hidden using PIMPL pattern. - */ -class ConsolePresenter : public Presenter { -public: - ConsolePresenter(); - ~ConsolePresenter() override; - - // Non-copyable due to PIMPL - ConsolePresenter(const ConsolePresenter&) = delete; - ConsolePresenter& operator=(const ConsolePresenter&) = delete; - - // Movable - ConsolePresenter(ConsolePresenter&&) noexcept; - ConsolePresenter& operator=(ConsolePresenter&&) noexcept; - - // Configuration methods - void setOutputFormat(OutputFormat format) override; - void setVerbosityLevel(VerbosityLevel level) override; - void setColorEnabled(bool enabled) override; - void setProgressStyle(ProgressStyle style) override; - - // Output methods - void displayInfo(const std::string& message) override; - void displaySuccess(const std::string& message) override; - void displayWarning(const std::string& message) override; - void displayError(const std::string& message) override; - void displayDebug(const std::string& message) override; - - // Progress methods - void startProgress(const std::string& task, size_t total) override; - void updateProgress(size_t current) override; - void updateProgress(size_t current, const std::string& currentItem) override; - void finishProgress() override; - - // Structured output methods - void displayTable(const Table& table) override; - void displayTree(const Tree& tree) override; - void displayList(const std::string& title, const std::vector& items) override; - -private: - class Impl; - std::unique_ptr impl_; -}; - -/** - * @brief Factory for creating console presenters - */ -class ConsolePresenterFactory : public PresenterFactory { -public: - std::unique_ptr createPresenter() override; -}; - -} // namespace scrap diff --git a/src/template/TemplateModule.cpp b/src/template/TemplateModule.cpp deleted file mode 100644 index 55304c9..0000000 --- a/src/template/TemplateModule.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "TemplateModule.h" -#include "shared/command/CLIParser.h" -#include "shared/command/CommandDispatcher.h" -#include "shared/presentation/Presenter.h" -#include "template/command/TemplateOperation.h" - -namespace scrap::template_system { - -std::shared_ptr TemplateModule::createTemplateService(std::shared_ptr presenter) -{ - // Get default templates directory, fallback to ./scrap/templates if it fails - auto templatesDir = service::DefaultTemplateService::defaultTemplatesDirectory(); - std::filesystem::path path = templatesDir.has_value() ? *templatesDir : std::filesystem::path("./scrap/templates"); - - return std::make_shared(path, nullptr, presenter); -} - -std::shared_ptr -TemplateModule::createTemplateService(const std::filesystem::path& templatesDir, std::shared_ptr presenter) -{ - return std::make_shared(templatesDir, nullptr, presenter); -} - -void TemplateModule::registerCommands(CommandDispatcher& dispatcher, - std::shared_ptr /* parser */, - std::shared_ptr presenter) -{ - auto templateService = createTemplateService(presenter); - auto templateOperation = std::make_shared(templateService); - templateOperation->setPresenter(presenter); - - dispatcher.registerOperation("template", templateOperation); -} - -std::vector> TemplateModule::availableCommands() -{ - return {{"template", "Manage project templates"}}; -} - -std::vector> TemplateModule::availableSubcommands() -{ - return {{"list", "List available templates"}, {"update", "Update template sources"}}; -} - -} // namespace scrap::template_system diff --git a/src/template/TemplateModule.h b/src/template/TemplateModule.h deleted file mode 100644 index 355d368..0000000 --- a/src/template/TemplateModule.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include "template/service/TemplateService.h" -#include -#include -#include - -namespace scrap { -class Presenter; -class CLIParser; -class CommandDispatcher; -} // namespace scrap - -namespace scrap::template_system { - -/** - * @brief Template system module for dependency injection - * - * This module provides factory methods for creating template system components - * and manages their lifecycle according to Clean Architecture principles. - */ -class TemplateModule { -public: - /** - * @brief Create default template service - * @param presenter Optional presenter for output operations - * @return Shared pointer to template service instance - */ - static std::shared_ptr - createTemplateService(std::shared_ptr presenter = nullptr); - - /** - * @brief Create template service with custom templates directory - * @param templatesDir Custom templates directory path - * @param presenter Optional presenter for output operations - * @return Shared pointer to template service instance - */ - static std::shared_ptr - createTemplateService(const std::filesystem::path& templatesDir, std::shared_ptr presenter = nullptr); - - /** - * @brief Register template commands with the command dispatcher - * @param dispatcher Command dispatcher to register with - * @param parser CLI parser for command configuration - * @param presenter Presenter for output operations - */ - static void registerCommands(CommandDispatcher& dispatcher, - std::shared_ptr parser, - std::shared_ptr presenter); - - /** - * @brief Get list of available template commands - * @return Vector of command name and description pairs - */ - static std::vector> availableCommands(); - - /** - * @brief Get list of available template subcommands - * @return Vector of subcommand name and description pairs - */ - static std::vector> availableSubcommands(); - -private: - TemplateModule() = default; // Static class -}; - -} // namespace scrap::template_system diff --git a/src/template/command/ListOperation.cpp b/src/template/command/ListOperation.cpp deleted file mode 100644 index 174625c..0000000 --- a/src/template/command/ListOperation.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "template/command/ListOperation.h" -#include "shared/command/CommandOptions.h" -#include "shared/presentation/Presenter.h" -#include "template/service/TemplateService.h" - -namespace scrap::template_system::command { - -class ListOperation::Internal { -public: - explicit Internal(std::shared_ptr service) - : service_(service) - { - } - - std::shared_ptr service_; -}; - -ListOperation::ListOperation(std::shared_ptr service) - : impl_(std::make_unique(service)) -{ -} - -ListOperation::~ListOperation() = default; - -ListOperation::ListOperation(ListOperation&&) noexcept = default; - -ListOperation& ListOperation::operator=(ListOperation&&) noexcept = default; - -void ListOperation::execute(const std::vector& /* args */) -{ - if (! impl_->service_) { - if (presenter()) { - presenter()->displayError("Template service not available"); - } - return; - } - - // For now, display all templates grouped by source - displayTemplatesBySource(); -} - -CommandOptions ListOperation::describeOptions() const -{ - CommandOptions options; - // CommandOptions uses builder pattern, not direct field assignment - return options; -} - -void ListOperation::displayTemplateList() -{ - auto templates = impl_->service_->listAllTemplates(); - - if (templates.empty()) { - if (presenter()) { - presenter()->displayInfo("No templates available."); - presenter()->displayInfo("Run 'scrap template update' to download templates."); - } - return; - } - - if (presenter()) { - presenter()->displayInfo("Available templates:"); - presenter()->displayInfo(""); - - for (const auto& tmpl : templates) { - std::string line = " " + tmpl.name(); - if (! tmpl.description().empty()) { - line += " - " + tmpl.description(); - } - presenter()->displayInfo(line); - } - } -} - -void ListOperation::displayTemplatesBySource() -{ - auto sources = impl_->service_->listTemplateSources(); - - if (sources.empty()) { - if (presenter()) { - presenter()->displayInfo("No template sources configured."); - } - return; - } - - bool hasAnyTemplates = false; - - if (presenter()) { - presenter()->displayInfo("Available templates by source:"); - presenter()->displayInfo(""); - } - - for (const auto& source : sources) { - auto templates = impl_->service_->listTemplatesFromSource(source.name); - - if (! templates.empty()) { - hasAnyTemplates = true; - - if (presenter()) { - presenter()->displayInfo("From " + source.name + ":"); - - for (const auto& tmpl : templates) { - std::string line = " " + tmpl.name(); - if (! tmpl.description().empty()) { - line += " - " + tmpl.description(); - } - presenter()->displayInfo(line); - } - presenter()->displayInfo(""); - } - } - } - - if (! hasAnyTemplates && presenter()) { - presenter()->displayInfo("No templates available."); - presenter()->displayInfo("Run 'scrap template update' to download templates."); - } -} - -} // namespace scrap::template_system::command diff --git a/src/template/command/ListOperation.h b/src/template/command/ListOperation.h deleted file mode 100644 index 1e97bc1..0000000 --- a/src/template/command/ListOperation.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include "shared/command/Operation.h" -#include - -namespace scrap::template_system { - -namespace service { -class TemplateService; -} - -namespace command { - -/** - * @brief Operation for listing available templates - * - * This class handles the "scrap template list" command, displaying - * all available templates from all configured sources. - */ -class ListOperation : public Operation { -public: - explicit ListOperation(std::shared_ptr service); - ~ListOperation() override; - - // Non-copyable - ListOperation(const ListOperation&) = delete; - ListOperation& operator=(const ListOperation&) = delete; - - // Movable - ListOperation(ListOperation&&) noexcept; - ListOperation& operator=(ListOperation&&) noexcept; - - void execute(const std::vector& args) override; - CommandOptions describeOptions() const override; - -private: - class Internal; - std::unique_ptr impl_; - - void displayTemplateList(); - void displayTemplatesBySource(); -}; - -} // namespace command -} // namespace scrap::template_system diff --git a/src/template/command/TemplateOperation.cpp b/src/template/command/TemplateOperation.cpp deleted file mode 100644 index c0d324c..0000000 --- a/src/template/command/TemplateOperation.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "template/command/TemplateOperation.h" -#include "shared/command/CommandOptions.h" -#include "shared/presentation/Presenter.h" -#include "template/command/ListOperation.h" -#include "template/command/UpdateOperation.h" -#include "template/service/TemplateService.h" - -namespace scrap::template_system::command { - -class TemplateOperation::Internal { -public: - explicit Internal(std::shared_ptr service) - : service_(service) - { - } - - std::shared_ptr service_; -}; - -TemplateOperation::TemplateOperation(std::shared_ptr service) - : impl_(std::make_unique(service)) -{ - // Add subcommands - addSubOperation("list", std::make_shared(impl_->service_)); - addSubOperation("update", std::make_shared(impl_->service_)); -} - -TemplateOperation::~TemplateOperation() = default; - -TemplateOperation::TemplateOperation(TemplateOperation&&) noexcept = default; - -TemplateOperation& TemplateOperation::operator=(TemplateOperation&&) noexcept = default; - -CommandOptions TemplateOperation::describeOptions() const -{ - CommandOptions options; - // CommandOptions uses builder pattern, not direct field assignment - return options; -} - -void TemplateOperation::displayHelp() const -{ - if (presenter()) { - presenter()->displayInfo("Template Management Commands:"); - presenter()->displayInfo(""); - presenter()->displayInfo("Available subcommands:"); - presenter()->displayInfo(" list List available templates"); - presenter()->displayInfo(" update Update template sources"); - presenter()->displayInfo(""); - presenter()->displayInfo( - "Use 'scrap template --help' for more information about a specific subcommand."); - } -} - -} // namespace scrap::template_system::command diff --git a/src/template/command/TemplateOperation.h b/src/template/command/TemplateOperation.h deleted file mode 100644 index 729baaf..0000000 --- a/src/template/command/TemplateOperation.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include "shared/command/CompositeOperation.h" -#include - -namespace scrap::template_system { - -namespace service { -class TemplateService; -} - -namespace command { - -/** - * @brief Main template command that manages subcommands - * - * This class handles the "scrap template" command and its subcommands - * (list, update) following the Composite pattern. - */ -class TemplateOperation : public CompositeOperation { -public: - explicit TemplateOperation(std::shared_ptr service); - ~TemplateOperation() override; - - // Non-copyable - TemplateOperation(const TemplateOperation&) = delete; - TemplateOperation& operator=(const TemplateOperation&) = delete; - - // Movable - TemplateOperation(TemplateOperation&&) noexcept; - TemplateOperation& operator=(TemplateOperation&&) noexcept; - - CommandOptions describeOptions() const override; - -protected: - void displayHelp() const override; - -private: - class Internal; - std::unique_ptr impl_; -}; - -} // namespace command -} // namespace scrap::template_system diff --git a/src/template/command/UpdateOperation.cpp b/src/template/command/UpdateOperation.cpp deleted file mode 100644 index 79d8966..0000000 --- a/src/template/command/UpdateOperation.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include "template/command/UpdateOperation.h" -#include "shared/command/CommandOptions.h" -#include "shared/presentation/Presenter.h" -#include "template/service/TemplateService.h" - -namespace scrap::template_system::command { - -class UpdateOperation::Internal { -public: - explicit Internal(std::shared_ptr service) - : service_(service) - { - } - - std::shared_ptr service_; -}; - -UpdateOperation::UpdateOperation(std::shared_ptr service) - : impl_(std::make_unique(service)) -{ -} - -UpdateOperation::~UpdateOperation() = default; - -UpdateOperation::UpdateOperation(UpdateOperation&&) noexcept = default; - -UpdateOperation& UpdateOperation::operator=(UpdateOperation&&) noexcept = default; - -void UpdateOperation::execute(const std::vector& args) -{ - if (! impl_->service_) { - if (presenter()) { - presenter()->displayError("Template service not available"); - } - return; - } - - // Check if a specific source is specified - if (args.size() > 1) { - // Update specific source - updateSpecificSource(args[1]); - } else { - // Update all sources - updateAllSources(); - } -} - -CommandOptions UpdateOperation::describeOptions() const -{ - CommandOptions options; - // CommandOptions uses builder pattern, not direct field assignment - return options; -} - -void UpdateOperation::updateAllSources() -{ - if (presenter()) { - presenter()->displayInfo("Updating all template sources..."); - } - - auto result = impl_->service_->updateTemplateSources(); - - if (result.has_value()) { - if (presenter()) { - presenter()->displaySuccess("All template sources updated successfully"); - } - } else { - if (presenter()) { - presenter()->displayError("Failed to update template sources: " + result.error()); - } - } -} - -void UpdateOperation::updateSpecificSource(const std::string& sourceName) -{ - if (presenter()) { - presenter()->displayInfo("Updating template source: " + sourceName + "..."); - } - - auto result = impl_->service_->updateTemplateSource(sourceName); - - if (result.has_value()) { - if (presenter()) { - presenter()->displaySuccess("Template source '" + sourceName + "' updated successfully"); - } - } else { - if (presenter()) { - presenter()->displayError("Failed to update template source '" + sourceName + "': " + result.error()); - } - } -} - -} // namespace scrap::template_system::command diff --git a/src/template/command/UpdateOperation.h b/src/template/command/UpdateOperation.h deleted file mode 100644 index 0e71a23..0000000 --- a/src/template/command/UpdateOperation.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include "shared/command/Operation.h" -#include - -namespace scrap::template_system { - -namespace service { -class TemplateService; -} - -namespace command { - -/** - * @brief Operation for updating template sources - * - * This class handles the "scrap template update" command, updating - * all configured template sources (e.g., git pull for git repositories). - */ -class UpdateOperation : public Operation { -public: - explicit UpdateOperation(std::shared_ptr service); - ~UpdateOperation() override; - - // Non-copyable - UpdateOperation(const UpdateOperation&) = delete; - UpdateOperation& operator=(const UpdateOperation&) = delete; - - // Movable - UpdateOperation(UpdateOperation&&) noexcept; - UpdateOperation& operator=(UpdateOperation&&) noexcept; - - void execute(const std::vector& args) override; - CommandOptions describeOptions() const override; - -private: - class Internal; - std::unique_ptr impl_; - - void updateAllSources(); - void updateSpecificSource(const std::string& sourceName); -}; - -} // namespace command -} // namespace scrap::template_system diff --git a/src/template/model/Template.cpp b/src/template/model/Template.cpp deleted file mode 100644 index abbac83..0000000 --- a/src/template/model/Template.cpp +++ /dev/null @@ -1,390 +0,0 @@ -#include "Template.h" -#include -#include -#include - -namespace scrap::template_system::model { - -// TemplateVariable implementation -TemplateVariable::TemplateVariable(const std::string& n, const std::string& p) - : name(n), prompt(p) -{ -} - -// TemplateSource implementation -TemplateSource::TemplateSource(const std::string& n, TemplateSourceType t) - : name(n), type(t) -{ -} - -bool TemplateRequirements::isCompatible() const -{ - // For now, just return true - // In future, implement actual compatibility checking - return true; -} - -Template::Template(const std::string& name, const std::filesystem::path& path, const TemplateSource& source) - : name_(name), path_(path), source_(source) -{ - loadMetadata(); -} - -// Template getters -const std::string& Template::name() const -{ - return name_; -} - -const std::string& Template::version() const -{ - return version_; -} - -const std::string& Template::description() const -{ - return description_; -} - -const std::string& Template::author() const -{ - return author_; -} - -const std::string& Template::license() const -{ - return license_; -} - -const std::vector& Template::tags() const -{ - return tags_; -} - -const std::filesystem::path& Template::path() const -{ - return path_; -} - -const TemplateSource& Template::source() const -{ - return source_; -} - -const std::vector& Template::variables() const -{ - return variables_; -} - -const TemplateRequirements& Template::requirements() const -{ - return requirements_; -} - -const std::map& Template::defaultDependencies() const -{ - return defaultDependencies_; -} - -// Template setters -void Template::setVersion(const std::string& version) -{ - version_ = version; -} - -void Template::setDescription(const std::string& description) -{ - description_ = description; -} - -void Template::setAuthor(const std::string& author) -{ - author_ = author; -} - -void Template::setLicense(const std::string& license) -{ - license_ = license; -} - -void Template::addTag(const std::string& tag) -{ - tags_.push_back(tag); -} - -void Template::addVariable(const TemplateVariable& variable) -{ - variables_.push_back(variable); -} - -void Template::setRequirements(const TemplateRequirements& requirements) -{ - requirements_ = requirements; -} - -void Template::addDefaultDependency(const std::string& name, const std::string& version) -{ - defaultDependencies_[name] = version; -} - -bool Template::isValid() const -{ - return validate().empty(); -} - -std::vector Template::validate() const -{ - std::vector errors; - - if (name_.empty()) { - errors.push_back("Template name cannot be empty"); - } - - if (! std::filesystem::exists(path_)) { - errors.push_back("Template path does not exist: " + path_.string()); - } - - // Check for required files - auto templateToml = path_ / "template.toml"; - if (! std::filesystem::exists(templateToml)) { - errors.push_back("template.toml not found in template directory"); - } - - // Validate variable names (should be valid identifiers) - for (const auto& var : variables_) { - if (var.name.empty()) { - errors.push_back("Template variable name cannot be empty"); - continue; - } - - // Check if variable name is a valid identifier - std::regex identifierPattern("^[a-zA-Z_][a-zA-Z0-9_]*$"); - if (! std::regex_match(var.name, identifierPattern)) { - errors.push_back("Invalid variable name: " + var.name); - } - } - - return errors; -} - -std::vector Template::templateFiles() const -{ - std::vector files; - - if (! std::filesystem::exists(path_)) { - return files; - } - - // Recursively collect all files except template.toml and .scrap-ignore - std::error_code ec; - for (auto& entry : std::filesystem::recursive_directory_iterator(path_, ec)) { - if (ec) - continue; // Skip errors - - if (entry.is_regular_file()) { - auto relativePath = std::filesystem::relative(entry.path(), path_); - auto filename = relativePath.filename().string(); - - // Skip metadata files - if (filename == "template.toml" || filename == ".scrap-ignore") { - continue; - } - - files.push_back(relativePath); - } - } - - return files; -} - -bool Template::hasTemplateFile(const std::string& filename) const -{ - auto filePath = path_ / filename; - return std::filesystem::exists(filePath); -} - -std::string Template::fullName() const -{ - return source_.name + "/" + name_; -} - -void Template::loadMetadata() -{ - auto templateToml = path_ / "template.toml"; - - if (! std::filesystem::exists(templateToml)) { - // If no template.toml exists, use defaults based on directory name - return; - } - - // TODO: Implement TOML parsing when dross TOML support is ready - // For now, just set some defaults - description_ = "Template: " + name_; - author_ = "Unknown"; - license_ = "MIT"; -} - -void VariableMap::set(const std::string& name, const std::string& value) -{ - variables_[name] = value; -} - -std::optional VariableMap::get(const std::string& name) const -{ - auto it = variables_.find(name); - if (it != variables_.end()) { - return it->second; - } - return std::nullopt; -} - -bool VariableMap::has(const std::string& name) const -{ - return variables_.find(name) != variables_.end(); -} - -const std::map& VariableMap::all() const -{ - return variables_; -} - -void VariableMap::setStandardVariables(const std::string& projectName, const std::string& projectVersion) -{ - set("name", projectName); - set("version", projectVersion); - set("year", currentYear()); - set("date", currentDate()); - set("author", currentUser()); - set("scrap_version", "0.0.1"); // TODO: Get actual scrap version -} - -std::string VariableMap::applyTransform(const std::string& value, const std::string& transform) const -{ - if (transform == "lower_case") { - std::string result = value; - std::transform(result.begin(), result.end(), result.begin(), ::tolower); - return result; - } - - if (transform == "UPPER_CASE") { - std::string result = value; - std::transform(result.begin(), result.end(), result.begin(), ::toupper); - return result; - } - - if (transform == "snake_case") { - std::string result; - bool prevWasUpper = false; - - for (size_t i = 0; i < value.length(); ++i) { - char c = value[i]; - - if (std::isupper(c)) { - if (i > 0 && ! prevWasUpper) { - result += '_'; - } - result += std::tolower(c); - prevWasUpper = true; - } else if (c == '-' || c == ' ') { - result += '_'; - prevWasUpper = false; - } else { - result += c; - prevWasUpper = false; - } - } - - return result; - } - - if (transform == "PascalCase") { - std::string result; - bool nextUpper = true; - - for (char c : value) { - if (c == '_' || c == '-' || c == ' ') { - nextUpper = true; - } else if (nextUpper) { - result += std::toupper(c); - nextUpper = false; - } else { - result += std::tolower(c); - } - } - - return result; - } - - if (transform == "camelCase") { - std::string pascalCase = applyTransform(value, "PascalCase"); - if (! pascalCase.empty()) { - pascalCase[0] = std::tolower(pascalCase[0]); - } - return pascalCase; - } - - if (transform == "kebab-case") { - std::string snakeCase = applyTransform(value, "snake_case"); - std::replace(snakeCase.begin(), snakeCase.end(), '_', '-'); - return snakeCase; - } - - // Unknown transform, return original value - return value; -} - -std::string VariableMap::currentYear() const -{ - auto now = std::chrono::system_clock::now(); - auto time_t = std::chrono::system_clock::to_time_t(now); - auto tm = *std::localtime(&time_t); - return std::to_string(1900 + tm.tm_year); -} - -std::string VariableMap::currentDate() const -{ - auto now = std::chrono::system_clock::now(); - auto time_t = std::chrono::system_clock::to_time_t(now); - auto tm = *std::localtime(&time_t); - - char buffer[32]; - std::strftime(buffer, sizeof(buffer), "%Y-%m-%d", &tm); - return std::string(buffer); -} - -std::string VariableMap::currentUser() const -{ - const char* user = std::getenv("USER"); - if (! user) { - user = std::getenv("USERNAME"); // Windows - } - return user ? std::string(user) : "unknown"; -} - -std::string templateSourceTypeToString(TemplateSourceType type) -{ - switch (type) { - case TemplateSourceType::Official: - return "official"; - case TemplateSourceType::Git: - return "git"; - case TemplateSourceType::Local: - return "local"; - } - return "unknown"; -} - -std::expected stringToTemplateSourceType(const std::string& str) noexcept -{ - if (str == "official") - return TemplateSourceType::Official; - if (str == "git") - return TemplateSourceType::Git; - if (str == "local") - return TemplateSourceType::Local; - - auto errorCode = make_error_code(TemplateError::InvalidSourceType); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); -} - -} // namespace scrap::template_system::model diff --git a/src/template/model/Template.h b/src/template/model/Template.h deleted file mode 100644 index d799cf0..0000000 --- a/src/template/model/Template.h +++ /dev/null @@ -1,162 +0,0 @@ -#pragma once - -#include "TemplateError.h" -#include -#include -#include -#include -#include -#include -#include - -namespace scrap::template_system::model { - -/** - * @brief Template variable definition - */ -struct TemplateVariable { - enum class Type { - String, - Boolean, - Number - }; - - std::string name; - std::string prompt; - std::optional defaultValue; - Type type = Type::String; - bool required = false; - std::vector choices; // For enum-like variables - std::optional validation; // Regex pattern - std::optional transform; // Variable transformation - - TemplateVariable(const std::string& n, const std::string& p); -}; - -/** - * @brief Template source information - */ -enum class TemplateSourceType { - Official, - Git, - Local -}; - -struct TemplateSource { - std::string name; - TemplateSourceType type; - std::optional url; // For Git sources - std::optional path; // For Local sources - std::string branch = "main"; - bool autoUpdate = true; - - TemplateSource(const std::string& n, TemplateSourceType t); -}; - -/** - * @brief Template requirements specification - */ -struct TemplateRequirements { - std::vector features; - std::optional toolchain; - std::optional minCppStandard; - std::optional minScrapVersion; - - bool isCompatible() const; -}; - -/** - * @brief Template metadata and content - */ -class Template { -public: - Template(const std::string& name, const std::filesystem::path& path, const TemplateSource& source); - - // Basic information - const std::string& name() const; - const std::string& version() const; - const std::string& description() const; - const std::string& author() const; - const std::string& license() const; - const std::vector& tags() const; - - // Paths - const std::filesystem::path& path() const; - const TemplateSource& source() const; - - // Variables and requirements - const std::vector& variables() const; - const TemplateRequirements& requirements() const; - const std::map& defaultDependencies() const; - - // Setters (used during loading) - void setVersion(const std::string& version); - void setDescription(const std::string& description); - void setAuthor(const std::string& author); - void setLicense(const std::string& license); - void addTag(const std::string& tag); - void addVariable(const TemplateVariable& variable); - void setRequirements(const TemplateRequirements& requirements); - void addDefaultDependency(const std::string& name, const std::string& version); - - // Validation - bool isValid() const; - std::vector validate() const; - - // Template file operations - std::vector templateFiles() const; - bool hasTemplateFile(const std::string& filename) const; - std::string fullName() const; - -private: - // Basic metadata - std::string name_; - std::string version_ = "1.0.0"; - std::string description_; - std::string author_; - std::string license_; - std::vector tags_; - - // Paths and source - std::filesystem::path path_; - TemplateSource source_; - - // Template configuration - std::vector variables_; - TemplateRequirements requirements_; - std::map defaultDependencies_; - - void loadMetadata(); -}; - -/** - * @brief Collection of template variables with their resolved values - */ -class VariableMap { -public: - void set(const std::string& name, const std::string& value); - std::optional get(const std::string& name) const; - bool has(const std::string& name) const; - - // Standard variables (always available) - void setStandardVariables(const std::string& projectName, const std::string& projectVersion = "0.1.0"); - - const std::map& all() const; - - // Variable transformation - std::string applyTransform(const std::string& value, const std::string& transform) const; - -private: - std::map variables_; - - std::string currentYear() const; - std::string currentDate() const; - std::string currentUser() const; -}; - -// Helper functions -std::string templateSourceTypeToString(TemplateSourceType type); -[[nodiscard]] std::expected -stringToTemplateSourceType(const std::string& str) noexcept; - -} // namespace scrap::template_system::model diff --git a/src/template/model/TemplateError.cpp b/src/template/model/TemplateError.cpp deleted file mode 100644 index b71c25f..0000000 --- a/src/template/model/TemplateError.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "TemplateError.h" -#include - -// Error code creation function (global scope for ADL) - -std::error_code make_error_code(scrap::template_system::model::TemplateError e) noexcept -{ - struct TemplateErrorCategory : std::error_category { - [[nodiscard]] const char* name() const noexcept override - { - return "Template"; - } - - [[nodiscard]] std::string message(int ev) const override - { - switch (static_cast(ev)) { - case scrap::template_system::model::TemplateError::InvalidSourceType: - return "Invalid template source type"; - default: - return "Unknown Template error"; - } - } - }; - - static const TemplateErrorCategory ErrorCategory{}; - return {static_cast(e), ErrorCategory}; -} diff --git a/src/template/model/TemplateError.h b/src/template/model/TemplateError.h deleted file mode 100644 index 5d4612c..0000000 --- a/src/template/model/TemplateError.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace scrap::template_system::model { - -/** - * @brief Error codes for Template validation - */ -enum class TemplateError : std::uint8_t { - InvalidSourceType ///< Invalid template source type string -}; - -} // namespace scrap::template_system::model - -// Error code creation function declaration (must be in global scope for ADL) -// NOLINTNEXTLINE(readability-identifier-naming) - C++ standard requires this exact name for ADL -std::error_code make_error_code(scrap::template_system::model::TemplateError e) noexcept; - -// C++ standard requires specializing std::is_error_code_enum for custom error enums -namespace std { - -template <> struct is_error_code_enum : true_type { }; - -} // namespace std diff --git a/src/template/service/TemplateProcessor.cpp b/src/template/service/TemplateProcessor.cpp deleted file mode 100644 index edf326c..0000000 --- a/src/template/service/TemplateProcessor.cpp +++ /dev/null @@ -1,345 +0,0 @@ -#include "TemplateProcessor.h" -#include -#include -#include - -namespace scrap::template_system::service { - -// SimpleTemplateProcessor static members -const std::regex SimpleTemplateProcessor::VARIABLE_PATTERN(R"(\{\{([^}|]+)(\|([^}]+))?\}\})"); -const std::regex SimpleTemplateProcessor::TRANSFORM_PATTERN(R"(\{\{([^}|]+)\|([^}]+)\}\})"); - -std::string TemplateProcessor::processContent(const std::string& content, const VariableMap& variables) -{ - std::string result = content; - - // Process conditionals first - result = processConditionals(result, variables); - - // Then process variable substitutions - result = substituteVariables(result, variables); - - return result; -} - -std::string TemplateProcessor::processFileName(const std::string& name, const VariableMap& variables) -{ - return SimpleTemplateProcessor::processFileName(name, variables); -} - -void TemplateProcessor::processTemplateDirectory(const std::filesystem::path& templatePath, - const std::filesystem::path& targetPath, - const VariableMap& variables, - const std::vector& ignorePatterns) -{ - if (! std::filesystem::exists(templatePath)) { - throw std::runtime_error("Template path does not exist: " + templatePath.string()); - } - - // Load .scrap-ignore file if it exists - auto allIgnorePatterns = ignorePatterns; - auto ignoreFilePatterns = loadIgnoreFile(templatePath); - allIgnorePatterns.insert(allIgnorePatterns.end(), ignoreFilePatterns.begin(), ignoreFilePatterns.end()); - - // Always ignore template metadata files - allIgnorePatterns.push_back("template.toml"); - allIgnorePatterns.push_back(".scrap-ignore"); - - // Create target directory - std::filesystem::create_directories(targetPath); - - // Process all files and directories recursively - std::error_code ec; - for (auto& entry : std::filesystem::recursive_directory_iterator(templatePath, ec)) { - if (ec) { - std::cerr << "Warning: Error accessing " << entry.path() << ": " << ec.message() << std::endl; - continue; - } - - auto relativePath = std::filesystem::relative(entry.path(), templatePath); - - // Check if file should be ignored - if (shouldIgnoreFile(relativePath, allIgnorePatterns)) { - continue; - } - - // Process filename/directory name - std::string processedName = processFileName(relativePath.string(), variables); - auto targetFilePath = targetPath / processedName; - - if (entry.is_directory()) { - // Create directory - std::filesystem::create_directories(targetFilePath); - } else if (entry.is_regular_file()) { - // Process and copy file - copyTemplateFile(entry.path(), targetFilePath, variables); - } - } -} - -std::string TemplateProcessor::substituteVariables(const std::string& content, const VariableMap& variables) -{ - std::string result = content; - std::regex variablePattern(R"(\{\{([^}|]+)(\|([^}]+))?\}\})"); - std::smatch match; - - while (std::regex_search(result, match, variablePattern)) { - std::string variableName = trim(match[1].str()); - std::string transform = match[3].matched ? trim(match[3].str()) : ""; - - auto value = variables.get(variableName); - std::string replacement; - - if (value) { - replacement = *value; - if (! transform.empty()) { - replacement = variables.applyTransform(replacement, transform); - } - } else { - // Variable not found, leave placeholder or use empty string - replacement = ""; - std::cerr << "Warning: Variable '" << variableName << "' not found" << std::endl; - } - - result.replace(match.position(), match.length(), replacement); - } - - return result; -} - -std::string TemplateProcessor::processVariableExpression(const std::string& expression, const VariableMap& variables) -{ - // Handle variable with optional transform - auto pipePos = expression.find('|'); - if (pipePos != std::string::npos) { - std::string variableName = trim(expression.substr(0, pipePos)); - std::string transform = trim(expression.substr(pipePos + 1)); - - auto value = variables.get(variableName); - if (value) { - return variables.applyTransform(*value, transform); - } - } else { - std::string variableName = trim(expression); - auto value = variables.get(variableName); - if (value) { - return *value; - } - } - - return ""; -} - -std::string TemplateProcessor::processConditionals(const std::string& content, const VariableMap& variables) -{ - std::string result = content; - - // Simple conditional processing: {{#if variable}} ... {{/if}} - std::regex conditionalPattern(R"(\{\{#if\s+([^}]+)\}\}(.*?)\{\{/if\}\})"); - std::smatch match; - - while (std::regex_search(result, match, conditionalPattern)) { - std::string condition = trim(match[1].str()); - std::string conditionalContent = match[2].str(); - - std::string replacement; - if (evaluateCondition(condition, variables)) { - // Recursively process the content inside the conditional - replacement = processConditionals(conditionalContent, variables); - } - - result.replace(match.position(), match.length(), replacement); - } - - return result; -} - -bool TemplateProcessor::evaluateCondition(const std::string& condition, const VariableMap& variables) -{ - std::string trimmedCondition = trim(condition); - - // Handle negation - bool negate = false; - if (trimmedCondition.starts_with("!")) { - negate = true; - trimmedCondition = trim(trimmedCondition.substr(1)); - } - - // Check if it's a simple variable existence check - if (trimmedCondition.find(' ') == std::string::npos) { - auto value = variables.get(trimmedCondition); - bool exists = value.has_value() && ! value->empty() && *value != "false" && *value != "0"; - return negate ? ! exists : exists; - } - - // Handle simple comparisons (variable == value) - auto eqPos = trimmedCondition.find("=="); - if (eqPos != std::string::npos) { - std::string varName = trim(trimmedCondition.substr(0, eqPos)); - std::string expectedValue = trim(trimmedCondition.substr(eqPos + 2)); - - // Remove quotes from expected value - if ((expectedValue.starts_with("\"") && expectedValue.ends_with("\"")) || - (expectedValue.starts_with("'") && expectedValue.ends_with("'"))) { - expectedValue = expectedValue.substr(1, expectedValue.length() - 2); - } - - auto actualValue = variables.get(varName); - bool equal = actualValue && *actualValue == expectedValue; - return negate ? ! equal : equal; - } - - // Handle simple comparisons (variable != value) - auto neqPos = trimmedCondition.find("!="); - if (neqPos != std::string::npos) { - std::string varName = trim(trimmedCondition.substr(0, neqPos)); - std::string expectedValue = trim(trimmedCondition.substr(neqPos + 2)); - - // Remove quotes from expected value - if ((expectedValue.starts_with("\"") && expectedValue.ends_with("\"")) || - (expectedValue.starts_with("'") && expectedValue.ends_with("'"))) { - expectedValue = expectedValue.substr(1, expectedValue.length() - 2); - } - - auto actualValue = variables.get(varName); - bool equal = actualValue && *actualValue == expectedValue; - return negate ? equal : ! equal; - } - - // Default: treat as variable existence check - auto value = variables.get(trimmedCondition); - bool exists = value.has_value() && ! value->empty() && *value != "false" && *value != "0"; - return negate ? ! exists : exists; -} - -void TemplateProcessor::copyTemplateFile(const std::filesystem::path& sourcePath, - const std::filesystem::path& targetPath, - const VariableMap& variables) -{ - // Create target directory if needed - auto targetDir = targetPath.parent_path(); - if (! targetDir.empty() && ! std::filesystem::exists(targetDir)) { - std::filesystem::create_directories(targetDir); - } - - // Read source file - std::ifstream source(sourcePath); - if (! source) { - throw std::runtime_error("Cannot read template file: " + sourcePath.string()); - } - - std::string content((std::istreambuf_iterator(source)), std::istreambuf_iterator()); - - // Process content - std::string processedContent = processContent(content, variables); - - // Write target file - std::ofstream target(targetPath); - if (! target) { - throw std::runtime_error("Cannot write target file: " + targetPath.string()); - } - - target << processedContent; -} - -bool TemplateProcessor::shouldIgnoreFile(const std::filesystem::path& filePath, - const std::vector& ignorePatterns) -{ - std::string filePathStr = filePath.string(); - std::string fileName = filePath.filename().string(); - - for (const auto& pattern : ignorePatterns) { - // Simple pattern matching (support * wildcards) - std::string regexPattern = pattern; - - // Convert glob pattern to regex - std::replace(regexPattern.begin(), regexPattern.end(), '*', '.'); - regexPattern = ".*" + regexPattern + ".*"; - - try { - std::regex patternRegex(regexPattern); - if (std::regex_match(filePathStr, patternRegex) || std::regex_match(fileName, patternRegex)) { - return true; - } - } catch (const std::exception&) { - // Invalid regex, try simple string match - if (filePathStr.find(pattern) != std::string::npos || fileName.find(pattern) != std::string::npos) { - return true; - } - } - } - - return false; -} - -std::string TemplateProcessor::trim(const std::string& str) -{ - auto start = str.find_first_not_of(" \t\n\r"); - if (start == std::string::npos) { - return ""; - } - auto end = str.find_last_not_of(" \t\n\r"); - return str.substr(start, end - start + 1); -} - -std::vector TemplateProcessor::loadIgnoreFile(const std::filesystem::path& templatePath) -{ - std::vector patterns; - auto ignoreFile = templatePath / ".scrap-ignore"; - - if (! std::filesystem::exists(ignoreFile)) { - return patterns; - } - - std::ifstream file(ignoreFile); - std::string line; - - while (std::getline(file, line)) { - line = trim(line); - if (! line.empty() && ! line.starts_with("#")) { - patterns.push_back(line); - } - } - - return patterns; -} - -// SimpleTemplateProcessor implementation - -std::string SimpleTemplateProcessor::process(const std::string& content, const VariableMap& variables) -{ - std::string result = content; - std::smatch match; - - while (std::regex_search(result, match, VARIABLE_PATTERN)) { - std::string variableName = match[1].str(); - std::string transform = match[3].matched ? match[3].str() : ""; - - // Trim whitespace - variableName.erase(0, variableName.find_first_not_of(" \t")); - variableName.erase(variableName.find_last_not_of(" \t") + 1); - - auto value = variables.get(variableName); - std::string replacement; - - if (value) { - replacement = *value; - if (! transform.empty()) { - transform.erase(0, transform.find_first_not_of(" \t")); - transform.erase(transform.find_last_not_of(" \t") + 1); - replacement = variables.applyTransform(replacement, transform); - } - } - - result.replace(match.position(), match.length(), replacement); - } - - return result; -} - -std::string SimpleTemplateProcessor::processFileName(const std::string& filename, const VariableMap& variables) -{ - return process(filename, variables); -} - -} // namespace scrap::template_system::service diff --git a/src/template/service/TemplateProcessor.h b/src/template/service/TemplateProcessor.h deleted file mode 100644 index 2d977c3..0000000 --- a/src/template/service/TemplateProcessor.h +++ /dev/null @@ -1,101 +0,0 @@ -#pragma once - -#include "template/model/Template.h" -#include -#include -#include - -namespace scrap::template_system::service { - -using namespace model; - -/** - * @brief Advanced template processing engine with mustache-like syntax - * - * Supports: - * - Variable substitution: {{variable}} - * - Transform pipes: {{variable|transform}} - * - Conditional sections: {{#if condition}} ... {{/if}} - * - File/directory name substitution - */ -class TemplateProcessor { -public: - /** - * @brief Process template content with variable substitution - * @param content Template content to process - * @param variables Variable map for substitution - * @return Processed content - */ - std::string processContent(const std::string& content, const VariableMap& variables); - - /** - * @brief Process file/directory name with variable substitution - * @param name File or directory name with placeholders - * @param variables Variable map for substitution - * @return Processed name - */ - std::string processFileName(const std::string& name, const VariableMap& variables); - - /** - * @brief Process entire template directory - * @param templatePath Path to template directory - * @param targetPath Target directory for output - * @param variables Variable map for substitution - * @param ignorePatterns File patterns to ignore - */ - void processTemplateDirectory(const std::filesystem::path& templatePath, - const std::filesystem::path& targetPath, - const VariableMap& variables, - const std::vector& ignorePatterns = {}); - -private: - // Variable substitution - std::string substituteVariables(const std::string& content, const VariableMap& variables); - std::string processVariableExpression(const std::string& expression, const VariableMap& variables); - - // Conditional processing - std::string processConditionals(const std::string& content, const VariableMap& variables); - bool evaluateCondition(const std::string& condition, const VariableMap& variables); - - // File operations - void copyTemplateFile(const std::filesystem::path& sourcePath, - const std::filesystem::path& targetPath, - const VariableMap& variables); - - bool shouldIgnoreFile(const std::filesystem::path& filePath, const std::vector& ignorePatterns); - - // Utility functions - std::string trim(const std::string& str); - std::vector loadIgnoreFile(const std::filesystem::path& templatePath); -}; - -/** - * @brief Simple template processor for basic variable substitution - * - * This is a simpler implementation that only handles {{variable}} substitution - * without advanced features like conditionals or loops. - */ -class SimpleTemplateProcessor { -public: - /** - * @brief Process content with simple variable substitution - * @param content Template content - * @param variables Variable map - * @return Processed content - */ - static std::string process(const std::string& content, const VariableMap& variables); - - /** - * @brief Process filename with variable substitution - * @param filename Filename with placeholders - * @param variables Variable map - * @return Processed filename - */ - static std::string processFileName(const std::string& filename, const VariableMap& variables); - -private: - static const std::regex VARIABLE_PATTERN; - static const std::regex TRANSFORM_PATTERN; -}; - -} // namespace scrap::template_system::service diff --git a/src/template/service/TemplateService.cpp b/src/template/service/TemplateService.cpp deleted file mode 100644 index 8e44b77..0000000 --- a/src/template/service/TemplateService.cpp +++ /dev/null @@ -1,528 +0,0 @@ -#include "TemplateService.h" -#include "TemplateProcessor.h" -#include "TemplateServiceError.h" -#include "repository/driver/GitDriver.h" -#include "shared/presentation/driver/ConsolePresenter.h" -#include -#include -#include -#include - -namespace scrap::template_system::service { - -// Base interface implementation -TemplateService::~TemplateService() = default; - -class DefaultTemplateService::Internal { -public: - std::filesystem::path templatesDir_; - std::filesystem::path registryFile_; - std::shared_ptr gitDriver_; - std::shared_ptr presenter_; - std::vector templateSources_; - - Internal(const std::filesystem::path& templatesDir, - std::shared_ptr gitDriver, - std::shared_ptr presenter) - : templatesDir_(templatesDir), - registryFile_(templatesDir / "registry.toml"), - gitDriver_(gitDriver ? gitDriver : std::make_shared()), - presenter_(presenter ? presenter : std::make_shared()) - { - initializeTemplateDirectory(); - loadTemplateRegistry(); - // Ignore errors during initialization - templates can be cloned on demand - auto result = ensureOfficialTemplatesExist(); - if (! result) { - presenter_->displayWarning(result.error()); - } - } - - // Internal helper methods - void initializeTemplateDirectory() - { - std::filesystem::create_directories(templatesDir_); - std::filesystem::create_directories(templatesDir_ / "official"); - std::filesystem::create_directories(templatesDir_ / "user"); - } - - std::expected ensureOfficialTemplatesExist() - { - // Check if official templates source is configured - auto officialSource = findTemplateSource("official"); - if (! officialSource) { - // Add official template source - auto official = TemplateSource("official", TemplateSourceType::Git); - official.url = "https://github.com/skipbit/scrap-templates.git"; - official.autoUpdate = true; - - templateSources_.push_back(official); - [[maybe_unused]] auto saveResult = saveTemplateRegistry(); - // Ignore registry save errors during initialization - officialSource = findTemplateSource("official"); - } - - // Check if official templates are cloned - if (officialSource && officialSource->type == TemplateSourceType::Git && officialSource->url) { - auto targetDir = sourceDirectory("official"); - - // Clone if directory doesn't exist - if (! std::filesystem::exists(targetDir)) { - try { - // Create parent directory if needed - std::filesystem::create_directories(targetDir.parent_path()); - - // Clone the repository - gitDriver_->clone(*officialSource->url, targetDir); - - presenter_->displaySuccess("Successfully cloned official templates from " + *officialSource->url); - } catch (const std::exception& e) { - return std::unexpected("Failed to clone official templates: " + std::string(e.what())); - } - } - } - - return {}; - } - - void loadTemplateRegistry() - { - if (! std::filesystem::exists(registryFile_)) { - return; - } - - // TODO: Implement TOML parsing when dross support is ready - // For now, start with empty registry - templateSources_.clear(); - } - - [[nodiscard]] std::expected saveTemplateRegistry() noexcept - { - std::ofstream registry(registryFile_); - if (! registry) { - auto errorCode = make_error_code(TemplateServiceError::RegistryWriteFailed); - return std::unexpected(dross::error{errorCode.value(), errorCode.category()}); - } - - // TODO: Implement TOML serialization when dross support is ready - // For now, write a simple format - registry << "# Template Sources Registry\n"; - registry << "# This file is managed by scrap\n\n"; - - for (const auto& source : templateSources_) { - registry << "[[sources]]\n"; - registry << "name = \"" << source.name << "\"\n"; - registry << "type = \"" << templateSourceTypeToString(source.type) << "\"\n"; - - if (source.url) { - registry << "url = \"" << *source.url << "\"\n"; - } - - if (source.path) { - registry << "path = \"" << source.path->string() << "\"\n"; - } - - registry << "branch = \"" << source.branch << "\"\n"; - registry << "auto_update = " << (source.autoUpdate ? "true" : "false") << "\n"; - registry << "\n"; - } - - return {}; - } - - std::filesystem::path sourceDirectory(const std::string& sourceName) - { - if (sourceName == "official") { - return templatesDir_ / "official" / "scrap-templates"; - } - return templatesDir_ / "user" / sourceName; - } - - std::optional findTemplateSource(const std::string& sourceName) - { - auto it = - std::find_if(templateSources_.begin(), templateSources_.end(), [&sourceName](const TemplateSource& source) { - return source.name == sourceName; - }); - - if (it != templateSources_.end()) { - return *it; - } - - return std::nullopt; - } - - std::vector