本课为 fswatch(第一“杰”)的示例项目加上对配置文件读取的支持,同时借助 第三“杰” CLI11 的支持,完美实现命令行参数与配置文件的逻辑统一。
012-nlohmann/json-4-项目应用
项目基于原有的 CMake 项目 HelloFSWatch 修改。
CMakeLists.txt :该文件基于原项目,没有任何改动。
.vscode/setting.json ,改动如下:
{
"cmake.debugConfig": {
"cwd": "${workspaceFolder}",
"args": ["-m", "3", "--log-level", "info"],
"externalConsole": false
}
}
重点:
① args字段:添加命令行参数;
② cwd 字段:设置程序在项目根目录下运行(而在程序所在的 build 子目录内)。
- myConfig.json 测试用的配置文件
{
"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
#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(); // 进入死循环
}