edgewall/trac

View on GitHub
contrib/workflow/workflow_parser.py

Summary

Maintainability
A
55 mins
Test Coverage
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007-2023 Edgewall Software
# Copyright (C) 2007 Eli Carter <retracile@gmail.com>
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at https://trac.edgewall.org/wiki/TracLicense.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at https://trac.edgewall.org/.

import sys
import getopt
import locale

import pkg_resources
pkg_resources.require('Trac')

from trac.config import Configuration
from trac.ticket.default_workflow import parse_workflow_config

_debug = False
def debug(s):
    if _debug:
        sys.stderr.write(s)


def readconfig(filename):
    """Returns a list of raw config options"""
    config = Configuration(filename)
    rawactions = list(config.options('ticket-workflow'))
    debug("%s\n" % str(rawactions))
    if not rawactions:
        sys.stderr.write("ERROR: You don't seem to have a [ticket-workflow] "
                         "section.\n")
        sys.exit(1)
    return rawactions


class ColorScheme(object):
    # cyan, yellow are too light in color
    colors = ['black', 'blue', 'red', 'green', 'purple', 'orange',
              'darkgreen']

    def __init__(self):
        self.mapping = {}
        self.coloruse = [0] * len(self.colors)

    def get_color(self, name):
        try:
            colornum = self.mapping[name]
        except KeyError:
            self.mapping[name] = colornum = self.pick_color(name)
        self.coloruse[colornum] += 1
        return self.colors[colornum]

    def pick_color(self, name):
        """Pick a color that has not been used much so far."""
        return self.coloruse.index(min(self.coloruse))


def actions2graphviz(actions, show_ops=False, show_perms=False):
    """Returns a list of lines to be fed to graphviz."""
    # The size value makes it easier to create a useful printout.
    color_scheme = ColorScheme()
    digraph_lines = ["""
digraph G {
  center=1
  size="10,8"
  { rank=source; new [ shape=invtrapezium ] }
  { rank=sink; closed [ shape=trapezium ] }
    """]
    for action, attributes in actions.items():
        label = [attributes['label']]
        if show_ops:
            label += attributes['operations']
        if show_perms:
            label += attributes['permissions']
        if 'set_resolution' in attributes:
            label += ['(' + attributes['set_resolution'] + ')']
        for oldstate in attributes['oldstates']:
            color = color_scheme.get_color(attributes['label'])
            digraph_lines.append(
                '  "%s" -> "%s" [label="%s" color=%s fontcolor=%s]' %
                (oldstate, attributes['newstate'], '\\n'.join(label), color,
                 color))
    digraph_lines.append('}')
    return digraph_lines


def main(filename, output, show_ops=False, show_perms=False):
    # Read in the config
    rawactions = readconfig(filename)

    # Parse the config information
    actions = parse_workflow_config(rawactions)

    # Convert to graphviz
    digraph_lines = actions2graphviz(actions, show_ops, show_perms)

    # And output
    output.write('\n'.join(digraph_lines))


def usage(output):
    output.write('workflow_parser [options] configfile.ini [output.dot]\n'
                 '-h --help shows this message\n'
                 '-o --operations include operations in the graph\n'
                 '-p --permissions include permissions in the graph\n')


if __name__ == '__main__':
    show_ops = False
    show_perms = False
    try:
        opts, args = getopt.getopt(sys.argv[1:], 'hop', ['help', 'operations',
                                                         'permissions'])
    except getopt.GetoptError:
        usage(sys.stderr)
        sys.exit(1)

    for option, argument in opts:
        if option in ('-h', '--help'):
            usage(sys.stdout)
            sys.exit(0)
        elif option in ('-o', '--operations'):
            show_ops = True
        elif option in ('-p', '--permissions'):
            show_perms = True

    if not args:
        sys.stderr.write('Syntax error: config filename required.\n')
        usage(sys.stderr)
        sys.stderr.flush()
        sys.exit(1)
    ini_filename = args[0]
    if len(args) > 1:
        output = open(args[1], 'w', encoding='utf-8')
    else:
        output = sys.stdout

    main(ini_filename, output, show_ops, show_perms)