Personal emacs config
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.

140 lines
4.7 KiB

  1. # Copyright (C) 2013 Marko Bencun
  2. #
  3. # This file is part of visual-regexp-steroids
  4. #
  5. # visual-regexp-steroids is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # visual-regexp-steroids is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with visual-regexp-steroids. If not, see <http://www.gnu.org/licenses/>.
  17. import sys, re, base64
  18. # True if we are running on Python 3.
  19. PY3 = sys.version_info[0] == 3
  20. if not PY3:
  21. import codecs
  22. sys.stdout = codecs.getwriter('utf-8')(sys.stdout)
  23. sys.stdin = codecs.getreader('utf-8')(sys.stdin)
  24. argv = sys.argv
  25. # not using argparse because it is not in the stdlib of python2.7/3.1.
  26. BOOL_ARGS = ('--eval', '--feedback', '--backwards')
  27. STR_ARGS = ('--regexp', '--replace', )
  28. INT_ARGS = ('--feedback-limit', )
  29. def parse_arg(arg, required=True):
  30. if not required and arg not in sys.argv:
  31. return None
  32. if arg in BOOL_ARGS:
  33. return arg in sys.argv
  34. def lookahead():
  35. try:
  36. return sys.argv[sys.argv.index(arg)+1]
  37. except ValueError:
  38. raise Exception("Argument missing: %s" % arg)
  39. if arg in STR_ARGS:
  40. return lookahead()
  41. if arg in INT_ARGS:
  42. return int(lookahead())
  43. raise Exception("Unrecognized argument: %s" % arg)
  44. def escape(s):
  45. return base64.b64encode(s.encode('utf8')).decode('utf8')
  46. def message(msg):
  47. sys.stdout.write(escape(msg))
  48. sys.stdout.write('\n')
  49. if argv[1] == 'matches':
  50. # output positions of matches
  51. regexp = parse_arg('--regexp')
  52. region = sys.stdin.read()
  53. if not PY3:
  54. regexp = regexp.decode('utf-8')
  55. feedback_limit = parse_arg('--feedback-limit', required=False)
  56. try:
  57. matches = list(re.finditer(regexp, region))
  58. if parse_arg('--backwards'):
  59. matches.reverse()
  60. for i, match in enumerate(matches):
  61. if feedback_limit is not None and i >= feedback_limit:
  62. break
  63. # show only if match length is nonzero
  64. #if match.start() != match.end():
  65. sys.stdout.write(' '.join("%s %s" % span for span in match.regs))
  66. sys.stdout.write('\n')
  67. if matches:
  68. message("%d matches" % len(matches))
  69. else:
  70. message("no match")
  71. except re.error as e:
  72. message("Invalid: %s" % e)
  73. elif argv[1] == "replace":
  74. regexp = parse_arg('--regexp')
  75. replace = parse_arg('--replace')
  76. do_eval = parse_arg('--eval')
  77. feedback = parse_arg('--feedback')
  78. feedback_limit = parse_arg('--feedback-limit', required=False)
  79. region = sys.stdin.read()
  80. if not PY3:
  81. regexp = regexp.decode('utf-8')
  82. replace = replace.decode('utf-8')
  83. if do_eval:
  84. # use \1, \2 instead of m.group(0), m.group(1), ...
  85. replace = re.sub(r'\\(\d+)', r'm.group(\1)', replace)
  86. match_counter = [0]
  87. def eval_replace(match):
  88. _globals = {}
  89. # those variables can be used in the replacement expression
  90. _locals = {
  91. 'm': match,
  92. 'i': match_counter[0],
  93. }
  94. if do_eval:
  95. replacement = (str if PY3 else unicode)(eval(replace, _globals, _locals))
  96. else:
  97. replacement = match.expand(replace)
  98. # output one replacement per line
  99. #if not feedback or match.start() != match.end():
  100. sys.stdout.write("%s %s " % match.span())
  101. sys.stdout.write(escape(replacement))
  102. sys.stdout.write('\n')
  103. match_counter[0] += 1
  104. # return does not really matter, we are using re.sub only to have a callback on each match.
  105. return match.group(0)
  106. try:
  107. # call eval_replace on each match.
  108. # we cannot loop through and replace matches one by one (regexp replacing match.group(0)) because zero-width patterns (i.e. "(A(?=B))")
  109. # are not part of match.group(0) and the regexp would not match again.
  110. re.sub(regexp, eval_replace, region, count=feedback_limit if feedback and feedback_limit else 0)
  111. # this line is only for counting matches
  112. matches = len(list(re.finditer(regexp, region)))
  113. if feedback:
  114. if matches:
  115. message("%d matches" % matches)
  116. else:
  117. message("no match")
  118. else:
  119. message("replaced %d matches" % matches)
  120. except Exception as e:
  121. message("Invalid: %s" % e)