007-nlohmann/json 项目应用-C++开源库108杰

本课为 fswatch(第一"杰")的示例项目加上对配置文件读取的支持,同时借助 第三"杰" CLI11 的支持,完美实现命令行参数与配置文件的逻辑统一。

012-nlohmann/json-4-项目应用

项目基于原有的 CMake 项目 HelloFSWatch 修改。

  • CMakeLists.txt :该文件基于原项目,没有任何改动。

  • .vscode/setting.json ,改动如下:

javascript 复制代码
{   
    "cmake.debugConfig": {
        "cwd": "${workspaceFolder}",
        "args": ["-m", "3", "--log-level", "info"],
        "externalConsole": false
    }
}

重点:

① args字段:添加命令行参数;

② cwd 字段:设置程序在项目根目录下运行(而在程序所在的 build 子目录内)。

  • myConfig.json 测试用的配置文件
javascript 复制代码
{
    "paths": [ "c:\\tmp", "c:/tmp/aaa" , "d:/tmp" ],
    
    "maxOutput": -1,      
    "createdOnly" : false,
    
    "destination": "d:\\我的学习资料",
    
    "toBase64": [".png", ".jpg", ".jpeg"],
    "toSnappy": [".txt", ".pdf"],

    "logFile": ".\\log\\log.txt",
    "logLevel": "off"
 }
  • main.cpp
cpp 复制代码
#include <ctime>

#include <iostream>
#include <iomanip>
#include <memory> // 智能指针 shared_ptr<>

#include <libfswatch/c++/monitor_factory.hpp>
#include <CLI/CLI.hpp> 

#include <nlohmann/json.hpp>

#include "myiconv.hpp"

using json = nlohmann::json;

namespace Watch::settings
{
//----------------------------------------------    

// 日志级别(暂使用手工定义,007杰讲改用三方库中的定义)
enum class LogLevel 
{
    // 跟踪、调试、信息、警告、错误 、危急、关闭
    trace, debug, info, warn, err, critical, off
};

// 让枚举 LogLevel 支持与JSON双向转换
NLOHMANN_JSON_SERIALIZE_ENUM(LogLevel,
{
    {LogLevel::trace, "trace"},
    {LogLevel::debug, "debug"},
    {LogLevel::info, "info"},
    {LogLevel::warn, "warn"},
    {LogLevel::err, "err"},
    {LogLevel::critical, "critical"},
    {LogLevel::off, "off"}
})

// 配置数据的结构体
struct Config
{
    std::vector<std::string> paths;
    int maxOutput = -1;
    bool createdOnly = false;

    std::string destination; 
    std::vector<std::string> toBase64;
    std::vector<std::string> toSnappy;
    std::string logFile = "./log.txt";
    LogLevel logLevel = LogLevel::off;     
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Config,     
    paths, maxOutput, createdOnly, 
    destination, toBase64, toSnappy, 
    logFile, logLevel)

std::string getLogLevelName(LogLevel ll)
{
    json j = ll;
    return j.get<std::string>();
}

std::map<std::string, LogLevel> logLevelNameMap
{
    {getLogLevelName(LogLevel::trace), LogLevel::trace},

    #define llNameItem(ll) {getLogLevelName(LogLevel::ll), LogLevel::ll}

    llNameItem(debug),
    llNameItem(info),
    llNameItem(warn),
    llNameItem(err),
    llNameItem(critical),
    llNameItem(off)

    #undef llNameItem
};

// 配置数据"格式器"
class MyCLIConfigAdaptor : public CLI::Config
{
public:
    // 如何从 JSON 数据读出 各个配置项
    std::vector<CLI::ConfigItem> from_config (std::istream &input) const override;

    // 如何从app,生成配置文件内容(字符串)
    std::string to_config (CLI::App const* app, 
        bool default_also, bool write_description, std::string prefix) const override
    { return ""; }
};

std::vector<CLI::ConfigItem> MyCLIConfigAdaptor::from_config (std::istream &input) const 
{
    try
    {
        json j = json::parse(input, nullptr, true /*允许异常*/, true /*允许注释*/);

        auto cfg = j.get<settings::Config>();

        auto items = std::vector<CLI::ConfigItem>
        {
            {{}, "paths", cfg.paths },
            {{}, "max-ouput", { std::to_string(cfg.maxOutput) }}, // 视频中误为 "max-count"
            {{}, "created-only", { cfg.createdOnly? "true" : "false"} },
            {{}, "destination", { cfg.destination }},
            {{}, "to-base64", cfg.toBase64},
            {{}, "to-snappy", cfg.toSnappy},
            {{}, "log-file", {cfg.logFile}},
            {{}, "log-level", {std::to_string(static_cast<int>(cfg.logLevel))}}
        };

        return items;
    }
    catch(json::exception const& e)
    {
        std::cerr << "JSON 配置数据有误。" << e.what() << std::endl;
    }
    catch(std::exception const& e)
    {
        std::cerr << "读取并转换配置数据发生异常。" << e.what() << std::endl;
    }

    return {};
}

//----------------------------------------------    
} // namespace Watch::settings

