[Debtags-commits] [svn] r1631 - in tagcoll/trunk: tagcoll tools
Enrico Zini
enrico at costa.debian.org
Sat Mar 4 01:28:48 UTC 2006
Author: enrico
Date: Sat Mar 4 01:28:46 2006
New Revision: 1631
Modified:
tagcoll/trunk/tagcoll/Commandline.cc
tagcoll/trunk/tagcoll/Commandline.h
tagcoll/trunk/tools/tagcoll.cc
Log:
Put the new commandline parser into use
Modified: tagcoll/trunk/tagcoll/Commandline.cc
==============================================================================
--- tagcoll/trunk/tagcoll/Commandline.cc (original)
+++ tagcoll/trunk/tagcoll/Commandline.cc Sat Mar 4 01:28:46 2006
@@ -19,10 +19,54 @@
return true;
}
-
namespace Tagcoll {
namespace commandline {
+int Option::intValue() const
+{
+ return strtoul(stringValue().c_str(), NULL, 10);
+}
+
+static string fmtshort(char c, const std::string& usage)
+{
+ if (usage.empty())
+ return string("-") + c;
+ else
+ return string("-") + c + " " + usage;
+}
+
+static string fmtlong(const std::string& c, const std::string& usage)
+{
+ if (usage.empty())
+ return string("--") + c;
+ else
+ return string("--") + c + "=" + usage;
+}
+
+const std::string& Option::fullUsage() const
+{
+ if (m_fullUsage.empty())
+ {
+ for (vector<char>::const_iterator i = shortNames.begin();
+ i != shortNames.end(); i++)
+ {
+ if (!m_fullUsage.empty())
+ m_fullUsage += ", ";
+ m_fullUsage += fmtshort(*i, usage);
+ }
+
+ for (vector<string>::const_iterator i = longNames.begin();
+ i != longNames.end(); i++)
+ {
+ if (!m_fullUsage.empty())
+ m_fullUsage += ", ";
+ m_fullUsage += fmtlong(*i, usage);
+ }
+ }
+ return m_fullUsage;
+}
+
+
bool StringOption::parse(const char* str)
{
if (!str) throw BadOption("no string argument found");
@@ -30,9 +74,35 @@
return true;
}
-void OptionParser::add(Option* o)
+std::string IntOption::stringValue() const
{
- const vector<char>& shorts = o->shortNames();
+ return stringf::fmt(m_value);
+}
+
+bool IntOption::parse(const char* str)
+{
+ if (!str) throw BadOption("no int argument found");
+ // Ensure that we're all numeric
+ for (const char* s = str; *s; ++s)
+ if (!isdigit(*s))
+ throw BadOption(string("value ") + str + " must be numeric");
+ m_value = strtoul(str, 0, 10);
+ return true;
+}
+
+bool ExistingFileOption::parse(const char* str)
+{
+ if (!str) throw BadOption("no file argument found");
+ if (access(str, F_OK) == -1)
+ throw BadOption(string("file ") + str + " must exist");
+ m_value = str;
+ return true;
+}
+
+
+void OptionParser::addWithoutAna(Option* o)
+{
+ const vector<char>& shorts = o->shortNames;
for (vector<char>::const_iterator i = shorts.begin(); i != shorts.end(); i++)
{
map<char, Option*>::iterator j = m_short.find(*i);
@@ -42,7 +112,7 @@
m_short[*i] = o;
}
- const vector<string>& longs = o->longNames();
+ const vector<string>& longs = o->longNames;
for (vector<string>::const_iterator i = longs.begin(); i != longs.end(); i++)
{
map<string, Option*>::iterator j = m_long.find(*i);
@@ -51,19 +121,20 @@
string("long option ") + *i + " is already mapped to " + j->second->name());
m_long[*i] = o;
}
+}
- map<string, Option*>::iterator i = m_options.find(o->name());
- if (i != m_options.end())
- throw ConsistencyCheckException("option " + o->name() + " has already been added");
- m_options[o->name()] = o;
+void OptionParser::add(Option* o)
+{
+ addWithoutAna(o);
+ m_options.push_back(o);
}
-const Option* OptionParser::option(const std::string& str) const
+void OptionParser::add(OptionGroup* group)
{
- map<string, Option*>::const_iterator i = m_options.find(str);
- if (i == m_options.end())
- return 0;
- return i->second;
+ const vector<Option*>& v = group->options;
+ for (vector<Option*>::const_iterator i = v.begin(); i != v.end(); i++)
+ addWithoutAna(*i);
+ m_groups.push_back(group);
}
iter OptionParser::parseConsecutiveSwitches(arglist& list, iter begin)
@@ -155,27 +226,29 @@
-const OptionParser* CommandParser::command(const std::string& str) const
-{
- map<string, OptionParser*>::const_iterator i = m_commands.find(str);
- if (i == m_commands.end())
- return 0;
- return i->second;
-}
-
void CommandParser::add(const std::string& alias, OptionParser* o)
{
map<string, OptionParser*>::iterator a = m_aliases.find(alias);
if (a != m_aliases.end())
throw ConsistencyCheckException("command " + alias + " has already been set to " + a->second->name());
+ m_aliases[alias] = o;
+}
- map<string, OptionParser*>::iterator i = m_commands.find(o->name());
- if (i != m_commands.end() && i->second != o)
- throw ConsistencyCheckException("trying to add two different parsers both named " + o->name());
- else
- m_commands[o->name()] = o;
+void CommandParser::add(OptionParser& o)
+{
+ add(o.primaryAlias, &o);
+ for (vector<string>::const_iterator i = o.aliases.begin();
+ i != o.aliases.end(); ++i)
+ add(*i, &o);
+}
- m_aliases[alias] = o;
+OptionParser* CommandParser::command(const std::string& name) const
+{
+ map<string, OptionParser*>::const_iterator i = m_aliases.find(name);
+ if (i == m_aliases.end())
+ return 0;
+ else
+ return i->second;
}
iter CommandParser::parse(arglist& list, iter begin)
@@ -185,21 +258,28 @@
while (cmd != list.end() && isSwitch(*cmd))
++cmd;
+ map<string, OptionParser*>::iterator a;
if (cmd == list.end())
- return begin;
+ {
+ // No command has been found. Try to see if there is a 'generic'
+ // OptionParser with the "" alias.
+ if ((a = m_aliases.find(string())) != m_aliases.end())
+ ;
+ else
+ return begin;
+ } else {
+ // Handle the command
+ a = m_aliases.find(*cmd);
+ if (a == m_aliases.end())
+ throw BadOption("unknown command " + string(*cmd));
- // Handle the command
- string command(*cmd);
- map<string, OptionParser*>::iterator a = m_aliases.find(command);
- if (a == m_aliases.end())
- throw BadOption("unknown command " + command);
-
- // Remove the command from list
- if (cmd == begin)
- ++begin;
- list.erase(cmd);
+ // Remove the command from list
+ if (cmd == begin)
+ ++begin;
+ list.erase(cmd);
+ }
- m_last_command = a->second->name();
+ m_last_command = a->second;
// Invoke the selected parser on the list
return a->second->parse(list, begin);
@@ -207,302 +287,238 @@
+class WordWrapper
+{
+ const std::string& s;
+ size_t cursor;
-#if 0
-#include <tagcoll/stringf.h>
-#include <assert.h>
-#include <ctype.h>
-#include <stdlib.h>
-#include <stdio.h>
+public:
+ WordWrapper(const std::string& s) : s(s), cursor(0) {}
-using namespace std;
-using namespace stringf;
+ void restart() { cursor = 0; }
-string CommandlineParser::WordWrapper::get(unsigned int width) throw ()
-{
- if (cursor >= s.size())
- return "";
-
- // Find the last work break before `width'
- unsigned int brk = cursor;
- for (unsigned int j = cursor; j < s.size() && j < cursor + width; j++)
+ bool hasData() const { return cursor < s.size(); }
+
+ string get(unsigned int width)
{
- if (s[j] == '\n')
+ if (cursor >= s.size())
+ return "";
+
+ // Find the last work break before `width'
+ unsigned int brk = cursor;
+ for (unsigned int j = cursor; j < s.size() && j < cursor + width; j++)
{
- brk = j;
- break;
- } else if (!isspace(s[j]) && (j + 1 == s.size() || isspace(s[j + 1])))
- brk = j + 1;
- }
- if (brk == cursor)
- brk = cursor + width;
-
- string res;
- if (brk >= s.size())
- {
- res = string(s, cursor, string::npos);
- cursor = s.size();
- } else {
- res = string(s, cursor, brk - cursor);
- cursor = brk;
- while (cursor < s.size() && isspace(s[cursor]))
- cursor++;
+ if (s[j] == '\n')
+ {
+ brk = j;
+ break;
+ } else if (!isspace(s[j]) && (j + 1 == s.size() || isspace(s[j + 1])))
+ brk = j + 1;
+ }
+ if (brk == cursor)
+ brk = cursor + width;
+
+ string res;
+ if (brk >= s.size())
+ {
+ res = string(s, cursor, string::npos);
+ cursor = s.size();
+ } else {
+ res = string(s, cursor, brk - cursor);
+ cursor = brk;
+ while (cursor < s.size() && isspace(s[cursor]))
+ cursor++;
+ }
+ return res;
}
- return res;
-}
+};
-/*
-string CommandlineParser::WordWrapper::get(unsigned int width) throw ()
+class HelpWriter
{
- if (i >= s.size())
- return "";
-
- int k = width;
- while (k > 0 && k + i < s.size() && !isspace(s[i + k]))
- k--;
- if (k == 0)
- k = width;
+ // Width of the console
+ std::ostream& out;
+ int m_width;
- string res(s, i, k);
- i += k;
- return res;
-}
-*/
+public:
+ HelpWriter(std::ostream& out);
-int CommandlineParser::option::intVal() const throw ()
-{
- return atoi(_value.c_str());
-}
+ // Write 'size' spaces to out
+ void pad(size_t size);
+
+ // Output an item from a list. The first bulletsize columns will be used to
+ // output bullet, the rest will have text, wordwrapped and properly aligned
+ void outlist(const std::string& bullet, size_t bulletsize, const std::string& text);
-const CommandlineParser::option& CommandlineParser::get(const string& name) const throw ()
+ void outstring(const std::string& str);
+};
+
+HelpWriter::HelpWriter(std::ostream& out) : out(out)
{
- map<string, int>::const_iterator i = byname.find(name);
- if (i == byname.end())
- {
- fprintf(stderr, "Program error: requested info about nonexistant commandline option \"%.*s\".\nAvailable: \n", PFSTR(name));
- for (i = byname.begin(); i != byname.end(); i++)
- fprintf(stderr, " \"%.*s\"\n", PFSTR(i->first));
- }
- assert (i != byname.end());
- const option& o = options[i->second];
- return o;
+ char* columns = getenv("COLUMNS");
+ m_width = columns ? atoi(columns) : 80;
}
-void CommandlineParser::add(const string& name, char shortopt, const string& longopt, const
- string& help, const string& valname, bool val_optional) throw ()
+void HelpWriter::pad(size_t size)
{
- options.push_back(option(name, shortopt, longopt, help, valname, val_optional));
- if (shortopt)
- shortopts.insert(pair<char, int>(shortopt, options.size() - 1));
- if (longopt.size())
- longopts.insert(pair<string, int>(longopt, options.size() - 1));
- byname.insert(pair<string, int>(name, options.size() - 1));
+ for (size_t i = 0; i < size; i++) out << " ";
}
-void CommandlineParser::printHelp() throw ()
+void HelpWriter::outlist(const std::string& bullet, size_t bulletsize, const std::string& text)
{
- char* columns = getenv("COLUMNS");
- int width = columns ? atoi(columns) : 80;
- unsigned int summax = 0;
- vector<string> summaries;
-
- // Print the first usage line
- fprintf(stderr, "Usage: %.*s %.*s\n",
- PFSTR(argv0), PFSTR(cmdline_summary));
-
- // Prepare switch summaries and compute their maximum width
- for (unsigned int i = 0; i < options.size(); i++)
- {
- string s;
- if (options[i]._shortopt)
- s += string("-") + options[i]._shortopt;
- if (options[i]._longopt.size())
- {
- if (s.size())
- s += ", ";
- s += "--" + options[i]._longopt;
- }
- if (options[i]._valname.size())
- {
- string vname = options[i]._val_optional ?
- "[" + options[i]._valname + "]"
- : options[i]._valname;
- if (options[i]._longopt.size())
- s += "=" + vname;
- else
- s += " " + vname;
- }
- if (s.size() > summax)
- summax = s.size();
- summaries.push_back(s);
- }
-
- // Print the help
- for (unsigned int i = 0; i < options.size(); i++)
- {
- WordWrapper ww(options[i]._help);
+ WordWrapper wrapper(text);
+ size_t rightcol = m_width - bulletsize;
- string h = ww.get(width - summax - 4);
- fprintf(stderr, " %-*.*s %.*s\n",
- summax, summax, summaries[i].c_str(), PFSTR(h));
+ out << bullet;
+ pad(bulletsize - bullet.size());
+ out << wrapper.get(rightcol);
+ out << endl;
- while (ww.hasData())
- {
- string h = ww.get(width - summax - 4);
- fprintf(stderr, " %-*.*s %.*s\n",
- summax, summax, "", PFSTR(h));
- }
+ while (wrapper.hasData())
+ {
+ pad(bulletsize);
+ out << wrapper.get(rightcol);
+ out << endl;
}
+}
- fputc('\n', stderr);
+void HelpWriter::outstring(const std::string& str)
+{
+ WordWrapper wrapper(str);
- fprintf(stderr, "%.*s\n", PFSTR(description));
- /*
- WordWrapper ww(description);
- while (ww.hasData())
+ while (wrapper.hasData())
{
- string h = ww.get(width);
- fprintf(stderr, "%.*s\n", PFSTR(h));
+ out << wrapper.get(m_width);
+ out << endl;
}
- */
}
-bool CommandlineParser::parseLongOption(const string& name, const string& value)
- throw ()
+void Help::outputVersion(std::ostream& out)
{
- // Have argument
- map<string, int>::iterator o = longopts.find(name);
- if (o == longopts.end())
- {
- fprintf(stderr, "Unknown option name: %.*s\n", PFSTR(name));
- return false;
- }
- options[o->second]._defined = true;
+ out << m_app << " version " << m_ver << endl;
+}
+
+void Help::outputHelp(std::ostream& out, const CommandParser& cp)
+{
+ std::map<std::string, OptionParser*> m_info;
- if (options[o->second]._valname.size())
- {
- if (value.size())
- options[o->second]._value = value;
- else if (!options[o->second]._val_optional)
- {
- fprintf(stderr, "Option %.*s requires the %.*s argument\n",
- PFSTR(name), PFSTR(options[o->second]._valname));
- return false;
+ // Dig informations from cp
+ for(map<string, OptionParser*>::const_iterator i = cp.m_aliases.begin();
+ i != cp.m_aliases.end(); i++)
+ {
+ if (i->first == string())
+ continue;
+ map<string, OptionParser*>::iterator j = m_info.find(i->second->name());
+ if (j == m_info.end())
+ m_info[i->second->name()] = i->second;
+ }
+
+ HelpWriter writer(out);
+
+ // Compute the maximum length of alias names
+ size_t maxAliasSize = 0;
+ for (map<string, OptionParser*>::const_iterator i = m_info.begin();
+ i != m_info.end(); i++)
+ {
+ const string& str = i->second->primaryAlias;
+ if (maxAliasSize < str.size())
+ maxAliasSize = str.size();
+ }
+
+ out << "Usage: " << m_app << " [options] " << cp.usage << endl;
+ out << endl;
+ writer.outstring("Description: " + cp.description);
+ out << endl;
+ out << "Commands are:" << endl;
+ out << endl;
+
+ // Print the list
+ for (map<string, OptionParser*>::const_iterator i = m_info.begin();
+ i != m_info.end(); i++)
+ {
+ string aliases;
+ const vector<string>& v = i->second->aliases;
+ if (!v.empty())
+ {
+ aliases += " May also be invoked as ";
+ for (vector<string>::const_iterator j = v.begin();
+ j != v.end(); j++)
+ if (j == v.begin())
+ aliases += *j;
+ else
+ aliases += " or " + *j;
+ aliases += ".";
}
+
+ writer.outlist(" " + i->second->primaryAlias, maxAliasSize + 3, i->second->description + "." + aliases);
}
- return true;
+
+ out << endl;
}
-bool CommandlineParser::parseShortOption(char opt, const string& value)
- throw ()
+void Help::outputHelp(std::ostream& out, const OptionParser& o)
{
- // Have argument
- map<char, int>::iterator o = shortopts.find(opt);
- if (o == shortopts.end())
+ HelpWriter writer(out);
+
+ out << "Usage: " << m_app << " [options] " << o.primaryAlias << " [options] " << o.usage << endl;
+ out << endl;
+ if (!o.aliases.empty())
{
- fprintf(stderr, "Unknown option: `%c'\n", opt);
- return false;
+ out << "Command aliases: ";
+ for (vector<string>::const_iterator i = o.aliases.begin();
+ i != o.aliases.end(); i++)
+ if (i == o.aliases.begin())
+ out << *i;
+ else
+ out << ", " << *i;
+ out << "." << endl;
+ out << endl;
}
- options[o->second]._defined = true;
+ writer.outstring("Description: " + o.description);
- if (options[o->second]._valname.size())
- {
- if (value.size())
- options[o->second]._value = value;
- else if (!options[o->second]._val_optional)
+ // Compute size of option display
+ size_t maxLeftCol = 0;
+ for (vector<OptionGroup*>::const_iterator i = o.m_groups.begin();
+ i != o.m_groups.end(); i++)
+ for (vector<Option*>::const_iterator j = (*i)->options.begin();
+ j != (*i)->options.end(); j++)
{
- fprintf(stderr, "Option `%c' requires the %.*s argument\n",
- opt, PFSTR(options[o->second]._valname));
- return false;
+ size_t w = (*j)->fullUsage().size();
+ if (w > maxLeftCol)
+ maxLeftCol = w;
}
+ for (vector<Option*>::const_iterator j = o.m_options.begin();
+ j != o.m_options.end(); j++)
+ {
+ size_t w = (*j)->fullUsage().size();
+ if (w > maxLeftCol)
+ maxLeftCol = w;
}
- return true;
-}
-bool CommandlineParser::parse(int& argc, const char**& argv) throw ()
-{
- int nargc = 1;
- bool success = true;
- bool parses_opts = true;
-
- for (int i = 1; i < argc; i++)
+ if (maxLeftCol)
{
- if (parses_opts && argv[i][0] == '-' && argv[i][1] != 0)
+ // Output the options
+ out << endl;
+ out << "Options are:" << endl;
+ for (vector<OptionGroup*>::const_iterator i = o.m_groups.begin();
+ i != o.m_groups.end(); i++)
{
- if (argv[i][1] == '-')
- {
- if (argv[i][2] == 0)
- // -- option terminator
- parses_opts = false;
- else
- {
- // Long option
- string optName = string(argv[i], 2, string::npos);
- unsigned int eqsign = optName.find('=');
- if (eqsign == string::npos)
- {
- // No argument
- if (!parseLongOption(optName))
- success = false;
- } else {
- // With argument
- if (!parseLongOption(optName.substr(0, eqsign),
- optName.substr(eqsign + 1)))
- success = false;
- }
- }
- }
- else
- {
- // Short option(s)
- if (argv[i][2] == 0)
- {
- // Unpacked short option, might have an argument
- char opt = argv[i][1];
- map<char, int>::iterator o = shortopts.find(opt);
- if (o == shortopts.end())
- {
- fprintf(stderr, "Unknown option: `%c'\n", opt);
- return false;
- }
- if (options[o->second]._valname.size())
- {
- // Accepts arguments
- if (argv[i + 1] && argv[i + 1][0] != '-')
- {
- // Is followed by an argument
- i++;
- if (!parseShortOption(opt, argv[i]))
- success = false;
- } else
- // Is not followed by an argument
- if (!parseShortOption(opt))
- success = false;
- } else
- // Does not accept arguments
- if (!parseShortOption(opt))
- success = false;
- } else {
- // Packed short options
- for (const char* s = argv[i] + 1; *s; s++)
- if (!parseShortOption(*s))
- success = false;
- }
- }
+ if (!(*i)->description.empty())
+ writer.outstring((*i)->description + ":");
+ for (vector<Option*>::const_iterator j = (*i)->options.begin();
+ j != (*i)->options.end(); j++)
+ writer.outlist(" " + (*j)->fullUsage(), maxLeftCol + 3, (*j)->description);
}
- else
+ if (!o.m_options.empty())
{
- // Not a switch: keep it in the new argv
- if (i != nargc)
- argv[nargc] = argv[i];
- nargc++;
- }
- }
- argc = nargc;
- argv[nargc] = 0;
- return success;
+ out << endl;
+ writer.outstring("Other options:");
+ for (vector<Option*>::const_iterator j = o.m_options.begin();
+ j != o.m_options.end(); j++)
+ writer.outlist(" " + (*j)->fullUsage(), maxLeftCol + 3, (*j)->description);
+ }
+ }
+ out << endl;
}
-#endif
}
}
@@ -679,6 +695,7 @@
yell.addAlias("yell");
add(&random);
add(&yell);
+ aliases.push_back("mess");
}
BoolOption random;
@@ -706,9 +723,8 @@
TestCParser() :
CommandParser("test")
{
- add("scramble", &scramble);
- add("mess", &scramble);
- add("fix", &fix);
+ add(scramble);
+ add(fix);
}
Scramble scramble;
@@ -729,7 +745,7 @@
iter i = parser.parseList(opts);
gen_ensure(i == opts.end());
gen_ensure_equals(opts.size(), 0u);
- gen_ensure_equals(parser.lastCommand(), "scramble");
+ gen_ensure_equals(parser.lastCommand(), &parser.scramble);
gen_ensure_equals(parser.scramble.yell.stringValue(), "foo");
gen_ensure_equals(parser.scramble.random.boolValue(), true);
gen_ensure_equals(parser.fix.yell.stringValue(), string());
@@ -745,7 +761,7 @@
iter i = parser.parseList(opts);
gen_ensure(i == opts.end());
gen_ensure_equals(opts.size(), 0u);
- gen_ensure_equals(parser.lastCommand(), "fix");
+ gen_ensure_equals(parser.lastCommand(), &parser.fix);
gen_ensure_equals(parser.scramble.yell.stringValue(), string());
gen_ensure_equals(parser.scramble.random.boolValue(), false);
gen_ensure_equals(parser.fix.yell.stringValue(), "foo");
Modified: tagcoll/trunk/tagcoll/Commandline.h
==============================================================================
--- tagcoll/trunk/tagcoll/Commandline.h (original)
+++ tagcoll/trunk/tagcoll/Commandline.h Sat Mar 4 01:28:46 2006
@@ -6,6 +6,7 @@
#include <vector>
#include <list>
#include <map>
+#include <ostream>
namespace Tagcoll {
namespace commandline {
@@ -59,23 +60,27 @@
class Option
{
std::string m_name;
- std::vector<char> m_shortNames;
- std::vector<std::string> m_longNames;
+ mutable std::string m_fullUsage;
public:
Option(const std::string& name) : m_name(name) {}
+ Option(const std::string& name, char shortName, const std::string& longName) : m_name(name)
+ {
+ if (shortName != 0)
+ shortNames.push_back(shortName);
+ if (!longName.empty())
+ longNames.push_back(longName);
+ }
virtual ~Option() {}
const std::string& name() const { return m_name; }
- const std::vector<char>& shortNames() const { return m_shortNames; }
- const std::vector<std::string>& longNames() const { return m_longNames; }
-
- void addAlias(char c) { m_shortNames.push_back(c); }
- void addAlias(const std::string& str) { m_longNames.push_back(str); }
+ void addAlias(char c) { shortNames.push_back(c); }
+ void addAlias(const std::string& str) { longNames.push_back(str); }
virtual bool boolValue() const = 0;
virtual std::string stringValue() const = 0;
+ virtual int intValue() const;
/**
* Signal that the option has been found, with the given argument (or 0 if
@@ -85,6 +90,15 @@
* true if it used the argument, else false
*/
virtual bool parse(const char* str = 0) = 0;
+
+ /// Return a full usage message including all the aliases for this option
+ const std::string& fullUsage() const;
+
+ std::vector<char> shortNames;
+ std::vector<std::string> longNames;
+
+ std::string usage;
+ std::string description;
};
/// Boolean option
@@ -94,6 +108,8 @@
public:
BoolOption(const std::string& name)
: Option(name), m_value(false) {}
+ BoolOption(const std::string& name, char shortName, const std::string& longName)
+ : Option(name, shortName, longName), m_value(false) {}
bool boolValue() const { return m_value; }
std::string stringValue() const { return m_value ? "true" : "false"; }
@@ -107,7 +123,61 @@
std::string m_value;
public:
StringOption(const std::string& name)
- : Option(name) {}
+ : Option(name)
+ {
+ usage = "<val>";
+ }
+ StringOption(const std::string& name, char shortName, const std::string& longName)
+ : Option(name, shortName, longName)
+ {
+ usage = "<val>";
+ }
+
+ bool boolValue() const { return !m_value.empty(); }
+ std::string stringValue() const { return m_value; }
+
+ bool parse(const char* str);
+};
+
+// Option needing a compulsory int value
+class IntOption : public Option
+{
+ bool m_has_value;
+ int m_value;
+
+public:
+ IntOption(const std::string& name)
+ : Option(name), m_has_value(false), m_value(0)
+ {
+ usage = "<num>";
+ }
+ IntOption(const std::string& name, char shortName, const std::string& longName)
+ : Option(name, shortName, longName), m_has_value(false), m_value(0)
+ {
+ usage = "<num>";
+ }
+
+ bool boolValue() const { return m_has_value; }
+ int intValue() const { return m_value; }
+ std::string stringValue() const;
+
+ bool parse(const char* str);
+};
+
+class ExistingFileOption : public Option
+{
+ std::string m_value;
+public:
+ ExistingFileOption(const std::string& name)
+ : Option(name)
+ {
+ usage = "<file>";
+ }
+ ExistingFileOption(const std::string& name, char shortName, const std::string& longName)
+ : Option(name, shortName, longName)
+ {
+ usage = "<file>";
+ }
bool boolValue() const { return !m_value.empty(); }
std::string stringValue() const { return m_value; }
@@ -115,44 +185,70 @@
bool parse(const char* str);
};
+class OptionGroup
+{
+
+public:
+ void add(Option* o) { options.push_back(o); }
+
+ std::vector<Option*> options;
+
+ std::string description;
+};
+
/// Parser of many short or long switches all starting with '-'
class OptionParser : public Parser
{
std::map<char, Option*> m_short;
std::map<std::string, Option*> m_long;
- std::map<std::string, Option*> m_options;
/// Parse a consecutive sequence of switches
iter parseConsecutiveSwitches(arglist& list, iter begin);
+ void addWithoutAna(Option* o);
+
+protected:
+ std::vector<OptionGroup*> m_groups;
+ std::vector<Option*> m_options;
+
public:
OptionParser(const std::string& name)
- : Parser(name) {}
+ : Parser(name), primaryAlias(name) {}
void add(Option* o);
- const Option* option(const std::string& str) const;
+ void add(OptionGroup* group);
/**
* Parse all the switches in list, leaving only the non-switch arguments or
* the arguments following "--"
*/
virtual iter parse(arglist& list, iter begin);
+
+ std::string primaryAlias;
+ std::vector<std::string> aliases;
+ std::string usage;
+ std::string description;
+
+ friend class Help;
};
class CommandParser : public Parser
{
- std::string m_last_command;
+ OptionParser* m_last_command;
+
+ void add(const std::string& alias, OptionParser* o);
+
+protected:
std::map<std::string, OptionParser*> m_aliases;
- std::map<std::string, OptionParser*> m_commands;
public:
CommandParser(const std::string& name)
- : Parser(name) {}
+ : Parser(name), m_last_command(0) {}
- const std::string& lastCommand() const { return m_last_command; }
- const OptionParser* command(const std::string& str) const;
+ OptionParser* lastCommand() const { return m_last_command; }
+ OptionParser* command(const std::string& name) const;
- void add(const std::string& alias, OptionParser* o);
+ void add(OptionParser& o);
/**
* Look for a command as the first non-switch parameter found, then invoke
@@ -163,85 +259,26 @@
* If no commands have been found, returns begin.
*/
virtual iter parse(arglist& list, iter begin);
-};
-#if 0
-class Option
-{
-protected:
- std::string _name;
- std::string _value;
- char _shortopt;
- std::string _longopt;
- std::string _help;
- std::string _valname;
- bool _val_optional;
- bool _defined;
-
-public:
- Option(const std::string& name, char shortopt, const std::string& longopt, const
- std::string& help, const std::string& valname, bool val_optional)
- throw() : _name(name), _shortopt(shortopt), _longopt(longopt),
- _help(help), _valname(valname),
- _val_optional(val_optional), _defined(false) {}
-
- bool defined() const throw () { return _defined; }
- bool hasValue() const throw () { return _value.size() > 0; }
- std::string stringVal() const throw () { return _value; }
- int intVal() const throw ();
+ std::string usage;
+ std::string description;
- friend class Parser;
+ friend class Help;
};
-class Parser
+class Help
{
-protected:
- class WordWrapper
- {
- protected:
- const std::string& s;
- unsigned int cursor;
-
- public:
- WordWrapper(const std::string& s) throw () : s(s), cursor(0) {}
-
- void restart() throw () { cursor = 0; }
-
- bool hasData() const throw () { return cursor < s.size(); }
-
- std::string get(unsigned int width) throw ();
- };
-
- std::vector<option> options;
- std::map<char, int> shortopts;
- std::map<std::string, int> longopts;
- std::map<std::string, int> byname;
- std::string argv0;
- std::string cmdline_summary;
- std::string description;
-
- bool parseLongOption(const std::string& name, const std::string& value = "") throw ();
- bool parseShortOption(char opt, const std::string& value = "") throw ();
-
-public:
- CommandlineParser(const std::string& argv0, const std::string& cmdline_summary,
- const std::string& description) throw ()
- : argv0(argv0), cmdline_summary(cmdline_summary), description(description)
- {
- add("help", 0, "help", "print this help message");
- }
+ std::string m_app;
+ std::string m_ver;
- void add(const std::string& name, char shortopt, const std::string& longopt, const
- std::string& help, const std::string& valname = "",
- bool val_optional = false) throw ();
+public:
+ Help(const std::string& app, const std::string& ver)
+ : m_app(app), m_ver(ver) {}
- void printHelp() throw ();
-
- bool parse(int& argc, const char**& argv) throw ();
-
- const option& get(const std::string& name) const throw ();
+ void outputVersion(std::ostream& out);
+ void outputHelp(std::ostream& out, const CommandParser& cp);
+ void outputHelp(std::ostream& out, const OptionParser& cp);
};
-#endif
}
}
Modified: tagcoll/trunk/tools/tagcoll.cc
==============================================================================
--- tagcoll/trunk/tools/tagcoll.cc (original)
+++ tagcoll/trunk/tools/tagcoll.cc Sat Mar 4 01:28:46 2006
@@ -27,8 +27,6 @@
#define VERSION "unknown"
#endif
-#include "CommandlineParser.h"
-
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
@@ -42,6 +40,7 @@
#include <tagcoll/stringf.h>
#include <tagcoll/Exception.h>
+#include <tagcoll/Commandline.h>
#include <tagcoll/CardinalityStore.h>
#include <tagcoll/SmartHierarchy.h>
@@ -61,10 +60,299 @@
#include <tagcoll/Expression.h>
#include <algorithm>
+#include <iostream>
using namespace std;
using namespace Tagcoll;
+namespace Tagcoll {
+namespace commandline {
+
+struct CommandlineParser : public CommandParser
+{
+ struct HelpGroup : public OptionGroup
+ {
+ BoolOption* help;
+ BoolOption* version;
+
+ HelpGroup()
+ {
+ add(help = new BoolOption("help", 'h', "help"));
+ add(version = new BoolOption("version", 'V', "version"));
+ help->shortNames.push_back('?');
+ help->description = "show commandline help";
+ version->description = "show program version";
+ }
+ ~HelpGroup()
+ {
+ delete help;
+ }
+ } helpGroup;
+
+ struct InputGroup : public OptionGroup
+ {
+ ExistingFileOption* derived;
+ ExistingFileOption* extimpl;
+ ExistingFileOption* rename;
+ ExistingFileOption* patch;
+ StringOption* rmunfaceted;
+ StringOption* rmtags;
+
+ InputGroup()
+ {
+ add(derived = new ExistingFileOption("derived-tags-from", 'e', "derived"));
+ add(extimpl = new ExistingFileOption("extimpl", 'i', "implications-from"));
+ add(rename = new ExistingFileOption("rename", 'r', "rename-from"));
+ add(patch = new ExistingFileOption("patch", 'p', "patch-with"));
+ add(rmunfaceted = new StringOption("rmunfaceted", 0, "remove-unfaceted"));
+ add(rmtags = new StringOption("rmtags", 0, "remove-tags"));
+
+ derived->description = "expand derived tags using the given list";
+ extimpl->description = "use an external list of implications";
+ rename->description = "rename tags using the given mapping list";
+ patch->description = "apply patches from the given tag patch file";
+ patch->longNames.push_back("patch");
+ rmunfaceted->description = "while parsing, remove all tags with no facet part";
+ rmtags->usage = "<expression>";
+ rmtags->description = "while parsing, remove all tags matching the given tag expression";
+ }
+ ~InputGroup()
+ {
+ delete derived; delete extimpl; delete rename; delete patch;
+ delete rmunfaceted; delete rmtags;
+ }
+ } inputGroup;
+
+ struct OutputGroup : public OptionGroup
+ {
+ BoolOption* group;
+ BoolOption* redundant;
+
+ OutputGroup()
+ {
+ add(group = new BoolOption("group", 'g', "group"));
+ add(redundant = new BoolOption("redundant", 0, "redundant"));
+ group->description = "group items with the same tagset in the output collection";
+ group->longNames.push_back("group-items");
+ redundant->description = "when implications are provided, expand them explicitly in the output";
+ }
+ ~OutputGroup()
+ {
+ delete group;
+ delete redundant;
+ }
+ } outputGroup;
+
+ struct HierarchyGroup : public OptionGroup
+ {
+ IntOption* flatten;
+ IntOption* filter;
+
+ HierarchyGroup()
+ {
+ add(flatten = new IntOption("flatten", 0, "flatten-threshold"));
+ add(filter = new IntOption("filter", 'f', "filter"));
+
+ flatten->description = "set the number of total items below which a branch is flattened when using the \"hierarchy\" command (defaults to 0, meaning \"don't flatten\")";
+ filter->description = "filter out the tags with cardinality less than the given value (defaults to not filter; currently only works when building hierarchies)";
+ }
+
+ ~HierarchyGroup()
+ {
+ delete flatten;
+ delete filter;
+ }
+ } hierarchyGroup;
+
+ struct Generic : public OptionParser
+ {
+ Generic(CommandlineParser* cp) : OptionParser("")
+ {
+ add(&cp->helpGroup);
+ }
+ } generic;
+ struct Help : public OptionParser
+ {
+ Help(CommandlineParser* cp) : OptionParser("help")
+ {
+ usage = "[command]";
+ description = "print help informations";
+ }
+ } help;
+ struct Copy : public OptionParser
+ {
+ Copy(CommandlineParser* cp) : OptionParser("copy")
+ {
+ usage = "[files...]";
+ description = "output the collection";
+ add(&cp->inputGroup);
+ add(&cp->outputGroup);
+ add(&cp->helpGroup);
+ aliases.push_back("cat");
+ }
+ } copy;
+ struct Reverse : public OptionParser
+ {
+ StringOption* untaggedTag;
+
+ Reverse(CommandlineParser* cp) : OptionParser("reverse")
+ {
+ add(untaggedTag = new StringOption("untagged-tag", 0, "untagged-tag"));
+ untaggedTag->usage = "<tag>";
+ untaggedTag->description = "set item name to use for associating untagged items when using the \"reverse\" command. If not specified, untagged items are not included in the output";
+
+ usage = "[files...]";
+ description = "\"reverse\" the collection, outputting one with items associated to tags";
+ add(&cp->helpGroup);
+ }
+ ~Reverse()
+ {
+ delete untaggedTag;
+ }
+ } reverse;
+ struct Diff : public OptionParser
+ {
+ Diff(CommandlineParser* cp) : OptionParser("diff")
+ {
+ usage = "<file1> <file2>";
+ description = "output a tag patch file with the differences between two files";
+ add(&cp->helpGroup);
+ }
+ } diff;
+ struct Related : public OptionParser
+ {
+ IntOption* distance;
+
+ Related(CommandlineParser* cp) : OptionParser("related")
+ {
+ add(distance = new IntOption("distance", 'd', "distance"));
+ distance->description = "set the maximum distance to use for the \"related\" command (defaults to 0)";
+
+ usage = "<item> [files...]";
+ description = "print a list of items related to the given one";
+ add(&cp->helpGroup);
+ }
+ ~Related()
+ {
+ delete distance;
+ }
+ } related;
+ struct Implications : public OptionParser
+ {
+ Implications(CommandlineParser* cp) : OptionParser("implications")
+ {
+ usage = "[files...]";
+ description = "compute a list of tag implications";
+ add(&cp->helpGroup);
+ }
+ } implications;
+ struct Hierarchy : public OptionParser
+ {
+ Hierarchy(CommandlineParser* cp) : OptionParser("hierarchy")
+ {
+ usage = "[files...]";
+ description = "build a smart hierarchy with the collection data";
+ add(&cp->hierarchyGroup);
+ add(&cp->helpGroup);
+ }
+ } hierarchy;
+ struct CleanHierarchy : public OptionParser
+ {
+ CleanHierarchy(CommandlineParser* cp) : OptionParser("cleanhierarchy")
+ {
+ usage = "[files...]";
+ description = "build a cleaned smart hierarchy with the collection data";
+ add(&cp->hierarchyGroup);
+ add(&cp->helpGroup);
+ }
+ } cleanhierarchy;
+ struct FindSpecials : public OptionParser
+ {
+ FindSpecials(CommandlineParser* cp) : OptionParser("findspecials")
+ {
+ usage = "[files...]";
+ description =
+ "generate a smart hierarchy and print, for each toplevel tag, "
+ "what are the items that make it toplevel instead of going below "
+ "another tag";
+ add(&cp->hierarchyGroup);
+ add(&cp->helpGroup);
+ }
+ } findspecials;
+ struct Grep : public OptionParser
+ {
+ Grep(CommandlineParser* cp) : OptionParser("grep")
+ {
+ usage = "<expression> [files...]";
+ description = "output the collection of tags that match the given tag expression";
+ add(&cp->helpGroup);
+ }
+ } grep;
+ struct Items : public OptionParser
+ {
+ Items(CommandlineParser* cp) : OptionParser("items")
+ {
+ usage = "[files...]";
+ description = "output only the items of the input collection";
+ add(&cp->helpGroup);
+ }
+ } items;
+
+ arglist args;
+
+ CommandlineParser(int argc, const char* argv[]) : CommandParser("main"),
+ generic(this),
+ help(this), copy(this), reverse(this), diff(this), related(this), implications(this),
+ hierarchy(this), cleanhierarchy(this), findspecials(this), grep(this), items(this)
+ {
+ add(generic);
+ add(help);
+ add(copy);
+ add(reverse);
+ add(diff);
+ add(related);
+ add(implications);
+ add(hierarchy);
+ add(cleanhierarchy);
+ add(findspecials);
+ add(grep);
+ add(items);
+
+ usage = "<command> [options and arguments]";
+ description = "Perform various operations on a tagged collection";
+
+ for (int i = 1; i < argc; i++)
+ args.push_back(argv[i]);
+ }
+
+ arglist parse()
+ {
+ parseList(args);
+ if (!lastCommand())
+ throw commandline::BadOption("could not understand the command to execute");
+ return args;
+ }
+
+ bool hasNext() const { return !args.empty(); }
+ string next()
+ {
+ if (args.empty())
+ return string();
+ string res(*args.begin());
+ args.erase(args.begin());
+ return res;
+ }
+
+#if 0
+ //opts.add("verbose", 'v', "verbose", "enable verbose output");
+ //opts.add("debug", 0, "debug", "enable debugging output (including verbose output)");
+#endif
+};
+
+}
+}
+
+
bool isdir(const std::string& file)
{
struct stat st;
@@ -278,92 +566,6 @@
ItemsOnly(Consumer<ITEM, TAG>& cons) : Filter<ITEM, TAG>(cons) {}
};
-class CommandlineParserWithCommand : public CommandlineParser
-{
-protected:
- map<string, int> command_map;
-
-public:
- CommandlineParserWithCommand(const std::string& argv0,
- const std::string& cmdline_summary,
- const std::string& description) throw ()
- : CommandlineParser(argv0, cmdline_summary, description)
- {
- add("version", 0, "version", "print the program version, then exit");
- }
-
- void addCommand(const std::string name, int id) throw ()
- {
- command_map[name] = id;
- }
-
- int parse(int& argc, const char**& argv) throw ()
- {
- if (!CommandlineParser::parse(argc, argv))
- {
- printHelp();
- return false;
- }
- if (get("help").defined())
- {
- printHelp();
- exit(0);
- }
- if (get("version").defined())
- {
- printf("%s ver." VERSION "\n", APPNAME);
- exit(0);
- }
- if (argc == 1)
- {
- printHelp();
- exit(1);
- }
-
- string command_string = argv[1];
- map<string, int>::const_iterator cmap_i = command_map.find(command_string);
- if (cmap_i == command_map.end())
- {
- fprintf(stderr, "Invalid command: \"%.*s\"\n", PFSTR(command_string));
- printHelp();
- exit(1);
- }
-
- for (int i = 1; i < argc; i++)
- argv[i] = argv[i + 1];
- --argc;
-
- return cmap_i->second;
- }
-};
-
-class CommandlineArgs
-{
-protected:
- int argc;
- const char** argv;
- int _next;
-
-public:
- CommandlineArgs(int argc, const char* argv[]) throw () : argc(argc), argv(argv), _next(1) {}
-
- // Return true if there is another argument left in the list
- bool hasNext() const throw () { return argc >= _next + 1; }
-
- // Return the next argument in the list
- string next() throw ()
- {
- if (hasNext())
- {
- return argv[_next++];
- } else {
- return "-";
- }
- }
-};
-
-enum valid_command { COPY, DIFF, RELATED, IMPLICATIONS, HIERARCHY, CLEANHIERARCHY, REVERSE, FINDSPECIALS, GREP, ITEMS };
-
class Reader
{
// Prepare the input filter chain
@@ -381,35 +583,35 @@
FilterTagsByExpression<string, string> filterByExpression;
public:
- Reader(CommandlineParserWithCommand& opts, valid_command cmd)
+ Reader(commandline::CommandlineParser& opts)
: addImplied(implications), removeImplied(implications),
addDerived(derivedTags), removeDerived(derivedTags),
unfacetedRemover("::"), filterByExpression("")
{
- if (opts.get("rename").defined())
+ if (opts.inputGroup.rename->boolValue())
{
- readCollection(opts.get("rename").stringVal(), substitutions.substitutions());
+ readCollection(opts.inputGroup.rename->stringValue(), substitutions.substitutions());
filters.appendFilter(substitutions);
}
- if (opts.get("patch").defined())
+ if (opts.inputGroup.patch->boolValue())
{
- patches = readPatches(opts.get("patch").stringVal());
+ patches = readPatches(opts.inputGroup.patch->stringValue());
filters.appendFilter(patches);
}
- if (opts.get("extimpl").defined())
+ if (opts.inputGroup.extimpl->boolValue())
{
- readCollection(opts.get("extimpl").stringVal(), implications);
+ readCollection(opts.inputGroup.extimpl->stringValue(), implications);
// Pack the structure for faster expansions
implications.pack();
}
- if (opts.get("derived").defined())
- readDerivedTags(opts.get("derived").stringVal(), derivedTags);
+ if (opts.inputGroup.derived->boolValue())
+ readDerivedTags(opts.inputGroup.derived->stringValue(), derivedTags);
// Intermix implications and derived tags as seems best
- bool compressOutput = (cmd == COPY && !opts.get("expanded").defined());
- bool hasImpl = opts.get("extimpl").defined();
- bool hasDerv = opts.get("derived").defined();
+ bool compressOutput = (opts.lastCommand()->name() == "copy" && !opts.outputGroup.redundant->boolValue());
+ bool hasImpl = opts.inputGroup.extimpl->boolValue();
+ bool hasDerv = opts.inputGroup.derived->boolValue();
if (compressOutput)
{
@@ -438,12 +640,12 @@
}
}
- if (opts.get("remove-unfaceted").defined())
+ if (opts.inputGroup.rmunfaceted->boolValue())
filters.appendFilter(unfacetedRemover);
- if (opts.get("remove-tags").defined())
+ if (opts.inputGroup.rmtags->boolValue())
{
- filterByExpression.setExpression(not Expression(opts.get("remove-tags").stringVal()));
+ filterByExpression.setExpression(not Expression(opts.inputGroup.rmtags->stringValue()));
filters.appendFilter(filterByExpression);
}
}
@@ -452,345 +654,360 @@
filters.setConsumer(cons);
readCollection(file, filters);
}
- void output(CommandlineArgs args, Consumer<string, string>& cons)
+ void output(commandline::CommandlineParser& opts, Consumer<string, string>& cons)
{
- if (args.hasNext())
- while (args.hasNext())
- output(args.next(), cons);
+ if (opts.hasNext())
+ while (opts.hasNext())
+ output(opts.next(), cons);
else
output("-", cons);
}
};
-int main(int argc, const char* argv[])
+class Writer : public Consumer<string, string>
{
- try {
- CommandlineParserWithCommand opts(APPNAME, "[options] [command] [file1|-] [file2|-]",
- "Perform various operations on a tagged collection\n\n"
- "Commands are:\n"
- " copy or cat output the collection\n"
- " reverse \"reverse\" the collection, outputting one with items\n"
- " associated to tags\n"
- " diff output a tag patch file with the differences between two files\n"
- " related <item> print a list of items related to the given one\n"
- " implications compute a list of tag implications\n"
- " hierarchy build a smart hierarchy with the collection data\n"
- " cleanhierarchy build a cleaned smart hierarchy with the collection data\n"
- " findspecials generate a smart hierarchy and print, for each toplevel tag,\n"
- " what are the items that make it toplevel instead of going below\n"
- " another tag.\n"
- " grep <expr> output the collection of tags that match the given tag expression\n"
- " items output only the items of the input collection\n");
-
- /*
- opts.add("hierarchy", 's', "smart-hierarchy", "build a smart hierarchy");
- opts.add("implications", 'm', "show-implications", "output a list of tag implications");
- opts.add("copy", 'c', "copy", "output the collection");
- opts.add("diff", 'd', "diff", "output a tag patch file with the differences between two files (requires two file arguments)");
- */
-
- opts.add("derived", 'e', "derived-tags-from", "use an external list of derived tags", "file");
- opts.add("extimpl", 'i', "implications-from", "use an external list of implications", "file");
- opts.add("rename", 'r', "rename-from", "rename tags using the given mapping list", "file");
- opts.add("patch", 'p', "patch-with", "apply patches from the given tag patch file", "file");
-
- opts.add("expanded", 'x', "expanded-output", "produce full (and redundant) output data instead of compact");
- opts.add("groupitems", 'g', "group-items", "group items with the same tagset in the output collection");
- opts.add("distance", 'd', "distance", "set the maximum distance to use for the \"related\" command (defaults to 0)", "num");
- opts.add("flatten", 0, "flatten-threshold", "set the number of total items below which a branch is flattened when using the \"hierarchy\" command (defaults to 0, meaning \"don't flatten\")", "num");
- opts.add("filter", 'f', "filter", "filter out the tags with cardinality less than the given value (defaults to not filter; currently only works when building hierarchies)", "num");
- opts.add("untagged-tag", 0, "untagged-tag", "set item name to use for associating untagged items when using the \"reverse\" command. If not specified, untagged items are not included in the output.", "tag");
- opts.add("remove-unfaceted", 0, "remove-unfaceted", "while parsing, remove all tags with no facet part.");
- opts.add("remove-tags", 0, "remove-tags", "while parsing, remove all tags matching the given tag expression.", "expression");
-
- //opts.add("verbose", 'v', "verbose", "enable verbose output");
- //opts.add("debug", 0, "debug", "enable debugging output (including verbose output)");
+ TrivialConverter<string, string> conv;
+ TextFormat<string, string> output;
+ ItemGrouper<string, string>* grouper;
- opts.addCommand("copy", (int)COPY);
- opts.addCommand("cat", (int)COPY);
- opts.addCommand("reverse", (int)REVERSE);
- opts.addCommand("diff", (int)DIFF);
- opts.addCommand("related", (int)RELATED);
- opts.addCommand("implications", (int)IMPLICATIONS);
- opts.addCommand("hierarchy", (int)HIERARCHY);
- opts.addCommand("cleanhierarchy", (int)CLEANHIERARCHY);
- opts.addCommand("findspecials", (int)FINDSPECIALS);
- opts.addCommand("grep", (int)GREP);
- opts.addCommand("items", (int)ITEMS);
+protected:
+ /// Process an untagged item
+ virtual void consumeItemUntagged(const string& item)
+ {
+ if (grouper)
+ grouper->consume(item);
+ else
+ output.consume(item);
+ }
+
+ /// Process a tagged item, with its tags
+ virtual void consumeItem(const string& item, const OpSet<string>& tags)
+ {
+ if (grouper)
+ grouper->consume(item, tags);
+ else
+ output.consume(item, tags);
+ }
+
+ /// Process a set of items, all with no tags
+ virtual void consumeItemsUntagged(const OpSet<string>& items)
+ {
+ if (grouper)
+ grouper->consume(items);
+ else
+ // Explicitly split groups
+ for (OpSet<string>::const_iterator i = items.begin();
+ i != items.end(); i++)
+ output.consume(*i);
+ }
+
+ /// Process a set of items identically tagged, with their tags
+ virtual void consumeItems(const OpSet<string>& items, const OpSet<string>& tags)
+ {
+ if (grouper)
+ grouper->consume(items, tags);
+ else
+ // Explicitly split groups
+ for (OpSet<string>::const_iterator i = items.begin();
+ i != items.end(); i++)
+ output.consume(*i, tags);
+ }
+
+public:
+ Writer(commandline::CommandlineParser& opts)
+ : output(conv, conv, stdout), grouper(0)
+ {
+ if (opts.outputGroup.group->boolValue())
+ grouper = new ItemGrouper<string, string>;
+ }
+ ~Writer()
+ {
+ // Flush output if needed
+ if (grouper)
+ {
+ grouper->output(output);
+ delete grouper;
+ }
+ }
+};
- // Process the commandline
- valid_command cmd = (valid_command)opts.parse(argc, argv);
+int main(int argc, const char* argv[])
+{
+ commandline::CommandlineParser opts(argc, argv);
- CommandlineArgs args(argc, argv);
+ try {
+ opts.parse();
- Reader reader(opts, cmd);
+ Reader reader(opts);
// Perform the correct operation
- switch (cmd)
+ if (opts.helpGroup.help->boolValue())
{
- case IMPLICATIONS:
- {
- CardinalityStore<string, string> coll;
- reader.output(args, coll);
+ // Provide help as requested
+ commandline::Help help(APPNAME, VERSION);
+ commandline::OptionParser* o = opts.lastCommand();
+
+ if (o && !o->name().empty())
+ // Help on a specific command
+ help.outputHelp(cout, *o);
+ else
+ // General help
+ help.outputHelp(cout, opts);
+ }
+ else if (opts.helpGroup.version->boolValue())
+ {
+ // Print the program version
+ commandline::Help help(APPNAME, VERSION);
+ help.outputVersion(cout);
+ }
+ else if (opts.lastCommand() == &opts.generic)
+ {
+ commandline::Help help(APPNAME, VERSION);
+ help.outputHelp(cout, opts);
+ }
+ else if (opts.lastCommand() == &opts.help)
+ {
+ commandline::Help help(APPNAME, VERSION);
+ commandline::OptionParser* o = 0;
+ if (opts.hasNext())
+ o = opts.command(opts.next());
- Implications<string> newImpls;
+ if (o)
+ // Help on a specific command
+ help.outputHelp(cout, *o);
+ else
+ // General help
+ help.outputHelp(cout, opts);
+ }
+ else if (opts.lastCommand() == &opts.implications)
+ {
+ CardinalityStore<string, string> coll;
+ reader.output(opts, coll);
- // Find tag implications
- OpSet<string> allTags = coll.getAllTags();
- for (OpSet<string>::const_iterator t = allTags.begin();
- t != allTags.end(); t++)
- {
- OpSet<string> implied = coll.getImpliedBy(*t);
- if (!implied.empty())
- newImpls.consume(*t, implied);
- }
+ Implications<string> newImpls;
- newImpls.pack();
-
- TrivialConverter<string, string> conv;
- TextFormat<string, string> output(conv, conv, stdout);
- if (opts.get("expanded").defined())
- newImpls.outputFull(output);
- else
- newImpls.output(output);
- break;
- }
- case HIERARCHY:
- {
- int flattenThreshold = 0;
- if (opts.get("flatten").defined())
- flattenThreshold = opts.get("flatten").intVal();
-
- CardinalityStore<string, string> coll;
- reader.output(args, coll);
-
- if (opts.get("filter").defined())
- coll.removeTagsWithCardinalityLessThan(opts.get("filter").intVal());
-
- // Default operation: build the smart hierarchy
- HierarchyNode<string, string>* root =
- new SmartHierarchyNode<string, string>("_top", coll, flattenThreshold);
- printNode(root, "/");
- break;
- }
- case CLEANHIERARCHY:
+ // Find tag implications
+ OpSet<string> allTags = coll.getAllTags();
+ for (OpSet<string>::const_iterator t = allTags.begin();
+ t != allTags.end(); t++)
{
- int flattenThreshold = 0;
- if (opts.get("flatten").defined())
- flattenThreshold = opts.get("flatten").intVal();
-
- CardinalityStore<string, string> coll;
- reader.output(args, coll);
-
- if (opts.get("filter").defined())
- coll.removeTagsWithCardinalityLessThan(opts.get("filter").intVal());
-
- // Default operation: build the smart hierarchy
- HierarchyNode<string, string>* root = new CleanSmartHierarchyNode<string, string>("_top", coll, flattenThreshold);
- printNode(root, "/");
- break;
+ OpSet<string> implied = coll.getImpliedBy(*t);
+ if (!implied.empty())
+ newImpls.consume(*t, implied);
}
- case DIFF:
- {
- InputMerger<string, string> merger1;
- reader.output(args.next(), merger1);
- InputMerger<string, string> merger2;
- reader.output(args.next(), merger2);
+ newImpls.pack();
- PatchList<string, string> newpatches;
- newpatches.addPatch(merger1, merger2);
+ TrivialConverter<string, string> conv;
+ TextFormat<string, string> output(conv, conv, stdout);
+ if (opts.outputGroup.redundant->boolValue())
+ newImpls.outputFull(output);
+ else
+ newImpls.output(output);
+ }
+ else if (opts.lastCommand() == &opts.hierarchy)
+ {
+ int flattenThreshold = 0;
+ if (opts.hierarchyGroup.flatten->boolValue())
+ flattenThreshold = opts.hierarchyGroup.flatten->intValue();
- TrivialConverter<string, string> conv;
- TextFormat<string, string>::outputPatch(conv, conv, newpatches, stdout);
- break;
- }
- case RELATED:
- {
- string item = args.next();
- InputMerger<string, string> merger;
- reader.output(args, merger);
-
- int maxdist = 0;
- if (opts.get("distance").defined())
- maxdist = opts.get("distance").intVal();
-
- // Split the items on commas
- string splititem;
- set<string> splititems;
- for (string::const_iterator c = item.begin();
- c != item.end(); c++)
- if (*c == ',')
- {
- if (!merger.hasItem(splititem))
- {
- fprintf(stderr, "Item \"%.*s\" does not exist in the collection\n", PFSTR(splititem));
- return 1;
- }
- splititems.insert(splititem);
- splititem = string();
- } else
- splititem += *c;
- if (!merger.hasItem(splititem))
- {
- fprintf(stderr, "Item \"%.*s\" does not exist in the collection\n", PFSTR(splititem));
- return 1;
- }
- splititems.insert(splititem);
-
- // Get the tagset as the intersection of the tagsets of all input items
- set<string>::const_iterator i = splititems.begin();
- OpSet<string> ts = merger.getTags(*i);
- for (++i; i != splititems.end(); i++)
- ts = ts ^ merger.getTags(*i);
+ CardinalityStore<string, string> coll;
+ reader.output(opts, coll);
- if (ts.empty())
- {
- if (splititems.size() > 1)
- fprintf(stderr, "The items %.*s are unrelated: cannot find a barycenter to start computing relationships from.\n", PFSTR(item));
- else
- fprintf(stderr, "The items %.*s has no tags attached.\n", PFSTR(item));
- return 1;
- }
-
- // Build a full TagCollection
- CardinalityStore<string, string> coll;
- merger.output(coll);
+ if (opts.hierarchyGroup.filter->boolValue())
+ coll.removeTagsWithCardinalityLessThan(opts.hierarchyGroup.filter->intValue());
- printItems(coll.getItemsExactMatch(ts));
+ // Default operation: build the smart hierarchy
+ HierarchyNode<string, string>* root =
+ new SmartHierarchyNode<string, string>("_top", coll, flattenThreshold);
+ printNode(root, "/");
+ }
+ else if (opts.lastCommand() == &opts.cleanhierarchy)
+ {
+ int flattenThreshold = 0;
+ if (opts.hierarchyGroup.flatten->boolValue())
+ flattenThreshold = opts.hierarchyGroup.flatten->intValue();
- if (maxdist)
- {
- // Get the related tagsets
- list< OpSet<string> > rel = coll.getRelatedTagsets(ts, maxdist);
+ CardinalityStore<string, string> coll;
+ reader.output(opts, coll);
- // Print the output list
- for (list< OpSet<string> >::const_iterator i = rel.begin();
- i != rel.end(); i++)
- printItems(coll.getItemsExactMatch(*i));
- }
- break;
- }
- case REVERSE:
- {
- ItemGrouper<string, string> reverser;
- reader.output(args, reverser);
+ if (opts.hierarchyGroup.filter->boolValue())
+ coll.removeTagsWithCardinalityLessThan(opts.hierarchyGroup.filter->intValue());
+
+ // Default operation: build the smart hierarchy
+ HierarchyNode<string, string>* root = new CleanSmartHierarchyNode<string, string>("_top", coll, flattenThreshold);
+ printNode(root, "/");
+ }
+ else if (opts.lastCommand() == &opts.diff)
+ {
+ InputMerger<string, string> merger1;
+ reader.output(opts.next(), merger1);
- /*
- if (opts.get("untagged-tag").defined())
- reverser.setUntaggedItemName(opts.get("untagged-tag").stringVal());
- */
-
- TrivialConverter<string, string> conv;
- TextFormat<string, string> writer(conv, conv, stdout);
- if (opts.get("groupitems").defined())
+ InputMerger<string, string> merger2;
+ reader.output(opts.next(), merger2);
+
+ PatchList<string, string> newpatches;
+ newpatches.addPatch(merger1, merger2);
+
+ TrivialConverter<string, string> conv;
+ TextFormat<string, string>::outputPatch(conv, conv, newpatches, stdout);
+ }
+ else if (opts.lastCommand() == &opts.related)
+ {
+ string item = opts.next();
+ InputMerger<string, string> merger;
+ reader.output(opts, merger);
+
+ int maxdist = 0;
+ if (opts.related.distance->boolValue())
+ maxdist = opts.related.distance->intValue();
+
+ // Split the items on commas
+ string splititem;
+ set<string> splititems;
+ for (string::const_iterator c = item.begin();
+ c != item.end(); c++)
+ if (*c == ',')
{
- reverser.outputReversed(writer);
+ if (!merger.hasItem(splititem))
+ {
+ fprintf(stderr, "Item \"%.*s\" does not exist in the collection\n", PFSTR(splititem));
+ return 1;
+ }
+ splititems.insert(splititem);
+ splititem = string();
} else
- // FIXME: we need a filter that does the opposite of ItemGrouper
- reverser.outputReversed(writer);
- break;
- }
- case COPY:
+ splititem += *c;
+ if (!merger.hasItem(splititem))
{
- TrivialConverter<string, string> conv;
- TextFormat<string, string> writer(conv, conv, stdout);
- if (opts.get("groupitems").defined())
- {
- ItemGrouper<string, string> grouper;
- reader.output(args, grouper);
- grouper.output(writer);
- } else
- reader.output(args, writer);
- break;
+ fprintf(stderr, "Item \"%.*s\" does not exist in the collection\n", PFSTR(splititem));
+ return 1;
}
- case FINDSPECIALS:
+ splititems.insert(splititem);
+
+ // Get the tagset as the intersection of the tagsets of all input items
+ set<string>::const_iterator i = splititems.begin();
+ OpSet<string> ts = merger.getTags(*i);
+ for (++i; i != splititems.end(); i++)
+ ts = ts ^ merger.getTags(*i);
+
+ if (ts.empty())
{
- int flattenThreshold = 0;
- if (opts.get("flatten").defined())
- flattenThreshold = opts.get("flatten").intVal();
-
- CardinalityStore<string, string> coll;
- reader.output(args, coll);
-
- if (opts.get("filter").defined())
- coll.removeTagsWithCardinalityLessThan(opts.get("filter").intVal());
-
- // Default operation: build the smart hierarchy
- SmartHierarchyNode<string, string> root("_top", coll, flattenThreshold);
-
- OpSet<string> seen;
- for (HierarchyNode<string, string>::iterator i = root.begin();
- i != root.end(); i++)
- {
- OpSet<string> items = getItems(*i);
+ if (splititems.size() > 1)
+ fprintf(stderr, "The items %.*s are unrelated: cannot find a barycenter to start computing relationships from.\n", PFSTR(item));
+ else
+ fprintf(stderr, "The items %.*s has no tags attached.\n", PFSTR(item));
+ return 1;
+ }
- // Find the items in this branch that are not present in
- // any of the previous ones
- OpSet<string> newItems;
- if (!seen.empty())
- {
- for (OpSet<string>::const_iterator j = items.begin();
- j != items.end(); j++)
- {
- OpSet<string> tags = coll.getTags(*j) ^ seen;
- if (tags.empty())
- newItems += *j;
- }
-
- printf("%.*s: %d items, %d special items:\n",
- PFSTR((*i)->tag()), items.size(), newItems.size());
-
- int indent = (*i)->tag().size() + 2;
- for (OpSet<string>::const_iterator j = newItems.begin(); j != newItems.end(); j++)
- printf("%*s%.*s\n", indent, "", PFSTR(*j));
- }
+ // Build a full TagCollection
+ CardinalityStore<string, string> coll;
+ merger.output(coll);
- seen += (*i)->tag();
- }
+ printItems(coll.getItemsExactMatch(ts));
- break;
- }
- case GREP:
+ if (maxdist)
{
- FilterItemsByExpression<string, string> filter(args.next());
+ // Get the related tagsets
+ list< OpSet<string> > rel = coll.getRelatedTagsets(ts, maxdist);
- TrivialConverter<string, string> conv;
- TextFormat<string, string> writer(conv, conv, stdout);
- if (opts.get("groupitems").defined())
- {
- ItemGrouper<string, string> grouper;
- filter.setConsumer(grouper);
- reader.output(args, filter);
- grouper.output(writer);
- } else {
- filter.setConsumer(writer);
- reader.output(args, filter);
- }
- break;
+ // Print the output list
+ for (list< OpSet<string> >::const_iterator i = rel.begin();
+ i != rel.end(); i++)
+ printItems(coll.getItemsExactMatch(*i));
}
- case ITEMS:
+ }
+ else if (opts.lastCommand() == &opts.reverse)
+ {
+ ItemGrouper<string, string> reverser;
+ reader.output(opts, reverser);
+
+ /*
+ if (opts.reverse.untaggedTag->boolValue())
+ reverser.setUntaggedItemName(opts.reverse.untaggedTag->stringValue());
+ */
+
+ Writer writer(opts);
+ reverser.outputReversed(writer);
+ }
+ else if (opts.lastCommand() == &opts.copy)
+ {
+ Writer writer(opts);
+ reader.output(opts, writer);
+ }
+ else if (opts.lastCommand() == &opts.findspecials)
+ {
+ int flattenThreshold = 0;
+ if (opts.hierarchyGroup.flatten->boolValue())
+ flattenThreshold = opts.hierarchyGroup.flatten->intValue();
+
+ CardinalityStore<string, string> coll;
+ reader.output(opts, coll);
+
+ if (opts.hierarchyGroup.filter->boolValue())
+ coll.removeTagsWithCardinalityLessThan(opts.hierarchyGroup.filter->intValue());
+
+ // Default operation: build the smart hierarchy
+ SmartHierarchyNode<string, string> root("_top", coll, flattenThreshold);
+
+ OpSet<string> seen;
+ for (HierarchyNode<string, string>::iterator i = root.begin();
+ i != root.end(); i++)
{
- ItemsOnly<string, string> filter;
- TrivialConverter<string, string> conv;
- TextFormat<string, string> writer(conv, conv, stdout);
- if (opts.get("groupitems").defined())
+ OpSet<string> items = getItems(*i);
+
+ // Find the items in this branch that are not present in
+ // any of the previous ones
+ OpSet<string> newItems;
+ if (!seen.empty())
{
- ItemGrouper<string, string> grouper;
- filter.setConsumer(grouper);
- reader.output(args, filter);
- grouper.output(writer);
- } else {
- filter.setConsumer(writer);
- reader.output(args, filter);
+ for (OpSet<string>::const_iterator j = items.begin();
+ j != items.end(); j++)
+ {
+ OpSet<string> tags = coll.getTags(*j) ^ seen;
+ if (tags.empty())
+ newItems += *j;
+ }
+
+ printf("%.*s: %d items, %d special items:\n",
+ PFSTR((*i)->tag()), items.size(), newItems.size());
+
+ int indent = (*i)->tag().size() + 2;
+ for (OpSet<string>::const_iterator j = newItems.begin(); j != newItems.end(); j++)
+ printf("%*s%.*s\n", indent, "", PFSTR(*j));
}
- break;
+
+ seen += (*i)->tag();
}
}
+ else if (opts.lastCommand() == &opts.grep)
+ {
+ Writer writer(opts);
+ FilterItemsByExpression<string, string> filter(writer, opts.next());
+ reader.output(opts, filter);
+ }
+ else if (opts.lastCommand() == &opts.items)
+ {
+ Writer writer(opts);
+ ItemsOnly<string, string> filter(writer);
+ reader.output(opts, filter);
+ }
+ else
+ throw commandline::BadOption(string("unhandled command ") +
+ (opts.lastCommand() ? opts.lastCommand()->name() : "(null)"));
return 0;
- }
- catch (Exception& e)
+ } catch (commandline::BadOption& e) {
+ cerr << e.desc() << endl;
+ commandline::Help help(APPNAME, VERSION);
+ if (opts.lastCommand())
+ {
+ help.outputHelp(cerr, *opts.lastCommand());
+ } else {
+ help.outputHelp(cerr, opts);
+ }
+ exit(1);
+ } catch (Exception& e)
{
fprintf(stderr, "%s: %.*s\n", e.type(), PFSTR(e.desc()));
return 1;
More information about the Debtags-commits
mailing list