Printing Usage
While we're dealing with options at the command line, we might as well address the small bit of functionality regrading printing usage.
I frequently see code which just prints out usage as one large block of text. There's nothing really wrong with this although it has the small drawback that it can sometimes be a pain to edit on changes and the slightly bigger drawback that it's freeform and can lead to irregular standards of what you might expect when you invoke an application with -?. It also tends to mean that some text gets repeated by copy/paste within any instance.
It's not that hard to come up with a class for encapsulating a bit of the formatting logic and having a setting model which is a little easier to update.
So without further ado:
namespace JSBUtil
{
class UsagePrinter
{
public:
class UsageInfo
{
public:
UsageInfo(const char inShortOption, const std::string &inLongOption,
const std::string &inArgValue, const std::string &inDescription,
const bool inMandatory):
m_shortOption(inShortOption),
m_longOption(inLongOption), m_argValue(inArgValue),
m_description(inDescription), m_mandatory(inMandatory)
{ }
void print(std::ostream &outStream) const;
void printDescription(std::ostream &outStream, const int inOffset) const;
private:
char m_shortOption;
std::string m_longOption;
std::string m_argValue;
std::string m_description;
bool m_mandatory;
};
void addUsage(const char inShortOption,
const std::string &inLongOption, const std::string &inArgValue,
const std::string &inDescription,
const bool inMandatory = false);
void print(std::ostream& outStream, const std::string& inAppName) const;
private:
std::vector<UsageInfo> m_info;
};
}
inline std::ostream &operator<<(std::ostream &inStream,
const JSBUtil::UsagePrinter::UsageInfo &inInfo)
{
inInfo.print(inStream);
return inStream;
}
Implementations are:
void UsagePrinter::addUsage(const char inShortOption,
const std::string &inLongOption,
const std::string &inArgValue,
const std::string &inDescription,
const bool inMandatory)
{
m_info.emplace_back(inShortOption, inLongOption, inArgValue, inDescription,
inMandatory);
}
void UsagePrinter::UsageInfo::print(std::ostream &outStream) const
{
if (!m_mandatory)
outStream << "[ ";
if (m_shortOption == '\0')
{
if (m_longOption.empty())
outStream << "<" << m_argValue << ">";
else
{
outStream << "--" << m_longOption;
if (!m_argValue.empty())
outStream << " <" << m_argValue << ">";
}
}
else
{
if (m_longOption.empty())
{
outStream << '-' << m_shortOption;
if (!m_argValue.empty())
outStream << " <" << m_argValue << ">";
}
else
{
outStream << '-' << m_shortOption << "| --" << m_longOption;
if (!m_argValue.empty())
outStream << " <" << m_argValue << ">";
}
}
if (!m_mandatory) outStream << " ] ";
else outStream << " ";
}
void UsagePrinter::UsageInfo::printDescription(std::ostream &outStream,
const int inOffset) const
{
if (m_shortOption == '\0')
{
if (!m_longOption.empty())
outStream << m_longOption << ": ";
}
else
{
if (m_longOption.empty())
{
outStream << m_shortOption << ": ";
}
else
{
outStream << "Short form: " << m_shortOption
<< "; Long form: " << m_longOption << ": ";
}
}
if (m_mandatory)
outStream << " (Mandatory argument)";
if (!m_argValue.empty())
{
if (m_longOption.empty() && (m_shortOption == '\0'))
outStream << "Argument " << inOffset + 1 << ": ";
outStream << m_argValue;
}
outStream << " " << m_description << std::endl;
}
void UsagePrinter::print(std::ostream &outStream,
const std::string &inAppName) const
{
outStream << "Usage: " << inAppName << " ";
int count = 0;
std::ranges::for_each(m_info, [&outStream, &count](const auto &inVal) {
outStream << inVal;
++count;
});
outStream << "\n";
std::ranges::for_each(std::ranges::iota_view{ 0, count },
[&](const int inVal) {
m_info[inVal].printDescription(outStream, inVal);
});
}
This is a little finicky, but the details aren't easily reducible.
This gets invoked as:
JSBUtil::UsagePrinter printer;
printer.addUsage('\0', "", "Config file", "General configuration file");
printer.addUsage('?', "", "", "Print this message");
printer.addUsage('D', "", "Date", "Date in MM-DD format for office date");
printer.addUsage('O', "", "Office", "Office name (exclusive with -x)");
printer.addUsage('P', "", "", "Office is said by priest");
printer.addUsage(
'e', "", "Easter",
"Date of Easter in YYYY-MM-DD format (Exclusive with -y option)");
printer.addUsage('f', "", "Filename", "Output filename");
printer.addUsage('x', "", "", "Choose office for current time of day");
printer.addUsage('y', "", "Year", "Year for office");
printer.print(std::cout, inApp);
which means that the option information is nicely structured and easy to add to, and also that the summary execution line stays in sync with the longer descriptions.
This produces output that looks like:
Usage: ./breviary_app [ <Config file> ] [ -? ] [ -D <Date> ] [ -O <Office> ] [ -P ] [ -e <Easter> ] [ -f <Filename> ] [ -x ] [ -y <Year> ]
Argument 1: Config file General configuration file
?: Print this message
D: Date Date in MM-DD format for office date
O: Office Office name (exclusive with -x)
P: Office is said by priest
e: Easter Date of Easter in YYYY-MM-DD format (Exclusive with -y option)
f: Filename Output filename
x: Choose office for current time of day
y: Year Year for office
Comments
Post a Comment