推送代码工程a #2

Merged
root merged 1 commits from 20260506-135259 into main 2026-05-06 05:58:14 +00:00
7 changed files with 8463 additions and 4922 deletions
Showing only changes of commit 9631d2c4a5 - Show all commits

27
CMakeLists.txt Normal file
View File

@ -0,0 +1,27 @@
cmake_minimum_required(VERSION 3.14)
project(todo_manager VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if (MSVC)
add_compile_options(/utf-8)
endif()
# ============================================================
#
# ============================================================
add_executable(todo_manager
src/main.cpp
src/app.cpp
)
target_include_directories(todo_manager PRIVATE include)
# ============================================================
# 使 assert
# ============================================================
add_executable(basic_test
tests/basic_test.cpp
src/app.cpp
)
target_include_directories(basic_test PRIVATE include)

124
README.md
View File

@ -1,59 +1,95 @@
# Todo Manager - 任务管理器示例工程
# Todo Manager - C++ 命令行版
基于 FastAPI 的单体示例工程,使用内存假数据完成任务的增删改查核心流程。
将 Python Todo ManagerPyQt5 桌面版)转换为 C++ 命令行工程。
保留核心业务能力:任务创建、查询、更新、删除,以及 JSON 文件持久化。
## 技术栈
- Python 3.11+
- FastAPI (Web 框架)
- Pydantic (数据校验)
- Uvicorn (ASGI 服务器)
- Pytest + HTTPX (测试)
- **语言**: C++17
- **构建工具**: CMake 3.14+
- **持久化**: 纯手写简易 JSON 解析器(零外部依赖)
- **测试**: 标准库 `assert`(零外部依赖)
## 安装依赖
## 工程结构
```bash
cd codegen-runs/codegen_8a0046bfd6e148b3ab096039e55d5f72
pip install -r requirements.txt
```
codegen-runs/codegen_9ecc529e4d094130b0491b9d389ef836/
├── CMakeLists.txt # CMake 构建配置
├── README.md # 本文件
├── include/
│ └── app.hpp # TodoItem / TodoService 公开声明
├── src/
│ ├── app.cpp # TodoService 实现 + JSON 解析器
│ └── main.cpp # 命令行交互入口
├── tests/
│ └── basic_test.cpp # 单元测试assert
└── todos.json # 运行时自动生成的数据文件
```
## 启动服务
## 本地编译与运行
### 前置条件
- CMake 3.14 或更高版本
- 支持 C++17 的编译器GCC 7+、Clang 5+、MSVC 2019+
### 编译步骤
```bash
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
cd codegen-runs/codegen_9ecc529e4d094130b0491b9d389ef836
mkdir -p build && cd build
cmake ..
cmake --build .
```
服务启动后访问:
- API 文档: http://127.0.0.1:8000/docs
- 替代文档: http://127.0.0.1:8000/redoc
## API 接口
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | /api/todos | 获取所有任务 |
| GET | /api/todos/{id} | 根据 ID 获取任务 |
| POST | /api/todos | 创建新任务 |
| PUT | /api/todos/{id} | 更新任务 |
| DELETE | /api/todos/{id} | 删除任务 |
### 请求/响应示例
**创建任务:**
```bash
curl -X POST "http://127.0.0.1:8000/api/todos" \
-H "Content-Type: application/json" \
-d '{"title": "学习 FastAPI", "description": "完成 Todo 示例项目"}'
```
**获取所有任务:**
```bash
curl "http://127.0.0.1:8000/api/todos"
```
## 运行测试
### 运行主程序
```bash
cd codegen-runs/codegen_8a0046bfd6e148b3ab096039e55d5f72
pytest tests/ -v
# Linux / macOS
./todo_manager
# Windows
todo_manager.exe
```
程序启动后显示交互菜单:
```
===== Todo Manager =====
1. 列出所有任务
2. 添加任务
3. 编辑任务
4. 删除任务
5. 退出
请选择 (1-5):
```
### 运行测试
```bash
# 在 build 目录下
./basic_test
```
测试通过时会输出:
```
✓ test_create_and_list passed
✓ test_get_by_id passed
✓ test_update passed
✓ test_delete passed
✓ test_persistence passed
✓ ✓ ✓ 所有测试通过!
```
## 与 Python 原版的差异
| 特性 | Python 原版 | C++ 版 |
|---------------------|-------------------------------|--------------------------------|
| 用户界面 | PyQt5 桌面窗口 | 命令行交互菜单 |
| 数据校验 | Pydantic BaseModel | C++ 结构体 + 运行时检查 |
| JSON 解析 | 标准库 json 模块 | 手写简易递归下降解析器 |
| 测试框架 | pytest + httpx | 标准库 assert |
| 持久化文件 | todos.json | todos.json格式兼容 |
| 外部依赖 | PyQt5, Pydantic, pytest, httpx | **零外部依赖** |

File diff suppressed because one or more lines are too long

103
include/app.hpp Normal file
View File

@ -0,0 +1,103 @@
#ifndef TODO_MANAGER_APP_HPP
#define TODO_MANAGER_APP_HPP
#include <string>
#include <vector>
#include <optional>
/**
* @brief
*
* Python TodoItem(BaseModel)使 C++
*/
struct TodoItem {
int id = 0; ///< 任务唯一标识
std::string title; ///< 任务标题
std::string description; ///< 任务描述(可选)
bool completed = false; ///< 完成状态,默认未完成
};
/**
* @brief CRUD JSON
*
* Python TodoService使 std::vector
* JSON
*/
class TodoService {
public:
/**
* @brief
* @param filepath JSON "todos.json"
*/
explicit TodoService(const std::string& filepath = "todos.json");
/// @brief 析构函数(当前无需特殊清理)。
~TodoService() = default;
// 禁止拷贝和赋值,避免文件状态不一致
TodoService(const TodoService&) = delete;
TodoService& operator=(const TodoService&) = delete;
/// @brief 允许移动构造/赋值
TodoService(TodoService&&) = default;
TodoService& operator=(TodoService&&) = default;
/**
* @brief
* @return const
*/
const std::vector<TodoItem>& get_all() const;
/**
* @brief ID
* @param id ID
* @return TodoItem std::nullopt
*/
std::optional<TodoItem> get_by_id(int id) const;
/**
* @brief ID
* @param title
* @param description
* @return TodoItem ID
*/
TodoItem create(const std::string& title, const std::string& description = "");
/**
* @brief
* @param id ID
* @param title std::nullopt
* @param description std::nullopt
* @param completed std::nullopt
* @return TodoItemID std::nullopt
*/
std::optional<TodoItem> update(int id,
const std::optional<std::string>& title = std::nullopt,
const std::optional<std::string>& description = std::nullopt,
const std::optional<bool>& completed = std::nullopt);
/**
* @brief ID
* @param id ID
* @return trueID false
*/
bool delete_item(int id);
private:
/**
* @brief JSON
*
*/
void load();
/**
* @brief JSON
*/
void save();
std::string filepath_; ///< JSON 持久化文件路径
std::vector<TodoItem> items_; ///< 内存中的任务列表
int next_id_ = 1; ///< 下一个可用 ID
};
#endif // TODO_MANAGER_APP_HPP

383
src/app.cpp Normal file
View File

@ -0,0 +1,383 @@
#include "app.hpp"
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <stdexcept>
// ============================================================
// 匿名命名空间:简易 JSON 序列化 / 反序列化
// 仅支持当前 TodoItem 数据结构的 JSON 子集,无外部依赖。
// ============================================================
namespace {
/**
* @brief JSON
*/
static std::string json_escape(const std::string& s) {
std::string r;
r.reserve(s.size() + 4);
for (char c : s) {
switch (c) {
case '"': r += "\\\""; break;
case '\\': r += "\\\\"; break;
case '\n': r += "\\n"; break;
case '\t': r += "\\t"; break;
case '\r': r += "\\r"; break;
default: r += c; break;
}
}
return r;
}
/**
* @brief TodoItem next_id JSON
*/
static std::string items_to_json(const std::vector<TodoItem>& items, int next_id) {
std::ostringstream oss;
oss << "{\n \"items\": [\n";
for (size_t i = 0; i < items.size(); ++i) {
const auto& it = items[i];
oss << " {\n";
oss << " \"id\": " << it.id << ",\n";
oss << " \"title\": \"" << json_escape(it.title) << "\",\n";
oss << " \"description\": \"" << json_escape(it.description) << "\",\n";
oss << " \"completed\": " << (it.completed ? "true" : "false") << "\n";
oss << " }";
if (i + 1 < items.size()) oss << ",";
oss << "\n";
}
oss << " ],\n \"next_id\": " << next_id << "\n}\n";
return oss.str();
}
/**
* @brief JSON TodoItem
*
* JSON
*
*/
class JsonReader {
public:
explicit JsonReader(const std::string& input)
: s_(input), pos_(0) {}
/**
* @brief JSON items next_id
* @param[out] items
* @param[out] next_id ID
* @return true
*/
bool parse(std::vector<TodoItem>& items, int& next_id) {
items.clear();
skip_ws();
if (peek() != '{') return false;
advance(); // 跳过 '{'
while (pos_ < s_.size() && peek() != '}') {
skip_ws();
if (peek() == '}') break;
if (peek() == ',') { advance(); continue; }
std::string key = parse_string();
skip_ws();
if (peek() != ':') return false;
advance();
skip_ws();
if (key == "items") {
if (!parse_array(items)) return false;
} else if (key == "next_id") {
next_id = parse_int();
} else {
skip_value();
}
}
if (peek() == '}') advance();
return true;
}
private:
const std::string& s_;
size_t pos_ = 0;
/// @brief 查看当前字符,不移动指针。
char peek() const { return pos_ < s_.size() ? s_[pos_] : '\0'; }
/// @brief 读取当前字符并前进一位。
char advance() { return pos_ < s_.size() ? s_[pos_++] : '\0'; }
/// @brief 跳过空白字符。
void skip_ws() {
while (pos_ < s_.size() &&
std::isspace(static_cast<unsigned char>(s_[pos_]))) {
++pos_;
}
}
/// @brief 解析 JSON 字符串(含引号和转义处理)。
std::string parse_string() {
skip_ws();
if (peek() != '"') return {};
advance(); // 跳过开头的 "
std::string result;
while (pos_ < s_.size() && s_[pos_] != '"') {
if (s_[pos_] == '\\') {
advance();
if (pos_ < s_.size()) {
switch (s_[pos_]) {
case '"': result += '"'; break;
case '\\': result += '\\'; break;
case '/': result += '/'; break;
case 'n': result += '\n'; break;
case 't': result += '\t'; break;
case 'r': result += '\r'; break;
default: result += s_[pos_]; break;
}
}
} else {
result += s_[pos_];
}
++pos_;
}
if (peek() == '"') advance(); // 跳过结尾的 "
return result;
}
/// @brief 解析整数值(可选负号)。
int parse_int() {
skip_ws();
int sign = 1;
if (peek() == '-') { sign = -1; advance(); }
int val = 0;
while (pos_ < s_.size() &&
std::isdigit(static_cast<unsigned char>(s_[pos_]))) {
val = val * 10 + (s_[pos_] - '0');
++pos_;
}
return sign * val;
}
/**
* @brief true / false
* @param[out] value
* @return true
*/
bool parse_bool(bool& value) {
skip_ws();
if (s_.substr(pos_, 4) == "true") {
pos_ += 4;
value = true;
return true;
}
if (s_.substr(pos_, 5) == "false") {
pos_ += 5;
value = false;
return true;
}
return false;
}
/**
* @brief JSON TodoItem
*/
bool parse_array(std::vector<TodoItem>& items) {
skip_ws();
if (peek() != '[') return false;
advance(); // 跳过 '['
while (pos_ < s_.size() && peek() != ']') {
skip_ws();
if (peek() == ']') break;
if (peek() == ',') { advance(); continue; }
TodoItem item;
if (!parse_object(item)) return false;
items.push_back(std::move(item));
}
if (peek() == ']') advance();
return true;
}
/**
* @brief TodoItem JSON
*/
bool parse_object(TodoItem& item) {
skip_ws();
if (peek() != '{') return false;
advance(); // 跳过 '{'
while (pos_ < s_.size() && peek() != '}') {
skip_ws();
if (peek() == '}') break;
if (peek() == ',') { advance(); continue; }
std::string key = parse_string();
skip_ws();
if (peek() != ':') return false;
advance();
skip_ws();
if (key == "id") {
item.id = parse_int();
} else if (key == "title") {
item.title = parse_string();
} else if (key == "description") {
item.description = parse_string();
} else if (key == "completed") {
bool val = false;
if (parse_bool(val)) item.completed = val;
} else {
skip_value();
}
}
if (peek() == '}') advance();
return true;
}
/// @brief 跳过任意 JSON 值(用于忽略不关心的字段)。
void skip_value() {
skip_ws();
char c = peek();
if (c == '"') {
parse_string();
} else if (c == '{') {
int depth = 1;
advance();
while (pos_ < s_.size() && depth > 0) {
if (s_[pos_] == '{') ++depth;
else if (s_[pos_] == '}') --depth;
else if (s_[pos_] == '"') {
++pos_;
while (pos_ < s_.size() && s_[pos_] != '"') {
if (s_[pos_] == '\\') ++pos_;
++pos_;
}
}
++pos_;
}
} else if (c == '[') {
int depth = 1;
advance();
while (pos_ < s_.size() && depth > 0) {
if (s_[pos_] == '[') ++depth;
else if (s_[pos_] == ']') --depth;
else if (s_[pos_] == '"') {
++pos_;
while (pos_ < s_.size() && s_[pos_] != '"') {
if (s_[pos_] == '\\') ++pos_;
++pos_;
}
}
++pos_;
}
} else {
// 数字、布尔值、null 等
while (pos_ < s_.size() &&
!std::isspace(static_cast<unsigned char>(s_[pos_])) &&
s_[pos_] != ',' && s_[pos_] != '}' && s_[pos_] != ']') {
++pos_;
}
}
}
};
} // anonymous namespace
// ============================================================
// TodoService 实现
// ============================================================
TodoService::TodoService(const std::string& filepath)
: filepath_(filepath) {
load();
}
const std::vector<TodoItem>& TodoService::get_all() const {
return items_;
}
std::optional<TodoItem> TodoService::get_by_id(int id) const {
for (const auto& item : items_) {
if (item.id == id) return item;
}
return std::nullopt;
}
TodoItem TodoService::create(const std::string& title,
const std::string& description) {
TodoItem item;
item.id = next_id_++;
item.title = title;
item.description = description;
item.completed = false;
items_.push_back(item);
save();
return item;
}
std::optional<TodoItem> TodoService::update(
int id,
const std::optional<std::string>& title,
const std::optional<std::string>& description,
const std::optional<bool>& completed) {
for (auto& item : items_) {
if (item.id == id) {
if (title.has_value()) item.title = title.value();
if (description.has_value()) item.description = description.value();
if (completed.has_value()) item.completed = completed.value();
save();
return item;
}
}
return std::nullopt;
}
bool TodoService::delete_item(int id) {
auto it = std::find_if(items_.begin(), items_.end(),
[id](const TodoItem& item) { return item.id == id; });
if (it == items_.end()) return false;
items_.erase(it);
save();
return true;
}
void TodoService::load() {
std::ifstream ifs(filepath_);
if (!ifs.is_open()) {
items_.clear();
next_id_ = 1;
return;
}
std::stringstream buffer;
buffer << ifs.rdbuf();
std::string content = buffer.str();
if (content.empty()) {
items_.clear();
next_id_ = 1;
return;
}
items_.clear();
int parsed_next_id = 1;
JsonReader reader(content);
if (reader.parse(items_, parsed_next_id)) {
next_id_ = parsed_next_id;
// 确保 next_id 大于所有现有 ID避免冲突
for (const auto& item : items_) {
if (item.id >= next_id_) next_id_ = item.id + 1;
}
} else {
// 解析失败时回退到空状态
items_.clear();
next_id_ = 1;
}
}
void TodoService::save() {
std::ofstream ofs(filepath_);
if (!ofs.is_open()) return;
ofs << items_to_json(items_, next_id_);
}

161
src/main.cpp Normal file
View File

@ -0,0 +1,161 @@
#include "app.hpp"
#include <iostream>
#include <string>
#include <limits>
#include <stdexcept>
/**
* @brief
*/
static void print_menu() {
std::cout << "\n===== Todo Manager =====\n";
std::cout << "1. 列出所有任务\n";
std::cout << "2. 添加任务\n";
std::cout << "3. 编辑任务\n";
std::cout << "4. 删除任务\n";
std::cout << "5. 退出\n";
std::cout << "请选择 (1-5): ";
std::cout.flush();
}
/**
* @brief
*/
static void list_todos(const TodoService& service) {
const auto& todos = service.get_all();
std::cout << "\n--- 任务列表 ---\n";
if (todos.empty()) {
std::cout << "(空)\n";
return;
}
for (const auto& t : todos) {
std::cout << "[" << t.id << "] "
<< t.title
<< " | " << (t.description.empty() ? "-" : t.description)
<< " | " << (t.completed ? "\u2713 已完成" : "\u25CB 未完成")
<< "\n";
}
}
/**
* @brief
*/
static void add_todo(TodoService& service) {
std::string title, desc;
std::cout << "标题: ";
std::getline(std::cin, title);
if (title.empty()) {
std::cout << "标题不能为空!\n";
return;
}
std::cout << "描述 (可选): ";
std::getline(std::cin, desc);
auto item = service.create(title, desc);
std::cout << "\u2713 已创建任务 ID=" << item.id << "\n";
}
/**
* @brief
*/
static void edit_todo(TodoService& service) {
std::string input;
std::cout << "输入要编辑的任务 ID: ";
std::getline(std::cin, input);
int id = 0;
try {
id = std::stoi(input);
} catch (...) {
std::cout << "无效的 ID\n";
return;
}
auto todo = service.get_by_id(id);
if (!todo.has_value()) {
std::cout << "任务不存在!\n";
return;
}
std::string title, desc, status_str;
std::cout << "新标题 (留空保持不变,当前: "
<< todo->title << "): ";
std::getline(std::cin, title);
std::cout << "新描述 (留空保持不变,当前: "
<< todo->description << "): ";
std::getline(std::cin, desc);
std::cout << "完成状态? (y=已完成, n=未完成, 留空不变): ";
std::getline(std::cin, status_str);
std::optional<std::string> nt =
title.empty() ? std::nullopt : std::optional<std::string>(title);
std::optional<std::string> nd =
desc.empty() ? std::nullopt : std::optional<std::string>(desc);
std::optional<bool> nc;
if (status_str == "y" || status_str == "Y")
nc = true;
else if (status_str == "n" || status_str == "N")
nc = false;
auto updated = service.update(id, nt, nd, nc);
if (updated.has_value()) {
std::cout << "\u2713 已更新任务 ID=" << id << "\n";
}
}
/**
* @brief
*/
static void delete_todo(TodoService& service) {
std::string input;
std::cout << "输入要删除的任务 ID: ";
std::getline(std::cin, input);
int id = 0;
try {
id = std::stoi(input);
} catch (...) {
std::cout << "无效的 ID\n";
return;
}
if (service.delete_item(id)) {
std::cout << "\u2713 已删除任务 ID=" << id << "\n";
} else {
std::cout << "任务不存在!\n";
}
}
/**
* @brief Todo Manager
*/
int main() {
TodoService service;
std::cout << "欢迎使用 Todo Manager\n";
while (true) {
list_todos(service);
print_menu();
std::string choice;
std::getline(std::cin, choice);
if (choice == "1") {
// 已在循环开头列出
} else if (choice == "2") {
add_todo(service);
} else if (choice == "3") {
edit_todo(service);
} else if (choice == "4") {
delete_todo(service);
} else if (choice == "5") {
std::cout << "再见!\n";
break;
} else {
std::cout << "无效选择,请重试。\n";
}
}
return 0;
}

125
tests/basic_test.cpp Normal file
View File

@ -0,0 +1,125 @@
#include "app.hpp"
#include <cassert>
#include <cstdio>
#include <iostream>
/**
* @brief
*/
static void test_create_and_list() {
TodoService service("test_todos.json");
assert(service.get_all().empty());
auto item = service.create("\u6D4B\u8BD5\u4EFB\u52A1", "\u6D4B\u8BD5\u63CF\u8FF0");
assert(item.id == 1);
assert(item.title == "\u6D4B\u8BD5\u4EFB\u52A1");
assert(item.description == "\u6D4B\u8BD5\u63CF\u8FF0");
assert(item.completed == false);
assert(service.get_all().size() == 1);
std::cout << "\u2713 test_create_and_list passed\n";
}
/**
* @brief ID
*/
static void test_get_by_id() {
TodoService service("test_todos.json");
service.create("\u4EFB\u52A1A");
service.create("\u4EFB\u52A1B");
auto found = service.get_by_id(1);
assert(found.has_value());
assert(found->title == "\u4EFB\u52A1A");
auto not_found = service.get_by_id(999);
assert(!not_found.has_value());
std::cout << "\u2713 test_get_by_id passed\n";
}
/**
* @brief
*/
static void test_update() {
TodoService service("test_todos.json");
auto item = service.create("\u539F\u6807\u9898", "\u539F\u63CF\u8FF0");
// 全字段更新
auto updated = service.update(item.id, "\u65B0\u6807\u9898",
"\u65B0\u63CF\u8FF0", true);
assert(updated.has_value());
assert(updated->title == "\u65B0\u6807\u9898");
assert(updated->description == "\u65B0\u63CF\u8FF0");
assert(updated->completed == true);
// 部分更新(仅修改完成状态)
auto partial = service.update(item.id, std::nullopt, std::nullopt, false);
assert(partial.has_value());
assert(partial->title == "\u65B0\u6807\u9898");
assert(partial->completed == false);
std::cout << "\u2713 test_update passed\n";
}
/**
* @brief
*/
static void test_delete() {
TodoService service("test_todos.json");
service.create("\u5F85\u5220\u9664");
assert(service.get_all().size() == 1);
bool result = service.delete_item(1);
assert(result == true);
assert(service.get_all().empty());
// 删除不存在的 ID
result = service.delete_item(999);
assert(result == false);
std::cout << "\u2713 test_delete passed\n";
}
/**
* @brief JSON
*/
static void test_persistence() {
const char* persist_file = "test_persist.json";
// 创建数据并写入文件
{
TodoService service(persist_file);
service.create("\u6301\u4E45\u5316\u6D4B\u8BD5");
service.create("\u7B2C\u4E8C\u4E2A\u4EFB\u52A1");
}
// 重新加载并验证
{
TodoService service(persist_file);
assert(service.get_all().size() == 2);
assert(service.get_by_id(1).has_value());
assert(service.get_by_id(2).has_value());
}
std::remove(persist_file);
std::cout << "\u2713 test_persistence passed\n";
}
/**
* @brief
*/
int main() {
// 清理可能残留的测试文件
std::remove("test_todos.json");
test_create_and_list();
test_get_by_id();
test_update();
test_delete();
test_persistence();
// 最终清理
std::remove("test_todos.json");
std::cout << "\n\u2713 \u2713 \u2713 \u6240\u6709\u6D4B\u8BD5\u901A\u8FC7\uFF01\n";
return 0;
}