diff --git a/scripts/rfr b/scripts/rfr index 62c66dd6f..aa2076a3d 100755 --- a/scripts/rfr +++ b/scripts/rfr @@ -33,10 +33,14 @@ import smtplib from email.mime.text import MIMEText -VERSION = '1.0.3' +from rfr_addrbook import rfr_ab_load +from rfr_addrbook import rfr_ab_query +from rfr_addrbook import rfr_ab_lookup + +VERSION = '1.1.0' # Default email address. -to_addr = 'SW-Mobile-nvgpu-core ' +to_addr = 'sw-mobile-nvgpu-core ' # Gerrit commit URL formats. These are regular expressions to match the # incoming URLs against. @@ -63,7 +67,7 @@ def parse_args(): """ ep="""This program will format commit messages into something that can be -sent to the nvgpu mailing list for review +sent to the nvgpu mailing list for review. """ help_msg="""Git or gerrit commits to describe. Can be either a git or gerrit commit ID. If the ID starts with a 'I' then it will be treated as a gerrit ID. @@ -93,6 +97,18 @@ Otherwise it's treated as a git commit ID. 'Can be repeated (-c a -c b).') parser.add_argument('-F', '--file', action='store', default=None, help='File with commits, one per line') + parser.add_argument('-q', '--query-addrbook', action='store', default=None, + nargs='?', const='.', metavar='regex', + help='Regex query for address book') + parser.add_argument('-a', '--addrbook', action='store', default=None, + metavar='addrbook', + help='Specify location to look for address book') + parser.add_argument('-I', '--ignore-addrbook', action='store_true', + default=False, + help='Ignore address book lookup. Default is false.') + parser.add_argument('--no-default-addr', action='store_true', + default=False, + help='Do not use the default address.') # Positionals: the gerrit URLs. parser.add_argument('commits', metavar='Commit-IDs', @@ -297,9 +313,12 @@ def format_msg_comment(subject, to_addrs, cc_addrs): knows who they are sending an email to. """ - msg_comment = """# Lines that begin with a '#' will be ignored in the final -# message. This includes white space so ' # blah', for example, -# will _not_ be ignored. + msg_comment = """# RFR commit editor +# +# Lines that begin with a '#' are comments and will be ignored in the final +# message. '#' characters that appear else where in the line will be ignored. +# White space is not stripped from lines so, for example, ' # blah' will not +# be ignored! # """ @@ -324,8 +343,21 @@ def email_commits(commits_info, sender, subject, args): Directly email commits to the nvgpu-core mailing list for review! """ - to_addrs = [to_addr] + args.to - cc_addrs = args.cc + # Lets you drop nvgpu-core from the to-addr if you desire. You can then + # add it back to CC if you wish. + if not args.no_default_addr: + args.to.append(to_addr) + + to_addrs = rfr_ab_lookup(args.to, args.ignore_addrbook) + if to_addrs == None: + print('Junk address: aborting!') + print('Use `-I\' to ignore.') + return + cc_addrs = rfr_ab_lookup(args.cc, args.ignore_addrbook) + if cc_addrs == None: + print('Junk address: aborting!') + print('Use `-I\' to ignore.') + return if args.no_msg: args.msg = None @@ -380,6 +412,17 @@ def main(): print('Version: %s' % VERSION) exit(0) + if arg_parser.addrbook: + success = rfr_ab_load(arg_parser.addrbook) + if not success: + exit(1) + else: + rfr_ab_load(None) + + if arg_parser.query_addrbook: + rfr_ab_query(arg_parser.query_addrbook) + exit(0) + if arg_parser.file: filp = open(arg_parser.file, 'r') diff --git a/scripts/rfr_addrbook.py b/scripts/rfr_addrbook.py new file mode 100644 index 000000000..6026811d4 --- /dev/null +++ b/scripts/rfr_addrbook.py @@ -0,0 +1,191 @@ +# Copyright (c) 2019, NVIDIA CORPORATION. All Rights Reserved. +# +# Nvgpu address book. Entries in here let us do translations from human readable +# email names/aliases to the real NV email addresses. +# +# This is structured as a dictionary with keys as human readable names which +# point to the real email address. +# +# It's fine to have multiple keys pointing to the same thing. Aliases are nice. +# + +import re +import os + +# The table itself is private. Use the methods below to do AB lookups, etc. +# +# Also please note: the table should be all lower case! This makes it easy to +# just lowercase all incoming queries so that we can do case insensitive +# lookups. +__rfr_address_book = { } + +def __rfr_parse_addrbook(ab): + """ + Parse an address book. Ignore empty lines and lines that begin with '#'. + All other lines are split by the '|' character into 2 strings - a key and + a value. The key is a nickname and the value is the real address. For + example: + + alex | Alex Waterman + + Note: if there's a syntax error detected an error is always printed. Unlike + if there's missing files in which errors may be silent. + + Returns True/False for pass/fail. + """ + + global __rfr_address_book + + tmp_ab = { } + line_nr = 0 + + for line in ab.readlines(): + line_nr += 1 + l = line.strip() + + if len(l) == 0 or l[0] == '#': + continue + + # Only allow at most 1 split. + kv = l.split('|', 1) + + if len(kv) != 2: + print('Error: unable to parse address book. Invalid line:') + print(' > \'%s\' @ line: %d' % (line, line_nr)) + return False + + tmp_ab[kv[0].strip().lower()] = kv[1].strip().lower() + + __rfr_address_book = tmp_ab + return True + +def __rfr_load_ab(path, silent=False): + """ + Load the passed address book and print errors if silent is False. + """ + + success = True + + try: + with open(path) as ab: + if not __rfr_parse_addrbook(ab): + success = False + except Exception as err: + success = False + if not silent: + print 'Error: %s' % err + + # It's not a very helpful error message I suppose. Eh. We will get more + # detail from the __rfr_parse_addrbook() call itself. + if not success and not silent: + print('Failed to parse AB: %s' % path) + + return success + +def rfr_ab_load(ab_path): + """ + Attempt to load an address book. + + If ab_path is None then look for the book at the environment variable + RFR_ADDRBOOK. If that's not found or fails to load then fall back to trying + ~/.rfr-addrbook. If that doesn't work just silently return True and we will + have an empty address book. + + If ab_path is not None then try to load the addr book from the passed path + and if there's an error print an error message. This will return the result + of the AB load. + """ + + if ab_path: + return __rfr_load_ab(ab_path) + + # Ok, if we are here, do the whole env logic thing. + ab_path = os.getenv('RFR_ADDRBOOK') + if ab_path: + success = __rfr_load_ab(ab_path, silent=True) + if success: + return True + + # Otherwise... Try ~/.rfr-addrbook + ab_path = os.getenv('HOME') + '/.rfr-addrbook' + if ab_path: + success = __rfr_load_ab(ab_path, silent=True) + if success: + return True + + # Well, it all failed. But we don't care. + return True + +def rfr_ab_query(regex): + """ + Print a list of keys that match the passed regex string. + """ + + matches = [ ] + + p = re.compile(regex, re.IGNORECASE) + + for k in __rfr_address_book.keys(): + m = p.search(k) + if not m: + continue + + matches.append(k) + + print('Address book query: %s' % regex) + + if not matches: + print('> No matches') + return + + for addr in sorted(matches): + print('> %-15s | %s' % (addr, __rfr_address_book[addr])) + +def rfr_ab_lookup_single(addr): + """ + Exact lookup into the address book but if the addr isn't found in the keys + then also check the values. If the addr is not found in the values then + return None. + + This lets the rfr script either ignore the AB lookup failure or bail out. + """ + + # If there's no address book, just pass the addr through. + if len(__rfr_address_book.keys()) == 0: + return addr + + lc_addr = addr.lower() + + if lc_addr in __rfr_address_book: + return __rfr_address_book[lc_addr] + + if lc_addr in __rfr_address_book.values(): + # Return the orignal, un-lowercased. + return addr + + return None + +def rfr_ab_lookup(addrs, ignore_missing): + """ + Take a list of addresses and look them up in the address book. Return a new + list which contains the results of the lookups. + + If ignore_missing is True then if there's an address book lookup failure + just ignore it and pass the address through to the new list. If False then + fail and bail (return None). + """ + + lookups = [ ] + + for a in addrs: + lookup = rfr_ab_lookup_single(a) + if not lookup and not ignore_missing: + print('Unknown address: %s' % a) + return None + + if lookup: + lookups.append(lookup) + else: + lookups.append(a) + + return lookups