monotone-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Monotone-devel] patch: improved formatting of automate output


From: rghetta
Subject: [Monotone-devel] patch: improved formatting of automate output
Date: Thu, 21 Apr 2005 17:32:32 +0200
User-agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.7) Gecko/20050414

Attached is an improved version of the formatting patch. Now it enables formatting changeset informations The format string is made by a common part, applied to each revision, and one or more optional changeset subformats, applied to changeset data. Special changeset placeholders put in the common expression are substituted by the formatted changeset informations. The common formatting expression is separated from the changeset ones by apposite markers.

Common formatting specifiers:
%a : value of author certificate
%b : value of branch certificate
%d : value of date certificate
%e : value of comment certificate
%i : revision id
%l : value of changelog certificate
%s : value of testresult certificate
%t : value of tag certificate
%m : manifest id

Changeset Placeholders:
%P : ancestors (marker @P)
%A : added files (marker @A)
%D : deleted files (marker @D)
%E : deleted dirs (marker @E)
%R : renamed files (marker @R)
%C : renamed dirs (marker @C)
%M : modified files (marker @M)

The %P,%A,%D,%E,%M changeset placeholders supports the private specifier %f, substituted with the file/directory/ancestor name/id The %R and %M placeholders supports the specifiers %f, current file/directory name and %o, old file/directory name.
Plus, all format string support the  \n, \r, \a, \t, \b, \f, \v
modifiers with the usual c-like meaning.
\\, \% and \@ are used to obtain the \, % and @ char respectively.
The default format string is '%i\n'.

The following examples show how to format monotone automate output.
We use "monotone heads net.venge.monotone"

- Normal output (without --format)

$./monotone automate heads net.venge.monotone
1d47c52849852411a9c5bc53a6cb812e5234adeb


- Now we want every revision prefixed by "Rev-id: " and followed by a newline

$ ./monotone automate heads net.venge.monotone --format='Rev-id: %i\n'
Rev-id: 1d47c52849852411a9c5bc53a6cb812e5234adeb

Note:
From now on, the "./monotone automate heads net.venge.monotone" part of command
will be replaced with "...", just to make the command line a bit simpler.

- Adding the revision date on the same line and the author on a line of his own,
indented with a tabstop
$ ... --format='Rev-id: %i, Date: %d\n\tAuthor: %a\n'
Rev-id: 1d47c52849852411a9c5bc53a6cb812e5234adeb, Date: 2005-04-19T04:30:12
        Author: address@hidden

- Outputting only the raw data:
$ ... --format='%i, %d\n\t%a\n'
1d47c52849852411a9c5bc53a6cb812e5234adeb, 2005-04-19T04:30:12
        address@hidden

- We could also show changeset informations, like the parents of a revision:

$ ... --format='Rev-id: %i, Date: %d\n\tAuthor: %a\n%P\n'
Rev-id: 1d47c52849852411a9c5bc53a6cb812e5234adeb, Date: 2005-04-19T04:30:12
        Author: address@hidden
4cf007a89b597cf3a4f0ee30043cfc3048cf99ef
5df5fb7239214dd8e885c9807d4846d163523cd3

- If we want to put the ancestors on a single line, prefixed by "Parents: " we specify a parents subexpression following the format string and separated from it by "@P". This expression will be applied to every ancestor and dictates the
formatting of the %P specifier.

$ ... --format='Rev-id: %i, Date: %d\n\tAuthor: %a\nParents:address@hidden '
Rev-id: 1d47c52849852411a9c5bc53a6cb812e5234adeb, Date: 2005-04-19T04:30:12
        Author: address@hidden
Parents:
4cf007a89b597cf3a4f0ee30043cfc3048cf99ef 5df5fb7239214dd8e885c9807d4846d163523cd3

- A more complicated expression could give us an output similar to the
monotone log command (expression line wrapped for clarity)

