[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Monotone-devel] patch: formatting of automate output
From: |
rghetta |
Subject: |
[Monotone-devel] patch: formatting of automate output |
Date: |
Sun, 17 Apr 2005 22:48:02 +0200 |
User-agent: |
Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.6) Gecko/20050322 |
The attached patch adds some optional formatting to automate output.
It's more a proof of concept than a complete implementation.
Right now works only at the cert level, changeset still need some thought.
It adds two options applicable to all automate commands outputting
revision ids:
--xml
formats as an xml stream. Takes precedence over --format
-- format="format spec"
Uses a printf like format string. Right now are implemented these format
specifiers:
%a : value of author cert
%d : value of date cert
%e : value of comment cert
%i : revision id
%l : value of changelog cert
%s : value of testresult cert
%t : value of tag cert
%% : the % char
It knows also about \n, \r, \a, \t, \b, \f, \v (same meaning of printf).
\\ is used to obtain the \ char. \ is also an escape char of most
shells, use ' as the string delimiter.
Traditional automate output is equivalent to '%i\n' (default if no fmt
string specified)
Comments welcome,
Riccardo
#
# add_file "format.cc"
#
# add_file "format.hh"
#
# patch "Makefile.am"
# from [ad9e4d27f5baadaf99c7485aebecc2388b757d9a]
# to [d0becf27faf8004f507fb69d1a39640c3e7c792e]
#
# patch "app_state.cc"
# from [63120a266ad19ebd72022cf172ccbd0c299aa413]
# to [f765950b2adc0a5ca9d583668871ea54313ffc8f]
#
# patch "app_state.hh"
# from [9cfdb7a5976dc31edda11e99ee74046fdc318469]
# to [2688c96ff296b8457bbb52233376c7030027ad9e]
#
# patch "automate.cc"
# from [f10d2c6090c993b5c05fb824581aded812f59871]
# to [aebfc244cd560872619a0f678be33cc6d552fefb]
#
# patch "format.cc"
# from []
# to [8426726b657b73d7879d2115c21b04670197597c]
#
# patch "format.hh"
# from []
# to [42e3fffe2c47370ec888babd6acc961877dd554d]
#
# patch "monotone.cc"
# from [2419f27fdb3cba77c658c0f4eac4d0b5026e1884]
# to [b7be4b6724b9ac314ce588a6bbf13c4b75003e96]
#
--- Makefile.am
+++ Makefile.am
@@ -9,7 +9,7 @@
constants.cc netsync.cc netcmd.cc merkle_tree.cc basic_io.cc \
mkstemp.cc lcs.cc rcs_import.hh rcs_import.cc revision.cc \
change_set.cc mt_version.cc automate.cc database_check.cc \
- path_component.cc epoch.cc inodeprint.cc \
+ path_component.cc epoch.cc inodeprint.cc format.cc \
\
app_state.hh commands.hh file_io.hh manifest.hh packet.hh \
sanity.hh update.hh work.hh cert.hh database.hh keys.hh \
@@ -23,7 +23,7 @@
mkstemp.hh mt_version.hh automate.hh database_check.hh smap.hh \
gettext.h package_revision.c package_full_revision.c \
path_component.hh epoch.hh package_full_revision.h \
- package_revision.h inodeprint.hh
+ package_revision.h inodeprint.hh format.hh
NETXX_SOURCES =
\
netxx/accept.cxx netxx/accept.h netxx/address.cxx
\
--- app_state.cc
+++ app_state.cc
@@ -31,7 +31,8 @@
app_state::app_state()
: branch_name(""), db(""), stdhooks(true), rcfiles(true),
- search_root("/"), depth(-1)
+ search_root("/"), depth(-1),
+ format_string("%i\\n"), xml_enabled(false)
{
db.set_app(this);
}
@@ -281,6 +282,18 @@
}
void
+app_state::set_fmtstring(utf8 const & f)
+{
+ format_string = f;
+}
+
+void
+app_state::set_xml()
+{
+ xml_enabled = true;
+}
+
+void
app_state::add_revision(utf8 const & selector)
{
revision_selectors.push_back(selector);
--- app_state.hh
+++ app_state.hh
@@ -40,7 +40,10 @@
file_path relative_directory;
bool found_working_copy;
long depth;
+ utf8 format_string;
+ bool xml_enabled;
+
void allow_working_copy();
void require_working_copy();
void create_working_copy(std::string const & dir);
@@ -69,6 +72,10 @@
void set_rcfiles(bool b);
void add_rcfile(utf8 const & filename);
+ void set_fmtstring(utf8 const & fmtstring);
+ void set_xml();
+
+
explicit app_state();
~app_state();
--- automate.cc
+++ automate.cc
@@ -11,6 +11,7 @@
#include "app_state.hh"
#include "commands.hh"
#include "revision.hh"
+#include "format.hh"
static std::string const interface_version = "0.1";
@@ -55,8 +56,9 @@
std::set<revision_id> heads;
get_branch_heads(idx(args, 0)(), app, heads);
- for (std::set<revision_id>::const_iterator i = heads.begin(); i !=
heads.end(); ++i)
- output << (*i).inner()() << std::endl;
+
+ FormatFunc fmt(output, app);
+ for_each(heads.begin(), heads.end(), fmt);
}
// Name: descendents
@@ -101,9 +103,9 @@
}
}
}
- for (std::set<revision_id>::const_iterator i = descendents.begin();
- i != descendents.end(); ++i)
- output << (*i).inner()() << std::endl;
+
+ FormatFunc fmt(output, app);
+ for_each(descendents.begin(), descendents.end(), fmt);
}
// Name: erase_ancestors
@@ -133,8 +135,9 @@
revs.insert(rid);
}
erase_ancestors(revs, app);
- for (std::set<revision_id>::const_iterator i = revs.begin(); i !=
revs.end(); ++i)
- output << (*i).inner()() << std::endl;
+
+ FormatFunc fmt(output, app);
+ for_each(revs.begin(), revs.end(), fmt);
}
// Name: toposort
@@ -162,9 +165,9 @@
}
std::vector<revision_id> sorted;
toposort(revs, sorted, app);
- for (std::vector<revision_id>::const_iterator i = sorted.begin();
- i != sorted.end(); ++i)
- output << (*i).inner()() << std::endl;
+
+ FormatFunc fmt(output, app);
+ for_each(sorted.begin(), sorted.end(), fmt);
}
// Name: ancestry_difference
@@ -208,9 +211,9 @@
std::vector<revision_id> sorted;
toposort(ancestors, sorted, app);
- for (std::vector<revision_id>::const_iterator i = sorted.begin();
- i != sorted.end(); ++i)
- output << (*i).inner()() << std::endl;
+
+ FormatFunc fmt(output, app);
+ for_each(sorted.begin(), sorted.end(), fmt);
}
// Name: leaves
@@ -243,8 +246,9 @@
for (std::multimap<revision_id, revision_id>::const_iterator i =
graph.begin();
i != graph.end(); ++i)
leaves.erase(i->first);
- for (std::set<revision_id>::const_iterator i = leaves.begin(); i !=
leaves.end(); ++i)
- output << (*i).inner()() << std::endl;
+
+ FormatFunc fmt(output, app);
+ for_each(leaves.begin(), leaves.end(), fmt);
}
void
--- format.cc
+++ format.cc
@@ -0,0 +1,332 @@
+// copyright (C) 2005 R.Ghetta <address@hidden>
+// all rights reserved.
+// licensed to the public under the terms of the GNU GPL (>= 2)
+// see the file COPYING for details
+
+#include <app_state.hh>
+#include <revision.hh>
+#include <transforms.hh>
+#include <format.hh>
+
+using namespace std;
+
+// helper functions to navigate the given revision - shamelessy copied from
'log' command
+void
+walk_edges (const app_state & app, const revision_set & rev,
+ set < revision_id > &ancestors, vector < change_set > &changes,
+ set < file_path > &modified_files)
+{
+ for (edge_map::const_iterator e = rev.edges.begin ();
+ e != rev.edges.end (); ++e)
+ {
+ ancestors.insert (edge_old_revision (e));
+
+ change_set const &cs = edge_changes (e);
+ change_set::path_rearrangement const &pr = cs.rearrangement;
+
+ changes.push_back (cs);
+
+ for (change_set::delta_map::const_iterator i = cs.deltas.begin ();
+ i != cs.deltas.end (); i++)
+ {
+ if (pr.added_files.find (i->first ()) == pr.added_files.end ())
+ modified_files.insert (i->first ());
+ }
+ }
+}
+
+// ---------------------- formatting functor ----------------------------
+// IMPORTANT: to complete formatting, it *must* go out of scope (i.e. its
destructor
+// called)
+FormatFunc::FormatFunc(std::ostream &out, app_state &app)
+{
+ if (app.xml_enabled)
+ fmt = auto_ptr<BaseFormatter>(new XMLFormatter(out, app));
+ else
+ fmt = auto_ptr<BaseFormatter>(new PrintFormatter(out, app,
app.format_string));
+}
+
+FormatFunc::~FormatFunc()
+{
+}
+
+// ---------------------- base formatter ----------------------------
+BaseFormatter::BaseFormatter(app_state &a):
+app(a)
+{
+}
+
+BaseFormatter::~BaseFormatter()
+{
+}
+
+// ---------------------- format string support ----------------------------
+PrintFormatter::PrintFormatter(std::ostream & o, app_state &a, const utf8
&fmt):
+BaseFormatter(a),
+fmtstring(fmt),
+out(o)
+{
+}
+
+PrintFormatter::~PrintFormatter()
+{
+}
+
+void
+PrintFormatter::format_cert (vector < revision < cert > >&certs, const string
&name)
+{
+ for (vector < revision < cert > >::const_iterator i = certs.begin ();
+ i != certs.end (); ++i)
+ {
+ if (i->inner ().name () == name)
+ {
+ cert_value tv;
+ decode_base64 (i->inner ().value, tv);
+ out << tv;
+ return;
+ }
+ }
+}
+
+void
+PrintFormatter::handle_control(string::const_iterator &it, const
string::const_iterator &end)
+{
+ ++it;
+ if (it == end)
+ return;
+ switch (*it)
+ {
+ case '\\':
+ out << '\\';
+ break;
+ case 'n':
+ out<< endl;
+ break;
+ case 't':
+ out << '\t';
+ break;
+ case 'a':
+ out << '\a';
+ break;
+ case 'b':
+ out << '\b';
+ break;
+ case 'f':
+ out << '\f';
+ break;
+ case 'r':
+ out << '\r';
+ break;
+ case 'v':
+ out << '\v';
+ break;
+ default:
+ N (false, F ("\ninvalid control char %c\n") % (*it));
+ return;
+ }
+}
+
+void
+PrintFormatter::apply(const revision_id & rid)
+{
+ if (!app.db.revision_exists (rid))
+ {
+ L (F ("revision %s does not exist in db\n") % rid);
+ return;
+ }
+
+ revision_set rev;
+ app.db.get_revision (rid, rev);
+
+ vector < revision < cert > >certs;
+ app.db.get_revision_certs (rid, certs);
+ erase_bogus_certs (certs, app);
+
+ string::const_iterator it = fmtstring ().begin ();
+ while (it != fmtstring ().end ())
+ {
+ if ((*it) == '%')
+ {
+ ++it;
+ if (it == fmtstring ().end ())
+ return;
+ switch (*it)
+ {
+ case '%':
+ out << '%';
+ break;
+ case 'd':
+ format_cert (certs, date_cert_name);
+ break;
+ case 'a':
+ format_cert (certs, author_cert_name);
+ break;
+ case 't':
+ format_cert (certs, tag_cert_name);
+ break;
+ case 'l':
+ format_cert (certs, changelog_cert_name);
+ break;
+ case 'e':
+ format_cert (certs, comment_cert_name);
+ break;
+ case 's':
+ format_cert (certs, testresult_cert_name);
+ break;
+ case 'i':
+ out << rev.new_manifest.inner()();
+ break;
+ default:
+ N (false, F ("invalid format string"));
+ return;
+ }
+ }
+ else if ( (*it) == '\\')
+ handle_control(it, fmtstring ().end ());
+ else
+ out << (*it);
+
+ ++it;
+ }
+}
+
+
+// --------------- XML support -----------------
+
+XMLWriter::XMLWriter (ostream & o):
+out (o),
+open_tags(),
+decl_emitted(false)
+{
+}
+
+XMLWriter::~XMLWriter ()
+{
+ I (open_tags.size () == 0); // forgot to closing some tags ?
+}
+
+void
+XMLWriter::tag (const utf8 & tagname)
+{
+ if (!decl_emitted)
+ {
+ out << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?>"
<< endl;
+ decl_emitted=true;
+ }
+ out << "<" << tagname << ">" << endl;
+ open_tags.push_back (tagname);
+}
+
+void
+XMLWriter::end ()
+{
+ I (open_tags.size () > 0);
+ out << "</" << open_tags.back () << ">" << endl;
+ open_tags.pop_back ();
+}
+
+void
+XMLWriter::cdata (const string & opq)
+{
+ I (open_tags.size () > 0);
+
+ for (string::const_iterator i = opq.begin(); i != opq.end(); ++i)
+ {
+ switch ((*i))
+ {
+ case '<': out << "<"; break;
+ case '>': out << ">"; break;
+ case '&': out << "&"; break;
+ case '"': out << """; break;
+ case '\'': out << "'"; break;
+ default: out << *i; break;
+ }
+ }
+}
+
+// ---------------- the xml formatter -----------------------
+XMLFormatter::XMLFormatter(ostream &out, app_state &a):
+BaseFormatter(a),
+xw(out)
+{
+ xw.tag("monotone"); // docroot
+}
+
+XMLFormatter::~XMLFormatter()
+{
+ xw.end();
+}
+
+void
+XMLFormatter::xml_manifest(const manifest_id & mid)
+{
+ xw.tag("manifest");
+ xw.cdata (mid.inner ()());
+ xw.end();
+}
+
+// dumps all *valid* certs associated to a revision
+// FIXME: could be useful to optionally dump an invalid cert marking it with
another
+// tag (invalid_cert) or an attribute (valid=true/false)
+void
+XMLFormatter::xml_certs (const revision_id & rid)
+{
+ vector < revision < cert > >certs;
+
+ app.db.get_revision_certs (rid, certs);
+ erase_bogus_certs (certs, app);
+ for (vector < revision < cert > >::const_iterator i = certs.begin ();
+ i != certs.end (); ++i)
+ {
+ xw.tag ("cert");
+ xw.tag ("name");
+ xw.cdata (i->inner ().name ());
+ xw.end ();
+
+ xw.tag ("value");
+ cert_value tv;
+ decode_base64 (i->inner ().value, tv);
+ xw.cdata (tv ());
+ xw.end ();
+
+ xw.tag ("key-id");
+ xw.cdata (i->inner ().key ());
+ xw.end ();
+
+ xw.tag ("signature");
+ xw.cdata (i->inner ().sig ()); // only makes sense if encoded
+ xw.end ();
+
+ xw.end ();
+ }
+
+}
+
+void
+XMLFormatter::xml_changeset(const revision_set & rev)
+{
+}
+
+// dumps recursively a revision
+void
+XMLFormatter::apply(const revision_id & rid)
+{
+ if (!app.db.revision_exists (rid))
+ {
+ L (F ("revision %s does not exist in db\n") % rid);
+ return;
+ }
+
+ revision_set rev;
+ app.db.get_revision (rid, rev);
+
+ xw.tag ("revision");
+ xw.tag ("id");
+ xw.cdata(rid.inner()());
+ xw.end();
+ xml_manifest(rev.new_manifest);
+ xml_certs (rid);
+ xml_changeset(rev);
+
+ xw.end ();
+}
--- format.hh
+++ format.hh
@@ -0,0 +1,100 @@
+#ifndef __FORMAT_HH__
+#define __FORMAT_HH__
+
+// copyright (C) 2005 R.Ghetta <address@hidden>
+// all rights reserved.
+// licensed to the public under the terms of the GNU GPL (>= 2)
+// see the file COPYING for details
+
+#include "vocab.hh"
+
+#include <ostream>
+#include <vector>
+#include <string>
+#include <algorithm>
+#include <memory>
+
+// base class of all formatters
+class BaseFormatter
+{
+public:
+ BaseFormatter(app_state &app);
+ virtual ~BaseFormatter();
+
+ virtual void apply(const revision_id &rid) = 0;
+
+protected:
+ app_state &app;
+};
+
+// functor formatter
+// IMPORTANT: to complete formatting, it *must* go out of scope (i.e. its
destructor
+// called)
+class FormatFunc : public std::unary_function<revision_id, void>
+{
+public:
+ FormatFunc(std::ostream &out, app_state &app);
+ ~FormatFunc();
+ void operator ()(const revision_id &rid) { fmt->apply(rid); }
+private:
+ std::auto_ptr<BaseFormatter> fmt;
+};
+
+
+class PrintFormatter : public BaseFormatter
+{
+public:
+ PrintFormatter(std::ostream & out, app_state &app, const utf8 &fmtstring);
+ ~PrintFormatter();
+
+ // applies the fmt string to the given revision
+ void apply(const revision_id &rid);
+
+private:
+ void format_cert (std::vector < revision < cert > >&certs, const std::string
&name);
+ void handle_control(std::string::const_iterator &it, const
std::string::const_iterator &end);
+
+private:
+ const utf8 &fmtstring;
+ std::ostream & out;
+};
+
+
+// a very rudimentary XML writer
+class XMLWriter
+{
+public:
+ explicit XMLWriter (std::ostream & );
+ ~XMLWriter ();
+
+ void tag(const utf8 &tagname);
+ void tag(const std::string &tagname) { tag(utf8(tagname)); }
+ void cdata(const utf8 &opq) { cdata(opq()); }
+ void cdata(const std::string &opq);
+ void end();
+
+private:
+ std::ostream &out;
+ std::vector<utf8> open_tags;
+ bool decl_emitted;
+};
+
+class XMLFormatter: public BaseFormatter
+{
+public:
+ XMLFormatter(std::ostream &out, app_state &app);
+ ~XMLFormatter();
+
+ // applies the formatting to the given revision
+ void apply(const revision_id &rid);
+
+private:
+ void xml_manifest(const manifest_id & mid);
+ void xml_certs (const revision_id & rid);
+ void xml_changeset(const revision_set & rev);
+
+private:
+ XMLWriter xw;
+};
+
+#endif // header guard
--- monotone.cc
+++ monotone.cc
@@ -45,6 +45,8 @@
#define OPT_ROOT 16
#define OPT_DEPTH 17
#define OPT_ARGFILE 18
+#define OPT_FORMAT 19
+#define OPT_XML 20
// main option processing and exception handling code
@@ -73,6 +75,8 @@
{"root", 0, POPT_ARG_STRING, &argstr, OPT_ROOT, "limit search for working
copy to specified root", NULL},
{"depth", 0, POPT_ARG_LONG, &arglong, OPT_DEPTH, "limit the log output to
the given number of entries", NULL},
{"xargs", '@', POPT_ARG_STRING, &argstr, OPT_ARGFILE, "insert command line
arguments taken from the given file", NULL},
+ {"format", 0, POPT_ARG_STRING, &argstr, OPT_FORMAT, "specifies a format
string on automate output", NULL},
+ {"xml", 0, POPT_ARG_NONE, NULL, OPT_XML, "automate output will be in XML",
NULL},
{ NULL, 0, 0, NULL, 0 }
};
@@ -337,6 +341,14 @@
utf8(string(argstr))));
break;
+ case OPT_FORMAT:
+ app.set_fmtstring(string(argstr));
+ break;
+
+ case OPT_XML:
+ app.set_xml();
+ break;
+
case OPT_HELP:
default:
requested_help = true;
- [Monotone-devel] patch: formatting of automate output,
rghetta <=