| #! /usr/bin/env python3 | |
| # Add a new Write-After account to MAINTAINERS.yml | |
| # Copyright (C) 2026 Free Software Foundation, Inc. | |
| # | |
| # This file is part of GCC. | |
| # | |
| # GCC is free software; you can redistribute it and/or modify | |
| # it under the terms of the GNU General Public License as published by | |
| # the Free Software Foundation; either version 3, or (at your option) | |
| # any later version. | |
| # | |
| # GCC is distributed in the hope that it will be useful, | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| # GNU General Public License for more details. | |
| # | |
| # You should have received a copy of the GNU General Public License | |
| # along with GCC; see the file COPYING. If not, write to | |
| # the Free Software Foundation, 51 Franklin Street, Fifth Floor, | |
| # Boston, MA 02110-1301, USA. | |
| import os | |
| import pwd | |
| import re | |
| import sys | |
| import yaml | |
| from optparse import OptionParser | |
| import maintainer_utils as maintutils | |
| def get_surname(name): | |
| parts = name.split() | |
| surname = parts[-1] | |
| if surname.startswith("d'"): | |
| surname = surname[2:] | |
| return surname | |
| def ask(prompt, default, required=True): | |
| while True: | |
| print(f"{prompt} [{default if default else '(no default)'}]: ", end="", | |
| flush=True) | |
| result = sys.stdin.readline().strip() | |
| if result != "": | |
| return result | |
| if default or not required: | |
| return default | |
| def email_valid(e): | |
| template = r"^[A-Za-z0-9._%+-]+@(?:[A-Za-z0-9-]+\.)+[A-Za-z]{2,}$" | |
| if re.match(template, e): | |
| return True | |
| return False | |
| def getuserdata(): | |
| user = pwd.getpwuid(os.getuid()) | |
| # Make an initial guess that the name and GCC account will match | |
| # the local account name. | |
| # GECOS fields sometime contain a comma-separated list; in that case | |
| # we only want the first component (the user's name) | |
| print ("Please enter the details of your Sourceware account.") | |
| print ("Some details have been guessed, based on your local account,") | |
| print ("but you can adjust as required.") | |
| newuser = { | |
| 'sn': "", | |
| 'cn': "", | |
| 'email': [], | |
| 'roles': [], | |
| 'account': "", | |
| } | |
| newuser['cn'] = ask("Your name", user.pw_gecos.split(",", 1)[0]) | |
| newuser['sn'] = ask("Your surname (only used for sorting entries)", | |
| get_surname(newuser['cn'])) | |
| account = ask("Your sourceware account", user.pw_name) | |
| forgeid = ask("Your sourceware forge ID (if you have one)", None, | |
| required=False) | |
| while True: | |
| e = ask("Primary email address", None) | |
| if email_valid(e): | |
| break | |
| print ("That address does not look valid.") | |
| newuser['email'].append(e) | |
| while (e := ask("Additional email (return to stop)", None, | |
| required=False)): | |
| if email_valid(e): | |
| newuser['email'].append(e) | |
| else: | |
| print ("That address does not look valid. Ignored.") | |
| gcc_email = account + "@gcc.gnu.org" | |
| if not gcc_email in newuser['email']: | |
| print(f"Appending {gcc_email} to the list of email addresses") | |
| newuser['email'].append(gcc_email) | |
| newuser['roles'] = list() | |
| newuser['roles'].append('WriteAfter') | |
| newuser['account'] = account | |
| if forgeid: | |
| newuser['forgeid'] = forgeid | |
| print("If you are using a Developer Certificate of Origin (DCO)") | |
| print("you can add appropriate email addresses here") | |
| dco = [] | |
| inactive_emails = [] | |
| while (e := ask("DCO email (return to stop)", None, required=False)): | |
| if email_valid(e): | |
| dco.append(e) | |
| if e not in newuser['email']: | |
| print(f"<{e}> not mentioned in the above list of emails.") | |
| print("Adding it to inactive_emails list.") | |
| inactive_emails.append(e) | |
| else: | |
| print ("That address does not look valid. Ignored.") | |
| if len(inactive_emails) > 0: | |
| newuser['inactive_email'] = inactive_emails | |
| if len(dco) > 0: | |
| newuser['DCO'] = dco | |
| return newuser | |
| def main(): | |
| optp = OptionParser(usage="Usage: %prog [<options>] <maintainers.yml>") | |
| optp.add_option("-o", "--output", metavar="FILE", | |
| default=None, dest="outfilename", | |
| help="Write to FILE instead of updating original file") | |
| opts, args = optp.parse_args() | |
| if len(args) != 1: | |
| optp.print_help() | |
| sys.exit(1) | |
| data = maintutils.load(args[0]) | |
| # Check the data before we try to update it. | |
| maintutils.validate(data) | |
| newuser = getuserdata() | |
| if not newuser: | |
| return 0 | |
| existing = [u for u in filter(lambda x: (x.get('account') | |
| == newuser['account']), | |
| data['users'])] | |
| if len(existing): | |
| print(f"Sorry that account name ({newuser['account']}) has already been used.") | |
| print("Note, this script can only be used to add new accounts.") | |
| return 1 | |
| data['users'].append (newuser) | |
| if opts.outfilename and opts.outfilename == '-': | |
| maintutils.store(data) | |
| else: | |
| maintutils.store( | |
| data, | |
| file=opts.outfilename if opts.outfilename else args[0]) | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit (main()) |