$ ... --format='-----------------------------------------------------
\nRevision: %i\n%PAuthor: %a\nDate: %d\nBranch: %b\n\nAdded files:
\n%ADeleted files:\n%DChangelog:address@hidden:%f address@hidden@address@hidden'
--------------------------------------------------------------------
Revision: 1d47c52849852411a9c5bc53a6cb812e5234adeb
Ancestor: 4cf007a89b597cf3a4f0ee30043cfc3048cf99ef
Ancestor: 5df5fb7239214dd8e885c9807d4846d163523cd3
Author: address@hidden
Date: 2005-04-19T04:30:12
Branch: net.venge.monotone

Added files:
        contrib/monotone-import.pl
        tests/t_attr_drop.at
        tests/t_drop_attr.at
        tests/t_log_depth_single.at
Deleted files:
Changelog:
merge of 4cf007a89b597cf3a4f0ee30043cfc3048cf99ef
     and 5df5fb7239214dd8e885c9807d4846d163523cd3

In this output you the main limitations of --format current implementation.
A @ format string is applied to all corresponding changeset items. As an example,
you can't print two files per line, like monotone log.
Also, the "Deleted files: " line will be shown even if there aren't deleted
files in the changeset, because is in the common part of the format string.


# 
# add_file "format.cc"
# 
# add_file "format.hh"
# 
# patch "Makefile.am"
#  from [e06716c8fe396a1b540b37122e303393b1f71067]
#    to [a611fcfe1e5557c3ab28f1090d9899f63009e9ba]
# 
# patch "app_state.cc"
#  from [a7ed8e649668aca70e915e9071469219ecf9db75]
#    to [bfb69c9c4208fcb47f88fec946bae1d5083c14f6]
# 
# patch "app_state.hh"
#  from [27b58d6683bc026b41c07308738952067bf82926]
#    to [bc3210458ab0b9627cf4423ed5fa5f443111cb33]
# 
# patch "automate.cc"
#  from [dc6c34d27c31057be28379b197a9ba535716e713]
#    to [dc7440e7fbdf4ae71ca5b41cca57a579343589c7]
# 
# patch "format.cc"
#  from []
#    to [c8c5d8e51a17b1d270c09a6f7b15a804170f8305]
# 
# patch "format.hh"
#  from []
#    to [48cf073bf27c42e96afcb69e9073ca26f0b8b517]
# 
# patch "monotone.1"
#  from [33e5e256e415dd185535ac1331d44a9de3f243cc]
#    to [af9974e2e1d29e9a5f47d81f7cc9c0810aa5a139]
# 
# patch "monotone.cc"
#  from [7b86d611ebb4f89b6fb733b5e322bd500302d020]
#    to [1cb31fc15fc566524015c303d061b0d10613df11]
# 
# patch "monotone.texi"
#  from [86788028f6e7a05e423af34635bd568f6966eb40]
#    to [18a31708c4b7fba6604afc56b98c609158c72f32]
# 
--- 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);
 }
@@ -296,6 +297,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
@@ -42,7 +42,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);
@@ -73,6 +76,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.2";
 
@@ -59,8 +60,9 @@
   }
   std::set<revision_id> heads;
   get_branch_heads(app.branch_name(), 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: ancestors
@@ -156,9 +158,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);
 }
 
 
