Using Refex as a Library

Alright, your one-line shell script change isn’t enough anymore. What’s next?

Create a searcher

The “thing that does a search and a replace” is a searcher: any subclass of refex.search.AbstractSearcher. You likely want PyExprRewritingSearcher, and, for the replacement, an instance of refex.python.syntactic_template.PythonExprTemplate.

This ends up a bit clunky, but you can see how it works in the example at the bottom of the page.

If you want to manipulate, filter, or otherwise look at the replacements being performed, this is where you can hook in: define a new searcher that wraps the old one and modifies its results.

Execute the search / replace

Apply a searcher to a string

refex.search.rewrite_string() executes a simple rewrite.

Alternatively, you can collect a list of Substitution objects and apply them in a second pass, using refex.search.find_iter() and refex.formatting.apply_substitutions() – but really, it’s better to manipulate those substitutions from a custom searcher, since that searcher can also be used e.g. to create an executable, as the section below describes.

Create an executable

The same colorization, diff display, etc. as the refex command can be yours: instead of rewriting individual strings, you can pass the searcher to refex.cli.run().

Here’s a complete example:

"""An example binary using refex.cli.run() to run a preprogrammed search/replace.

This is similar in functionality to a shell script that runs:

    refex --mode=py.expr hello --sub=world -i "$@"
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import sys

from refex import cli
from refex import search
from refex.python import syntactic_template
from refex.python.matchers import syntax_matchers


def main():
  cli.run(
      runner=cli.RefexRunner(
          searcher=search.PyExprRewritingSearcher.from_matcher(
              # --mode=py.expr is equivalent to PyExprRewritingSearcher paired
              # with an ExprPattern. However, you can pass any matcher, not just
              # an ExprPattern.
              syntax_matchers.ExprPattern('hello'),
              {
                  # Using ROOT_LABEL as a key is equivalent to --sub=world.
                  # To get the equivalent of --named-sub=x=world,
                  # it would 'x' as a key instead.
                  #
                  # The value type corresponds to the --sub-mode. While
                  # refex on the command line defaults to picking the paired
                  # --sub-mode that matches the --mode, here there are no
                  # defaults and you must be explicit.
                  # e.g. for unsafe textual substitutions, as with
                  # --sub-mode=sh, you would use formatting.ShTemplate.
                  search.ROOT_LABEL:
                      syntactic_template.PythonExprTemplate('world')
              },
          ),
          dry_run=False,
      ),
      files=sys.argv[1:],
      bug_report_url='<project bug report URL goes here>',
  )

if __name__ == '__main__':
  main()