C++命令行软件编写备忘 [WIP]

最近有一个写命令行小软件的需求,用户输入服务器所在ip及端口,而后输入账户及密码后由命令行软件将请求发出,并将返回的JSON打印到控制台上。

软件的功能比较简单,但由于我没有写过完整的命令行软件,因此将整套命令行软件所需要的一些东西都搞了一遍,本文即为记录该软件编写过程及设计的备忘文。

命令行设计

这里首先讨论下命令行软件的一些常见设计:

  • “-X” 短横加单字母则为缩写参数
  • “–XXX” 双横加单词为完成参数
  • “-h” 及 “–help” 为打印帮助手册后退出
  • 当某必填参数未填写时,提示用户某参数未填写,而后退出
  • 当程序第一次启动时生成默认配置文件(格式一般为yml、ini、cfg、conf等),当程序第二次运行时自动读取配置文件存储的用户配置信息

其实以上几点就包含了我暂时能想到的命令行软件的一些基本点,后面有时间再写下nodejs的命令行软件博客。

第三方库

命令行解析

首先命令行参数解析我之前一直直接用字符串匹配解析的方式,这次我在awsome c++ repo中找了个命令行参数解析库Argh!

这个库相对来说使用起来也比较简单,这里就不多叙述了。

HTTP请求封装库

HTTP请求使用elnormous/HTTPRequest库,这个库使用起来比较简单,而且是single-header library非常易于集成,因此就偷懒使用这个库作为HTTP请求封装库。

这个库的使用方法不是很复杂就不多赘述了。

Yaml配置文件解析库

这个软件中只是将一个简单的键值对yaml作为配置文件,因此这里没有使用一些重量级的yaml库,而是使用一个轻量级的mini-yaml库作为整个软件的配置文件写入及读出。

代码

这里没有对account、password及其他数据进行合法性校验,但做校验难度也不大,这里就不额外修改代码了。

默认情况使用admin作为登录用户名,默认服务器端口和路径分别为1000、/api/login。

软件必填项为服务器IP及账户密码,当输入tLogin.exe -s=192.168.0.1 -P=123456时其余参数将使用缺省配置,即

POST http://192.168.0.1/api/login {"username":"admin",password:"123465"}

代码如下:

#include <iostream>
#include <string>
#include "./libs/argh.h"
#include "libs/HTTPRequest.hpp"
#include "libs/Yaml.hpp"

class LoginEntity{//简单封装
public:
    std::string username;//用户名
    std::string userpassword;//密码
    std::string server;//服务器IP
    std::string port;//服务端口
};
void show_help_header();//帮助手册打印函数
void login(LoginEntity& entity);//登录函数

Yaml::Node config;//yaml配置单例
void load_config();//加载配置文件

int main(int argc, char* argv[])
{

    load_config();


    LoginEntity entity;
    argh::parser cmdl(argc,argv);//解析命令行参数



    if(cmdl[{ "-h", "--help" }])//检查help参数是否存在
    {
        show_help_header();
        return 0;
    }

    if(cmdl({ "-s", "--server" }))//检查 服务器IP 参数是否存在
    {
        entity.server = cmdl({ "-s", "--server" }).str();
        config["server"]=entity.server;
    }else{
        if(!config["server"].IsNone()){
            std::cout<< "[WARNING]:use default server address "<<config["server"].As<std::string>()<<std::endl;
            entity.server = config["server"].As<std::string>();
        }
        else{
            std::cout<< "[ERROR]:please input server address use '-s=<server>'\n";
            return EXIT_FAILURE;
        }
    }

    if(cmdl({ "-p", "--port" })){//检查 服务器端口 参数是否存在
        try{
            entity.port = cmdl({ "-p", "--port" }).str();
            int port = stoi(entity.port);
            if(port < 0 || port > 25565){
                std::cout<<"[ERROR]:error server port ("<<cmdl({ "-p", "--port" }).str()<<")"<<std::endl;
                return EXIT_FAILURE;
            }
            config["port"]=entity.port;
        }
        catch (...){
            std::cout<<"[ERROR]:error server port ("<<cmdl({ "-p", "--port" }).str()<<")"<<std::endl;
            return EXIT_FAILURE;
        }
    }
    else{
        if(!config["server"].IsNone()){
            entity.port = config["port"].As<std::string>();
        }
        else{
            config["port"] = "1000";
            entity.port = "1000";
        }
        std::cout<<"[WARNING]:use default port ("<<entity.port<<")"<<std::endl;
    }


    if(cmdl({ "-P", "--password" }))//检查 密码是否存在
    {
        entity.userpassword = cmdl({ "-P", "--password" }).str();
    }
    else{
        std::cout<<"[ERROR]:user password not configured."<<std::endl;
        return EXIT_FAILURE;
    }


    if(cmdl({ "-a", "--account" })){//检查是否配置账户名
        entity.username =  cmdl({  "-a", "--account" }).str();
        config["username"] = entity.username;
    }
    else{
        if(!config["username"].IsNone()){
            entity.username = config["username"].As<std::string>();
        }
        else{
            std::cout<<"[ERROR]:user account not configured."<<std::endl;
            return EXIT_FAILURE;
        }
    }



    login(entity);

    return EXIT_SUCCESS;
}


void show_help_header(){
    std::cout << "-a=<account>, --account=<account>\n"
                 "\t\t user account name\n"
                 "-P=<password>, --password=<password>\n"
                 "\t\t user password\n"
                 "-s=<server>, --server=<server>\n"
                 "\t\t server ip address\n"
                 "-p=<port>, --port=<port>\n"
                 "\t\t server port\n";
}


void login(LoginEntity& entity){
    //构建登录api URL
    std::string url = config["protocol"].As<std::string>() +"://" + entity.server + ":" + entity.port +config["path"].As<std::string>();
    //构建HTTP请求对象
    http::Request request(url);
    //HTTP请求体构建
    //JSON类型数据,包含username及password数据,password没有加密,由于只是一个简单的应用因此没有做数据有效性校验
    std::string body = "{\"username\":\""+entity.username+"\",\"password\":\""+entity.userpassword+"\"}";
    try{
        std::cout<<"Login Request Send to:"<<url<<std::endl;
        const http::Response postResponse = request.send("POST",body,
                                                         { std::make_pair("Content-Type","application/json")},
                                                         //5s 超时
                                                         std::chrono::milliseconds{5000}
        );
        //打印返回数据
        std::cout << std::string(postResponse.body.begin(), postResponse.body.end()) << '\n';
        //序列化到配置文件中
        Yaml::Serialize(config,"tlogin.yml");
    }
    catch (http::ResponseError& e){
        std::cout<<e.what()<<".Please check configurations Or try again."<<std::endl;
    }

}

void load_config(){
    Yaml::Node& root = config;
    try{
        Yaml::Parse(root, "tlogin.yml");
    }
    catch (...){
        //默认配置文件
        Yaml::Node & ref = root;
        ref["port"]="1000";//服务器默认端口
        ref["username"]="admin";//默认用户名
        ref["path"]="/api/login";//服务器默认登录API路径
        ref["protocol"]="http";//服务器协议
        Yaml::Serialize(root,"tlogin.yml");//序列化默认配置文件(覆盖原文件)
    }
}

生成的默认yaml文件:


v0.3wep 简单写写

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注