@@ -189,8 +191,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
@@ -218,9 +221,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
@@ -264,9 +267,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
@@ -299,8 +302,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,703 @@
+// 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),
+out(o)
+{
+  assign_format_strings(fmt);
+}
+
+PrintFormatter::~PrintFormatter()
+{
+}
+
+// splits the given format string into the revision fmt string and 
+// one or more optional changeset fmt strings1
+void
+PrintFormatter::assign_format_strings(const utf8 &fmt)
+{
+  // establishing defaults
+  fmtstrs[FMTIDX_REVISION]=utf8("");
+  fmtstrs[FMTIDX_ANCESTORS]=utf8("%f\n");
+  fmtstrs[FMTIDX_DELFILES]=utf8("%f\n");
+  fmtstrs[FMTIDX_DELDIRS]=utf8("%f\n");
+  fmtstrs[FMTIDX_RENFILES]=utf8("%o -> %f\n");
+  fmtstrs[FMTIDX_RENDIRS]=utf8("%o -> %f\n");
+  fmtstrs[FMTIDX_ADDFILES]=utf8("%f\n");
+  fmtstrs[FMTIDX_MODFILES]=utf8("%f\n");
+  
+  // quick parse of the formatting string
+  string::const_iterator e=fmt().end();
+  string::const_iterator i=fmt().begin();
+  string::const_iterator start_current_fmt=fmt().begin();
+  FMTIDX current_fmt = FMTIDX_REVISION;
+  string buf;
+  while (i != e)
+  {
+    switch (*i)
+      {
+      case '@':
+        {
+          // seems a start of a changeset format
+          // stores the current fmt string (trying to work around ATOMIC 
limits)
+          buf.assign(start_current_fmt, i);
+          fmtstrs[current_fmt] = utf8(buf);
+          
+          ++i;
+          N (i!=e, F ("A format string could not end with '@'\n"));
+
+          // prepare for new fmt string
+          current_fmt = decode_cset_fmtid(i);
+          N (current_fmt != FMTIDX_REVISION, F ("invalid changeset string 
specifier\n"));
+
+          start_current_fmt=i;
+          ++start_current_fmt;
+        }
+        break;
+       
+      case '\\':
+      case '%':
+        // escape or fmt specifier, skipping
+        i++;
+        break;
+      }
+    if (i != e)  
+      ++i;
+  }
+
+  // final string  
+  buf.assign(start_current_fmt, i);
+  fmtstrs[current_fmt] = utf8(buf);
+}
+
+
+void
+PrintFormatter::print_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::print_cset_ancestor(const utf8 &fmtstring, const revision_id 
&rid)
+{
+  string::const_iterator i = fmtstring ().begin ();
+  while (i != fmtstring ().end ())
+    {
+    if ((*i) == '%')
+      {
+        ++i;
+        if (i == fmtstring ().end ())
+          break;
+        N (*i == 'f', F ("invalid ancestor format string\n"));
+        out << rid.inner ()();
+      }
+    else if ( (*i) == '\\')
+      handle_control(i, fmtstring ().end ());
+    else
+      out << (*i);
+    
+    ++i;
+    }
+}
+
+void
+PrintFormatter::print_cset_single(const utf8 &fmtstring, const set<file_path> 
&data)
+{
+  for (set<file_path>::const_iterator f = data.begin (); f != data.end (); ++f)
+    {
+    string::const_iterator i = fmtstring ().begin ();
+    while (i != fmtstring ().end ())
+      {
+      if ((*i) == '%')
+        {
+          ++i;
+          if (i == fmtstring ().end ())
+            break;
+          N (*i == 'f', F ("invalid file format string\n"));
+          out << (*f)();
+        }
+      else if ( (*i) == '\\')
+        handle_control(i, fmtstring ().end ());
+      else
+        out << (*i);
+      
+      ++i;
+      }
+    }        
+}
+
+void
+PrintFormatter::print_cset_pair(const utf8 &fmtstring, const map<file_path, 
file_path> &data)
+{
+  for (map<file_path, file_path>::const_iterator f = data.begin (); f != 
data.end (); ++f)
+    {
+    string::const_iterator i = fmtstring ().begin ();
+    while (i != fmtstring ().end ())
+      {
+      if ((*i) == '%')
+        {
+          ++i;
+          if (i == fmtstring ().end ())
+            break;
+          N (*i == 'o' || *i == 'f', F ("invalid rename format string\n"));
+          if (*i == 'o')
+            out << f->first();
+          else
+            out << f->second();
+        }
+      else if ( (*i) == '\\')
+        handle_control(i, fmtstring ().end ());
+      else
+        out << (*i);
+      
+      ++i;
+      }
+            
+    }        
+}
+
+void
+PrintFormatter::handle_cset(const string::const_iterator &fmt_i, const 
revision_set & rev)
+{
+    FMTIDX curfmt = decode_cset_fmtid(fmt_i);
+    N (curfmt != FMTIDX_REVISION, F ("invalid format specifier"));
+
+    for (edge_map::const_iterator e = rev.edges.begin ();
+       e != rev.edges.end (); ++e)
+    {
+      change_set const &cs = edge_changes (e);
+      change_set::path_rearrangement const &pr = cs.rearrangement;
+
+      switch (curfmt)
+        {
+        case FMTIDX_ANCESTORS:
+          print_cset_ancestor(fmtstrs[curfmt], edge_old_revision (e));
+          break;
+        case FMTIDX_DELFILES:
+          print_cset_single(fmtstrs[curfmt], pr.deleted_files);
+          break;
+        case FMTIDX_DELDIRS:
+          print_cset_single(fmtstrs[curfmt], pr.deleted_dirs);
+          break;
+        case FMTIDX_ADDFILES:
+          print_cset_single(fmtstrs[curfmt], pr.added_files);
+          break;
+        case FMTIDX_MODFILES:
+          {
+            std::set<file_path> modified_files;
+            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 ());
+            }
+            print_cset_single(fmtstrs[curfmt], modified_files);
+          }
+          break;
+        case FMTIDX_RENFILES:
+          print_cset_pair(fmtstrs[curfmt], pr.renamed_files);
+          break;
+        case FMTIDX_RENDIRS:
+          print_cset_pair(fmtstrs[curfmt], pr.renamed_dirs);
+          break;
+        
+        default:
+          break;
+      }
+    }
+    
+}
+
+void 
+PrintFormatter::handle_control(string::const_iterator &it, const 
string::const_iterator &end)
+{
+  ++it;
+  if (it == end)
+    return;
+  switch (*it)
+    {
+    case '\\':
+      out << '\\';
+      break;
+    case '%':
+      out << '%';
+      break;
+    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;
+    }
+}
+
+PrintFormatter::FMTIDX
+PrintFormatter::decode_cset_fmtid(const string::const_iterator &i)
+{
+    switch (*i)
+    {
+      case 'P': // ancestors
+        return FMTIDX_ANCESTORS;
+      case 'D': // deleted files
+        return FMTIDX_DELFILES;
+      case 'R': // renamed files
+        return FMTIDX_RENFILES;
+      case 'A': // added files
+        return FMTIDX_ADDFILES;
+      case 'M': // modified files
+        return FMTIDX_MODFILES;
+      case 'E': // deleted dirs
+        return FMTIDX_DELDIRS;
+      case 'C': // renamed dirs
+        return FMTIDX_RENDIRS;
+    }
+    
+    // everything else is handled as revision fmt
+    return FMTIDX_REVISION; 
+}
+
+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 i = fmtstrs[FMTIDX_REVISION]().begin ();
+  string::const_iterator e = fmtstrs[FMTIDX_REVISION]().end();
+  while (i != e)
+    {
+      if ((*i) == '%')
+        {
+          ++i;
+          if (i == e)
+            return;
+          switch (*i)
+            {
+            case 'd':
+              print_cert (certs, date_cert_name);
+              break;
+            case 'a':
+              print_cert (certs, author_cert_name);
+              break;
+            case 't':
+              print_cert (certs, tag_cert_name);
+              break;
+            case 'l':
+              print_cert (certs, changelog_cert_name);
+              break;
+            case 'e':
+              print_cert (certs, comment_cert_name);
+              break;
+            case 's':
+              print_cert (certs, testresult_cert_name);
+              break;
+            case 'b':
+              print_cert (certs, branch_cert_name);
+              break;
+            case 'm':
+              out << rev.new_manifest.inner()();
+              break;
+            case 'i':
+              out << rid.inner()();
+              break;
+            default:
+              // unrecognized specifier, perhaps is a changeset one ?
+              handle_cset(i, rev);
+            }
+        }
+      else if ( (*i) == '\\')
+        handle_control(i, e);
+      else
+        out << (*i);
+      
+      ++i;
+    }
+}
+
+
+// --------------- XML support -----------------
+
+XMLWriter::XMLWriter (ostream & o):
+out (o),
+open_tags(),
+decl_emitted(false),
+empty_tag(false)
+{
+}
+
+XMLWriter::~XMLWriter ()
+{
+  I (open_tags.size () == 0);  // forgot to closing some tags ?
+}
+
+void
+XMLWriter::encode(const utf8 & opq)
+{
+  for (string::const_iterator i = opq().begin(); i != opq().end(); ++i)
+  {
+     switch ((*i))
+    {
+        case '<': out << "&lt;"; break;
+        case '>': out << "&gt;"; break;
+        case '&': out << "&amp;"; break;
+        case '"': out << "&quot;"; break;
+        case '\'': out << "&apos;"; break;
+        default: out << *i; break;
+    }
+  }     
+}
+
+void
+XMLWriter::tag (const utf8 & tagname)
+{
+  if (!decl_emitted)
+  {
+     out << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?>" 
<< endl;
+     decl_emitted=true;
+  }
+
+  if (empty_tag)
+    out << ">" << endl;
+
+  out << "<" << tagname;
+  open_tags.push_back (tagname);
+  empty_tag = true; // right now, the tag is empty
+}
+
+void
+XMLWriter::end ()
+{
+  I (open_tags.size () > 0);
+  if (empty_tag)
+    out << "/>" << endl;
+  else  
+    out << "</" << open_tags.back () << ">" << endl;
+  empty_tag=false; // the containing tag is not empty1
+  open_tags.pop_back ();
+}
+
+void
+XMLWriter::attr(const utf8 & attrname, const utf8 &value)
+{
+  I (open_tags.size () > 0);
+  I (empty_tag);
+  
+  out << " " << attrname << "=\"";
+  encode(value);
+  out << "\"";
+}
+
+void
+XMLWriter::cdata (const utf8 & opq)
+{
+  I (open_tags.size () > 0);
+  
+  if (empty_tag)
+  {
+     // tag was empty until now, close it
+     out << ">" << endl;
+     empty_tag=false; 
+  }
+  encode(opq);
+}
+
+// ---------------- 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_revision_id(const revision_id & rid)
+{
+  xw.tag("id");
+  xw.cdata (rid.inner ()());   
+  xw.end();
+}
+
+void
+XMLFormatter::xml_manifest(const manifest_id & mid)
+{
+  xw.tag("manifest");
+  xw.cdata (mid.inner ()());   
+  xw.end();
+}
+
+void
+XMLFormatter::xml_file_id(const file_id & fid)
+{
+  xw.tag("file-id");
+  xw.cdata (fid.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.attr("name", i->inner ().name ());
+
+      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_ancestors(const revision_set & rev)
+{
+  for (edge_map::const_iterator e = rev.edges.begin ();
+       e != rev.edges.end (); ++e)
+    {
+      const revision_id &rid = edge_old_revision (e);
+      xw.tag("ancestor");
+      xml_revision_id(rid);
+      xw.end();
+    }
+}
+
+void
+XMLFormatter::xml_delta(const file_path& f, const change_set::delta_map &dm)
+{
+  change_set::delta_map::const_iterator i = dm.find(f);
+  if (i != dm.end())
+  {
+     xw.tag("delta");
+     xw.tag("old");
+     xml_file_id( i->second.first);
+     xw.end();
+     xw.tag("new");
+     xml_file_id( i->second.second);
+     xw.end();
+     xw.end();
+  }
+}
+
+void
+XMLFormatter::xml_changeset(const revision_set & rev)
+{
+  xml_ancestors(rev);
+
+  xw.tag("changeset");
+
+  for (edge_map::const_iterator e = rev.edges.begin ();
+       e != rev.edges.end (); ++e)
+    {
+      change_set const &cs = edge_changes (e);
+      change_set::path_rearrangement const &pr = cs.rearrangement;
+
+      std::set<file_path>::const_iterator f;
+      std::map<file_path, file_path>::const_iterator m;
+      for (f=pr.deleted_dirs.begin(); f != pr.deleted_dirs.end() ; ++f)
+      {
+         xw.tag("delete-dir");
+         xw.attr("name", (*f)());
+         xml_delta(*f, cs.deltas);
+         xw.end();
+      }
+      for (m=pr.renamed_dirs.begin(); m != pr.renamed_dirs.end() ; ++f)
+      {
+         xw.tag("rename-dir");
+         xw.attr("name", m->second());
+         xw.attr("old-name", m->first());
+         xml_delta(m->second, cs.deltas);
+         xml_delta(m->first, cs.deltas);
+         xw.end();
+      }
+      for (f=pr.added_files.begin(); f != pr.added_files.end() ; ++f)
+      {
+         xw.tag("add-file");
+         xw.attr("name", (*f)());
+         xml_delta(*f, cs.deltas);
+         xw.end();
+      }
+      for (f=pr.deleted_files.begin(); f != pr.deleted_files.end() ; ++f)
+      {
+         xw.tag("delete_file");
+         xw.attr("name", (*f)());
+         xml_delta(*f, cs.deltas);
+         xw.end();
+      }
+      for (m=pr.renamed_files.begin(); m != pr.renamed_files.end() ; ++f)
+      {
+         xw.tag("rename_file");
+         xw.attr("name", m->second());
+         xw.attr("old-name", m->first());
+         xml_delta(m->second, cs.deltas);
+         xml_delta(m->first, cs.deltas);
+         xw.end();
+      }
+      
+      set < file_path > modified_files;
+      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 ());
+      }
+      for (f=modified_files.begin(); f != modified_files.end() ; ++f)
+      {
+         xw.tag("change-file");
+         xw.attr("name", (*f)());
+         xml_delta(*f, cs.deltas);
+         xw.end();
+      }
+      
+    }
+
+    xw.end();
+}
+
+// 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");
+  xml_revision_id(rid);
+  xml_manifest(rev.new_manifest);
+  xml_certs (rid);
+  xml_changeset(rev);
+   
+  xw.end ();
+}
--- format.hh
+++ format.hh
@@ -0,0 +1,131 @@
+#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:
+  // changeset fmt strs indexing enum
+  enum FMTIDX
+  {
+     FMTIDX_REVISION,
+     FMTIDX_ANCESTORS,
+     FMTIDX_DELFILES,
+     FMTIDX_DELDIRS,
+     FMTIDX_RENFILES,
+     FMTIDX_RENDIRS,
+     FMTIDX_ADDFILES,
+     FMTIDX_MODFILES
+  };
+  
+  void print_cert (std::vector < revision < cert > >&certs, const std::string 
&name);
+  void print_changeset(const revision_set & rev);
+  void assign_format_strings(const utf8 &fmt); 
+
+  void print_cset_ancestor(const utf8 &fmtstring, const revision_id &data);
+  void print_cset_single(const utf8 &fmtstring, const std::set<file_path> 
&data);
+  void print_cset_pair(const utf8 &fmtstring, const std::map<file_path, 
file_path> &data);
+  FMTIDX decode_cset_fmtid(const std::string::const_iterator &i);
+  void handle_cset(const std::string::const_iterator &it, const revision_set & 
rev);
+
+  void handle_control(std::string::const_iterator &it, const 
std::string::const_iterator &end);
+
+private:
+  std::ostream & out; 
+  utf8 fmtstrs[8];  
+};
+
+
+// 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);
+  void end();
+  void attr(const utf8 &attrname, const utf8 &value);
+  void attr(const std::string &attrname, const utf8 &value){ 
attr(utf8(attrname), value);}
+
+private:  
+  void encode(const utf8 & opq);
+
+private:
+  std::ostream &out;
+  std::vector<utf8> open_tags;
+  bool decl_emitted;
+  bool empty_tag;
+};
+
+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_revision_id(const revision_id & rid);
+  void xml_manifest(const manifest_id & mid);
+  void xml_file_id(const file_id & fid);
+  void xml_certs (const revision_id & rid);
+  void xml_ancestors(const revision_set & rev);
+  void xml_delta(const file_path& f, const change_set::delta_map &dm);
+  void xml_changeset(const revision_set & rev);
+
+private:
+  XMLWriter xw;
+};
+
+#endif  // header guard
--- monotone.1
+++ monotone.1
@@ -284,7 +284,68 @@
 .TP
 \fB-@ \fI<file>\fP
 An alias for \fB--xargs=\fI<file>\fP
