#!/usr/bin/python # Copyright (c) 2017 Cisco and/or its affiliates. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at: # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """This script renames the given robot keywords in the given directory recursively. Example: ./rename_robot_keywords.py -i kws.csv -s ";" -d ~/ws/vpp/git/csit/ -vvv Input file "kws.csv" is CSV file exported from e.g. MS Excel. Its structure must be: One keyword per line. """ import argparse import sys import re from os import walk, rename from os.path import join def time_interval(func): """Decorator function to measure the time spent by the decorated function. :param func: Decorated function. :type func: Callable object. :returns: Wrapper function. :rtype: Callable object. """ import time def wrapper(*args, **kwargs): start = time.clock() result = func(*args, **kwargs) stop = time.clock() print("\nRenaming done in {:.5g} seconds\n". format(stop - start)) return result return wrapper def get_files(path, extension): """Generates the list of files to process. :param path: Path to files. :param extension: Extension of files to process. If it is the empty string, all files will be processed. :type path: str :type extension: str :returns: List of files to process. :rtype: list """ file_list = list() for root, dirs, files in walk(path): for filename in files: if extension: if filename.endswith(extension): file_list.append(join(root, filename)) else: file_list.append(join(root, filename)) return file_list def read_keywords(args): """This function reads the keywords from the input file and creates: - a dictionary where the key is the old name and the value is the new name, these keywords will be further processed. - a list of keywords which will not be processed, typically keywords with argument(s) in its names. - a list of duplicates - duplicated keyword names or names which are parts of another keyword name, they will not be processed. :param args: Parsed arguments. :type args: ArgumentParser :returns: keyword names - dictionary where the key is the old name and the value is the new name; ignored keyword names - list of keywords which will not be processed; duplicates - duplicated keyword names or names which are parts of another keyword name, they will not be processed. :rtype: tuple(dict, list, list) """ kw_names = dict() ignored_kw_names = list() duplicates = list() for line in args.input: old_name, new_name = line.split(args.separator) if '$' in old_name: ignored_kw_names.append((old_name, new_name[:-1])) elif old_name in kw_names.keys(): duplicates.append((old_name, new_name[:-1])) else: kw_names[old_name] = new_name[:-1] # Remove duplicates: for old_name, _ in duplicates: new_name = kw_names.pop(old_name, None) if new_name: duplicates.append((old_name, new_name)) # Find KW names which are parts of other KW names: for old_name in kw_names.keys(): count = 0 for key in kw_names.keys(): if old_name in key: count += 1 if old_name in kw_names[key]: if old_name != key: count += 1 if count > 1: duplicates.append((old_name, kw_names[old_name])) kw_names.pop(old_name) return kw_names, ignored_kw_names, duplicates def rename_keywords(file_list, kw_names, args): """Rename the keywords in specified files. :param file_list: List of files to be processed. :param kw_names: Dictionary where the key is the old name and the value is the new name :type file_list: list :type kw_names: dict """ kw_not_found = list() for old_name, new_name in kw_names.items(): kw_found = False if args.verbosity > 0: print("\nFrom: {}\n To: {}\n".format(old_name, new_name)) for file_name in file_list: tmp_file_name = file_name + ".new" with open(file_name) as file_read: file_write = open(tmp_file_name, 'w') occurrences = 0 for line in file_read: new_line = re.sub(old_name, new_name, line) file_write.write(new_line) if new_line != line: occurrences += 1 if occurrences: kw_found = True if args.verbosity > 1: print(" {:3d}: {}".format(occurrences, file_name)) file_write.close() rename(tmp_file_name, file_name) if not kw_found: kw_not_found.append(old_name) if args.verbosity > 0: print("\nKeywords not found:") for item in kw_not_found: print(" {}".format(item)) def parse_args(): """Parse arguments from command line. :returns: Parsed arguments. :rtype: ArgumentParser """ parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse. RawDescriptionHelpFormatter) parser.add_argument("-i", "--input", required=True, type=argparse.FileType('r'), help="Text file with the old keyword name and the new " "keyword name separated by separator per line.") parser.add_argument("-s", "--separator", default=";", type=str, help="Separator which separates the old and the new " "keyword name.") parser.add_argument("-d", "--dir", required=True, type=str, help="Directory with robot files where the keywords " "should be recursively searched.") parser.add_argument("-v", "--verbosity", action="count", help="Set the output verbosity.") return parser.parse_args() @time_interval def main(): """Main function.""" args = parse_args() kw_names, ignored_kw_names, duplicates = read_keywords(args) file_list = get_files(args.dir, "robot") if args.verbosity > 2: print("\nList of files to be processed:") for item in file_list: print(" {}".format(item)) print("\n{} files to be processed.\n".format(len(file_list))) print("\nList of keywords to be renamed:") for item in kw_names: print(" {}".format(item)) print("\n{} keywords to be renamed.\n".format(len(kw_names))) rename_keywords(file_list, kw_names, args) if args.verbosity >= 0: print("\nIgnored keywords: ({})".format(len(ignored_kw_names))) for old, new in ignored_kw_names: print(" From: {}\n To: {}\n".format(old, new)) print("\nIgnored duplicates ({}):".format(len(duplicates))) for old, new in duplicates: print(" From: {}\n To: {}\n".format(old, new)) if __name__ == "__main__": sys.exit(main())