├── README.md ├── example-tree-True.png ├── example.png └── outline_to_dot.py /README.md: -------------------------------------------------------------------------------- 1 | ## Indented outline => dot 2 | 3 | Convert a simple indented (markdown inspired) outline format to dot language. 4 | 5 | Example input file: 6 | 7 | # This is a comment; blank lines get eaten. 8 | 9 | main 10 | # Default indent marker is 4 spaces. This can be changed. 11 | topic one 12 | subtopic one 13 | topic two 14 | subtopic one 15 | subtopic two 16 | 17 | From the command line, running 18 | 19 | ./outline_to_dot.py input-file.txt 20 | 21 | produces the output: 22 | 23 | digraph G { 24 | "main" -> "topic one"; 25 | "topic one" -> "subtopic one"; 26 | "main" -> "topic two"; 27 | "topic two" -> "subtopic one"; 28 | "topic two" -> "subtopic two"; 29 | } 30 | 31 | The default output is to standard out, but one can specify an output file with 32 | 33 | ./outline_to_dot.py -o output-file.dot input-filt.txt 34 | 35 | So then 36 | 37 | dot -Tpng -o output-graph.png output-file.dot 38 | 39 | produces the following graph: 40 | 41 | ![](https://raw.githubusercontent.com/notmatthancock/outline_to_dot/master/example.png) 42 | 43 | Another useful option is to maintain a strict tree structure in the output. This is specified as follows: 44 | 45 | ./outline_to_dot.py --tree=True input-file.txt 46 | 47 | Using the same input as above, this produces a slightly different graph: 48 | 49 | ![](https://raw.githubusercontent.com/notmatthancock/outline_to_dot/master/example-tree-True.png) 50 | -------------------------------------------------------------------------------- /example-tree-True.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notmatthancock/outline_to_dot/9f33d3fa8abc5f15a5ea21991a6276a355794283/example-tree-True.png -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notmatthancock/outline_to_dot/9f33d3fa8abc5f15a5ea21991a6276a355794283/example.png -------------------------------------------------------------------------------- /outline_to_dot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | outline_to_dot.py 4 | Author: Matt Hancock 5 | 6 | Convert markdown-style outlines to dot language. 7 | """ 8 | 9 | def _get_indent_level(line, indent): 10 | """ 11 | Get indentation level of string, line, based on string, indent. 12 | """ 13 | i = 0 14 | while line.startswith(indent*i): 15 | i+=1 16 | return i-1 17 | 18 | def outline_to_dot(input, indent=' '*4, tree=False): 19 | """ 20 | outline_to_dot converts a file where a tree-like hierarchy is specified 21 | by indentation to dot language. 22 | 23 | input: str 24 | string to be converted to .dot language. 25 | indent: str 26 | specify the indentation marker used. default is 4 spaces, but 27 | any string could be used ('\\t' for instance). 28 | tree: bool 29 | Default False. If True, a strict tree-like hierarchy is kept. 30 | This means duplicate node names are allowed. See examples below. 31 | 32 | Example input: 33 | # This is a comment; blank lines get eaten. 34 | 35 | main 36 | topic one 37 | subtopic one 38 | topic two 39 | subtopic one 40 | subtopic two 41 | Example output: 42 | digraph G { 43 | "main" -> "topic one"; 44 | "topic one" -> "subtopic one"; 45 | "main" -> "topic two"; 46 | "topic two" -> "subtopic one"; 47 | "topic two" -> "subtopic two"; 48 | } 49 | """ 50 | # So quotes are interpreted are part of the name: 51 | input = input.replace('\'','\\\'').replace('\"','\\\"') 52 | lines = input.split('\n') 53 | 54 | # remove comments, blank lines 55 | lines = [line for line in lines if line.strip() and not line.strip().startswith('#')] 56 | lt = len(tab) 57 | 58 | output = 'digraph G {\n' 59 | parent_stack = [] 60 | ilbase = _get_indent_level(lines[0], tab) 61 | ilprev = ilbase # initialize 62 | count = 0 63 | 64 | for line in lines: 65 | il = _get_indent_level(line, tab) 66 | if il < ilbase: 67 | raise Exception('Indentation level began at %d, but current line is at level %d'%(ilbase,il)) 68 | elif il > ilprev+1: 69 | raise Exception('Indentation level skipped. %s has no parent.' % line[lt*il:]) 70 | else: 71 | stack_element = line[lt*il:] if not tree else (count, line[lt*il:]) 72 | count += 1 73 | 74 | if il == ilprev+1: 75 | parent_stack.append(stack_element) 76 | elif il == ilprev: 77 | if parent_stack: parent_stack.pop() 78 | parent_stack.append(stack_element) 79 | else: # il < ilprev 80 | for _ in range(ilprev - il): parent_stack.pop() 81 | if parent_stack: parent_stack.pop() 82 | parent_stack.append(stack_element) 83 | 84 | if not tree: 85 | output += ' \"%s\";\n' % parent_stack[-1] 86 | else: 87 | output += ' %d[label=\"%s\"];\n' % (parent_stack[-1][0], parent_stack[-1][1]) 88 | if il is not ilbase: 89 | if not tree: 90 | output += ' \"%s\" -> \"%s\";\n' % (parent_stack[-2], parent_stack[-1]) 91 | else: 92 | output += ' %d -> %d;\n' % (parent_stack[-2][0], parent_stack[-1][0]) 93 | 94 | 95 | ilprev = il 96 | output += '}' 97 | 98 | return output 99 | 100 | def usage(): 101 | return \ 102 | """ 103 | outline_to_dot.py: 104 | 105 | Convert a simple indented outline markup to the dot language. 106 | 107 | Usage: 108 | 109 | outline_to_dot.py [options] file 110 | 111 | Options: 112 | 113 | -h, --help 114 | Print this usage summary. 115 | -i STRING, --indent=STRING 116 | Specify indentation marker. The usual markers, spaces or 117 | tab characters, must be wrapped in quotes, i.e. --indent=' ', 118 | or --indent='\\t'. Default is 4 spaces. 119 | -t {True, False}, --tree={True, False} 120 | Specify if a tree-like structure should be kept. Default is False. 121 | -o STRING, --output=STRING 122 | Output file name. Output is piped to stdout be default. 123 | """ 124 | 125 | if __name__ == '__main__': 126 | import os,sys, getopt 127 | 128 | if not os.path.exists(sys.argv[-1]): 129 | print "Invalid input file specified." 130 | print usage() 131 | sys.exit(2) 132 | 133 | try: 134 | opts, args = getopt.getopt(sys.argv[1:], 'hi:t:o:', ['help', 'indent=', 'tree=','output=']) 135 | except getopt.GetoptError: 136 | print usage() 137 | sys.exit(2) 138 | 139 | # Defaults 140 | tab = ' '*4 141 | write_output = False 142 | tree = False 143 | 144 | # Parse and set options. 145 | for o, a in opts: 146 | if o in ('-h', '--help'): 147 | print usage() 148 | sys.exit(0) 149 | if o in ('-i', '--indent'): 150 | tab = a.replace('\\t', '\t') 151 | if o in ('-t', '--tree'): 152 | tree = (a == 'True') 153 | if o in ('-o', '--output'): 154 | write_output = True 155 | output_name = a 156 | 157 | with open(sys.argv[-1], 'r') as f: 158 | output = outline_to_dot(f.read(), tab, tree) 159 | if write_output: 160 | with open(output_name, 'w') as f: 161 | f.write(output) 162 | else: 163 | print output 164 | --------------------------------------------------------------------------------