Skip to content
Snippets Groups Projects
Forked from The Tor Project / Core / Tor
689 commits behind the upstream repository.
  • Samanta Navarro's avatar
    4a0cd795
    Fix typos. · 4a0cd795
    Samanta Navarro authored and David Goulet's avatar David Goulet committed
    Typos found with codespell.
    
    Please keep in mind that this should have impact on actual code
    and must be carefully evaluated:
    
    src/core/or/lttng_circuit.inc
    -    ctf_enum_value("CONTROLER", CIRCUIT_PURPOSE_CONTROLLER)
    +    ctf_enum_value("CONTROLLER", CIRCUIT_PURPOSE_CONTROLLER)
    4a0cd795
    History
    Fix typos.
    Samanta Navarro authored and David Goulet's avatar David Goulet committed
    Typos found with codespell.
    
    Please keep in mind that this should have impact on actual code
    and must be carefully evaluated:
    
    src/core/or/lttng_circuit.inc
    -    ctf_enum_value("CONTROLER", CIRCUIT_PURPOSE_CONTROLLER)
    +    ctf_enum_value("CONTROLLER", CIRCUIT_PURPOSE_CONTROLLER)
rename_c_identifier.py 7.91 KiB
#!/usr/bin/env python3
#
# Copyright (c) 2001 Matej Pfajfar.
# Copyright (c) 2001-2004, Roger Dingledine.
# Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
# Copyright (c) 2007-2019, The Tor Project, Inc.
# See LICENSE for licensing information

"""
Helpful script to replace one or more C identifiers, and optionally
generate a commit message explaining what happened.
"""

# Future imports for Python 2.7, mandatory in 3.0
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import argparse
import fileinput
import os
import re
import shlex
import subprocess
import sys
import tempfile

TOPDIR = "src"


def is_c_file(fn):
    """
       Return true iff fn is the name of a C file.

       >>> is_c_file("a/b/module.c")
       True
       >>> is_c_file("a/b/module.h")
       True
       >>> is_c_file("a/b/module.c~")
       False
       >>> is_c_file("a/b/.module.c")
       False
       >>> is_c_file("a/b/module.cpp")
       False
    """
    fn = os.path.split(fn)[1]
    # Avoid editor temporary files
    if fn.startswith(".") or fn.startswith("#"):
        return False
    ext = os.path.splitext(fn)[1]
    return ext in {".c", ".h", ".i", ".inc"}


def list_c_files(topdir=TOPDIR):
    """
       Use git to list all the C files under version control.

       >>> lst = list(list_c_files())
       >>> "src/core/mainloop/mainloop.c" in lst
       True
       >>> "src/core/mainloop/twiddledeedoo.c" in lst
       False
       >>> "micro-revision.i" in lst
       False
    """
    proc = subprocess.Popen(
        ["git", "ls-tree", "--name-only", "-r", "HEAD", topdir],
        stdout=subprocess.PIPE,
        encoding="utf-8")
    for line in proc.stdout.readlines():
        line = line.strip()
        if is_c_file(line):
            yield line


class Rewriter:
    """
       A rewriter applies a series of word-by-word replacements, in
       sequence.  Replacements only happen at "word boundaries",
       as determined by the \\b regular expression marker.

       ("A word is defined as a sequence of alphanumeric or underscore
       characters", according to the documentation.)

       >>> R = Rewriter([("magic", "secret"), ("words", "codes")])
       >>> R.apply("The magic words are rambunctious bluejay")
       'The secret codes are rambunctious bluejay'
       >>> R.apply("The magical words are rambunctious bluejay")
       'The magical codes are rambunctious bluejay'
       >>> R.get_count()
       3

    """

    def __init__(self, replacements):
        """Make a new Rewriter. Takes a sequence of pairs of
           (from_id, to_id), where from_id is an identifier to replace,
           and to_id is its replacement.
        """
        self._patterns = []
        for id1, id2 in replacements:
            pat = re.compile(r"\b{}\b".format(re.escape(id1)))
            self._patterns.append((pat, id2))

        self._count = 0

    def apply(self, line):
        """Return `line` as transformed by this rewriter."""
        for pat, ident in self._patterns:
            line, count = pat.subn(ident, line)
            self._count += count
        return line

    def get_count(self):
        """Return the number of identifiers that this rewriter has
           rewritten."""
        return self._count


