Skip to content

Commit 233cfd2

Browse files
committed
Add, transform actions to modify source arg before parsing
1 parent 4e5eb28 commit 233cfd2

4 files changed

Lines changed: 129 additions & 63 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](http://semver.org).
2323
- Added - cli.parseValue() overloads that include arg source
2424
- Deprecated - cli.parseValue() overload w/o arg source
2525
- Added - opt.initConfig() customization point
26+
- Added - Transform actions to modify source arg before parsing
2627

2728
## dimcli 7.5.0 (2025-11-20)
2829
- Added - cli.success() and cli.fail() overloads with just the exit code

docs/reference.adoc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,10 @@ present, multiplies by the corresponding factor.
260260
| opt.toString<T>
261261
| Inherited from <<string-conversions, Convert>>.
262262

263+
| opt.transform
264+
| Add action to transform value string before it is parsed, any number of
265+
transform actions can be added.
266+
263267
| optVec.<<guide.adoc#vector-options, size>>
264268
| Change the number of values that can be assigned to a vector option. Defaults
265269
to a minimum of 1 and a maximum of -1 (unlimited).
@@ -280,6 +284,10 @@ message and flags the in progress parse() to return false.
280284
implementation. Not reliable before cli.parse() has been called and had a
281285
chance to update the internal data structures.
282286

287+
| cli.newValue
288+
| Used by transform action callbacks to modify the current value string about
289+
to be parsed.
290+
283291
| cli.parseExit
284292
| Intended for use in action callbacks. Sets exit code to EX_OK, and causes an
285293
in progress cli.parse() or cli.exec() to return false.

libs/dimcli/cli.cpp

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,12 @@ struct Cli::Config {
191191
shared_ptr<locale> defLoc = make_shared<locale>();
192192
shared_ptr<locale> numLoc = make_shared<locale>("");
193193

194-
bool parseExit = false;
194+
Cli::OptBase * curOpt = {};
195+
string newValue;
195196
int exitCode = kExitOk;
196197
string errMsg;
197198
string errDetail;
199+
bool parseExit = false;
198200
vector<string> rawArgs;
199201
string progName;
200202
string command;
@@ -443,7 +445,7 @@ static bool equal(
443445
if (args.size() != sargs.size())
444446
return false;
445447
for (auto i = 0u; i < args.size(); ++i) {
446-
if (args[i].value != sargs[i])
448+
if (args[i].text != sargs[i])
447449
return false;
448450
}
449451
return true;
@@ -550,7 +552,8 @@ bool Cli::Convert::toString_impl<std::wstring>(
550552
size_t mblen = 0;
551553

552554
for (auto&& w : src) {
553-
assert(out.data() + out.size() - dst > MB_LEN_MAX);
555+
assert(out.data() + out.size() - dst > MB_LEN_MAX // LCOV_EXCL_LINE
556+
&& "Internal dimcli error: char encoding exceeds MB_LEN_MAX.");
554557
mblen = wcrtomb(dst, w, &state);
555558
if (mblen == -1) {
556559
auto bytes = src.size() * sizeof src[0];
@@ -763,6 +766,25 @@ string Cli::OptBase::defaultPrompt() const {
763766
return name;
764767
}
765768

769+
//===========================================================================
770+
void Cli::newValue(const string & value) {
771+
if (!m_cfg->curOpt) {
772+
assert(!"cli.newValue only allowed from transform action callbacks.");
773+
return;
774+
}
775+
auto & opt = *m_cfg->curOpt;
776+
if (!opt.m_bool) {
777+
m_cfg->newValue = value;
778+
} else {
779+
bool v;
780+
if (parseBool(v, value)) {
781+
m_cfg->newValue = v ? "1" : "0";
782+
} else {
783+
badUsage(opt, value);
784+
}
785+
}
786+
}
787+
766788
//===========================================================================
767789
void Cli::OptBase::setNameIfEmpty(const string & name) {
768790
if (m_fromName.empty())
@@ -1401,6 +1423,12 @@ Cli::OptBase * Cli::findOpt(const void * value) {
14011423
return nullptr;
14021424
}
14031425

1426+
//===========================================================================
1427+
// private
1428+
const string & Cli::newValue() const {
1429+
return m_cfg->newValue;
1430+
}
1431+
14041432
//===========================================================================
14051433
Cli::Opt<bool> & Cli::confirmOpt(const string & prompt) {
14061434
auto & ask = opt<bool>("y yes.")
@@ -1677,7 +1705,7 @@ static void doBefore(
16771705
) {
16781706
vector<string> sargs;
16791707
for (auto&& arg : args)
1680-
sargs.push_back(arg.value);
1708+
sargs.push_back(arg.text);
16811709
fn(cli, sargs);
16821710
if (!equal(args, sargs)) {
16831711
// String vector was changed, create new vector of args and completely
@@ -1951,7 +1979,7 @@ static bool expandResponseFile(
19511979
) {
19521980
string content;
19531981
error_code ec;
1954-
auto fn = args[pos].value.substr(1);
1982+
auto fn = args[pos].text.substr(1);
19551983
auto cfn = ancestors.empty()
19561984
? (fs::path) fn
19571985
: fs::path(ancestors.back()).parent_path() / fn;
@@ -1992,7 +2020,7 @@ static bool expandResponseFiles(
19922020
vector<string> & ancestors
19932021
) {
19942022
for (size_t pos = 0; pos < args.size(); ++pos) {
1995-
if (!args[pos].value.empty() && args[pos].value.front() == '@') {
2023+
if (!args[pos].text.empty() && args[pos].text.front() == '@') {
19962024
if (!expandResponseFile(cli, args, pos, ancestors))
19972025
return false;
19982026
}
@@ -2234,14 +2262,14 @@ bool Cli::OptIndex::parseOptionValue(
22342262
cli.badUsage("No value given for " + st.name);
22352263
return false;
22362264
}
2237-
auto ptr = args[st.argPos].value.c_str();
2265+
auto ptr = args[st.argPos].text.c_str();
22382266
addOptionMatch(out, st, ptr, args);
22392267

22402268
// Option has value list, use following arguments up to the next option as
22412269
// values.
22422270
if (st.optName.flags & fNameList) {
22432271
while (st.argPos + 1 < args.size()) {
2244-
ptr = args[st.argPos + 1].value.c_str();
2272+
ptr = args[st.argPos + 1].text.c_str();
22452273
if (*ptr == '-') {
22462274
// The next argument looks like an option, so stop taking
22472275
// arguments.
@@ -2273,7 +2301,7 @@ bool Cli::OptIndex::parseToRawValues(
22732301
const vector<Cli::Arg> & args,
22742302
Cli & cli
22752303
) {
2276-
cli.m_cfg->progName = args[0].value;
2304+
cli.m_cfg->progName = args[0].text;
22772305
ParseState st;
22782306
if (cli.m_cfg->cmds[""].unknownArgs) {
22792307
st.cmdMode = ParseState::kUnknown;
@@ -2283,7 +2311,7 @@ bool Cli::OptIndex::parseToRawValues(
22832311
}
22842312

22852313
for (; st.argPos < args.size(); ++st.argPos) {
2286-
st.ptr = args[st.argPos].value.c_str();
2314+
st.ptr = args[st.argPos].text.c_str();
22872315
if (*st.ptr == '-' && st.ptr[1] && st.moreOpts) {
22882316
// Argument contains one or more options.
22892317
st.ptr += 1;
@@ -2442,7 +2470,7 @@ static bool badMinMatched(
24422470
//===========================================================================
24432471
// static
24442472
void Cli::OptIndex::doAfters(OptBase & opt, Cli & cli) {
2445-
opt.doAfterActions(cli);
2473+
opt.doAfters(cli);
24462474
}
24472475

24482476
//===========================================================================
@@ -2595,6 +2623,8 @@ bool Cli::parse(const vector<string> & args) {
25952623
Cli & Cli::resetValues() & {
25962624
for (auto && opt : m_cfg->opts)
25972625
opt->reset();
2626+
m_cfg->curOpt = {};
2627+
m_cfg->newValue.clear();
25982628
m_cfg->parseExit = false;
25992629
m_cfg->exitCode = kExitOk;
26002630
m_cfg->errMsg.clear();
@@ -2716,16 +2746,21 @@ bool Cli::parseValue(
27162746
badUsage(prefix, ptr, detail);
27172747
return false;
27182748
}
2719-
string val;
27202749
if (ptr) {
2721-
val = ptr;
2722-
opt.doParseAction(*this, val);
2723-
if (parseAborted())
2724-
return false;
2750+
m_cfg->newValue = ptr;
2751+
m_cfg->curOpt = &opt;
2752+
opt.doTransforms(*this);
2753+
m_cfg->curOpt = {};
2754+
if (!parseAborted())
2755+
opt.doParse(*this);
2756+
if (!parseAborted())
2757+
opt.doChecks(*this);
27252758
} else {
2759+
m_cfg->newValue.clear();
27262760
opt.assignImplicit();
2761+
opt.doChecks(*this);
27272762
}
2728-
opt.doCheckActions(*this, val);
2763+
m_cfg->newValue.clear();
27292764
return !parseAborted();
27302765
}
27312766

@@ -3735,7 +3770,8 @@ static void calcColumns(
37353770
auto & col = cols[icol];
37363771
if (raw.newTable && col.minPct != -1) {
37373772
// Use min/max widths from preamble of this table cell.
3738-
assert(col.maxPct != -1);
3773+
assert(col.maxPct != -1 // LCOV_EXCL_LINE
3774+
&& "Internal dimcli error: column min width w/o max");
37393775
tcol.minWidth =
37403776
(int) round(col.minPct * cfg.maxLineWidth / 100);
37413777
tcol.maxWidth =

0 commit comments

Comments
 (0)