graphlog: extract ascii drawing code into graphmod
authorPatrick Mezard <patrick@mezard.eu>
Wed, 11 Jul 2012 17:13:39 +0200
changeset 17179 0849d725e2f9
parent 17178 8308f6284640
child 17180 ae0629161090
graphlog: extract ascii drawing code into graphmod
hgext/graphlog.py
mercurial/graphmod.py
--- a/hgext/graphlog.py	Sun Jun 24 18:11:52 2012 +0200
+++ b/hgext/graphlog.py	Wed Jul 11 17:13:39 2012 +0200
@@ -21,209 +21,6 @@
 command = cmdutil.command(cmdtable)
 testedwith = 'internal'
 
-def asciiedges(type, char, lines, seen, rev, parents):
-    """adds edge info to changelog DAG walk suitable for ascii()"""
-    if rev not in seen:
-        seen.append(rev)
-    nodeidx = seen.index(rev)
-
-    knownparents = []
-    newparents = []
-    for parent in parents:
-        if parent in seen:
-            knownparents.append(parent)
-        else:
-            newparents.append(parent)
-
-    ncols = len(seen)
-    nextseen = seen[:]
-    nextseen[nodeidx:nodeidx + 1] = newparents
-    edges = [(nodeidx, nextseen.index(p)) for p in knownparents]
-
-    while len(newparents) > 2:
-        # ascii() only knows how to add or remove a single column between two
-        # calls. Nodes with more than two parents break this constraint so we
-        # introduce intermediate expansion lines to grow the active node list
-        # slowly.
-        edges.append((nodeidx, nodeidx))
-        edges.append((nodeidx, nodeidx + 1))
-        nmorecols = 1
-        yield (type, char, lines, (nodeidx, edges, ncols, nmorecols))
-        char = '\\'
-        lines = []
-        nodeidx += 1
-        ncols += 1
-        edges = []
-        del newparents[0]
-
-    if len(newparents) > 0:
-        edges.append((nodeidx, nodeidx))
-    if len(newparents) > 1:
-        edges.append((nodeidx, nodeidx + 1))
-    nmorecols = len(nextseen) - ncols
-    seen[:] = nextseen
-    yield (type, char, lines, (nodeidx, edges, ncols, nmorecols))
-
-def _fixlongrightedges(edges):
-    for (i, (start, end)) in enumerate(edges):
-        if end > start:
-            edges[i] = (start, end + 1)
-
-def _getnodelineedgestail(
-        node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail):
-    if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0:
-        # Still going in the same non-vertical direction.
-        if n_columns_diff == -1:
-            start = max(node_index + 1, p_node_index)
-            tail = ["|", " "] * (start - node_index - 1)
-            tail.extend(["/", " "] * (n_columns - start))
-            return tail
-        else:
-            return ["\\", " "] * (n_columns - node_index - 1)
-    else:
-        return ["|", " "] * (n_columns - node_index - 1)
-
-def _drawedges(edges, nodeline, interline):
-    for (start, end) in edges:
-        if start == end + 1:
-            interline[2 * end + 1] = "/"
-        elif start == end - 1:
-            interline[2 * start + 1] = "\\"
-        elif start == end:
-            interline[2 * start] = "|"
-        else:
-            if 2 * end >= len(nodeline):
-                continue
-            nodeline[2 * end] = "+"
-            if start > end:
-                (start, end) = (end, start)
-            for i in range(2 * start + 1, 2 * end):
-                if nodeline[i] != "+":
-                    nodeline[i] = "-"
-
-def _getpaddingline(ni, n_columns, edges):
-    line = []
-    line.extend(["|", " "] * ni)
-    if (ni, ni - 1) in edges or (ni, ni) in edges:
-        # (ni, ni - 1)      (ni, ni)
-        # | | | |           | | | |
-        # +---o |           | o---+
-        # | | c |           | c | |
-        # | |/ /            | |/ /
-        # | | |             | | |
-        c = "|"
-    else:
-        c = " "
-    line.extend([c, " "])
-    line.extend(["|", " "] * (n_columns - ni - 1))
-    return line
-
-def asciistate():
-    """returns the initial value for the "state" argument to ascii()"""
-    return [0, 0]
-
-def ascii(ui, state, type, char, text, coldata):
-    """prints an ASCII graph of the DAG
-
-    takes the following arguments (one call per node in the graph):
-
-      - ui to write to
-      - Somewhere to keep the needed state in (init to asciistate())
-      - Column of the current node in the set of ongoing edges.
-      - Type indicator of node data, usually 'C' for changesets.
-      - Payload: (char, lines):
-        - Character to use as node's symbol.
-        - List of lines to display as the node's text.
-      - Edges; a list of (col, next_col) indicating the edges between
-        the current node and its parents.
-      - Number of columns (ongoing edges) in the current revision.
-      - The difference between the number of columns (ongoing edges)
-        in the next revision and the number of columns (ongoing edges)
-        in the current revision. That is: -1 means one column removed;
-        0 means no columns added or removed; 1 means one column added.
-    """
-
-    idx, edges, ncols, coldiff = coldata
-    assert -2 < coldiff < 2
-    if coldiff == -1:
-        # Transform
-        #
-        #     | | |        | | |
-        #     o | |  into  o---+
-        #     |X /         |/ /
-        #     | |          | |
-        _fixlongrightedges(edges)
-
-    # add_padding_line says whether to rewrite
-    #
-    #     | | | |        | | | |
-    #     | o---+  into  | o---+
-    #     |  / /         |   | |  # <--- padding line
-    #     o | |          |  / /
-    #                    o | |
-    add_padding_line = (len(text) > 2 and coldiff == -1 and
-                        [x for (x, y) in edges if x + 1 < y])
-
-    # fix_nodeline_tail says whether to rewrite
-    #
-    #     | | o | |        | | o | |
-    #     | | |/ /         | | |/ /
-    #     | o | |    into  | o / /   # <--- fixed nodeline tail
-    #     | |/ /           | |/ /
-    #     o | |            o | |
-    fix_nodeline_tail = len(text) <= 2 and not add_padding_line
-
-    # nodeline is the line containing the node character (typically o)
-    nodeline = ["|", " "] * idx
-    nodeline.extend([char, " "])
-
-    nodeline.extend(
-        _getnodelineedgestail(idx, state[1], ncols, coldiff,
-                              state[0], fix_nodeline_tail))
-
-    # shift_interline is the line containing the non-vertical
-    # edges between this entry and the next
-    shift_interline = ["|", " "] * idx
-    if coldiff == -1:
-        n_spaces = 1
-        edge_ch = "/"
-    elif coldiff == 0:
-        n_spaces = 2
-        edge_ch = "|"
-    else:
-        n_spaces = 3
-        edge_ch = "\\"
-    shift_interline.extend(n_spaces * [" "])
-    shift_interline.extend([edge_ch, " "] * (ncols - idx - 1))
-
-    # draw edges from the current node to its parents
-    _drawedges(edges, nodeline, shift_interline)
-
-    # lines is the list of all graph lines to print
-    lines = [nodeline]
-    if add_padding_line:
-        lines.append(_getpaddingline(idx, ncols, edges))
-    lines.append(shift_interline)
-
-    # make sure that there are as many graph lines as there are
-    # log strings
-    while len(text) < len(lines):
-        text.append("")
-    if len(lines) < len(text):
-        extra_interline = ["|", " "] * (ncols + coldiff)
-        while len(lines) < len(text):
-            lines.append(extra_interline)
-
-    # print lines
-    indentation_level = max(ncols, ncols + coldiff)
-    for (line, logstr) in zip(lines, text):
-        ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr)
-        ui.write(ln.rstrip() + '\n')
-
-    # ... and start over
-    state[0] = coldiff
-    state[1] = idx
-
 def _checkunsupportedflags(pats, opts):
     for op in ["newest_first"]:
         if op in opts and opts[op]:
