PageRenderTime 61ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 0ms

/dotviewer/graphparse.py

https://bitbucket.org/varialus/jyjy
Python | 158 lines | 140 code | 9 blank | 9 comment | 23 complexity | f243171cf8bd00043aca6455fcf29e20 MD5 | raw file
  1. """
  2. Graph file parsing.
  3. """
  4. import sys, re
  5. import subprocess
  6. import msgstruct
  7. re_nonword = re.compile(r'([^0-9a-zA-Z_.]+)')
  8. re_plain = re.compile(r'graph [-0-9.]+ [-0-9.]+ [-0-9.]+$', re.MULTILINE)
  9. re_digraph = re.compile(r'\b(graph|digraph)\b', re.IGNORECASE)
  10. def guess_type(content):
  11. # try to see whether it is a directed graph or not,
  12. # or already a .plain file
  13. # XXX not a perfect heursitic
  14. if re_plain.match(content):
  15. return 'plain' # already a .plain file
  16. # look for the word 'graph' or 'digraph' followed by a '{'.
  17. bracepos = None
  18. lastfound = ''
  19. for match in re_digraph.finditer(content):
  20. position = match.start()
  21. if bracepos is None:
  22. bracepos = content.find('{', position)
  23. if bracepos < 0:
  24. break
  25. elif position > bracepos:
  26. break
  27. lastfound = match.group()
  28. if lastfound.lower() == 'digraph':
  29. return 'dot'
  30. if lastfound.lower() == 'graph':
  31. return 'neato'
  32. print >> sys.stderr, "Warning: could not guess file type, using 'dot'"
  33. return 'unknown'
  34. def dot2plain_graphviz(content, contenttype, use_codespeak=False):
  35. if contenttype != 'neato':
  36. cmdline = 'dot -Tplain'
  37. else:
  38. cmdline = 'neato -Tplain'
  39. #print >> sys.stderr, '* running:', cmdline
  40. close_fds = sys.platform != 'win32'
  41. p = subprocess.Popen(cmdline, shell=True, close_fds=close_fds,
  42. stdin=subprocess.PIPE, stdout=subprocess.PIPE)
  43. (child_in, child_out) = (p.stdin, p.stdout)
  44. try:
  45. import thread
  46. except ImportError:
  47. bkgndwrite(child_in, content)
  48. else:
  49. thread.start_new_thread(bkgndwrite, (child_in, content))
  50. plaincontent = child_out.read()
  51. child_out.close()
  52. if not plaincontent: # 'dot' is likely not installed
  53. raise PlainParseError("no result from running 'dot'")
  54. return plaincontent
  55. def dot2plain_codespeak(content, contenttype):
  56. import urllib
  57. request = urllib.urlencode({'dot': content})
  58. url = 'http://codespeak.net/pypy/convertdot.cgi'
  59. print >> sys.stderr, '* posting:', url
  60. g = urllib.urlopen(url, data=request)
  61. result = []
  62. while True:
  63. data = g.read(16384)
  64. if not data:
  65. break
  66. result.append(data)
  67. g.close()
  68. plaincontent = ''.join(result)
  69. # very simple-minded way to give a somewhat better error message
  70. if plaincontent.startswith('<body'):
  71. raise Exception("the dot on codespeak has very likely crashed")
  72. return plaincontent
  73. def bkgndwrite(f, data):
  74. f.write(data)
  75. f.close()
  76. class PlainParseError(Exception):
  77. pass
  78. def splitline(line, re_word = re.compile(r'[^\s"]\S*|["]["]|["].*?[^\\]["]')):
  79. result = []
  80. for word in re_word.findall(line):
  81. if word.startswith('"'):
  82. word = eval(word)
  83. result.append(word)
  84. return result
  85. def parse_plain(graph_id, plaincontent, links={}, fixedfont=False):
  86. plaincontent = plaincontent.replace('\r\n', '\n') # fix Windows EOL
  87. lines = plaincontent.splitlines(True)
  88. for i in range(len(lines)-2, -1, -1):
  89. if lines[i].endswith('\\\n'): # line ending in '\'
  90. lines[i] = lines[i][:-2] + lines[i+1]
  91. del lines[i+1]
  92. header = splitline(lines.pop(0))
  93. if header[0] != 'graph':
  94. raise PlainParseError("should start with 'graph'")
  95. yield (msgstruct.CMSG_START_GRAPH, graph_id) + tuple(header[1:])
  96. texts = []
  97. for line in lines:
  98. line = splitline(line)
  99. if line[0] == 'node':
  100. if len(line) != 11:
  101. raise PlainParseError("bad 'node'")
  102. yield (msgstruct.CMSG_ADD_NODE,) + tuple(line[1:])
  103. texts.append(line[6])
  104. if line[0] == 'edge':
  105. yield (msgstruct.CMSG_ADD_EDGE,) + tuple(line[1:])
  106. i = 4 + 2 * int(line[3])
  107. if len(line) > i + 2:
  108. texts.append(line[i])
  109. if line[0] == 'stop':
  110. break
  111. if links:
  112. # only include the links that really appear in the graph
  113. seen = {}
  114. for text in texts:
  115. for word in re_nonword.split(text):
  116. if word and word in links and word not in seen:
  117. t = links[word]
  118. if isinstance(t, tuple):
  119. statusbartext, color = t
  120. else:
  121. statusbartext = t
  122. color = None
  123. if color is not None:
  124. yield (msgstruct.CMSG_ADD_LINK, word,
  125. statusbartext, color[0], color[1], color[2])
  126. else:
  127. yield (msgstruct.CMSG_ADD_LINK, word, statusbartext)
  128. seen[word] = True
  129. if fixedfont:
  130. yield (msgstruct.CMSG_FIXED_FONT,)
  131. yield (msgstruct.CMSG_STOP_GRAPH,)
  132. def parse_dot(graph_id, content, links={}, fixedfont=False):
  133. contenttype = guess_type(content)
  134. if contenttype == 'plain':
  135. plaincontent = content
  136. else:
  137. try:
  138. plaincontent = dot2plain_graphviz(content, contenttype)
  139. except PlainParseError, e:
  140. print e
  141. # failed, retry via codespeak
  142. plaincontent = dot2plain_codespeak(content, contenttype)
  143. return list(parse_plain(graph_id, plaincontent, links, fixedfont))