Skip to content

todo cli.cpp

文件信息

  • 📄 原文件:01_todo_cli.cpp
  • 🔤 语言:cpp

完整代码

cpp
// ============================================================
//                      项目实战:Todo CLI(C++ 现代风格)
// ============================================================
// 综合运用现代 C++ 特性:
//   类与 RAII、智能指针、optional、variant、ranges
//   JSON 序列化(手写)、文件 I/O、异常处理
// 编译:g++ -std=c++20 -Wall -O2 -o todo 01_todo_cli.cpp

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <string_view>
#include <vector>
#include <optional>
#include <algorithm>
#include <functional>
#include <chrono>
#include <iomanip>
#include <stdexcept>
#include <memory>
#include <ranges>

// ============================================================
//                      数据模型
// ============================================================

enum class Priority { Low, Medium, High };

struct TodoItem {
    int         id;
    std::string title;
    bool        completed = false;
    Priority    priority = Priority::Medium;
    std::string created_at;

    // 优先级字符串转换
    static std::string priority_str(Priority p) {
        switch (p) {
            case Priority::High:   return "high";
            case Priority::Low:    return "low";
            default:               return "medium";
        }
    }
    static Priority priority_from_str(std::string_view s) {
        if (s == "high")   return Priority::High;
        if (s == "low")    return Priority::Low;
        return Priority::Medium;
    }

    std::string priority_icon() const {
        switch (priority) {
            case Priority::High:   return "[H]";
            case Priority::Low:    return "[L]";
            default:               return "[M]";
        }
    }

    std::string status_icon() const {
        return completed ? "[x]" : "[ ]";
    }

    std::string to_string() const {
        std::ostringstream oss;
        oss << std::setw(4) << id << " " << status_icon()
            << " " << priority_icon() << " " << title;
        if (!created_at.empty())
            oss << "  (" << created_at << ")";
        return oss.str();
    }
};

// ============================================================
//                      JSON 序列化(轻量手写实现)
// ============================================================

class JsonBuilder {
    std::ostringstream oss_;
    bool first_ = true;

    void sep() { if (!first_) oss_ << ",\n"; first_ = false; }
    static std::string escape(const std::string& s) {
        std::string r;
        for (char c : s) {
            if (c == '"') r += "\\\"";
            else if (c == '\\') r += "\\\\";
            else if (c == '\n') r += "\\n";
            else r += c;
        }
        return r;
    }
public:
    void begin_object() { oss_ << "{\n"; first_ = true; }
    void end_object()   { oss_ << "\n}"; }
    void begin_array()  { oss_ << "[\n"; first_ = true; }
    void end_array()    { oss_ << "\n]"; }

    void field(const std::string& key, const std::string& val) {
        sep(); oss_ << "  \"" << key << "\": \"" << escape(val) << "\"";
    }
    void field(const std::string& key, int val) {
        sep(); oss_ << "  \"" << key << "\": " << val;
    }
    void field(const std::string& key, bool val) {
        sep(); oss_ << "  \"" << key << "\": " << (val ? "true" : "false");
    }

    std::string str() const { return oss_.str(); }
};

// ============================================================
//                      存储层
// ============================================================

class TodoStorage {
    std::string filepath_;

    static std::string get_timestamp() {
        auto now = std::chrono::system_clock::now();
        auto t = std::chrono::system_clock::to_time_t(now);
        std::ostringstream oss;
        oss << std::put_time(std::localtime(&t), "%Y-%m-%d %H:%M");
        return oss.str();
    }

    static std::string extract(const std::string& line, const std::string& key) {
        auto pos = line.find("\"" + key + "\":");
        if (pos == std::string::npos) return {};
        pos = line.find(':', pos) + 1;
        while (pos < line.size() && std::isspace(line[pos])) pos++;
        if (line[pos] == '"') {
            auto end = line.find('"', pos + 1);
            return line.substr(pos + 1, end - pos - 1);
        }
        // number or bool
        auto end = line.find_first_of(",}\n", pos);
        auto val = line.substr(pos, end - pos);
        while (!val.empty() && std::isspace(val.back())) val.pop_back();
        return val;
    }

public:
    explicit TodoStorage(std::string path) : filepath_(std::move(path)) {}

    std::vector<TodoItem> load() const {
        std::ifstream f(filepath_);
        if (!f.is_open()) return {};

        std::vector<TodoItem> items;
        std::string content((std::istreambuf_iterator<char>(f)),
                             std::istreambuf_iterator<char>());

        // 简单解析:按 "id" 分割对象
        size_t pos = 0;
        while ((pos = content.find("\"id\":", pos)) != std::string::npos) {
            auto start = content.rfind('{', pos);
            auto end   = content.find('}', pos);
            if (start == std::string::npos || end == std::string::npos) break;

            std::string obj = content.substr(start, end - start + 1);
            TodoItem item;
            item.id         = std::stoi(extract(obj, "id"));
            item.title      = extract(obj, "title");
            item.completed  = extract(obj, "completed") == "true";
            item.priority   = TodoItem::priority_from_str(extract(obj, "priority"));
            item.created_at = extract(obj, "created_at");
            items.push_back(std::move(item));
            pos = end + 1;
        }
        return items;
    }

