My dotfiles
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

134 rindas
4.9 KiB

  1. # Copyright (c) Microsoft Corporation. All rights reserved.
  2. # Licensed under the MIT License.
  3. import ast
  4. import io
  5. import operator
  6. import os
  7. import sys
  8. import token
  9. import tokenize
  10. class Visitor(ast.NodeVisitor):
  11. def __init__(self, lines):
  12. self._lines = lines
  13. self.line_numbers_with_nodes = set()
  14. self.line_numbers_with_statements = []
  15. def generic_visit(self, node):
  16. if hasattr(node, 'col_offset') and hasattr(node, 'lineno') and node.col_offset == 0:
  17. self.line_numbers_with_nodes.add(node.lineno)
  18. if isinstance(node, ast.stmt):
  19. self.line_numbers_with_statements.append(node.lineno)
  20. ast.NodeVisitor.generic_visit(self, node)
  21. def _tokenize(source):
  22. """Tokenize Python source code."""
  23. # Using an undocumented API as the documented one in Python 2.7 does not work as needed
  24. # cross-version.
  25. if sys.version_info < (3,) and isinstance(source, str):
  26. source = source.decode()
  27. return tokenize.generate_tokens(io.StringIO(source).readline)
  28. def _indent_size(line):
  29. for index, char in enumerate(line):
  30. if not char.isspace():
  31. return index
  32. def _get_global_statement_blocks(source, lines):
  33. """Return a list of all global statement blocks.
  34. The list comprises of 3-item tuples that contain the starting line number,
  35. ending line number and whether the statement is a single line.
  36. """
  37. tree = ast.parse(source)
  38. visitor = Visitor(lines)
  39. visitor.visit(tree)
  40. statement_ranges = []
  41. for index, line_number in enumerate(visitor.line_numbers_with_statements):
  42. remaining_line_numbers = visitor.line_numbers_with_statements[index+1:]
  43. end_line_number = len(lines) if len(remaining_line_numbers) == 0 else min(remaining_line_numbers) - 1
  44. current_statement_is_oneline = line_number == end_line_number
  45. if len(statement_ranges) == 0:
  46. statement_ranges.append((line_number, end_line_number, current_statement_is_oneline))
  47. continue
  48. previous_statement = statement_ranges[-1]
  49. previous_statement_is_oneline = previous_statement[2]
  50. if previous_statement_is_oneline and current_statement_is_oneline:
  51. statement_ranges[-1] = previous_statement[0], end_line_number, True
  52. else:
  53. statement_ranges.append((line_number, end_line_number, current_statement_is_oneline))
  54. return statement_ranges
  55. def normalize_lines(source):
  56. """Normalize blank lines for sending to the terminal.
  57. Blank lines within a statement block are removed to prevent the REPL
  58. from thinking the block is finished. Newlines are added to separate
  59. top-level statements so that the REPL does not think there is a syntax
  60. error.
  61. """
  62. lines = source.splitlines(False)
  63. # If we have two blank lines, then add two blank lines.
  64. # Do not trim the spaces, if we have blank lines with spaces, its possible
  65. # we have indented code.
  66. if (len(lines) > 1 and len(''.join(lines[-2:])) == 0) \
  67. or source.endswith(('\n\n', '\r\n\r\n')):
  68. trailing_newline = '\n' * 2
  69. # Find out if we have any trailing blank lines
  70. elif len(lines[-1].strip()) == 0 or source.endswith(('\n', '\r\n')):
  71. trailing_newline = '\n'
  72. else:
  73. trailing_newline = ''
  74. # Step 1: Remove empty lines.
  75. tokens = _tokenize(source)
  76. newlines_indexes_to_remove = (spos[0] for (toknum, tokval, spos, epos, line) in tokens
  77. if len(line.strip()) == 0
  78. and token.tok_name[toknum] == 'NL'
  79. and spos[0] == epos[0])
  80. for line_number in reversed(list(newlines_indexes_to_remove)):
  81. del lines[line_number-1]
  82. # Step 2: Add blank lines between each global statement block.
  83. # A consequtive single lines blocks of code will be treated as a single statement,
  84. # just to ensure we do not unnecessarily add too many blank lines.
  85. source = '\n'.join(lines)
  86. tokens = _tokenize(source)
  87. dedent_indexes = (spos[0] for (toknum, tokval, spos, epos, line) in tokens
  88. if toknum == token.DEDENT and _indent_size(line) == 0)
  89. global_statement_ranges = _get_global_statement_blocks(source, lines)
  90. start_positions = map(operator.itemgetter(0), reversed(global_statement_ranges))
  91. for line_number in filter(lambda x: x > 1, start_positions):
  92. lines.insert(line_number-1, '')
  93. sys.stdout.write('\n'.join(lines) + trailing_newline)
  94. sys.stdout.flush()
  95. if __name__ == '__main__':
  96. contents = sys.argv[1]
  97. try:
  98. default_encoding = sys.getdefaultencoding()
  99. encoded_contents = contents.encode(default_encoding, 'surrogateescape')
  100. contents = encoded_contents.decode(default_encoding, 'replace')
  101. except (UnicodeError, LookupError):
  102. pass
  103. if isinstance(contents, bytes):
  104. contents = contents.decode('utf8')
  105. normalize_lines(contents)