re-write CLI argument parsing for testability

This commit is contained in:
Michael Davidsaver committed 2026-06-28 11:47:57 -07:00
1 parent 8cb8d4bf69
commit 5ab17ec375
7 files changed
+552 -39

No files matched your search

+40 -38
View File
@@ -9,7 +9,6 @@
#include <atomic>
#include <epicsVersion.h>
#include <epicsGetopt.h>
#include <epicsThread.h>
#define PVXS_ENABLE_EXPERT_API
@@ -19,6 +18,11 @@
#include <pvxs/json.h>
#include "utilpvt.h"
#include "evhelper.h"
#include "cliutil.h"
#ifndef REALMAIN
# define REALMAIN main
#endif
using namespace pvxs;
@@ -37,9 +41,9 @@ void usage(const char* argv0)
;
}
}
} // namespace
int main(int argc, char *argv[])
int REALMAIN(int argc, char *argv[])
{
try {
logger_config_env(); // from $PVXS_LOG
@@ -47,55 +51,53 @@ int main(int argc, char *argv[])
bool verbose = false;
std::string request;
{
int opt;
while ((opt = getopt(argc, argv, "hvVdw:r:")) != -1) {
switch(opt) {
case 'h':
usage(argv[0]);
return 0;
case 'V':
std::cout<<pvxs::version_information;
return 0;
case 'v':
verbose = true;
break;
case 'd':
logger_level_set("pvxs.*", Level::Debug);
break;
case 'w':
timeout = parseTo<double>(optarg);
break;
case 'r':
request = optarg;
break;
default:
usage(argv[0]);
std::cerr<<"\nUnknown argument: "<<char(opt)<<std::endl;
return 1;
}
GetOpt opts(argc, argv, "hvVdw:r:");
for(auto& pair : opts.arguments) {
switch(pair.first) {
case 'h':
usage(opts.argv0);
return 0;
case 'V':
std::cout<<pvxs::version_information;
return 0;
case 'v':
verbose = true;
break;
case 'd':
logger_level_set("pvxs.*", Level::Debug);
break;
case 'w':
timeout = pair.second.as<double>();
break;
case 'r':
request = *pair.second;
break;
default:
usage(opts.argv0);
std::cerr<<"\nUnknown argument: "<<pair.first<<std::endl;
return 1;
}
}
if(optind==argc) {
usage(argv[0]);
std::cerr<<"\nExpected PV name\n";
if(opts.positional.size()<2) {
usage(opts.argv0);
std::cerr<<"\nExpected PV name and at least one value\n";
return 1;
}
std::string pvname(argv[optind++]);
const auto& pvname = opts.positional.front();
std::map<std::string, std::string> values;
if(argc-optind==1 && std::string(argv[optind]).find_first_of('=')==std::string::npos) {
if(opts.positional.size()==2 && std::string(opts.positional[1]).find_first_of('=')==std::string::npos) {
// only one field assignment, and field name omitted.
// if JSON map, treat as entire struct. Others imply .value
auto sval(argv[optind]);
const auto& sval = opts.positional[1];
values[sval[0]=='{' ? "" : "value"] = sval;
} else {
for(auto n : range(optind, argc)) {
std::string fv(argv[n]);
for(auto n : range(size_t(1), opts.positional.size())) {
std::string fv(opts.positional[n]);
auto sep = fv.find_first_of('=');
if(sep==std::string::npos) {