Watch::settings::Config theConfig; // 全局唯一的配置

// 返回值:必须是 void,入参必须是 std::vector<fsw::event> const & 和 void *
void on_file_changed(std::vector<fsw::event> const & events, void *)
{
    /* 略,保持原有实现不变;本课,配置数据尚未发挥作用 */    
}

int main(int argc, char** argv) // 主函数
{
    // 一、定义一个CLI::App 的变量
    CLI::App app("HelloFSWatch");

    // 1.1 指定(默认的)配置文件
    app.set_config("--config", "./myConfig.json", "指定配置文件");

    // 1.2 创建并指定定制的配置数据格式解析器
    app.config_formatter(std::make_shared<Watch::settings::MyCLIConfigAdaptor>());

    // 二、添加命令行参数
    try
    {
        app.add_option("paths", theConfig.paths, 
                     "待监控的文件夹路径(可含多个)")->required();
        app.add_option("--max-output,-m", 
               theConfig.maxOutput, "启动后输出事件个数")->default_val(-1);
        app.add_flag("-c,--created-only", theConfig.createdOnly, "只关注新建信息");
        app.add_option("--destination,-d", 
               theConfig.destination, "输出文件路径")->required();
        app.add_option("--to-base64", theConfig.toBase64,
                     "需要转成base64的文件的扩展名(数组)");
        app.add_option("--to-snappy", theConfig.toSnappy, 
                     "需要转成snappy的文件的扩展名(数组)");
        app.add_option("--log-file", 
             theConfig.logFile, "日志文件路径")->default_val("./log.txt"); 
        app.add_option("--log-level", 
             theConfig.logLevel, "可输出的最小日志级别")
               ->default_val(Watch::settings::LogLevel::off)
               ->transform(CLI::CheckedTransformer(Watch::settings::logLevelNameMap));
            
        // 三、开始解析命令行
        app.parse(argc, argv);
    }
    catch(std::exception const& e)
    {
        std::cerr << e.what() << std::endl;
        return -1;
    }

    // 显示当前生效的配置数据
    json j = theConfig;
    std::cout << "\n当前发挥作用的配置是:\n" << j.dump(2) << std::endl;

    auto *monitor = fsw::monitor_factory::create_monitor(
        system_default_monitor_type,
        theConfig.paths,
        on_file_changed
    );

    // 启动监控
    monitor->start();  // 进入死循环
}
相关推荐
小黄编程快乐屋7 分钟前
「源力觉醒 创作者计划」_文心 4.5 开源模型玩出花——教育场景下 Scratch 积木自动化生成的部署实践与优化
开源
码农编程录1 小时前
【c/c++3】类和对象,vector容器,类继承和多态,systemd,std&boost
c++
??tobenewyorker2 小时前
力扣打卡第二十一天 中后遍历+中前遍历 构造二叉树
数据结构·c++·算法·leetcode
oioihoii3 小时前
C++11 forward_list 从基础到精通:原理、实践与性能优化
c++·性能优化·list
m0_687399843 小时前
写一个Ununtu C++ 程序,调用ffmpeg API, 来判断一个数字电影的视频文件mxf 是不是Jpeg2000?
开发语言·c++·ffmpeg
Ronin3055 小时前
【C++】类型转换
开发语言·c++
mrbone115 小时前
Git-git worktree的使用
开发语言·c++·git·cmake·worktree·gitab
时序数据说6 小时前
时序数据库IoTDB用户自定义函数(UDF)使用指南
大数据·数据库·物联网·开源·时序数据库·iotdb
虾球xz6 小时前
CppCon 2018 学习:EFFECTIVE REPLACEMENT OF DYNAMIC POLYMORPHISM WITH std::variant
开发语言·c++·学习
津津有味道8 小时前
Qt C++串口SerialPort通讯发送指令读写NFC M1卡
linux·c++·qt·串口通信·serial·m1·nfc