@@ -441,7 +238,7 @@
 
 def generate(ui, dag, displayer, showparents, edgefn, getrenamed=None,
              filematcher=None):
-    seen, state = [], asciistate()
+    seen, state = [], graphmod.asciistate()
     for rev, type, ctx, parents in dag:
         char = 'o'
         if ctx.node() in showparents:
@@ -465,7 +262,7 @@
         displayer.flush(rev)
         edges = edgefn(type, char, lines, seen, rev, parents)
         for type, char, lines, coldata in edges:
-            ascii(ui, state, type, char, lines, coldata)
+            graphmod.ascii(ui, state, type, char, lines, coldata)
     displayer.close()
 
 @command('glog',
@@ -516,8 +313,8 @@
         getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
     displayer = show_changeset(ui, repo, opts, buffered=True)
     showparents = [ctx.node() for ctx in repo[None].parents()]
-    generate(ui, revdag, displayer, showparents, asciiedges, getrenamed,
-             filematcher)
+    generate(ui, revdag, displayer, showparents, graphmod.asciiedges,
+             getrenamed, filematcher)
 
 def graphrevs(repo, nodes, opts):
     limit = cmdutil.loglimit(opts)
@@ -544,7 +341,7 @@
     revdag = graphrevs(repo, o, opts)
     displayer = show_changeset(ui, repo, opts, buffered=True)
     showparents = [ctx.node() for ctx in repo[None].parents()]
-    generate(ui, revdag, displayer, showparents, asciiedges)
+    generate(ui, revdag, displayer, showparents, graphmod.asciiedges)
 
 def gincoming(ui, repo, source="default", **opts):
     """show the incoming changesets alongside an ASCII revision graph
@@ -562,7 +359,7 @@
     def display(other, chlist, displayer):
         revdag = graphrevs(other, chlist, opts)
         showparents = [ctx.node() for ctx in repo[None].parents()]
-        generate(ui, revdag, displayer, showparents, asciiedges)
+        generate(ui, revdag, displayer, showparents, graphmod.asciiedges)
 
     hg._incoming(display, subreporecurse, ui, repo, source, opts, buffered=True)
 
--- a/mercurial/graphmod.py	Sun Jun 24 18:11:52 2012 +0200
+++ b/mercurial/graphmod.py	Wed Jul 11 17:13:39 2012 +0200
@@ -163,3 +163,206 @@
                 pending.update([p for p in cl.parentrevs(r)])
             seen.add(r)
     return sorted(kept)
+
+def asciiedges(type, char, lines, seen, rev, parents):
+    """adds edge info to changelog DAG walk suitable for ascii()"""
+    if rev not in seen:
+        seen.append(rev)
+    nodeidx = seen.index(rev)
+
+    knownparents = []
+    newparents = []
+    for parent in parents:
+        if parent in seen:
+            knownparents.append(parent)
+        else:
+            newparents.append(parent)
+
+    ncols = len(seen)
+    nextseen = seen[:]
+    nextseen[nodeidx:nodeidx + 1] = newparents
+    edges = [(nodeidx, nextseen.index(p)) for p in knownparents]
+
+    while len(newparents) > 2:
+        # ascii() only knows how to add or remove a single column between two
+        # calls. Nodes with more than two parents break this constraint so we
+        # introduce intermediate expansion lines to grow the active node list
+        # slowly.
+        edges.append((nodeidx, nodeidx))
+        edges.append((nodeidx, nodeidx + 1))
+        nmorecols = 1
+        yield (type, char, lines, (nodeidx, edges, ncols, nmorecols))
+        char = '\\'
+        lines = []
+        nodeidx += 1
+        ncols += 1
+        edges = []
+        del newparents[0]
+
+    if len(newparents) > 0:
+        edges.append((nodeidx, nodeidx))
+    if len(newparents) > 1:
+        edges.append((nodeidx, nodeidx + 1))
+    nmorecols = len(nextseen) - ncols
+    seen[:] = nextseen
+    yield (type, char, lines, (nodeidx, edges, ncols, nmorecols))
+
+def _fixlongrightedges(edges):
+    for (i, (start, end)) in enumerate(edges):
+        if end > start:
+            edges[i] = (start, end + 1)
+
+def _getnodelineedgestail(
+        node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail):
+    if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0:
+        # Still going in the same non-vertical direction.
+        if n_columns_diff == -1:
+            start = max(node_index + 1, p_node_index)
+            tail = ["|", " "] * (start - node_index - 1)
+            tail.extend(["/", " "] * (n_columns - start))
+            return tail
+        else:
+            return ["\\", " "] * (n_columns - node_index - 1)
+    else:
+        return ["|", " "] * (n_columns - node_index - 1)
+
+def _drawedges(edges, nodeline, interline):
+    for (start, end) in edges:
+        if start == end + 1:
+            interline[2 * end + 1] = "/"
+        elif start == end - 1:
+            interline[2 * start + 1] = "\\"
+        elif start == end:
+            interline[2 * start] = "|"
+        else:
+            if 2 * end >= len(nodeline):
+                continue
+            nodeline[2 * end] = "+"
+            if start > end:
+                (start, end) = (end, start)
+            for i in range(2 * start + 1, 2 * end):
+                if nodeline[i] != "+":
+                    nodeline[i] = "-"
+
+def _getpaddingline(ni, n_columns, edges):
+    line = []
+    line.extend(["|", " "] * ni)
+    if (ni, ni - 1) in edges or (ni, ni) in edges:
+        # (ni, ni - 1)      (ni, ni)
+        # | | | |           | | | |
+        # +---o |           | o---+
+        # | | c |           | c | |
+        # | |/ /            | |/ /
+        # | | |             | | |
+        c = "|"
+    else:
+        c = " "
+    line.extend([c, " "])
+    line.extend(["|", " "] * (n_columns - ni - 1))
+    return line
+
+def asciistate():
+    """returns the initial value for the "state" argument to ascii()"""
+    return [0, 0]
+
+def ascii(ui, state, type, char, text, coldata):
+    """prints an ASCII graph of the DAG
+
+    takes the following arguments (one call per node in the graph):
+
+      - ui to write to
+      - Somewhere to keep the needed state in (init to asciistate())
+      - Column of the current node in the set of ongoing edges.
+      - Type indicator of node data, usually 'C' for changesets.
+      - Payload: (char, lines):
+        - Character to use as node's symbol.
+        - List of lines to display as the node's text.
+      - Edges; a list of (col, next_col) indicating the edges between
+        the current node and its parents.
+      - Number of columns (ongoing edges) in the current revision.
+      - The difference between the number of columns (ongoing edges)
+        in the next revision and the number of columns (ongoing edges)
+        in the current revision. That is: -1 means one column removed;
+        0 means no columns added or removed; 1 means one column added.
+    """
+
+    idx, edges, ncols, coldiff = coldata
+    assert -2 < coldiff < 2
+    if coldiff == -1:
+        # Transform
+        #
+        #     | | |        | | |
+        #     o | |  into  o---+
+        #     |X /         |/ /
+        #     | |          | |
+        _fixlongrightedges(edges)
+
+    # add_padding_line says whether to rewrite
+    #
+    #     | | | |        | | | |
+    #     | o---+  into  | o---+
+    #     |  / /         |   | |  # <--- padding line
+    #     o | |          |  / /
+    #                    o | |
+    add_padding_line = (len(text) > 2 and coldiff == -1 and
+                        [x for (x, y) in edges if x + 1 < y])
+
+    # fix_nodeline_tail says whether to rewrite
+    #
+    #     | | o | |        | | o | |
+    #     | | |/ /         | | |/ /
+    #     | o | |    into  | o / /   # <--- fixed nodeline tail
+    #     | |/ /           | |/ /
+    #     o | |            o | |
+    fix_nodeline_tail = len(text) <= 2 and not add_padding_line
+
+    # nodeline is the line containing the node character (typically o)
+    nodeline = ["|", " "] * idx
+    nodeline.extend([char, " "])
+
+    nodeline.extend(
+        _getnodelineedgestail(idx, state[1], ncols, coldiff,
+                              state[0], fix_nodeline_tail))
+
+    # shift_interline is the line containing the non-vertical
+    # edges between this entry and the next
+    shift_interline = ["|", " "] * idx
+    if coldiff == -1:
+        n_spaces = 1
+        edge_ch = "/"
+    elif coldiff == 0:
+        n_spaces = 2
+        edge_ch = "|"
+    else:
+        n_spaces = 3
+        edge_ch = "\\"
+    shift_interline.extend(n_spaces * [" "])
+    shift_interline.extend([edge_ch, " "] * (ncols - idx - 1))
+
+    # draw edges from the current node to its parents
+    _drawedges(edges, nodeline, shift_interline)
+
+    # lines is the list of all graph lines to print
+    lines = [nodeline]
+    if add_padding_line:
+        lines.append(_getpaddingline(idx, ncols, edges))
+    lines.append(shift_interline)
+
+    # make sure that there are as many graph lines as there are
+    # log strings
+    while len(text) < len(lines):
+        text.append("")
+    if len(lines) < len(text):
+        extra_interline = ["|", " "] * (ncols + coldiff)
+        while len(lines) < len(text):
+            lines.append(extra_interline)
+
+    # print lines
+    indentation_level = max(ncols, ncols + coldiff)
+    for (line, logstr) in zip(lines, text):
+        ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr)
+        ui.write(ln.rstrip() + '\n')
+
+    # ... and start over
+    state[0] = coldiff
+    state[1] = idx