生成代码工程

This commit is contained in:
root 2026-05-16 12:54:48 +08:00
parent d76132f12e
commit b82c63f118
19 changed files with 10084 additions and 2 deletions

38
CMakeLists.txt Normal file
View File

@ -0,0 +1,38 @@
cmake_minimum_required(VERSION 3.14)
project(ModularApp VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# MSVC UTF-8 support
if (MSVC)
add_compile_options(/utf-8)
endif()
# Header include directory
set(INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include)
# Collect all module source files (excluding main.cpp)
file(GLOB_RECURSE LIB_SOURCES
src/logger.cpp
src/configmanager.cpp
src/datamanager.cpp
src/utils.cpp
src/processor.cpp
src/adapter.cpp
)
# Main executable
add_executable(${PROJECT_NAME}
src/main.cpp
${LIB_SOURCES}
)
target_include_directories(${PROJECT_NAME} PRIVATE ${INCLUDE_DIR})
# Test executable (uses standard assert, no external testing framework)
add_executable(${PROJECT_NAME}_test
tests/basic_test.cpp
${LIB_SOURCES}
)
target_include_directories(${PROJECT_NAME}_test PRIVATE ${INCLUDE_DIR})

View File

@ -1,3 +1,60 @@
# 123
# ModularApp
123
A modular C++17 software project demonstrating clean module separation, CMake build system, and Doxygen-style API documentation.
## Features
- **Logger Module** — Hierarchical log output (DEBUG / INFO / WARN / ERROR)
- **ConfigManager Module** — Parse and manage key=value configuration files
- **DataManager Module** — In-memory data caching and lifecycle management
- **Utils Module** — String trimming, splitting, time formatting, file path helpers
- **Processor Module** — Core business logic processing with dependency injection
- **Adapter Module** — CLI argument parsing (--config, --help, --version)
## Build & Run
```bash
# Configure
cmake -B build -DCMAKE_BUILD_TYPE=Release
# Build
cmake --build build
# Run with a config file
./build/ModularApp --config example.conf
# Show help
./build/ModularApp --help
# Show version
./build/ModularApp --version
# Run tests
./build/ModularApp_test
```
## Project Structure
```
.
├── CMakeLists.txt
├── README.md
├── include/
│ ├── app.hpp # Master header (includes all modules)
│ ├── logger.hpp
│ ├── configmanager.hpp
│ ├── datamanager.hpp
│ ├── utils.hpp
│ ├── processor.hpp
│ └── adapter.hpp
├── src/
│ ├── logger.cpp
│ ├── configmanager.cpp
│ ├── datamanager.cpp
│ ├── utils.cpp
│ ├── processor.cpp
│ ├── adapter.cpp
│ └── main.cpp
└── tests/
└── basic_test.cpp
```

8966
events.ndjson Normal file

File diff suppressed because one or more lines are too long

41
generation.json Normal file
View File

@ -0,0 +1,41 @@
{
"projectId": 41,
"generationId": "codegen_f9c743f8bc84450ca3323bd45ef99089",
"language": "C++",
"status": "completed",
"fileIds": [
507
],
"outputDir": "D:\\workspace\\agent\\DocumentGenerateAgent\\agents\\ai_agents\\project-files\\codegen-runs\\codegen_f9c743f8bc84450ca3323bd45ef99089",
"relativeOutputDir": "codegen-runs/codegen_f9c743f8bc84450ca3323bd45ef99089",
"generatedFiles": [
"CMakeLists.txt",
"README.md",
"events.ndjson",
"include/adapter.hpp",
"include/app.hpp",
"include/configmanager.hpp",
"include/datamanager.hpp",
"include/logger.hpp",
"include/processor.hpp",
"include/utils.hpp",
"src/adapter.cpp",
"src/configmanager.cpp",
"src/datamanager.cpp",
"src/logger.cpp",
"src/main.cpp",
"src/processor.cpp",
"src/utils.cpp",
"tests/basic_test.cpp"
],
"analysisSummary": "### 业务目标\n- 开发一个基于C++的可扩展、模块化的软件工程,用于实现特定业务逻辑处理(具体业务未明确,需后续补充)。\n- 支持跨平台编译与运行,具备良好的可维护性和代码复用性。\n- 通过模块化设计提升开发效率,便于单元测试和团队协作。\n\n### 功能清单\n1. **核心逻辑处理模块**\n - 实现主要算法或数据处理流程。\n - 提供对外接口供其他模块调用。\n2. **数据管理模块**\n - 负责数据的加载、存储、缓存和生命周期管理。\n3. **配置管理模块**\n - 解析并管理外部配置文件如JSON、XML或INI格式。\n4. **日志记录模块**\n - 提供分级日志输出功能DEBUG/INFO/WARN/ERROR。\n5. **工具辅助模块**\n - 包含字符串处理、时间操作、文件路径解析等通用工具函数。\n6. **接口适配模块**\n - 封装外部通信接口如CLI命令行输入、模拟API调用等。\n\n### 数据结构\n```cpp\n// 示例结构(根据实际需求调整)\nstruct ConfigData {\n std::string app_name;\n int log_level;\n std::string data_path;\n bool enable_cache;\n};\n\nstruct ProcessResult {\n bool success;\n int code;\n std::string message;\n std::any output_data; // 或使用variant/shared_ptr\n};\n```\n\n### 接口或命令\n- **内部接口C++函数/类接口)**\n - `class DataProcessor`:提供 `process(const Input&) -> ProcessResult`\n - `class ConfigManager`:提供 `loadFrom(const std::string& path) -> bool`\n - `class Logger`:提供 `log(LogLevel level, const std::string& msg)`\n- **外部命令行接口CLI**\n - `app --config <path>`:指定配置文件启动程序\n - `app --help`:显示帮助信息\n - `app --version`:输出版本号\n\n### 约束\n- 使用标准C++17及以上版本不依赖非标准扩展。\n- 不使用第三方框架如Boost仅允许使用STL外部依赖需单独声明。\n- 模块间通过头文件接口通信,禁止跨模块直接访问私有成员。\n- 所有模块需支持独立编译(静态库或目录隔离)。\n- 工程结构需符合CMake构建规范。\n\n### 测试建议\n- 为每个模块编写独立的单元测试用例推荐使用Google Test框架。\n- 对核心处理逻辑进行边界值、异常输入测试。\n- 配置管理模块需测试非法格式、缺失字段等情况。\n- 日志模块验证不同级别日志是否正确输出到控制台或文件。\n- 建议集成CI流程执行编译检查与基础测试。",
"eventLogFile": "D:\\workspace\\agent\\DocumentGenerateAgent\\agents\\ai_agents\\project-files\\codegen-runs\\codegen_f9c743f8bc84450ca3323bd45ef99089\\events.ndjson",
"repoSettings": {
"username": "root",
"password": "pAssW0rd",
"repoUrl": "http://47.108.255.216:3000/root/test_123.git",
"branch": "main"
},
"repoUrl": "http://47.108.255.216:3000/root/test_123.git",
"branch": "main"
}

62
include/adapter.hpp Normal file
View File

@ -0,0 +1,62 @@
#ifndef MODULAR_ADAPTER_HPP
#define MODULAR_ADAPTER_HPP
#include <string>
#include <vector>
#include <memory>
/**
* @brief Parsed command-line arguments.
*/
struct CommandLineArgs {
std::string config_path; ///< Path to config file (--config <path>).
bool show_help = false; ///< Whether --help was passed.
bool show_version = false; ///< Whether --version was passed.
bool has_errors = false; ///< Whether there were parsing errors.
std::string error_msg; ///< Error message if has_errors is true.
};
/**
* @brief Adapter for parsing CLI arguments and dispatching commands.
*
* Supports:
* --config <path> Load configuration from file
* --help Display help message
* --version Display application version
*/
class CommandLineAdapter {
public:
/**
* @brief Construct the adapter with argc/argv.
* @param argc Argument count (from main).
* @param argv Argument vector (from main).
*/
CommandLineAdapter(int argc, char* argv[]);
/**
* @brief Parse the stored arguments.
* @return A CommandLineArgs struct containing parsed values.
*/
CommandLineArgs parse();
/**
* @brief Print the help message to stdout.
*/
static void printHelp();
/**
* @brief Print the version string to stdout.
*/
static void printVersion();
private:
int argc_;
char** argv_;
/**
* @brief Get argument at index i, or empty string if out of range.
*/
std::string argAt(int i) const;
};
#endif // MODULAR_ADAPTER_HPP

19
include/app.hpp Normal file
View File

@ -0,0 +1,19 @@
#ifndef MODULAR_APP_HPP
#define MODULAR_APP_HPP
/**
* @file app.hpp
* @brief Master header that includes all modular components.
*
* Include this single header to access every module of the application:
* Logger, ConfigManager, DataManager, Utils, DataProcessor, CommandLineAdapter.
*/
#include "logger.hpp"
#include "configmanager.hpp"
#include "datamanager.hpp"
#include "utils.hpp"
#include "processor.hpp"
#include "adapter.hpp"
#endif // MODULAR_APP_HPP

76
include/configmanager.hpp Normal file
View File

@ -0,0 +1,76 @@
#ifndef MODULAR_CONFIGMANAGER_HPP
#define MODULAR_CONFIGMANAGER_HPP
#include <string>
#include <unordered_map>
#include <vector>
#include <fstream>
/**
* @brief Manages application configuration loaded from a simple key=value file.
*
* Supported file format:
* - Lines starting with '#' are comments.
* - Empty lines are ignored.
* - Each line must be in the form: key = value
* - Leading / trailing whitespace around key and value is trimmed.
*/
class ConfigManager {
public:
/**
* @brief Load configuration from a file.
* @param path File path to the configuration file.
* @return true on success, false on failure (file not found or parse error).
*/
bool loadFrom(const std::string& path);
/**
* @brief Get a string value by key.
* @param key Configuration key.
* @param default_val Value returned if the key is not found.
* @return The value as string, or default_val.
*/
std::string getString(const std::string& key, const std::string& default_val = "") const;
/**
* @brief Get an integer value by key.
* @param key Configuration key.
* @param default_val Value returned if the key is not found or not convertible.
* @return The parsed integer, or default_val.
*/
int getInt(const std::string& key, int default_val = 0) const;
/**
* @brief Get a boolean value by key.
* @param key Configuration key.
* @param default_val Value returned if the key is not found.
* @return true if the value is "true", "1", or "yes" (case-insensitive); otherwise false.
*/
bool getBool(const std::string& key, bool default_val = false) const;
/**
* @brief Check if a key exists.
* @param key Configuration key.
* @return true if the key exists.
*/
bool hasKey(const std::string& key) const;
/**
* @brief Clear all loaded configuration data.
*/
void clear();
private:
std::unordered_map<std::string, std::string> config_;
/**
* @brief Parse a single line into key and value.
* @param line Input line.
* @param key [out] Parsed key.
* @param value [out] Parsed value.
* @return true if parsing succeeded.
*/
static bool parseLine(const std::string& line, std::string& key, std::string& value);
};
#endif // MODULAR_CONFIGMANAGER_HPP

64
include/datamanager.hpp Normal file
View File

@ -0,0 +1,64 @@
#ifndef MODULAR_DATAMANAGER_HPP
#define MODULAR_DATAMANAGER_HPP
#include <string>
#include <unordered_map>
#include <memory>
#include <any>
#include <vector>
/**
* @brief Manages in-memory data records with optional caching and lifecycle control.
*
* Each record is identified by a unique string key and stores a std::any value.
* The manager supports loading data, storing data, and clearing cached entries.
*/
class DataManager {
public:
/// @brief Default constructor.
DataManager() = default;
/**
* @brief Load a record by its key.
* @param key Unique identifier for the data.
* @return A shared_ptr to the data (std::any), or nullptr if not found.
*/
std::shared_ptr<std::any> load(const std::string& key) const;
/**
* @brief Store a data record.
* @param key Unique identifier.
* @param value Data value to store.
*/
void store(const std::string& key, const std::any& value);
/**
* @brief Check if a key exists in the manager.
* @param key Unique identifier.
* @return true if the key exists.
*/
bool exists(const std::string& key) const;
/**
* @brief Remove a record by key.
* @param key Unique identifier to remove.
* @return true if the record was removed, false if not found.
*/
bool remove(const std::string& key);
/**
* @brief Get the number of stored records.
* @return Record count.
*/
size_t size() const;
/**
* @brief Clear all stored data.
*/
void clear();
private:
std::unordered_map<std::string, std::any> data_;
};
#endif // MODULAR_DATAMANAGER_HPP

74
include/logger.hpp Normal file
View File

@ -0,0 +1,74 @@
#ifndef MODULAR_LOGGER_HPP
#define MODULAR_LOGGER_HPP
#include <string>
#include <mutex>
#include <iostream>
#include <sstream>
#include <ctime>
#include <iomanip>
/// @brief Log severity levels.
enum class LogLevel {
DEBUG, ///< Detailed debug information
INFO, ///< General informational messages
WARN, ///< Warning messages
ERROR ///< Error messages
};
/**
* @brief Thread-safe logger with hierarchical level filtering.
*
* Provides logging at DEBUG, INFO, WARN, and ERROR levels.
* Each log entry is prefixed with a timestamp and level tag.
*/
class Logger {
public:
/**
* @brief Construct a Logger with an optional minimum level.
* @param level Minimum level to log (default: INFO).
*/
explicit Logger(LogLevel level = LogLevel::INFO);
/**
* @brief Set the minimum log level.
* @param level New minimum level.
*/
void setLevel(LogLevel level);
/**
* @brief Get the current minimum log level.
* @return Current LogLevel.
*/
LogLevel level() const;
/**
* @brief Log a message at the given level.
* @param level Severity level.
* @param msg Message text.
*
* If @p level is below the configured minimum, the message is discarded.
*/
void log(LogLevel level, const std::string& msg);
/// @brief Convenience: log at DEBUG level.
void debug(const std::string& msg);
/// @brief Convenience: log at INFO level.
void info(const std::string& msg);
/// @brief Convenience: log at WARN level.
void warn(const std::string& msg);
/// @brief Convenience: log at ERROR level.
void error(const std::string& msg);
private:
LogLevel level_;
std::mutex mutex_;
/// @brief Convert a LogLevel to its string representation.
static std::string levelToString(LogLevel level);
/// @brief Get current local time as a formatted string "[YYYY-MM-DD HH:MM:SS]".
static std::string currentTimestamp();
};
#endif // MODULAR_LOGGER_HPP

52
include/processor.hpp Normal file
View File

@ -0,0 +1,52 @@
#ifndef MODULAR_PROCESSOR_HPP
#define MODULAR_PROCESSOR_HPP
#include <string>
#include <any>
#include <memory>
/**
* @brief Represents the result of a data processing operation.
*/
struct ProcessResult {
bool success = false; ///< Whether the operation succeeded.
int code = 0; ///< Result code (0 = success, non-zero = error).
std::string message; ///< Human-readable result message.
std::any output_data; ///< Optional output data payload.
};
/**
* @brief Core business logic processor.
*
* Accepts input data and produces a ProcessResult.
* The processor can be configured with a ConfigManager and Logger.
*/
class DataProcessor {
public:
/**
* @brief Construct a DataProcessor.
* @param log An optional shared Logger instance.
*/
explicit DataProcessor(std::shared_ptr<class Logger> log = nullptr);
/**
* @brief Set a configuration manager for the processor.
* @param cfg Shared pointer to a ConfigManager.
*/
void setConfig(std::shared_ptr<class ConfigManager> cfg);
/**
* @brief Process input data.
* @param input Arbitrary input data (std::any).
* @return A ProcessResult describing the outcome.
*
* This is a placeholder implementation. Override or extend for real logic.
*/
ProcessResult process(const std::any& input);
private:
std::shared_ptr<class Logger> logger_;
std::shared_ptr<class ConfigManager> config_;
};
#endif // MODULAR_PROCESSOR_HPP

59
include/utils.hpp Normal file
View File

@ -0,0 +1,59 @@
#ifndef MODULAR_UTILS_HPP
#define MODULAR_UTILS_HPP
#include <string>
#include <vector>
#include <sstream>
#include <algorithm>
#include <cctype>
#include <chrono>
#include <iomanip>
/// @brief General-purpose utility functions.
namespace utils {
/**
* @brief Trim leading and trailing whitespace from a string.
* @param s Input string (modified in place).
* @return Reference to the trimmed string.
*/
std::string& trim(std::string& s);
/**
* @brief Split a string by a delimiter.
* @param s Input string.
* @param delimiter Delimiter character.
* @return Vector of split substrings.
*/
std::vector<std::string> split(const std::string& s, char delimiter);
/**
* @brief Convert a string to lower case in place.
* @param s Input string (modified in place).
* @return Reference to the lowered string.
*/
std::string& toLower(std::string& s);
/**
* @brief Get the current timestamp as a string "YYYY-MM-DD HH:MM:SS".
* @return Formatted timestamp string.
*/
std::string currentTimestamp();
/**
* @brief Extract the file name from a full path.
* @param path Full path string (e.g. "/home/user/file.txt").
* @return File name part (e.g. "file.txt").
*/
std::string fileName(const std::string& path);
/**
* @brief Extract the file extension from a path.
* @param path Full path string.
* @return Extension without the dot (e.g. "txt"), or empty string.
*/
std::string fileExtension(const std::string& path);
} // namespace utils
#endif // MODULAR_UTILS_HPP

45
src/adapter.cpp Normal file
View File

@ -0,0 +1,45 @@
#include "adapter.hpp"
#include <iostream>
CommandLineAdapter::CommandLineAdapter(int argc, char* argv[])
: argc_(argc), argv_(argv) {}
std::string CommandLineAdapter::argAt(int i) const {
return (i >= 0 && i < argc_) ? std::string(argv_[i]) : std::string();
}
CommandLineArgs CommandLineAdapter::parse() {
CommandLineArgs args;
for (int i = 1; i < argc_; ++i) {
std::string token = argAt(i);
if (token == "--help") {
args.show_help = true;
} else if (token == "--version") {
args.show_version = true;
} else if (token == "--config") {
if (i + 1 < argc_) {
args.config_path = argAt(++i);
} else {
args.has_errors = true;
args.error_msg = "Missing value for --config";
}
} else {
args.has_errors = true;
args.error_msg = "Unknown argument: " + token;
}
}
return args;
}
void CommandLineAdapter::printHelp() {
std::cout
<< "ModularApp v1.0.0\n"
<< "Usage:\n"
<< " app --config <path> Load configuration from file\n"
<< " app --help Show this help message\n"
<< " app --version Show version information\n";
}
void CommandLineAdapter::printVersion() {
std::cout << "ModularApp version 1.0.0\n";
}

70
src/configmanager.cpp Normal file
View File

@ -0,0 +1,70 @@
#include "configmanager.hpp"
#include "utils.hpp"
bool ConfigManager::loadFrom(const std::string& path) {
std::ifstream file(path);
if (!file.is_open()) {
return false;
}
config_.clear();
std::string line;
while (std::getline(file, line)) {
// Skip comments and empty lines
if (line.empty() || line[0] == '#') {
continue;
}
std::string key, value;
if (parseLine(line, key, value)) {
config_[key] = value;
}
}
return true;
}
std::string ConfigManager::getString(const std::string& key, const std::string& default_val) const {
auto it = config_.find(key);
return (it != config_.end()) ? it->second : default_val;
}
int ConfigManager::getInt(const std::string& key, int default_val) const {
auto it = config_.find(key);
if (it == config_.end()) {
return default_val;
}
try {
return std::stoi(it->second);
} catch (...) {
return default_val;
}
}
bool ConfigManager::getBool(const std::string& key, bool default_val) const {
auto it = config_.find(key);
if (it == config_.end()) {
return default_val;
}
std::string val = it->second;
utils::toLower(val);
return (val == "true" || val == "1" || val == "yes");
}
bool ConfigManager::hasKey(const std::string& key) const {
return config_.find(key) != config_.end();
}
void ConfigManager::clear() {
config_.clear();
}
bool ConfigManager::parseLine(const std::string& line, std::string& key, std::string& value) {
auto eq_pos = line.find('=');
if (eq_pos == std::string::npos) {
return false;
}
key = line.substr(0, eq_pos);
value = line.substr(eq_pos + 1);
utils::trim(key);
utils::trim(value);
return !key.empty();
}

29
src/datamanager.cpp Normal file
View File

@ -0,0 +1,29 @@
#include "datamanager.hpp"
std::shared_ptr<std::any> DataManager::load(const std::string& key) const {
auto it = data_.find(key);
if (it == data_.end()) {
return nullptr;
}
return std::make_shared<std::any>(it->second);
}
void DataManager::store(const std::string& key, const std::any& value) {
data_[key] = value;
}
bool DataManager::exists(const std::string& key) const {
return data_.find(key) != data_.end();
}
bool DataManager::remove(const std::string& key) {
return data_.erase(key) > 0;
}
size_t DataManager::size() const {
return data_.size();
}
void DataManager::clear() {
data_.clear();
}

52
src/logger.cpp Normal file
View File

@ -0,0 +1,52 @@
#include "logger.hpp"
Logger::Logger(LogLevel level)
: level_(level) {}
void Logger::setLevel(LogLevel level) {
std::lock_guard<std::mutex> lock(mutex_);
level_ = level;
}
LogLevel Logger::level() const {
std::lock_guard<std::mutex> lock(mutex_);
return level_;
}
void Logger::log(LogLevel level, const std::string& msg) {
std::lock_guard<std::mutex> lock(mutex_);
if (level < level_) {
return;
}
std::cout << currentTimestamp() << " "
<< levelToString(level) << " "
<< msg << std::endl;
}
void Logger::debug(const std::string& msg) { log(LogLevel::DEBUG, msg); }
void Logger::info(const std::string& msg) { log(LogLevel::INFO, msg); }
void Logger::warn(const std::string& msg) { log(LogLevel::WARN, msg); }
void Logger::error(const std::string& msg) { log(LogLevel::ERROR, msg); }
std::string Logger::levelToString(LogLevel level) {
switch (level) {
case LogLevel::DEBUG: return "[DEBUG]";
case LogLevel::INFO: return "[INFO]";
case LogLevel::WARN: return "[WARN]";
case LogLevel::ERROR: return "[ERROR]";
default: return "[UNKNOWN]";
}
}
std::string Logger::currentTimestamp() {
std::time_t t = std::time(nullptr);
std::tm tm;
#ifdef _WIN32
localtime_s(&tm, &t);
#else
localtime_r(&t, &tm);
#endif
std::ostringstream oss;
oss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
return oss.str();
}

75
src/main.cpp Normal file
View File

@ -0,0 +1,75 @@
/**
* @file main.cpp
* @brief Application entry point.
*
* Parses command-line arguments, initializes modules, and runs the
* core processor.
*/
#include "app.hpp"
#include <iostream>
#include <memory>
/**
* @brief Main function.
* @param argc Argument count.
* @param argv Argument vector.
* @return Exit code (0 on success, 1 on error).
*/
int main(int argc, char* argv[]) {
// --- Parse CLI arguments ---
CommandLineAdapter adapter(argc, argv);
CommandLineArgs args = adapter.parse();
if (args.has_errors) {
std::cerr << "Error: " << args.error_msg << "\n";
adapter.printHelp();
return 1;
}
if (args.show_help) {
adapter.printHelp();
return 0;
}
if (args.show_version) {
adapter.printVersion();
return 0;
}
// --- Initialize modules ---
auto logger = std::make_shared<Logger>(LogLevel::DEBUG);
logger->info("Application started");
// --- Configuration ---
auto config = std::make_shared<ConfigManager>();
if (!args.config_path.empty()) {
if (config->loadFrom(args.config_path)) {
logger->info("Configuration loaded from " + args.config_path);
} else {
logger->error("Failed to load configuration from " + args.config_path);
return 1;
}
} else {
logger->warn("No configuration file specified, using defaults");
}
// --- Data processor ---
DataProcessor processor(logger);
processor.setConfig(config);
// --- Run processing ---
std::any input = std::string("sample input data");
ProcessResult result = processor.process(input);
logger->info("Process result: " + result.message);
logger->info("Exit code: " + std::to_string(result.code));
if (!result.success) {
logger->error("Processing failed");
return 1;
}
logger->info("Application finished successfully");
return 0;
}

47
src/processor.cpp Normal file
View File

@ -0,0 +1,47 @@
#include "processor.hpp"
#include "logger.hpp"
#include "configmanager.hpp"
/**
* @brief Construct a DataProcessor with an optional logger.
* @param log Shared pointer to a Logger instance (may be null).
*/
DataProcessor::DataProcessor(std::shared_ptr<Logger> log)
: logger_(std::move(log)) {}
/**
* @brief Inject a ConfigManager into the processor.
* @param cfg Shared pointer to a ConfigManager instance.
*/
void DataProcessor::setConfig(std::shared_ptr<ConfigManager> cfg) {
config_ = std::move(cfg);
}
/**
* @brief Execute the core processing logic.
*
* This placeholder implementation logs the operation and returns a
* default success result. Extend this method with real business logic.
*
* @param input Arbitrary input data.
* @return ProcessResult with success=true and a descriptive message.
*/
ProcessResult DataProcessor::process(const std::any& input) {
ProcessResult result;
result.success = true;
result.code = 0;
result.message = "Processed successfully";
if (logger_) {
logger_->info("DataProcessor::process invoked");
}
// If config is available, read an optional "app.name" setting
if (config_) {
std::string app_name = config_->getString("app.name", "Unnamed");
result.message = "Processed by " + app_name;
}
result.output_data = input; // Pass through the input as output
return result;
}

63
src/utils.cpp Normal file
View File

@ -0,0 +1,63 @@
#include "utils.hpp"
namespace utils {
std::string& trim(std::string& s) {
// Trim leading whitespace
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
// Trim trailing whitespace
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(), s.end());
return s;
}
std::vector<std::string> split(const std::string& s, char delimiter) {
std::vector<std::string> tokens;
std::istringstream stream(s);
std::string token;
while (std::getline(stream, token, delimiter)) {
tokens.push_back(token);
}
return tokens;
}
std::string& toLower(std::string& s) {
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
return s;
}
std::string currentTimestamp() {
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
std::tm tm;
#ifdef _WIN32
localtime_s(&tm, &in_time_t);
#else
localtime_r(&in_time_t, &tm);
#endif
std::ostringstream oss;
oss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
return oss.str();
}
std::string fileName(const std::string& path) {
auto pos = path.find_last_of("/\\");
if (pos == std::string::npos) {
return path;
}
return path.substr(pos + 1);
}
std::string fileExtension(const std::string& path) {
auto pos = path.find_last_of('.');
if (pos == std::string::npos || pos == 0) {
return "";
}
return path.substr(pos + 1);
}
} // namespace utils

