No Description
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.

commenter.py 5.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. # Copyright (C) 2017 Michał Góral
  2. #
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. import sys
  16. import re
  17. import argparse
  18. import enum
  19. import collections
  20. import contextlib
  21. def eprint(*args, **kwargs):
  22. print(*args, file=sys.stderr, **kwargs)
  23. class Actions(enum.Enum):
  24. comment = 1
  25. uncomment = 2
  26. pass_ = 3
  27. class BlockStack:
  28. _stack_elem = collections.namedtuple("StackElement", ['prefix', 'action'])
  29. def __init__(self):
  30. self._stack = []
  31. def push(self, prefix, action):
  32. self._stack.append(self._stack_elem(prefix, action))
  33. def pop(self):
  34. return self._stack.pop()
  35. @property
  36. def action(self):
  37. return self._stack[-1].action
  38. @property
  39. def prefix(self):
  40. return self._stack[-1].prefix
  41. def __len__(self):
  42. return len(self._stack)
  43. def _tokenize(s, defines):
  44. tokens = {}
  45. for d in defines:
  46. tokens[d] = 'True'
  47. tokens['&&'] = 'and'
  48. tokens['||'] = 'or'
  49. tokens['('] = '('
  50. tokens[')'] = ')'
  51. tokens['!'] = ' not'
  52. s = s.replace('(', ' ( ').replace(')', ' ) ').replace('!', ' ! ')
  53. return [tokens.get(elem, 'False') for elem in s.split()]
  54. def find(lst, what, start=0):
  55. return [i for i,it in enumerate(lst) if it == what and i >= start]
  56. def find_closing_parentheses(tokens, start=0):
  57. nested_open = 0
  58. for i, token in enumerate(tokens[start:]):
  59. if token == '(':
  60. nested_open += 1
  61. elif token == ')':
  62. if nested_open > 0:
  63. nested_open -= 1
  64. else:
  65. return i + start
  66. eprint("Missing closing parentheses")
  67. sys.exit(1)
  68. def eval_tokenized(tokens):
  69. if not tokens:
  70. return False
  71. return eval(" ".join(tokens))
  72. def should_comment(expr, defines):
  73. tokens = _tokenize(expr, defines)
  74. try:
  75. result = eval_tokenized(tokens)
  76. except SyntaxError as e:
  77. eprint("Syntax error: %s" % e)
  78. eprint(" in line: %s " % expr)
  79. sys.exit(1)
  80. if result is True:
  81. return Actions.uncomment
  82. elif result is False:
  83. return Actions.comment
  84. def comment(line, prefix, action):
  85. if action == Actions.comment:
  86. if line.strip().startswith(prefix):
  87. return line
  88. return re.sub(r'(\S)', r'%s\1' % prefix, line, 1)
  89. elif action == Actions.uncomment:
  90. pattern = r'^(\s*)%s' % prefix
  91. return re.sub(pattern, r'\1', line)
  92. def parse(lines, args):
  93. begin = re.compile(r'^\s*(?P<prefix>\S+\s*)comm:begin\s+(?P<pattern>[\w!|&() ]+)')
  94. end = re.compile(r'^\s*(?P<prefix>\S+\s*)comm:end')
  95. stack = BlockStack()
  96. stack.push(prefix=None, action=Actions.pass_)
  97. for line in lines:
  98. bm = begin.match(line)
  99. if bm is not None:
  100. if stack.action == Actions.comment:
  101. stack.push(bm.group('prefix'), Actions.comment)
  102. else:
  103. stack.push(bm.group('prefix'),
  104. should_comment(bm.group('pattern'), args.defines))
  105. yield line
  106. continue
  107. em = end.match(line)
  108. if em is not None:
  109. stack.pop()
  110. yield line
  111. continue
  112. if args.force_uncomment is True:
  113. yield comment(line, stack.prefix, Actions.uncomment)
  114. elif stack.action != Actions.pass_:
  115. yield comment(line, stack.prefix, stack.action)
  116. else:
  117. yield line
  118. if len(stack) > 1:
  119. eprint("unterminated block")
  120. @contextlib.contextmanager
  121. def read_input(infile):
  122. ''' Reads input either from file or stdin.'''
  123. if infile is None:
  124. yield sys.stdin
  125. else:
  126. try:
  127. f = open(infile)
  128. except FileNotFoundError:
  129. eprint("File not found: %s" % infile)
  130. sys.exit(1)
  131. try:
  132. yield f
  133. finally:
  134. f.close()
  135. def parse_args():
  136. parser = argparse.ArgumentParser()
  137. parser.add_argument('-i', dest='infile',
  138. help='input file. By default input is read from stdin')
  139. parser.add_argument('-D', dest='defines', action='append', default=[],
  140. help='list of block names that should be uncommented')
  141. parser.add_argument('-u', '--force-uncomment', action='store_true',
  142. help='skip checking selected defines and force '
  143. 'uncommenting all sections')
  144. args = parser.parse_args()
  145. return args
  146. def main():
  147. args = parse_args()
  148. with read_input(args.infile) as f:
  149. for line in parse(f, args):
  150. sys.stdout.write(line)
  151. return 0