119ea8026Sopenharmony_ci#!/usr/bin/env python3
219ea8026Sopenharmony_ci#
319ea8026Sopenharmony_ci# Change prefixes in files/filenames. Useful for creating different versions
419ea8026Sopenharmony_ci# of a codebase that don't conflict at compile time.
519ea8026Sopenharmony_ci#
619ea8026Sopenharmony_ci# Example:
719ea8026Sopenharmony_ci# $ ./scripts/changeprefix.py lfs lfs3
819ea8026Sopenharmony_ci#
919ea8026Sopenharmony_ci# Copyright (c) 2022, The littlefs authors.
1019ea8026Sopenharmony_ci# Copyright (c) 2019, Arm Limited. All rights reserved.
1119ea8026Sopenharmony_ci# SPDX-License-Identifier: BSD-3-Clause
1219ea8026Sopenharmony_ci#
1319ea8026Sopenharmony_ci
1419ea8026Sopenharmony_ciimport glob
1519ea8026Sopenharmony_ciimport itertools
1619ea8026Sopenharmony_ciimport os
1719ea8026Sopenharmony_ciimport os.path
1819ea8026Sopenharmony_ciimport re
1919ea8026Sopenharmony_ciimport shlex
2019ea8026Sopenharmony_ciimport shutil
2119ea8026Sopenharmony_ciimport subprocess
2219ea8026Sopenharmony_ciimport tempfile
2319ea8026Sopenharmony_ci
2419ea8026Sopenharmony_ciGIT_PATH = ['git']
2519ea8026Sopenharmony_ci
2619ea8026Sopenharmony_ci
2719ea8026Sopenharmony_cidef openio(path, mode='r', buffering=-1):
2819ea8026Sopenharmony_ci    # allow '-' for stdin/stdout
2919ea8026Sopenharmony_ci    if path == '-':
3019ea8026Sopenharmony_ci        if mode == 'r':
3119ea8026Sopenharmony_ci            return os.fdopen(os.dup(sys.stdin.fileno()), mode, buffering)
3219ea8026Sopenharmony_ci        else:
3319ea8026Sopenharmony_ci            return os.fdopen(os.dup(sys.stdout.fileno()), mode, buffering)
3419ea8026Sopenharmony_ci    else:
3519ea8026Sopenharmony_ci        return open(path, mode, buffering)
3619ea8026Sopenharmony_ci
3719ea8026Sopenharmony_cidef changeprefix(from_prefix, to_prefix, line):
3819ea8026Sopenharmony_ci    line, count1 = re.subn(
3919ea8026Sopenharmony_ci        '\\b'+from_prefix,
4019ea8026Sopenharmony_ci        to_prefix,
4119ea8026Sopenharmony_ci        line)
4219ea8026Sopenharmony_ci    line, count2 = re.subn(
4319ea8026Sopenharmony_ci        '\\b'+from_prefix.upper(),
4419ea8026Sopenharmony_ci        to_prefix.upper(),
4519ea8026Sopenharmony_ci        line)
4619ea8026Sopenharmony_ci    line, count3 = re.subn(
4719ea8026Sopenharmony_ci        '\\B-D'+from_prefix.upper(),
4819ea8026Sopenharmony_ci        '-D'+to_prefix.upper(),
4919ea8026Sopenharmony_ci        line)
5019ea8026Sopenharmony_ci    return line, count1+count2+count3
5119ea8026Sopenharmony_ci
5219ea8026Sopenharmony_cidef changefile(from_prefix, to_prefix, from_path, to_path, *,
5319ea8026Sopenharmony_ci        no_replacements=False):
5419ea8026Sopenharmony_ci    # rename any prefixes in file
5519ea8026Sopenharmony_ci    count = 0
5619ea8026Sopenharmony_ci
5719ea8026Sopenharmony_ci    # create a temporary file to avoid overwriting ourself
5819ea8026Sopenharmony_ci    if from_path == to_path and to_path != '-':
5919ea8026Sopenharmony_ci        to_path_temp = tempfile.NamedTemporaryFile('w', delete=False)
6019ea8026Sopenharmony_ci        to_path = to_path_temp.name
6119ea8026Sopenharmony_ci    else:
6219ea8026Sopenharmony_ci        to_path_temp = None
6319ea8026Sopenharmony_ci
6419ea8026Sopenharmony_ci    with openio(from_path) as from_f:
6519ea8026Sopenharmony_ci        with openio(to_path, 'w') as to_f:
6619ea8026Sopenharmony_ci            for line in from_f:
6719ea8026Sopenharmony_ci                if not no_replacements:
6819ea8026Sopenharmony_ci                    line, n = changeprefix(from_prefix, to_prefix, line)
6919ea8026Sopenharmony_ci                    count += n
7019ea8026Sopenharmony_ci                to_f.write(line)
7119ea8026Sopenharmony_ci
7219ea8026Sopenharmony_ci    if from_path != '-' and to_path != '-':
7319ea8026Sopenharmony_ci        shutil.copystat(from_path, to_path)
7419ea8026Sopenharmony_ci
7519ea8026Sopenharmony_ci    if to_path_temp:
7619ea8026Sopenharmony_ci        os.rename(to_path, from_path)
7719ea8026Sopenharmony_ci    elif from_path != '-':
7819ea8026Sopenharmony_ci        os.remove(from_path)
7919ea8026Sopenharmony_ci
8019ea8026Sopenharmony_ci    # Summary
8119ea8026Sopenharmony_ci    print('%s: %d replacements' % (
8219ea8026Sopenharmony_ci        '%s -> %s' % (from_path, to_path) if not to_path_temp else from_path,
8319ea8026Sopenharmony_ci        count))
8419ea8026Sopenharmony_ci
8519ea8026Sopenharmony_cidef main(from_prefix, to_prefix, paths=[], *,
8619ea8026Sopenharmony_ci        verbose=False,
8719ea8026Sopenharmony_ci        output=None,
8819ea8026Sopenharmony_ci        no_replacements=False,
8919ea8026Sopenharmony_ci        no_renames=False,
9019ea8026Sopenharmony_ci        git=False,
9119ea8026Sopenharmony_ci        no_stage=False,
9219ea8026Sopenharmony_ci        git_path=GIT_PATH):
9319ea8026Sopenharmony_ci    if not paths:
9419ea8026Sopenharmony_ci        if git:
9519ea8026Sopenharmony_ci            cmd = git_path + ['ls-tree', '-r', '--name-only', 'HEAD']
9619ea8026Sopenharmony_ci            if verbose:
9719ea8026Sopenharmony_ci                print(' '.join(shlex.quote(c) for c in cmd))
9819ea8026Sopenharmony_ci            paths = subprocess.check_output(cmd, encoding='utf8').split()
9919ea8026Sopenharmony_ci        else:
10019ea8026Sopenharmony_ci            print('no paths?', file=sys.stderr)
10119ea8026Sopenharmony_ci            sys.exit(1)
10219ea8026Sopenharmony_ci
10319ea8026Sopenharmony_ci    for from_path in paths:
10419ea8026Sopenharmony_ci        # rename filename?
10519ea8026Sopenharmony_ci        if output:
10619ea8026Sopenharmony_ci            to_path = output
10719ea8026Sopenharmony_ci        elif no_renames:
10819ea8026Sopenharmony_ci            to_path = from_path
10919ea8026Sopenharmony_ci        else:
11019ea8026Sopenharmony_ci            to_path = os.path.join(
11119ea8026Sopenharmony_ci                os.path.dirname(from_path),
11219ea8026Sopenharmony_ci                changeprefix(from_prefix, to_prefix,
11319ea8026Sopenharmony_ci                    os.path.basename(from_path))[0])
11419ea8026Sopenharmony_ci
11519ea8026Sopenharmony_ci        # rename contents
11619ea8026Sopenharmony_ci        changefile(from_prefix, to_prefix, from_path, to_path,
11719ea8026Sopenharmony_ci            no_replacements=no_replacements)
11819ea8026Sopenharmony_ci
11919ea8026Sopenharmony_ci        # stage?
12019ea8026Sopenharmony_ci        if git and not no_stage:
12119ea8026Sopenharmony_ci            if from_path != to_path:
12219ea8026Sopenharmony_ci                cmd = git_path + ['rm', '-q', from_path]
12319ea8026Sopenharmony_ci                if verbose:
12419ea8026Sopenharmony_ci                    print(' '.join(shlex.quote(c) for c in cmd))
12519ea8026Sopenharmony_ci                subprocess.check_call(cmd)
12619ea8026Sopenharmony_ci            cmd = git_path + ['add', to_path]
12719ea8026Sopenharmony_ci            if verbose:
12819ea8026Sopenharmony_ci                print(' '.join(shlex.quote(c) for c in cmd))
12919ea8026Sopenharmony_ci            subprocess.check_call(cmd)
13019ea8026Sopenharmony_ci
13119ea8026Sopenharmony_ci
13219ea8026Sopenharmony_ciif __name__ == "__main__":
13319ea8026Sopenharmony_ci    import argparse
13419ea8026Sopenharmony_ci    import sys
13519ea8026Sopenharmony_ci    parser = argparse.ArgumentParser(
13619ea8026Sopenharmony_ci        description="Change prefixes in files/filenames. Useful for creating "
13719ea8026Sopenharmony_ci            "different versions of a codebase that don't conflict at compile "
13819ea8026Sopenharmony_ci            "time.",
13919ea8026Sopenharmony_ci        allow_abbrev=False)
14019ea8026Sopenharmony_ci    parser.add_argument(
14119ea8026Sopenharmony_ci        'from_prefix',
14219ea8026Sopenharmony_ci        help="Prefix to replace.")
14319ea8026Sopenharmony_ci    parser.add_argument(
14419ea8026Sopenharmony_ci        'to_prefix',
14519ea8026Sopenharmony_ci        help="Prefix to replace with.")
14619ea8026Sopenharmony_ci    parser.add_argument(
14719ea8026Sopenharmony_ci        'paths',
14819ea8026Sopenharmony_ci        nargs='*',
14919ea8026Sopenharmony_ci        help="Files to operate on.")
15019ea8026Sopenharmony_ci    parser.add_argument(
15119ea8026Sopenharmony_ci        '-v', '--verbose',
15219ea8026Sopenharmony_ci        action='store_true',
15319ea8026Sopenharmony_ci        help="Output commands that run behind the scenes.")
15419ea8026Sopenharmony_ci    parser.add_argument(
15519ea8026Sopenharmony_ci        '-o', '--output',
15619ea8026Sopenharmony_ci        help="Output file.")
15719ea8026Sopenharmony_ci    parser.add_argument(
15819ea8026Sopenharmony_ci        '-N', '--no-replacements',
15919ea8026Sopenharmony_ci        action='store_true',
16019ea8026Sopenharmony_ci        help="Don't change prefixes in files")
16119ea8026Sopenharmony_ci    parser.add_argument(
16219ea8026Sopenharmony_ci        '-R', '--no-renames',
16319ea8026Sopenharmony_ci        action='store_true',
16419ea8026Sopenharmony_ci        help="Don't rename files")
16519ea8026Sopenharmony_ci    parser.add_argument(
16619ea8026Sopenharmony_ci        '--git',
16719ea8026Sopenharmony_ci        action='store_true',
16819ea8026Sopenharmony_ci        help="Use git to find/update files.")
16919ea8026Sopenharmony_ci    parser.add_argument(
17019ea8026Sopenharmony_ci        '--no-stage',
17119ea8026Sopenharmony_ci        action='store_true',
17219ea8026Sopenharmony_ci        help="Don't stage changes with git.")
17319ea8026Sopenharmony_ci    parser.add_argument(
17419ea8026Sopenharmony_ci        '--git-path',
17519ea8026Sopenharmony_ci        type=lambda x: x.split(),
17619ea8026Sopenharmony_ci        default=GIT_PATH,
17719ea8026Sopenharmony_ci        help="Path to git executable, may include flags. "
17819ea8026Sopenharmony_ci            "Defaults to %r." % GIT_PATH)
17919ea8026Sopenharmony_ci    sys.exit(main(**{k: v
18019ea8026Sopenharmony_ci        for k, v in vars(parser.parse_intermixed_args()).items()
18119ea8026Sopenharmony_ci        if v is not None}))
182