1*4882a593Smuzhiyun# Copyright (c) 2011 The Chromium OS Authors. 2*4882a593Smuzhiyun# 3*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0+ 4*4882a593Smuzhiyun# 5*4882a593Smuzhiyun 6*4882a593Smuzhiyunfrom __future__ import print_function 7*4882a593Smuzhiyun 8*4882a593Smuzhiyuntry: 9*4882a593Smuzhiyun import configparser as ConfigParser 10*4882a593Smuzhiyunexcept: 11*4882a593Smuzhiyun import ConfigParser 12*4882a593Smuzhiyun 13*4882a593Smuzhiyunimport os 14*4882a593Smuzhiyunimport re 15*4882a593Smuzhiyun 16*4882a593Smuzhiyunimport command 17*4882a593Smuzhiyunimport gitutil 18*4882a593Smuzhiyun 19*4882a593Smuzhiyun"""Default settings per-project. 20*4882a593Smuzhiyun 21*4882a593SmuzhiyunThese are used by _ProjectConfigParser. Settings names should match 22*4882a593Smuzhiyunthe "dest" of the option parser from patman.py. 23*4882a593Smuzhiyun""" 24*4882a593Smuzhiyun_default_settings = { 25*4882a593Smuzhiyun "u-boot": {}, 26*4882a593Smuzhiyun "linux": { 27*4882a593Smuzhiyun "process_tags": "False", 28*4882a593Smuzhiyun } 29*4882a593Smuzhiyun} 30*4882a593Smuzhiyun 31*4882a593Smuzhiyunclass _ProjectConfigParser(ConfigParser.SafeConfigParser): 32*4882a593Smuzhiyun """ConfigParser that handles projects. 33*4882a593Smuzhiyun 34*4882a593Smuzhiyun There are two main goals of this class: 35*4882a593Smuzhiyun - Load project-specific default settings. 36*4882a593Smuzhiyun - Merge general default settings/aliases with project-specific ones. 37*4882a593Smuzhiyun 38*4882a593Smuzhiyun # Sample config used for tests below... 39*4882a593Smuzhiyun >>> try: 40*4882a593Smuzhiyun ... from StringIO import StringIO 41*4882a593Smuzhiyun ... except ImportError: 42*4882a593Smuzhiyun ... from io import StringIO 43*4882a593Smuzhiyun >>> sample_config = ''' 44*4882a593Smuzhiyun ... [alias] 45*4882a593Smuzhiyun ... me: Peter P. <likesspiders@example.com> 46*4882a593Smuzhiyun ... enemies: Evil <evil@example.com> 47*4882a593Smuzhiyun ... 48*4882a593Smuzhiyun ... [sm_alias] 49*4882a593Smuzhiyun ... enemies: Green G. <ugly@example.com> 50*4882a593Smuzhiyun ... 51*4882a593Smuzhiyun ... [sm2_alias] 52*4882a593Smuzhiyun ... enemies: Doc O. <pus@example.com> 53*4882a593Smuzhiyun ... 54*4882a593Smuzhiyun ... [settings] 55*4882a593Smuzhiyun ... am_hero: True 56*4882a593Smuzhiyun ... ''' 57*4882a593Smuzhiyun 58*4882a593Smuzhiyun # Check to make sure that bogus project gets general alias. 59*4882a593Smuzhiyun >>> config = _ProjectConfigParser("zzz") 60*4882a593Smuzhiyun >>> config.readfp(StringIO(sample_config)) 61*4882a593Smuzhiyun >>> config.get("alias", "enemies") 62*4882a593Smuzhiyun 'Evil <evil@example.com>' 63*4882a593Smuzhiyun 64*4882a593Smuzhiyun # Check to make sure that alias gets overridden by project. 65*4882a593Smuzhiyun >>> config = _ProjectConfigParser("sm") 66*4882a593Smuzhiyun >>> config.readfp(StringIO(sample_config)) 67*4882a593Smuzhiyun >>> config.get("alias", "enemies") 68*4882a593Smuzhiyun 'Green G. <ugly@example.com>' 69*4882a593Smuzhiyun 70*4882a593Smuzhiyun # Check to make sure that settings get merged with project. 71*4882a593Smuzhiyun >>> config = _ProjectConfigParser("linux") 72*4882a593Smuzhiyun >>> config.readfp(StringIO(sample_config)) 73*4882a593Smuzhiyun >>> sorted(config.items("settings")) 74*4882a593Smuzhiyun [('am_hero', 'True'), ('process_tags', 'False')] 75*4882a593Smuzhiyun 76*4882a593Smuzhiyun # Check to make sure that settings works with unknown project. 77*4882a593Smuzhiyun >>> config = _ProjectConfigParser("unknown") 78*4882a593Smuzhiyun >>> config.readfp(StringIO(sample_config)) 79*4882a593Smuzhiyun >>> sorted(config.items("settings")) 80*4882a593Smuzhiyun [('am_hero', 'True')] 81*4882a593Smuzhiyun """ 82*4882a593Smuzhiyun def __init__(self, project_name): 83*4882a593Smuzhiyun """Construct _ProjectConfigParser. 84*4882a593Smuzhiyun 85*4882a593Smuzhiyun In addition to standard SafeConfigParser initialization, this also loads 86*4882a593Smuzhiyun project defaults. 87*4882a593Smuzhiyun 88*4882a593Smuzhiyun Args: 89*4882a593Smuzhiyun project_name: The name of the project. 90*4882a593Smuzhiyun """ 91*4882a593Smuzhiyun self._project_name = project_name 92*4882a593Smuzhiyun ConfigParser.SafeConfigParser.__init__(self) 93*4882a593Smuzhiyun 94*4882a593Smuzhiyun # Update the project settings in the config based on 95*4882a593Smuzhiyun # the _default_settings global. 96*4882a593Smuzhiyun project_settings = "%s_settings" % project_name 97*4882a593Smuzhiyun if not self.has_section(project_settings): 98*4882a593Smuzhiyun self.add_section(project_settings) 99*4882a593Smuzhiyun project_defaults = _default_settings.get(project_name, {}) 100*4882a593Smuzhiyun for setting_name, setting_value in project_defaults.items(): 101*4882a593Smuzhiyun self.set(project_settings, setting_name, setting_value) 102*4882a593Smuzhiyun 103*4882a593Smuzhiyun def get(self, section, option, *args, **kwargs): 104*4882a593Smuzhiyun """Extend SafeConfigParser to try project_section before section. 105*4882a593Smuzhiyun 106*4882a593Smuzhiyun Args: 107*4882a593Smuzhiyun See SafeConfigParser. 108*4882a593Smuzhiyun Returns: 109*4882a593Smuzhiyun See SafeConfigParser. 110*4882a593Smuzhiyun """ 111*4882a593Smuzhiyun try: 112*4882a593Smuzhiyun return ConfigParser.SafeConfigParser.get( 113*4882a593Smuzhiyun self, "%s_%s" % (self._project_name, section), option, 114*4882a593Smuzhiyun *args, **kwargs 115*4882a593Smuzhiyun ) 116*4882a593Smuzhiyun except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): 117*4882a593Smuzhiyun return ConfigParser.SafeConfigParser.get( 118*4882a593Smuzhiyun self, section, option, *args, **kwargs 119*4882a593Smuzhiyun ) 120*4882a593Smuzhiyun 121*4882a593Smuzhiyun def items(self, section, *args, **kwargs): 122*4882a593Smuzhiyun """Extend SafeConfigParser to add project_section to section. 123*4882a593Smuzhiyun 124*4882a593Smuzhiyun Args: 125*4882a593Smuzhiyun See SafeConfigParser. 126*4882a593Smuzhiyun Returns: 127*4882a593Smuzhiyun See SafeConfigParser. 128*4882a593Smuzhiyun """ 129*4882a593Smuzhiyun project_items = [] 130*4882a593Smuzhiyun has_project_section = False 131*4882a593Smuzhiyun top_items = [] 132*4882a593Smuzhiyun 133*4882a593Smuzhiyun # Get items from the project section 134*4882a593Smuzhiyun try: 135*4882a593Smuzhiyun project_items = ConfigParser.SafeConfigParser.items( 136*4882a593Smuzhiyun self, "%s_%s" % (self._project_name, section), *args, **kwargs 137*4882a593Smuzhiyun ) 138*4882a593Smuzhiyun has_project_section = True 139*4882a593Smuzhiyun except ConfigParser.NoSectionError: 140*4882a593Smuzhiyun pass 141*4882a593Smuzhiyun 142*4882a593Smuzhiyun # Get top-level items 143*4882a593Smuzhiyun try: 144*4882a593Smuzhiyun top_items = ConfigParser.SafeConfigParser.items( 145*4882a593Smuzhiyun self, section, *args, **kwargs 146*4882a593Smuzhiyun ) 147*4882a593Smuzhiyun except ConfigParser.NoSectionError: 148*4882a593Smuzhiyun # If neither section exists raise the error on... 149*4882a593Smuzhiyun if not has_project_section: 150*4882a593Smuzhiyun raise 151*4882a593Smuzhiyun 152*4882a593Smuzhiyun item_dict = dict(top_items) 153*4882a593Smuzhiyun item_dict.update(project_items) 154*4882a593Smuzhiyun return item_dict.items() 155*4882a593Smuzhiyun 156*4882a593Smuzhiyundef ReadGitAliases(fname): 157*4882a593Smuzhiyun """Read a git alias file. This is in the form used by git: 158*4882a593Smuzhiyun 159*4882a593Smuzhiyun alias uboot u-boot@lists.denx.de 160*4882a593Smuzhiyun alias wd Wolfgang Denk <wd@denx.de> 161*4882a593Smuzhiyun 162*4882a593Smuzhiyun Args: 163*4882a593Smuzhiyun fname: Filename to read 164*4882a593Smuzhiyun """ 165*4882a593Smuzhiyun try: 166*4882a593Smuzhiyun fd = open(fname, 'r') 167*4882a593Smuzhiyun except IOError: 168*4882a593Smuzhiyun print("Warning: Cannot find alias file '%s'" % fname) 169*4882a593Smuzhiyun return 170*4882a593Smuzhiyun 171*4882a593Smuzhiyun re_line = re.compile('alias\s+(\S+)\s+(.*)') 172*4882a593Smuzhiyun for line in fd.readlines(): 173*4882a593Smuzhiyun line = line.strip() 174*4882a593Smuzhiyun if not line or line[0] == '#': 175*4882a593Smuzhiyun continue 176*4882a593Smuzhiyun 177*4882a593Smuzhiyun m = re_line.match(line) 178*4882a593Smuzhiyun if not m: 179*4882a593Smuzhiyun print("Warning: Alias file line '%s' not understood" % line) 180*4882a593Smuzhiyun continue 181*4882a593Smuzhiyun 182*4882a593Smuzhiyun list = alias.get(m.group(1), []) 183*4882a593Smuzhiyun for item in m.group(2).split(','): 184*4882a593Smuzhiyun item = item.strip() 185*4882a593Smuzhiyun if item: 186*4882a593Smuzhiyun list.append(item) 187*4882a593Smuzhiyun alias[m.group(1)] = list 188*4882a593Smuzhiyun 189*4882a593Smuzhiyun fd.close() 190*4882a593Smuzhiyun 191*4882a593Smuzhiyundef CreatePatmanConfigFile(config_fname): 192*4882a593Smuzhiyun """Creates a config file under $(HOME)/.patman if it can't find one. 193*4882a593Smuzhiyun 194*4882a593Smuzhiyun Args: 195*4882a593Smuzhiyun config_fname: Default config filename i.e., $(HOME)/.patman 196*4882a593Smuzhiyun 197*4882a593Smuzhiyun Returns: 198*4882a593Smuzhiyun None 199*4882a593Smuzhiyun """ 200*4882a593Smuzhiyun name = gitutil.GetDefaultUserName() 201*4882a593Smuzhiyun if name == None: 202*4882a593Smuzhiyun name = raw_input("Enter name: ") 203*4882a593Smuzhiyun 204*4882a593Smuzhiyun email = gitutil.GetDefaultUserEmail() 205*4882a593Smuzhiyun 206*4882a593Smuzhiyun if email == None: 207*4882a593Smuzhiyun email = raw_input("Enter email: ") 208*4882a593Smuzhiyun 209*4882a593Smuzhiyun try: 210*4882a593Smuzhiyun f = open(config_fname, 'w') 211*4882a593Smuzhiyun except IOError: 212*4882a593Smuzhiyun print("Couldn't create patman config file\n") 213*4882a593Smuzhiyun raise 214*4882a593Smuzhiyun 215*4882a593Smuzhiyun print('''[alias] 216*4882a593Smuzhiyunme: %s <%s> 217*4882a593Smuzhiyun 218*4882a593Smuzhiyun[bounces] 219*4882a593Smuzhiyunnxp = Zhikang Zhang <zhikang.zhang@nxp.com> 220*4882a593Smuzhiyun''' % (name, email), file=f) 221*4882a593Smuzhiyun f.close(); 222*4882a593Smuzhiyun 223*4882a593Smuzhiyundef _UpdateDefaults(parser, config): 224*4882a593Smuzhiyun """Update the given OptionParser defaults based on config. 225*4882a593Smuzhiyun 226*4882a593Smuzhiyun We'll walk through all of the settings from the parser 227*4882a593Smuzhiyun For each setting we'll look for a default in the option parser. 228*4882a593Smuzhiyun If it's found we'll update the option parser default. 229*4882a593Smuzhiyun 230*4882a593Smuzhiyun The idea here is that the .patman file should be able to update 231*4882a593Smuzhiyun defaults but that command line flags should still have the final 232*4882a593Smuzhiyun say. 233*4882a593Smuzhiyun 234*4882a593Smuzhiyun Args: 235*4882a593Smuzhiyun parser: An instance of an OptionParser whose defaults will be 236*4882a593Smuzhiyun updated. 237*4882a593Smuzhiyun config: An instance of _ProjectConfigParser that we will query 238*4882a593Smuzhiyun for settings. 239*4882a593Smuzhiyun """ 240*4882a593Smuzhiyun defaults = parser.get_default_values() 241*4882a593Smuzhiyun for name, val in config.items('settings'): 242*4882a593Smuzhiyun if hasattr(defaults, name): 243*4882a593Smuzhiyun default_val = getattr(defaults, name) 244*4882a593Smuzhiyun if isinstance(default_val, bool): 245*4882a593Smuzhiyun val = config.getboolean('settings', name) 246*4882a593Smuzhiyun elif isinstance(default_val, int): 247*4882a593Smuzhiyun val = config.getint('settings', name) 248*4882a593Smuzhiyun parser.set_default(name, val) 249*4882a593Smuzhiyun else: 250*4882a593Smuzhiyun print("WARNING: Unknown setting %s" % name) 251*4882a593Smuzhiyun 252*4882a593Smuzhiyundef _ReadAliasFile(fname): 253*4882a593Smuzhiyun """Read in the U-Boot git alias file if it exists. 254*4882a593Smuzhiyun 255*4882a593Smuzhiyun Args: 256*4882a593Smuzhiyun fname: Filename to read. 257*4882a593Smuzhiyun """ 258*4882a593Smuzhiyun if os.path.exists(fname): 259*4882a593Smuzhiyun bad_line = None 260*4882a593Smuzhiyun with open(fname) as fd: 261*4882a593Smuzhiyun linenum = 0 262*4882a593Smuzhiyun for line in fd: 263*4882a593Smuzhiyun linenum += 1 264*4882a593Smuzhiyun line = line.strip() 265*4882a593Smuzhiyun if not line or line.startswith('#'): 266*4882a593Smuzhiyun continue 267*4882a593Smuzhiyun words = line.split(' ', 2) 268*4882a593Smuzhiyun if len(words) < 3 or words[0] != 'alias': 269*4882a593Smuzhiyun if not bad_line: 270*4882a593Smuzhiyun bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum, 271*4882a593Smuzhiyun line) 272*4882a593Smuzhiyun continue 273*4882a593Smuzhiyun alias[words[1]] = [s.strip() for s in words[2].split(',')] 274*4882a593Smuzhiyun if bad_line: 275*4882a593Smuzhiyun print(bad_line) 276*4882a593Smuzhiyun 277*4882a593Smuzhiyundef _ReadBouncesFile(fname): 278*4882a593Smuzhiyun """Read in the bounces file if it exists 279*4882a593Smuzhiyun 280*4882a593Smuzhiyun Args: 281*4882a593Smuzhiyun fname: Filename to read. 282*4882a593Smuzhiyun """ 283*4882a593Smuzhiyun if os.path.exists(fname): 284*4882a593Smuzhiyun with open(fname) as fd: 285*4882a593Smuzhiyun for line in fd: 286*4882a593Smuzhiyun if line.startswith('#'): 287*4882a593Smuzhiyun continue 288*4882a593Smuzhiyun bounces.add(line.strip()) 289*4882a593Smuzhiyun 290*4882a593Smuzhiyundef GetItems(config, section): 291*4882a593Smuzhiyun """Get the items from a section of the config. 292*4882a593Smuzhiyun 293*4882a593Smuzhiyun Args: 294*4882a593Smuzhiyun config: _ProjectConfigParser object containing settings 295*4882a593Smuzhiyun section: name of section to retrieve 296*4882a593Smuzhiyun 297*4882a593Smuzhiyun Returns: 298*4882a593Smuzhiyun List of (name, value) tuples for the section 299*4882a593Smuzhiyun """ 300*4882a593Smuzhiyun try: 301*4882a593Smuzhiyun return config.items(section) 302*4882a593Smuzhiyun except ConfigParser.NoSectionError as e: 303*4882a593Smuzhiyun return [] 304*4882a593Smuzhiyun except: 305*4882a593Smuzhiyun raise 306*4882a593Smuzhiyun 307*4882a593Smuzhiyundef Setup(parser, project_name, config_fname=''): 308*4882a593Smuzhiyun """Set up the settings module by reading config files. 309*4882a593Smuzhiyun 310*4882a593Smuzhiyun Args: 311*4882a593Smuzhiyun parser: The parser to update 312*4882a593Smuzhiyun project_name: Name of project that we're working on; we'll look 313*4882a593Smuzhiyun for sections named "project_section" as well. 314*4882a593Smuzhiyun config_fname: Config filename to read ('' for default) 315*4882a593Smuzhiyun """ 316*4882a593Smuzhiyun # First read the git alias file if available 317*4882a593Smuzhiyun _ReadAliasFile('doc/git-mailrc') 318*4882a593Smuzhiyun config = _ProjectConfigParser(project_name) 319*4882a593Smuzhiyun if config_fname == '': 320*4882a593Smuzhiyun config_fname = '%s/.patman' % os.getenv('HOME') 321*4882a593Smuzhiyun 322*4882a593Smuzhiyun if not os.path.exists(config_fname): 323*4882a593Smuzhiyun print("No config file found ~/.patman\nCreating one...\n") 324*4882a593Smuzhiyun CreatePatmanConfigFile(config_fname) 325*4882a593Smuzhiyun 326*4882a593Smuzhiyun config.read(config_fname) 327*4882a593Smuzhiyun 328*4882a593Smuzhiyun for name, value in GetItems(config, 'alias'): 329*4882a593Smuzhiyun alias[name] = value.split(',') 330*4882a593Smuzhiyun 331*4882a593Smuzhiyun _ReadBouncesFile('doc/bounces') 332*4882a593Smuzhiyun for name, value in GetItems(config, 'bounces'): 333*4882a593Smuzhiyun bounces.add(value) 334*4882a593Smuzhiyun 335*4882a593Smuzhiyun _UpdateDefaults(parser, config) 336*4882a593Smuzhiyun 337*4882a593Smuzhiyun# These are the aliases we understand, indexed by alias. Each member is a list. 338*4882a593Smuzhiyunalias = {} 339*4882a593Smuzhiyunbounces = set() 340*4882a593Smuzhiyun 341*4882a593Smuzhiyunif __name__ == "__main__": 342*4882a593Smuzhiyun import doctest 343*4882a593Smuzhiyun 344*4882a593Smuzhiyun doctest.testmod() 345