    void save(const std::vector<TodoItem>& items) const {
        std::ofstream f(filepath_);
        if (!f.is_open()) throw std::runtime_error("无法写入文件: " + filepath_);

        f << "[\n";
        for (size_t i = 0; i < items.size(); i++) {
            const auto& it = items[i];
            JsonBuilder jb;
            jb.begin_object();
            jb.field("id", it.id);
            jb.field("title", it.title);
            jb.field("completed", it.completed);
            jb.field("priority", TodoItem::priority_str(it.priority));
            jb.field("created_at", it.created_at);
            jb.end_object();
            f << jb.str();
            if (i + 1 < items.size()) f << ",";
            f << "\n";
        }
        f << "]\n";
    }

    static std::string now() { return get_timestamp(); }
};

// ============================================================
//                      业务逻辑
// ============================================================

class TodoService {
    std::vector<TodoItem> items_;
    TodoStorage           storage_;
    int                   next_id_ = 1;

public:
    explicit TodoService(const std::string& path) : storage_(path) {
        items_ = storage_.load();
        if (!items_.empty()) {
            next_id_ = 1 + std::ranges::max(items_, {}, &TodoItem::id).id;
        }
    }

    TodoItem& add(const std::string& title, Priority priority = Priority::Medium) {
        items_.push_back({next_id_++, title, false, priority, TodoStorage::now()});
        storage_.save(items_);
        return items_.back();
    }

    bool complete(int id) {
        auto it = std::ranges::find_if(items_, [id](const auto& t) { return t.id == id; });
        if (it == items_.end() || it->completed) return false;
        it->completed = true;
        storage_.save(items_);
        return true;
    }

    bool remove(int id) {
        auto it = std::ranges::find_if(items_, [id](const auto& t) { return t.id == id; });
        if (it == items_.end()) return false;
        items_.erase(it);
        storage_.save(items_);
        return true;
    }

    std::optional<TodoItem*> find(int id) {
        auto it = std::ranges::find_if(items_, [id](const auto& t) { return t.id == id; });
        if (it == items_.end()) return std::nullopt;
        return &(*it);
    }

    std::vector<TodoItem*> search(std::string_view keyword) {
        std::vector<TodoItem*> results;
        for (auto& item : items_) {
            if (item.title.find(keyword) != std::string::npos)
                results.push_back(&item);
        }
        return results;
    }

    const std::vector<TodoItem>& all() const { return items_; }

    std::vector<const TodoItem*> pending() const {
        std::vector<const TodoItem*> r;
        for (const auto& t : items_)
            if (!t.completed) r.push_back(&t);
        std::ranges::sort(r, [](const auto* a, const auto* b) {
            return static_cast<int>(a->priority) > static_cast<int>(b->priority);
        });
        return r;
    }

    struct Stats { int total, done, pending; double pct; };
    Stats stats() const {
        int done = static_cast<int>(std::ranges::count_if(items_, &TodoItem::completed));
        int total = static_cast<int>(items_.size());
        return {total, done, total - done, total ? 100.0 * done / total : 0.0};
    }
};

// ============================================================
//                      命令行界面
// ============================================================

class Cli {
    TodoService& svc_;

    static void print_line() { std::cout << std::string(58, '-') << "\n"; }

    void cmd_add(const std::vector<std::string>& args) {
        if (args.empty()) { std::cout << "用法: add <标题> [--priority high|medium|low]\n"; return; }
        std::string title = args[0];
        Priority prio = Priority::Medium;
        for (size_t i = 1; i + 1 < args.size(); i++) {
            if (args[i] == "--priority" || args[i] == "-p")
                prio = TodoItem::priority_from_str(args[++i]);
        }
        auto& item = svc_.add(title, prio);
        std::cout << "已添加: " << item.to_string() << "\n";
    }

