From 9e9a432f70f5c17029541fab6a5a56279035637e Mon Sep 17 00:00:00 2001 From: Daniel Wolf Date: Wed, 6 Jan 2016 20:45:04 +0100 Subject: [PATCH] Improved formatting of command-line output --- CMakeLists.txt | 2 + src/NiceCmdLineOutput.cpp | 98 +++++++++++++++++++++++++++++++++++++++ src/NiceCmdLineOutput.h | 19 ++++++++ src/TablePrinter.cpp | 63 +++++++++++++++++++++++++ src/TablePrinter.h | 19 ++++++++ src/main.cpp | 13 ++++-- 6 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 src/NiceCmdLineOutput.cpp create mode 100644 src/NiceCmdLineOutput.h create mode 100644 src/TablePrinter.cpp create mode 100644 src/TablePrinter.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8871a62..9beb013 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,6 +100,8 @@ set(SOURCE_FILES src/audioInput/WaveFileReader.cpp src/audioInput/waveFileWriting.cpp src/stringTools.cpp + src/NiceCmdLineOutput.cpp + src/TablePrinter.cpp ) add_executable(rhubarb ${SOURCE_FILES}) target_link_libraries(rhubarb ${Boost_LIBRARIES} cppFormat sphinxbase pocketSphinx) diff --git a/src/NiceCmdLineOutput.cpp b/src/NiceCmdLineOutput.cpp new file mode 100644 index 0000000..86567ba --- /dev/null +++ b/src/NiceCmdLineOutput.cpp @@ -0,0 +1,98 @@ +#include "regex" +#include "NiceCmdLineOutput.h" +#include "platformTools.h" +#include "TablePrinter.h" + +using std::string; +using std::vector; +using TCLAP::CmdLineInterface; +using std::cout; +using std::endl; + +string getBinaryName() { + return getBinPath().filename().string(); +} + +void NiceCmdLineOutput::version(CmdLineInterface& cli) { + cout << endl << cli.getMessage() << " version " << cli.getVersion() << endl << endl; +} + +void NiceCmdLineOutput::usage(CmdLineInterface& cli) { + cout << endl << "Short usage:" << endl; + printShortUsage(cli, cout); + cout << endl; + + cout << "Long usage:" << endl << endl; + printLongUsage(cli, cout); + + cout << endl; +} + +void NiceCmdLineOutput::failure(CmdLineInterface& cli, TCLAP::ArgException& e) { + std::cerr << "Invalid command-line arguments. " << e.argId() << endl; + std::cerr << e.error() << endl << endl; + + if (cli.hasHelpAndVersion()) { + std::cerr << "Short usage:" << endl; + printShortUsage(cli, std::cerr); + + std::cerr << endl << "For complete usage and help, type `" << getBinaryName() << " --help`" << endl << endl; + } else { + usage(cli); + } +} + +void NiceCmdLineOutput::printShortUsage(CmdLineInterface& cli, std::ostream& outStream) const { + string shortUsage = getBinaryName() + " "; + + // Print XOR arguments + TCLAP::XorHandler xorHandler = cli.getXorHandler(); + const vector> xorArgGroups = xorHandler.getXorList(); + for (const vector& xorArgGroup : xorArgGroups) { + shortUsage += " {"; + + for (auto arg : xorArgGroup) shortUsage += arg->shortID() + "|"; + shortUsage.pop_back(); + + shortUsage += '}'; + } + + // Print regular arguments + std::list argList = cli.getArgList(); + for (auto arg : argList) { + if (xorHandler.contains(arg)) continue; + + shortUsage += " " + arg->shortID(); + } + + outStream << shortUsage << endl; +} + +string wrapLongID(const string& s) { + return std::regex_replace(s, std::regex(", "), ",\n"); +} + +void NiceCmdLineOutput::printLongUsage(CmdLineInterface& cli, std::ostream& outStream) const { + TablePrinter tablePrinter(&outStream, { 20, 56 }); + + // Print XOR arguments + TCLAP::XorHandler xorHandler = cli.getXorHandler(); + const vector> xorArgGroups = xorHandler.getXorList(); + for (const vector& xorArgGroup : xorArgGroups) { + for (auto arg : xorArgGroup) { + if (arg != xorArgGroup[0]) + outStream << "-- or --" << endl; + + tablePrinter.printRow({ wrapLongID(arg->longID()), arg->getDescription() }); + } + outStream << endl; + } + + // Print regular arguments + std::list argList = cli.getArgList(); + for (auto arg : argList) { + if (xorHandler.contains(arg)) continue; + + tablePrinter.printRow({ wrapLongID(arg->longID()), arg->getDescription() }); + } +} \ No newline at end of file diff --git a/src/NiceCmdLineOutput.h b/src/NiceCmdLineOutput.h new file mode 100644 index 0000000..a1d787a --- /dev/null +++ b/src/NiceCmdLineOutput.h @@ -0,0 +1,19 @@ +#ifndef RHUBARB_LIP_SYNC_NICECMDLINEOUTPUT_H +#define RHUBARB_LIP_SYNC_NICECMDLINEOUTPUT_H + +#include + +class NiceCmdLineOutput : public TCLAP::CmdLineOutput { +public: + void usage(TCLAP::CmdLineInterface& cli) override; + void version(TCLAP::CmdLineInterface& cli) override; + void failure(TCLAP::CmdLineInterface& cli, TCLAP::ArgException& e) override; +private: + // Writes a brief usage message with short args. + void printShortUsage(TCLAP::CmdLineInterface& cli, std::ostream& outStream) const; + + // Writes a longer usage message with long and short args, providing descriptions + void printLongUsage(TCLAP::CmdLineInterface& cli, std::ostream& outStream) const; +}; + +#endif //RHUBARB_LIP_SYNC_NICECMDLINEOUTPUT_H diff --git a/src/TablePrinter.cpp b/src/TablePrinter.cpp new file mode 100644 index 0000000..1f8b80f --- /dev/null +++ b/src/TablePrinter.cpp @@ -0,0 +1,63 @@ +#include "TablePrinter.h" +#include +#include +#include +#include "stringTools.h" + +using std::ostream; +using std::initializer_list; +using std::invalid_argument; +using std::vector; +using std::string; + +TablePrinter::TablePrinter(ostream *stream, initializer_list columnWidths, int columnSpacing) : + stream(stream), + columnWidths(columnWidths.begin(), columnWidths.end()), + columnSpacing(columnSpacing) +{ + if (stream == nullptr) throw invalid_argument("stream is null."); + if (columnWidths.size() < 1) throw invalid_argument("No columns defined."); + if (std::any_of(columnWidths.begin(), columnWidths.end(), [](int width){ return width <= 1; })) { + throw invalid_argument("All columns must have a width of at least 1."); + } + if (columnSpacing < 0) throw invalid_argument("columnSpacing must not be negative."); +} + +void TablePrinter::printRow(initializer_list columns) const { + if (columns.size() != columnWidths.size()) throw invalid_argument("Number of specified strings does not match number of defined columns."); + + // Some cells may span multiple lines. + // Create matrix of text lines in columns. + vector> strings(columns.size()); + size_t lineCount = 0; + { + int columnIndex = 0; + for (const string& column : columns) { + vector lines = wrapString(column, columnWidths[columnIndex]); + if (lines.size() > lineCount) lineCount = lines.size(); + strings[columnIndex] = move(lines); + + columnIndex++; + } + // Make sure the matrix is uniform + for (vector& columnRows : strings) { + columnRows.resize(lineCount); + } + } + + // Save stream flags, restore them at end of scope + boost::io::ios_flags_saver ifs(*stream); + + // Print lines + *stream << std::left; + string spacer(columnSpacing, ' '); + for (size_t rowIndex = 0; rowIndex < lineCount; rowIndex++) { + for (size_t columnIndex = 0; columnIndex < columns.size(); columnIndex++) { + if (columnIndex != 0) { + *stream << spacer; + } + *stream << std::setw(columnWidths[columnIndex]) << strings[columnIndex][rowIndex]; + } + *stream << std::endl; + } +} diff --git a/src/TablePrinter.h b/src/TablePrinter.h new file mode 100644 index 0000000..467dffc --- /dev/null +++ b/src/TablePrinter.h @@ -0,0 +1,19 @@ +#ifndef RHUBARB_LIP_SYNC_TABLEPRINTER_H +#define RHUBARB_LIP_SYNC_TABLEPRINTER_H + + +#include +#include +#include + +class TablePrinter { +public: + TablePrinter(std::ostream* stream, std::initializer_list columnWidths, int columnSpacing = 2); + void printRow(std::initializer_list columns) const; +private: + std::ostream* const stream; + const std::vector columnWidths; + const int columnSpacing; +}; + +#endif //RHUBARB_LIP_SYNC_TABLEPRINTER_H diff --git a/src/main.cpp b/src/main.cpp index ed1d5f5..f39defc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ #include "mouthAnimation.h" #include "platformTools.h" #include "appInfo.h" +#include "NiceCmdLineOutput.h" using std::exception; using std::string; @@ -69,13 +70,14 @@ ptree createXmlTree(const path& filePath, const map& phones } int main(int argc, char *argv[]) { - try { // Define command-line parameters const char argumentValueSeparator = ' '; TCLAP::CmdLine cmd(appName, argumentValueSeparator, appVersion); cmd.setExceptionHandling(false); + cmd.setOutput(new NiceCmdLineOutput()); TCLAP::UnlabeledValueArg inputFileName("inputFile", "The input file. Must be a sound file in WAVE format.", true, "", "string", cmd); + try { // Parse command line cmd.parse(argc, argv); @@ -93,10 +95,15 @@ int main(int argc, char *argv[]) { boost::property_tree::write_xml(std::cout, xmlTree, boost::property_tree::xml_writer_settings(' ', 2)); return 0; - } catch (const TCLAP::ArgException& e) { - std::cerr << "Invalid command-line arguments regarding `" << e.argId() << "`. " << e.error(); + } catch (TCLAP::ArgException& e) { + // Error parsing command-line args. + cmd.getOutput()->failure(cmd, e); return 1; + } catch (TCLAP::ExitException& e) { + // A built-in TCLAP command (like --help) has finished. Exit application. + return 0; } catch (const exception& e) { + // Generic error std::cerr << "An error occurred. " << getMessage(e); return 1; }