Main program.

def main():
    """Main program."""

    global totalMethod

    formatNames = list(formats.keys())
    formatNames.sort()

    themeNames = list(themes.keys())
    themeNames.sort()

    labelNames = list(labels.keys())
    labelNames.sort()

    optparser = optparse.OptionParser(
        usage="\n\t%prog [options] [file] ...")
    optparser.add_option(
        '-o', '--output', metavar='FILE',
        type="string", dest="output",
        help="output filename [stdout]")
    optparser.add_option(
        '-n', '--node-thres', metavar='PERCENTAGE',
        type="float", dest="node_thres", default=0.5,
        help="eliminate nodes below this threshold [default: %default]")
    optparser.add_option(
        '-e', '--edge-thres', metavar='PERCENTAGE',
        type="float", dest="edge_thres", default=0.1,
        help="eliminate edges below this threshold [default: %default]")
    optparser.add_option(
        '-f', '--format',
        type="choice", choices=formatNames,
        dest="format", default="prof",
        help="profile format: %s [default: %%default]" % naturalJoin(formatNames))
    optparser.add_option(
        '--total',
        type="choice", choices=('callratios', 'callstacks'),
        dest="totalMethod", default=totalMethod,
        help="preferred method of calculating total time: callratios or callstacks (currently affects only perf format) [default: %default]")
    optparser.add_option(
        '-c', '--colormap',
        type="choice", choices=themeNames,
        dest="theme", default="color",
        help="color map: %s [default: %%default]" % naturalJoin(themeNames))
    optparser.add_option(
        '-s', '--strip',
        action="store_true",
        dest="strip", default=False,
        help="strip function parameters, template parameters, and const modifiers from demangled C++ function names")
    optparser.add_option(
        '--color-nodes-by-selftime',
        action="store_true",
        dest="color_nodes_by_selftime", default=False,
        help="color nodes by self time, rather than by total time (sum of self and descendants)")
    optparser.add_option(
        '--colour-nodes-by-selftime',
        action="store_true",
        dest="color_nodes_by_selftime",
        help=optparse.SUPPRESS_HELP)
    optparser.add_option(
        '-w', '--wrap',
        action="store_true",
        dest="wrap", default=False,
        help="wrap function names")
    optparser.add_option(
        '--show-samples',
        action="store_true",
        dest="show_samples", default=False,
        help="show function samples")
    optparser.add_option(
        '--node-label', metavar='MEASURE',
        type='choice', choices=labelNames,
        action='append',
        dest='node_labels',
        help="measurements to on show the node (can be specified multiple times): %s [default: %s]" % (
            naturalJoin(labelNames), ', '.join(defaultLabelNames)))
    # add option to create subtree or show paths
    optparser.add_option(
        '-z', '--root',
        type="string",
        dest="root", default="",
        help="prune call graph to show only descendants of specified root function")
    optparser.add_option(
        '-l', '--leaf',
        type="string",
        dest="leaf", default="",
        help="prune call graph to show only ancestors of specified leaf function")
    optparser.add_option(
        '--depth',
        type="int",
        dest="depth", default=-1,
        help="prune call graph to show only descendants or ancestors until specified depth")
    # add a new option to control skew of the colorization curve
    optparser.add_option(
        '--skew',
        type="float", dest="theme_skew", default=1.0,
        help="skew the colorization curve.  Values < 1.0 give more variety to lower percentages.  Values > 1.0 give less variety to lower percentages")
    # add option for filtering by file path
    optparser.add_option(
        '-p', '--path', action="append",
        type="string", dest="filter_paths",
        help="Filter all modules not in a specified path")
    (options, args) = optparser.parse_args(sys.argv[1:])

    if len(args) > 1 and options.format != 'pstats':
        optparser.error('incorrect number of arguments')

    try:
        theme = themes[options.theme]
    except KeyError:
        optparser.error('invalid colormap \'%s\'' % options.theme)

    # set skew on the theme now that it has been picked.
    if options.theme_skew:
        theme.skew = options.theme_skew

    totalMethod = options.totalMethod

    try:
        Format = formats[options.format]
    except KeyError:
        optparser.error('invalid format \'%s\'' % options.format)

    if Format.stdinInput:
        if not args:
            fp = sys.stdin
        elif PYTHON_3:
            fp = open(args[0], 'rt', encoding='UTF-8')
        else:
            fp = open(args[0], 'rt')
        parser = Format(fp)
    elif Format.multipleInput:
        if not args:
            optparser.error('at least a file must be specified for %s input' % options.format)
        parser = Format(*args)
    else:
        if len(args) != 1:
            optparser.error('exactly one file must be specified for %s input' % options.format)
        parser = Format(args[0])

    profile = parser.parse()

    if options.output is None:
        if PYTHON_3:
            output = open(sys.stdout.fileno(), mode='wt', encoding='UTF-8', closefd=False)
        else:
            output = sys.stdout
    else:
        if PYTHON_3:
            output = open(options.output, 'wt', encoding='UTF-8')
        else:
            output = open(options.output, 'wt')

    dot = DotWriter(output)
    dot.strip = options.strip
    dot.wrap = options.wrap

    labelNames = options.node_labels or defaultLabelNames
    dot.show_function_events = [labels[l] for l in labelNames]
    if options.show_samples:
        dot.show_function_events.append(SAMPLES)

    profile = profile
    profile.prune(options.node_thres/100.0, options.edge_thres/100.0, options.filter_paths, options.color_nodes_by_selftime)

    if options.root:
        rootIds = profile.getFunctionIds(options.root)
        if not rootIds:
            sys.stderr.write('root node ' + options.root + ' not found (might already be pruned : try -e0 -n0 flags)\n')
            sys.exit(1)
        profile.prune_root(rootIds, options.depth)
    if options.leaf:
        leafIds = profile.getFunctionIds(options.leaf)
        if not leafIds:
            sys.stderr.write('leaf node ' + options.leaf + ' not found (maybe already pruned : try -e0 -n0 flags)\n')
            sys.exit(1)
        profile.prune_leaf(leafIds, options.depth)

    dot.graph(profile, theme)