    void cmd_list(const std::vector<std::string>& args) {
        std::string filter = args.empty() ? "all" : args[0];
        std::cout << "\n📋 Todo 列表";

        if (filter == "pending" || filter == "p") {
            auto items = svc_.pending();
            std::cout << "(待完成)\n"; print_line();
            for (const auto* t : items) std::cout << t->to_string() << "\n";
            std::cout << "共 " << items.size() << " 项\n";
        } else if (filter == "done" || filter == "completed") {
            std::cout << "(已完成)\n"; print_line();
            int cnt = 0;
            for (const auto& t : svc_.all())
                if (t.completed) { std::cout << t.to_string() << "\n"; cnt++; }
            std::cout << "共 " << cnt << " 项\n";
        } else {
            std::cout << "\n"; print_line();
            for (const auto& t : svc_.all()) std::cout << t.to_string() << "\n";
            auto s = svc_.stats();
            print_line();
            std::cout << "共 " << s.total << " 项 | 已完成 " << s.done
                      << " | 待完成 " << s.pending << " | 完成率 "
                      << std::fixed << std::setprecision(1) << s.pct << "%\n";
        }
    }

    void cmd_done(const std::vector<std::string>& args) {
        if (args.empty()) { std::cout << "用法: done <ID>\n"; return; }
        int id = std::stoi(args[0]);
        std::cout << (svc_.complete(id) ? "✓ 已完成 #" + args[0] : "未找到 #" + args[0]) << "\n";
    }

    void cmd_delete(const std::vector<std::string>& args) {
        if (args.empty()) { std::cout << "用法: delete <ID>\n"; return; }
        int id = std::stoi(args[0]);
        std::cout << (svc_.remove(id) ? "✗ 已删除 #" + args[0] : "未找到 #" + args[0]) << "\n";
    }

    void cmd_search(const std::vector<std::string>& args) {
        if (args.empty()) { std::cout << "用法: search <关键词>\n"; return; }
        auto results = svc_.search(args[0]);
        std::cout << "搜索 \"" << args[0] << "\" 结果 (" << results.size() << " 条):\n";
        for (const auto* t : results) std::cout << "  " << t->to_string() << "\n";
    }

    void cmd_stats() {
        auto s = svc_.stats();
        std::cout << "\n📊 统计\n";
        std::cout << "  总计:   " << s.total  << "\n";
        std::cout << "  已完成: " << s.done   << "\n";
        std::cout << "  待完成: " << s.pending << "\n";
        std::cout << "  完成率: " << std::fixed << std::setprecision(1) << s.pct << "%\n";
    }

    void print_help() {
        std::cout << R"(
Todo CLI — 现代 C++ 待办应用

命令:
  add <标题> [--priority high|medium|low]  添加任务
  list [all|pending|done]                  列出任务
  done <ID>                                标记完成
  delete <ID>                              删除任务
  search <关键词>                          搜索
  stats                                    统计
  help                                     帮助
)";
    }

public:
    explicit Cli(TodoService& svc) : svc_(svc) {}

    void run(const std::vector<std::string>& args) {
        if (args.empty()) { print_help(); return; }

        std::string cmd = args[0];
        std::vector<std::string> rest(args.begin() + 1, args.end());

        if      (cmd == "add")                cmd_add(rest);
        else if (cmd == "list" || cmd == "ls") cmd_list(rest);
        else if (cmd == "done")               cmd_done(rest);
        else if (cmd == "delete" || cmd == "rm") cmd_delete(rest);
        else if (cmd == "search")             cmd_search(rest);
        else if (cmd == "stats")              cmd_stats();
        else if (cmd == "help")               print_help();
        else std::cout << "未知命令: " << cmd << "\n";
    }
};

// ============================================================
//                      主函数
// ============================================================

int main(int argc, char* argv[]) {
    const std::string data_file = ".todo_cpp.json";

    try {
        TodoService svc(data_file);
        Cli cli(svc);

        if (argc <= 1) {
            // 演示模式
            std::cout << "=== C++ Todo CLI 演示 ===\n\n";

            cli.run({"add", "掌握现代 C++20 特性", "--priority", "high"});
            cli.run({"add", "学习 STL 容器与算法", "--priority", "high"});
            cli.run({"add", "理解智能指针与 RAII"});
            cli.run({"add", "实现线程池", "--priority", "low"});
            cli.run({"add", "阅读《Effective Modern C++》"});

            std::cout << "\n";
            cli.run({"list"});
            std::cout << "\n";

            cli.run({"done", "1"});
            cli.run({"done", "3"});
            std::cout << "\n";

            cli.run({"list", "pending"});
            std::cout << "\n";

            cli.run({"search", "C++"});
            std::cout << "\n";

            cli.run({"stats"});
            std::cout << "\n";

            cli.run({"delete", "4"});
            cli.run({"list"});

            // 清理演示文件
            std::remove(data_file.c_str());
        } else {
            std::vector<std::string> args(argv + 1, argv + argc);
            cli.run(args);
        }
    } catch (const std::exception& e) {
        std::cerr << "错误: " << e.what() << "\n";
        return 1;
    }
    return 0;
}

💬 讨论

使用 GitHub 账号登录后即可参与讨论

基于 MIT 许可发布