+.TP
+\fB--format \fI<format string>\fP
+Applies the specified format string at automate output.
+The format string is made by a common part, applied to each revision,
+and one or more optional changeset subformats, applied to changeset
+data.
+Special changeset placeholders put in the common expression are
+substituted by the formatted changeset informations.
+The common formatting expression is separated from the changeset ones by 
apposite markers. 
 
+Common formatting specifiers:
+
+%a : value of author certificate
+.br
+%b : value of branch certificate
+.br
+%d : value of date certificate
+.br
+%e : value of comment certificate
+.br
+%i : revision id
+.br
+%l : value of changelog certificate
+.br
+%s : value of testresult certificate
+.br
+%t : value of tag certificate
+.br
+%m : manifest id
+
+Changeset Placeholders:
+
+%P : ancestors (marker @P) 
+.br
+%A : added files (marker @A)
+.br
+%D : deleted files (marker @D)
+.br
+%E : deleted dirs (marker @E)
+.br
+%R : renamed files (marker @R)
+.br
+%C : renamed dirs (marker @C)
+.br
+%M : modified files (marker @M)
+
+The %P,%A,%D,%E,%M changeset placeholders supports the private
+specifier %f, substituted with the file/directory/ancestor name/id
+The %R and %M placeholders supports the specifiers %f, current
+file/directory name and %o, old file/directory name.
+ 
+Plus, all format string support the  \\n, \\r, \\a, \\t, \\b, \\f, \\v
+modifiers with the usual c-like meaning.
+\\\\, \\% and \\@ are used to obtain the \\, % and @ char respectively.
+
+The default format string is '%i\\n'. 
+.TP
+\fB--xml\fP
+Generate an xml document based on automate output and containing full
+revision and changeset data.
+
+
 .SH ENVIRONMENT
 .TP
 \fBEDITOR\fP
