summaryrefslogtreecommitdiff
path: root/dviware/dvisvgm/src/CLCommandLine.cpp
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /dviware/dvisvgm/src/CLCommandLine.cpp
Initial commit
Diffstat (limited to 'dviware/dvisvgm/src/CLCommandLine.cpp')
-rw-r--r--dviware/dvisvgm/src/CLCommandLine.cpp242
1 files changed, 242 insertions, 0 deletions
diff --git a/dviware/dvisvgm/src/CLCommandLine.cpp b/dviware/dvisvgm/src/CLCommandLine.cpp
new file mode 100644
index 0000000000..2b0c1b5359
--- /dev/null
+++ b/dviware/dvisvgm/src/CLCommandLine.cpp
@@ -0,0 +1,242 @@
+/*************************************************************************
+** CLCommandLine.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <algorithm>
+#include <cstring>
+#include <unordered_map>
+#include "CLCommandLine.hpp"
+#include "version.hpp"
+
+using namespace CL;
+using namespace std;
+
+
+CommandLine::CommandLine (const char *summary, const char *usage, const char *copyright)
+ : _summary(summary), _usage(usage), _copyright(copyright)
+{
+}
+
+
+void CommandLine::parse (int argc, char **argv) {
+ bool filesOnly = false;
+ for (int i=1; i < argc; i++) {
+ istringstream iss(argv[i]);
+ if (filesOnly || iss.peek() != '-')
+ _files.emplace_back(argv[i]);
+ else {
+ iss.get(); // skip dash
+ if (iss.peek() < 0)
+ _singleDashParsed = true;
+ else if (iss.peek() != '-')
+ parseShortOption(iss, argc, argv, i);
+ else {
+ iss.get(); // skip dash
+ if (iss.peek() == EOF) // "--" only, no following option name?
+ filesOnly = true; // treat all following options as filenames
+ else
+ parseLongOption(iss);
+ }
+ }
+ }
+}
+
+
+static void error (const Option &option, bool shortname, const string &msg) {
+ ostringstream oss;
+ oss << "option ";
+ if (shortname)
+ oss << '-' << option.shortName();
+ else
+ oss << "--" << option.longName();
+ oss << ": " << msg;
+ throw CommandLineException(oss.str());
+}
+
+
+static void type_error (const Option &option, bool shortname) {
+ ostringstream oss;
+ switch (option.argMode()) {
+ case Option::ArgMode::NONE:
+ error(option, shortname, "no argument expected");
+ break;
+ case Option::ArgMode::OPTIONAL:
+ oss << "optional argument ";
+ if (!option.argName().empty())
+ oss << "'" << option.argName() << "' ";
+ oss << "must be of "+option.typeString()+" type";
+ error(option, shortname, oss.str());
+ break;
+ default:
+ oss << option.typeString()+" argument ";
+ if (!option.argName().empty())
+ oss << "'" << option.argName() << "' ";
+ oss << "expected";
+ error(option, shortname, oss.str());
+ }
+}
+
+
+void CommandLine::parseShortOption (istringstream &iss, int argc, char **argv, int &argn) {
+ bool combined = false;
+ do {
+ char shortname = static_cast<char>(iss.get());
+ if (!isalnum(shortname))
+ throw CommandLineException(string("syntax error: -")+shortname);
+ if (Option *option = lookupOption(shortname)) {
+ if (!combined || option->argMode() == Option::ArgMode::NONE) {
+ if (option->argMode() == Option::ArgMode::REQUIRED && strlen(argv[argn]) == 2) { // required argument separated by whitespace?
+ if (argn+1 < argc && argv[argn+1][0] != '-') {
+ iss.clear(); // reset error flags
+ iss.str(argv[++argn]); // continue parsing with next command-line field
+ }
+ }
+ if (!option->parse(iss, false))
+ type_error(*option, true);
+ if (option->argMode() == Option::ArgMode::NONE)
+ combined = true;
+ }
+ else
+ throw CommandLineException(string("option -")+ shortname + " must be given separately");
+ }
+ else
+ throw CommandLineException(string("unknown option -")+shortname);
+ }
+ while (combined && !iss.eof());
+}
+
+
+void CommandLine::parseLongOption (istream &is) {
+ string optname;
+ while (isalnum(is.peek()) || (!optname.empty() && is.peek() == '-'))
+ optname += char(is.get());
+ if (optname.empty())
+ throw CommandLineException("unexpected character '" + string(1, is.peek()) + "' after '--'");
+ vector<Option*> options = lookupOption(optname);
+ if (options.empty())
+ throw CommandLineException("unknown option --"+optname);
+ else if (options.size() == 1) {
+ if (!options[0]->parse(is, true))
+ type_error(*options[0], false);
+ }
+ else { // is partially given option ambiguous?
+ sort(options.begin(), options.end(), [](const Option *opt1, const Option *opt2) {
+ return opt1->longName() < opt2->longName();
+ });
+ string msg = "option --" + optname + " is ambiguous (";
+ for (const Option *opt : options) {
+ if (opt != options[0])
+ msg += ", ";
+ msg += opt->longName();
+ }
+ msg += ')';
+ throw CommandLineException(msg);
+ }
+}
+
+
+/** Returns all options that match the given long name. */
+vector<Option*> CommandLine::lookupOption (const string &optname) const {
+ vector<Option*> matches;
+ int len = optname.length();
+ for (OptSectPair optsect : options()) {
+ if (optsect.first->longName() == optname) { // exact match?
+ matches.clear();
+ matches.push_back(optsect.first);
+ break;
+ }
+ if (optsect.first->longName().substr(0, len) == optname) // partial match?
+ matches.push_back(optsect.first);
+ }
+ return matches;
+}
+
+
+/** Returns the option that match the given short name. */
+Option* CommandLine::lookupOption (char optchar) const {
+ auto it = find_if(options().begin(), options().end(), [=](const OptSectPair &optsect) {
+ return optsect.first->shortName() == optchar;
+ });
+ return (it != options().end()) ? it->first : nullptr;
+}
+
+
+/** Prints help text including summary of options.
+ * @param[in] os output stream the help text is printed to
+ * @param[in] mode output mode (0=section, 1=sorted by short options, 2=sorted by long options) */
+void CommandLine::help (ostream &os, int mode) const {
+ os << PROGRAM_NAME << ' '<< PROGRAM_VERSION << "\n\n";
+ os << _summary << "\n\n";
+ // print usage info
+ string usage = _usage;
+ int count=0;
+ while (!usage.empty()) {
+ size_t pos = usage.find('\n');
+ os << (count++ == 0 ? "Usage: " : " ") << PROGRAM_NAME << ' ' << usage.substr(0, pos) << '\n';
+ if (pos != string::npos)
+ usage = usage.substr(pos+1);
+ else
+ usage.clear();
+ }
+ if (mode > 0)
+ os << '\n';
+
+ // compute width of first column of help output
+ unordered_map<Option*, pair<string,string>> linecols;
+ size_t col1width=0;
+ for (const OptSectPair &ospair : options()) {
+ size_t pos;
+ string line = ospair.first->helpline();
+ if ((pos = line.find('\t')) != string::npos) {
+ linecols.emplace(ospair.first, pair<string,string>(line.substr(0, pos), line.substr(pos+1)));
+ col1width = max(col1width, pos);
+ }
+ }
+ if (mode > 0) {
+ bool (*isless)(const OptSectPair&, const OptSectPair&) = [](const OptSectPair &p1, const OptSectPair &p2) {
+ return p1.first->longName() < p2.first->longName();
+ };
+ if (mode == 1) {
+ isless = [](const OptSectPair &p1, const OptSectPair &p2) {
+ char c1 = p1.first->shortName(), c2 = p2.first->shortName();
+ if (c1 == c2)
+ return p1.first->longName() < p2.first->longName();
+ if (tolower(c1) == tolower(c2))
+ return c1 > c2; // lower case before upper case letters
+ return tolower(c1) < tolower(c2);
+ };
+ }
+ sort(options().begin(), options().end(), isless);
+ }
+
+ // print summary of options
+ col1width += 2;
+ int sectno=-1;
+ for (const OptSectPair &ospair : options()) {
+ if (mode == 0 && ospair.second != sectno) { // first option in current section?
+ sectno = ospair.second;
+ if (section(sectno))
+ os << '\n' << section(sectno) << ":\n";
+ }
+ Option *opt = ospair.first;
+ os << " " << setw(col1width) << left << linecols[opt].first;
+ os << setw(0) << linecols[opt].second << '\n';
+ }
+ os << '\n' << _copyright << '\n';
+}