2. CommandLine — CmdLine
CmdLine converts raw argv tokens into an
std::unordered_map<std::string, std::string> of
options. A token starting with / or - and
followed by a single alphabetic character is treated as an option key;
the next token (if it does not itself start with / or
-) becomes its value, otherwise the value defaults to
"true". Accepting both prefixes lets the same CLI work in
PowerShell (/P .) and in bash (-P .).
2.1 Design Points
-
Single storage layer. Every parsed option lives
in one
std::unordered_map<std::string, std::string>.
The /p value is not eagerly expanded — the
patterns() accessor splits it on demand using
std::views::split(',').
-
Defaults applied after parsing. The constructor
calls
parse() first, then apply_defaults(),
so any argv value stays put while missing keys are filled in with
"P=.", "r=.", "s=true",
"H=true". This matches the Rust variant's ordering
but uses a member function rather than a public
default_options() that callers must remember to
invoke.
-
Flag validation.
is_flag requires
exactly two characters — a prefix and one alphabetic character.
Longer tokens like /path or -help are
treated as values, not keys, avoiding accidental collisions.
-
Named module, no headers.
CmdLine is
declared with export class CmdLine inside an
export module cmd_line; unit. Callers write
import cmd_line; — there is no header file and no
include guard.
-
Ranges for pattern splitting. The
patterns() method uses
raw | std::views::split(',') and constructs each part
from an iterator pair — a modern C++23 idiom that avoids manual
find/substr loops.
2.2 Public API Summary
| Method |
Purpose |
CmdLine(argc, argv) |
Parse argv, then apply defaults for any missing key. |
path() |
Return the /P value (defaulted to "."). |
regex() |
Return the /r value (defaulted to "."). |
patterns() |
Split the /p value on , and return a std::vector<std::string>. |
recurse() / hide() |
Return the boolean interpretation of /s and /H. |
verbose() / help() |
Presence flags — true if /v or /h appeared on the command line. |
help_text() (static) |
Multi-line help string printed by main on /h or on empty argv. |
2.3 Source — CommandLine/src/CmdLine.ixx
export module cmd_line;
import std;
export class CmdLine {
public:
CmdLine(int argc, const char* argv[]) {
parse(argc, argv);
apply_defaults();
}
std::string path() const {
return get("P");
}
std::string regex() const {
return get("r");
}
std::vector<std::string> patterns() const {
std::string raw = get("p");
if (raw.empty())
return {};
std::vector<std::string> result;
for (auto part : raw | std::views::split(',')) {
std::string s(part.begin(), part.end());
if (!s.empty())
result.push_back(std::move(s));
}
return result;
}
bool recurse() const {
return get("s") == "true";
}
bool hide() const {
return get("H") == "true";
}
bool verbose() const {
return present("v");
}
bool help() const {
return present("h");
}
static std::string help_text() {
return
"CppTextFinder — search a directory tree for files whose content matches a regex\n"
"\n"
"Usage:\n"
" text_finder [/P <path>] [/p <exts>] [/s <bool>] [/H <bool>]\n"
" [/r <regex>] [/v] [/h]\n"
"\n"
" Flag prefix: both '/' (Windows style) and '-' (Unix style) are accepted,\n"
" e.g. \"/P .\" and \"-P .\" are equivalent.\n"
"\n"
"Options:\n"
" /P <path> Root path for the search (default: \".\")\n"
" /p <exts> Comma-separated file extensions to\n"
" include, e.g. \".cpp,.h\" (default: all files)\n"
" /s <bool> Recurse into subdirectories (default: true)\n"
" /H <bool> true = print directory only when it\n"
" contains a match (clean output).\n"
" false = print every directory entered (default: true)\n"
" /r <regex> Regular expression matched against\n"
" file content (default: \".\")\n"
" /v Verbose: echo all options before\n"
" searching\n"
" /h Print this help text and exit\n";
}
private:
std::unordered_map<std::string, std::string> options_;
void parse(int argc, const char* argv[]) {
auto is_flag = [](const std::string& t) {
return t.size() == 2 && (t[0] == '/' || t[0] == '-') && std::isalpha(static_cast<unsigned char>(t[1]));
};
int i = 1;
while (i < argc) {
std::string token = argv[i];
if (is_flag(token)) {
std::string key(1, token[1]);
if (i + 1 < argc) {
std::string next = argv[i + 1];
if (is_flag(next)) {
// Next token is another flag — value is implicit "true"
options_[key] = "true";
} else {
// Consume next token as value
options_[key] = next;
++i;
}
} else {
// Last token on the command line — implicit "true"
options_[key] = "true";
}
}
++i;
}
}
void apply_defaults() {
if (!options_.contains("P")) { options_["P"] = "."; }
if (!options_.contains("r")) { options_["r"] = "."; }
if (!options_.contains("s")) { options_["s"] = "true"; }
if (!options_.contains("H")) { options_["H"] = "true"; }
// /p has no meaningful default value; leave absent so patterns() returns empty
// /v and /h are boolean presence flags; leave absent when not supplied
}
bool present(const std::string& key) const {
return options_.contains(key);
}
std::string get(const std::string& key) const {
auto it = options_.find(key);
if (it != options_.end()) {
return it->second;
}
return {};
}
};