gpu: nvgpu: rfr: Add address book

Add an address book that lets devs use short hand for specifying
--to and --cc targets. For example, if a dev wants to CC the MISRA
list this can be used:

  $ ./scripts/rfr -e --cc misra ...

The address book also lets devs add their own names/email addresses
since this makes it convenient to CC individual people. For example:

  $ ./scripts/rfr -e --cc alex ...

Several new arguments were added to support the address book. There
are arguments to list/search the address book, ignore the address
book, and to prevent nvgpu-core from being added to the to address
by default. For more details see the help page.

To use an address book there's several options: place one at

  ~/.rfr-addrbook

Export an RFR_ADDRBOOK environment variable pointing to the address
book, or specify one with the `-a' option. The address book contents
is simple. All empty lines and lines beginning with '#' are ignored.
The remaining lines are split by '|' and the first half of the line
is considered a nickname and the latter half the address. An example:

  alex | alex waterman <alexw@nvidia.com>

This will let you specify `--to alex' instead of the full email
address. This is especially useful for mailing lists.

Lastly there is more documentation located at:

  https://confluence.nvidia.com/display/TGS/NVGPU+Request+For+Review

[Bump version to 1.1.0]

Change-Id: Iac7ec05ae28d7e888d2bf36bd23574ec49eb04dc
Signed-off-by: Alex Waterman <alexw@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/1983695
Reviewed-by: Rohit Khanna <rokhanna@nvidia.com>
This commit is contained in:
Alex Waterman
2018-12-28 14:24:00 -08:00
committed by Rohit Khanna
parent 164e387940
commit dd4c60aeb5
2 changed files with 242 additions and 8 deletions

View File

@@ -33,10 +33,14 @@ import smtplib
from email.mime.text import MIMEText 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. # Default email address.
to_addr = 'SW-Mobile-nvgpu-core <SW-Mobile-nvgpu-core@exchange.nvidia.com>' to_addr = 'sw-mobile-nvgpu-core <sw-mobile-nvgpu-core@exchange.nvidia.com>'
# Gerrit commit URL formats. These are regular expressions to match the # Gerrit commit URL formats. These are regular expressions to match the
# incoming URLs against. # incoming URLs against.
@@ -63,7 +67,7 @@ def parse_args():
""" """
ep="""This program will format commit messages into something that can be 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 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. 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).') 'Can be repeated (-c a -c b).')
parser.add_argument('-F', '--file', action='store', default=None, parser.add_argument('-F', '--file', action='store', default=None,
help='File with commits, one per line') 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. # Positionals: the gerrit URLs.
parser.add_argument('commits', metavar='Commit-IDs', 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. knows who they are sending an email to.
""" """
msg_comment = """# Lines that begin with a '#' will be ignored in the final msg_comment = """# RFR commit editor
# message. This includes white space so ' # blah', for example, #
# will _not_ be ignored. # 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! Directly email commits to the nvgpu-core mailing list for review!
""" """
to_addrs = [to_addr] + args.to # Lets you drop nvgpu-core from the to-addr if you desire. You can then
cc_addrs = args.cc # 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: if args.no_msg:
args.msg = None args.msg = None
@@ -380,6 +412,17 @@ def main():
print('Version: %s' % VERSION) print('Version: %s' % VERSION)
exit(0) 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: if arg_parser.file:
filp = open(arg_parser.file, 'r') filp = open(arg_parser.file, 'r')

191
scripts/rfr_addrbook.py Normal file
View File

@@ -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 <alexw@nvidia.com>
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