生成代码工程
This commit is contained in:
parent
d76132f12e
commit
b82c63f118
|
|
@ -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})
|
||||
61
README.md
61
README.md
|
|
@ -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
|
||||
```
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue