My dotfiles
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

96 lines
3.0 KiB

  1. # Copyright (c) Microsoft Corporation. All rights reserved.
  2. # Licensed under the MIT License.
  3. import ast
  4. import json
  5. import sys
  6. class Visitor(ast.NodeVisitor):
  7. def __init__(self):
  8. self.symbols = {"classes": [], "methods": [], "functions": []}
  9. def visit_Module(self, node):
  10. self.visitChildren(node)
  11. def visitChildren(self, node, namespace=""):
  12. for child in node.body:
  13. if isinstance(child, ast.FunctionDef):
  14. self.visitDef(child, namespace)
  15. if isinstance(child, ast.ClassDef):
  16. self.visitClassDef(child, namespace)
  17. try:
  18. if isinstance(child, ast.AsyncFunctionDef):
  19. self.visitDef(child, namespace)
  20. except Exception:
  21. pass
  22. def visitDef(self, node, namespace=""):
  23. end_position = self.getEndPosition(node)
  24. symbol = "functions" if namespace == "" else "methods"
  25. self.symbols[symbol].append(self.getDataObject(node, namespace))
  26. def visitClassDef(self, node, namespace=""):
  27. end_position = self.getEndPosition(node)
  28. self.symbols['classes'].append(self.getDataObject(node, namespace))
  29. if len(namespace) > 0:
  30. namespace = "{0}::{1}".format(namespace, node.name)
  31. else:
  32. namespace = node.name
  33. self.visitChildren(node, namespace)
  34. def getDataObject(self, node, namespace=""):
  35. end_position = self.getEndPosition(node)
  36. return {
  37. "namespace": namespace,
  38. "name": node.name,
  39. "range": {
  40. "start": {
  41. "line": node.lineno - 1,
  42. "character": node.col_offset
  43. },
  44. "end": {
  45. "line": end_position[0],
  46. "character": end_position[1]
  47. }
  48. }
  49. }
  50. def getEndPosition(self, node):
  51. if not hasattr(node, 'body') or len(node.body) == 0:
  52. return (node.lineno - 1, node.col_offset)
  53. return self.getEndPosition(node.body[-1])
  54. def provide_symbols(source):
  55. """Provides a list of all symbols in provided code.
  56. The list comprises of 3-item tuples that contain the starting line number,
  57. ending line number and whether the statement is a single line.
  58. """
  59. tree = ast.parse(source)
  60. visitor = Visitor()
  61. visitor.visit(tree)
  62. sys.stdout.write(json.dumps(visitor.symbols))
  63. sys.stdout.flush()
  64. if __name__ == "__main__":
  65. if len(sys.argv) == 3:
  66. contents = sys.argv[2]
  67. else:
  68. with open(sys.argv[1], "r") as source:
  69. contents = source.read()
  70. try:
  71. default_encoding = sys.getdefaultencoding()
  72. encoded_contents = contents.encode(default_encoding, 'surrogateescape')
  73. contents = encoded_contents.decode(default_encoding, 'replace')
  74. except (UnicodeError, LookupError):
  75. pass
  76. if isinstance(contents, bytes):
  77. contents = contents.decode('utf8')
  78. provide_symbols(contents)