def rewrite_files(files, rewriter):
    """
       Apply `rewriter` to every file in `files`, replacing those files
       with their rewritten contents.
    """
    for line in fileinput.input(files, inplace=True):
        sys.stdout.write(rewriter.apply(line))


def make_commit_msg(pairs, no_verify):
    """Return a commit message to explain what was replaced by the provided
       arguments.
    """
    script = ["./scripts/maint/rename_c_identifier.py"]
    for id1, id2 in pairs:
        qid1 = shlex.quote(id1)
        qid2 = shlex.quote(id2)
        script.append("        {} {}".format(qid1, qid2))
    script = " \\\n".join(script)

    if len(pairs) == 1:
        line1 = "Rename {} to {}".format(*pairs[0])
    else:
        line1 = "Replace several C identifiers."

    msg = """\
{}

This is an automated commit, generated by this command:

{}
""".format(line1, script)

    if no_verify:
        msg += """
It was generated with --no-verify, so it probably breaks some commit hooks.
The committer should be sure to fix them up in a subsequent commit.
"""

    return msg


def commit(pairs, no_verify=False):
    """Try to commit the current git state, generating the commit message as
       appropriate.  If `no_verify` is True, pass the --no-verify argument to
       git commit.
    """
    args = []
    if no_verify:
        args.append("--no-verify")

    # We have to use a try block to delete the temporary file here, since we
    # are using tempfile with delete=False. We have to use delete=False,
    # since otherwise we are not guaranteed to be able to give the file to
    # git for it to open.
    fname = None
    try:
        with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
            fname = f.name
            f.write(make_commit_msg(pairs, no_verify))
        s = subprocess.run(["git", "commit", "-a", "-F", fname, "--edit"]+args)
        if s.returncode != 0 and not no_verify:
            print('"git commit" failed. Maybe retry with --no-verify?',
                  file=sys.stderr)
            revert_changes()
            return False
    finally:
        os.unlink(fname)

    return True


def any_uncommitted_changes():
    """Return True if git says there are any uncommitted changes in the current
       working tree; false otherwise.
    """
    s = subprocess.run(["git", "diff-index", "--quiet", "HEAD"])
    return s.returncode != 0


DESC = "Replace one identifier with another throughout our source."
EXAMPLES = """\
Examples:

   rename_c_identifier.py set_ctrl_id set_controller_id
      (Replaces every occurrence of "set_ctrl_id" with "set_controller_id".)

   rename_c_identifier.py --commit set_ctrl_id set_controller_id
      (As above, but also generate a git commit with an appropriate message.)

   rename_c_identifier.py a b c d
      (Replace "a" with "b", and "c" with "d".)"""


def revert_changes():
    """Tell git to revert all the changes in the current working tree.
    """
    print('Reverting changes.', file=sys.stderr)
    subprocess.run(["git", "checkout", "--quiet", TOPDIR])


def main(argv):
    import argparse
    parser = argparse.ArgumentParser(description=DESC, epilog=EXAMPLES,
                                     # prevent re-wrapping the examples
                                     formatter_class=argparse.RawDescriptionHelpFormatter)

    parser.add_argument("--commit", action='store_true',
                        help="Generate a Git commit.")
    parser.add_argument("--no-verify", action='store_true',
                        help="Tell Git not to run its pre-commit hooks.")
    parser.add_argument("from_id", type=str,  help="Original identifier")
    parser.add_argument("to_id", type=str, help="New identifier")
    parser.add_argument("more", type=str, nargs=argparse.REMAINDER,
                        help="Additional identifier pairs")

    args = parser.parse_args(argv[1:])

    if len(args.more) % 2 != 0:
        print("I require an even number of identifiers.", file=sys.stderr)
        return 1

    if args.commit and any_uncommitted_changes():
        print("Uncommitted changes found. Not running.", file=sys.stderr)
        return 1

    pairs = []
    print("renaming {} to {}".format(args.from_id, args.to_id), file=sys.stderr)
    pairs.append((args.from_id, args.to_id))
    for idx in range(0, len(args.more), 2):
        id1 = args.more[idx]
        id2 = args.more[idx+1]
        print("renaming {} to {}".format(id1, id2))
        pairs.append((id1, id2))

    rewriter = Rewriter(pairs)

    rewrite_files(list_c_files(), rewriter)

    print("Replaced {} identifiers".format(rewriter.get_count()),
          file=sys.stderr)

    if args.commit:
        commit(pairs, args.no_verify)


if __name__ == '__main__':
    main(sys.argv)