193
tests/basic_test.cpp Normal file
View File

@ -0,0 +1,193 @@
/**
* @file basic_test.cpp
* @brief Minimal unit tests using standard library assert() only.
*
* Tests each module: Logger, ConfigManager, DataManager, Utils,
* DataProcessor, and CommandLineAdapter.
*/
#include <cassert>
#include <iostream>
#include <string>
#include <memory>
#include <sstream>
// Include all module headers
#include "logger.hpp"
#include "configmanager.hpp"
#include "datamanager.hpp"
#include "utils.hpp"
#include "processor.hpp"
#include "adapter.hpp"
/// @brief Test the Logger module.
static void test_logger() {
Logger logger(LogLevel::DEBUG);
assert(logger.level() == LogLevel::DEBUG);
// Changing level should work
logger.setLevel(LogLevel::WARN);
assert(logger.level() == LogLevel::WARN);
// Logging at a lower level should not crash; we just test no throw
logger.debug("this should be suppressed");
logger.info("this should be suppressed");
logger.warn("this should appear");
logger.error("this should appear");
std::cout << "[PASS] test_logger\n";
}
/// @brief Test the ConfigManager module.
static void test_configmanager() {
// Create a temporary config file in memory using stringstream
// We'll use loadFrom with a real file path; write a small tmp file
{
std::ofstream tmp("test_config.txt");
tmp << "# Comment line\n"
<< "app.name = MyApp\n"
<< "log.level = 2\n"
<< "enable.cache = true\n"
<< " empty.value = \n";
}
ConfigManager cfg;
bool ok = cfg.loadFrom("test_config.txt");
assert(ok);
assert(cfg.getString("app.name") == "MyApp");
assert(cfg.getInt("log.level") == 2);
assert(cfg.getBool("enable.cache") == true);
assert(cfg.hasKey("app.name"));
assert(!cfg.hasKey("nonexistent"));
// Test default values
assert(cfg.getString("missing", "default") == "default");
assert(cfg.getInt("missing", 42) == 42);
assert(cfg.getBool("missing", true) == true);
// Clean up
std::remove("test_config.txt");
std::cout << "[PASS] test_configmanager\n";
}
/// @brief Test the DataManager module.
static void test_datamanager() {
DataManager dm;
assert(dm.size() == 0);
dm.store("key1", std::any(42));
assert(dm.exists("key1"));
assert(dm.size() == 1);
auto val = dm.load("key1");
assert(val != nullptr);
assert(std::any_cast<int>(*val) == 42);
bool removed = dm.remove("key1");
assert(removed);
assert(!dm.exists("key1"));
assert(dm.size() == 0);
dm.clear();
assert(dm.size() == 0);
std::cout << "[PASS] test_datamanager\n";
}
/// @brief Test the Utils module.
static void test_utils() {
std::string s = " hello world ";
utils::trim(s);
assert(s == "hello world");
auto parts = utils::split("a,b,c", ',');
assert(parts.size() == 3);
assert(parts[0] == "a");
assert(parts[1] == "b");
assert(parts[2] == "c");
std::string mixed = "HeLLo";
utils::toLower(mixed);
assert(mixed == "hello");
assert(!utils::currentTimestamp().empty());
assert(utils::fileName("/path/to/file.txt") == "file.txt");
assert(utils::fileExtension("/path/to/file.txt") == "txt");
assert(utils::fileExtension("noext") == "");
std::cout << "[PASS] test_utils\n";
}
/// @brief Test the DataProcessor module.
static void test_processor() {
auto logger = std::make_shared<Logger>(LogLevel::ERROR);
DataProcessor proc(logger);
ProcessResult res = proc.process(std::any(123));
assert(res.success);
assert(res.code == 0);
// Ensure output_data contains the input pass-through
assert(std::any_cast<int>(res.output_data) == 123);
std::cout << "[PASS] test_processor\n";
}
/// @brief Test the CommandLineAdapter module.
static void test_adapter() {
{
const char* argv[] = {"app", "--help"};
CommandLineAdapter a(2, const_cast<char**>(argv));
CommandLineArgs args = a.parse();
assert(args.show_help);
assert(!args.show_version);
assert(!args.has_errors);
}
{
const char* argv[] = {"app", "--version"};
CommandLineAdapter a(2, const_cast<char**>(argv));
CommandLineArgs args = a.parse();
assert(args.show_version);
assert(!args.show_help);
assert(!args.has_errors);
}
{
const char* argv[] = {"app", "--config", "my.cfg"};
CommandLineAdapter a(3, const_cast<char**>(argv));
CommandLineArgs args = a.parse();
assert(!args.has_errors);
assert(args.config_path == "my.cfg");
}
{
const char* argv[] = {"app", "--unknown"};
CommandLineAdapter a(2, const_cast<char**>(argv));
CommandLineArgs args = a.parse();
assert(args.has_errors);
assert(!args.error_msg.empty());
}
std::cout << "[PASS] test_adapter\n";
}
/**
* @brief Main test runner.
* @return 0 on success, 1 on any assertion failure.
*/
int main() {
std::cout << "=== Running ModularApp Unit Tests ===\n";
test_logger();
test_configmanager();
test_datamanager();
test_utils();
test_processor();
test_adapter();
std::cout << "=== ALL TESTS PASSED ===\n";
return 0;
}