--- monotone.cc
+++ monotone.cc
@@ -47,6 +47,8 @@
 #define OPT_ARGFILE 18
 #define OPT_DATE 19
 #define OPT_AUTHOR 20
+#define OPT_FORMAT 21
+#define OPT_XML 22
 
 // main option processing and exception handling code
 
@@ -72,12 +74,12 @@
     {"ticker", 0, POPT_ARG_STRING, &argstr, OPT_TICKER, "set ticker style 
(count|dot|none) [count]", NULL},
     {"revision", 'r', POPT_ARG_STRING, &argstr, OPT_REVISION, "select revision 
id for operation", NULL},
     {"message", 'm', POPT_ARG_STRING, &argstr, OPT_MESSAGE, "set commit 
changelog message", NULL},
-    {"date", 0, POPT_ARG_STRING, &argstr, OPT_DATE, "override date/time for 
commit", NULL},
-    {"author", 0, POPT_ARG_STRING, &argstr, OPT_AUTHOR, "override author for 
commit", NULL},
     {"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},
-    { NULL, 0, 0, NULL, 0, NULL, 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 }
   };
 
 // there are 3 variables which serve as roots for our system.
@@ -189,8 +191,8 @@
       // the argv array be null-terminated.
       I(argv[argc] == NULL);
       N((rc = poptStuffArgs(con, argv)) >= 0,
-        F("weird error when stuffing arguments read from %s: %s\n")
-        % filename % poptStrerror(rc));
+       F("weird error when stuffing arguments read from %s: %s\n")
+       % filename % poptStrerror(rc));
     }
   else
     {
@@ -328,14 +330,6 @@
               app.set_message(string(argstr));
               break;
 
-            case OPT_DATE:
-              app.set_date(string(argstr));
-              break;
-
-            case OPT_AUTHOR:
-              app.set_author(string(argstr));
-              break;
-
             case OPT_ROOT:
               app.set_root(string(argstr));
               break;
@@ -346,9 +340,17 @@
 
             case OPT_ARGFILE:
               sub_argvs.push_back(my_poptStuffArgFile(ctx(),
-                                                      utf8(string(argstr))));
+                                                     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.texi
+++ monotone.texi
@@ -4219,7 +4219,6 @@
 may also give useful chatter on stderr, including warnings and error
 messages.
 
-
 @ftable @command
 @item monotone automate interface_version
 
@@ -4543,6 +4542,58 @@
 
 @end ftable
 
+Every automation command except @code{interface_version} supports two
+optional formatting specifiers:
+
address@hidden --xml}
+Generate an xml document based on automate output and containing full
+revision and changeset data.
+
address@hidden address@hidden<format string>}
+Applies the specified format string at automate output.
+The format string is made by a common part, applied to each revision,
+and one or more optional changeset subformats, applied to changeset
+data.
+Special changeset placeholders put in the common expression are
+substituted by the formatted changeset informations.
+The separate the common formatting expression is separated from  the
+changeset ones by apposite markers. 
+
+Common formatting specifiers:
address@hidden
+%a : value of author certificate
+%b : value of branch certificate
+%d : value of date certificate
+%e : value of comment certificate
+%i : revision id
+%l : value of changelog certificate
+%s : value of testresult certificate
+%t : value of tag certificate
+%m : manifest id
address@hidden verbatim
+
+Changeset Placeholders:
address@hidden
+%P : ancestors (marker @P) 
+%A : added files (marker @A)
+%D : deleted files (marker @D)
+%E : deleted dirs (marker @E)
+%R : renamed files (marker @R)
+%C : renamed dirs (marker @C)
+%M : modified files (marker @M)
address@hidden verbatim
+
+The %P,%A,%D,%E,%M changeset placeholders supports the private
+specifier %f, substituted with the file/directory/ancestor name/id
+The %R and %M placeholders supports the specifiers %f, current
+file/directory name and %o, old file/directory name.
+ 
+Plus, all format string support the  \\n, \\r, \\a, \\t, \\b, \\f, \\v
+modifiers with the usual c-like meaning.
+\\\\, \\% and \\@ are used to obtain the \\, % and @ char respectively.
+
+The default format string is '%i\\n'. 
+
 @page
 @node    RCS
 @section RCS
@@ -4550,6 +4601,8 @@
 @ftable @command
 @item monotone rcs_import @var{filename...}
 
+
+
 This command imports all the file versions in each RCS file listed in
 @var{filename...}.  These files should be raw RCS files, ending in
 @code{,v}. Monotone parses them directly and inserts them into your
@@ -6190,6 +6243,56 @@
 at the specified root directory rather than at the physical root of the
 filesystem.
 
address@hidden @address@hidden<format string>}
+Applies the specified format string at automate output.
+The format string is made by a common part, applied to each revision,
+and one or more optional changeset subformats, applied to changeset
+data.
+Special changeset placeholders put in the common expression are
+substituted by the formatted changeset informations.
+The separate the common formatting expression is separated from  the
+changeset ones by apposite markers. 
+
+Common formatting specifiers:
address@hidden
+%a : value of author certificate
+%b : value of branch certificate
+%d : value of date certificate
+%e : value of comment certificate
+%i : revision id
+%l : value of changelog certificate
+%s : value of testresult certificate
+%t : value of tag certificate
+%m : manifest id
address@hidden verbatim
+
+Changeset Placeholders:
address@hidden
+%P : ancestors (marker @P) 
+%A : added files (marker @A)
+%D : deleted files (marker @D)
+%E : deleted dirs (marker @E)
+%R : renamed files (marker @R)
+%C : renamed dirs (marker @C)
+%M : modified files (marker @M)
address@hidden verbatim
+
+The %P,%A,%D,%E,%M changeset placeholders supports the private
+specifier %f, substituted with the file/directory/ancestor name/id
+The %R and %M placeholders supports the specifiers %f, current
+file/directory name and %o, old file/directory name.
+ 
+Plus, all format string support the  \\n, \\r, \\a, \\t, \\b, \\f, \\v
+modifiers with the usual c-like meaning.
+\\\\, \\% and \\@ are used to obtain the \\, % and @ char respectively.
+
+The default format string is '%i\\n'. 
+
address@hidden @b{--xml}
+Generate an xml document based on automate output and containing full
+revision and changeset data.
+
+
 @comment TROFF INPUT: .SH ENVIRONMENT
 
 @end table

reply via email to

[Prev in Thread] Current Thread [Next in Thread]