11a459660SWolfgang Denk# SPDX-License-Identifier: GPL-2.0+ 283d290c5STom Rini# Copyright (c) 2011 The Chromium OS Authors. 30d24de9dSSimon Glass# 40d24de9dSSimon Glass 5a920a17bSPaul Burtonfrom __future__ import print_function 6a920a17bSPaul Burton 72ce7b21eSPaul Burtontry: 82ce7b21eSPaul Burton import configparser as ConfigParser 92ce7b21eSPaul Burtonexcept: 100d24de9dSSimon Glass import ConfigParser 112ce7b21eSPaul Burton 120d24de9dSSimon Glassimport os 130d24de9dSSimon Glassimport re 140d24de9dSSimon Glass 150d24de9dSSimon Glassimport command 1687d65558SVikram Narayananimport gitutil 170d24de9dSSimon Glass 18a1dcee84SDoug Anderson"""Default settings per-project. 19a1dcee84SDoug Anderson 20a1dcee84SDoug AndersonThese are used by _ProjectConfigParser. Settings names should match 21a1dcee84SDoug Andersonthe "dest" of the option parser from patman.py. 22a1dcee84SDoug Anderson""" 23a1dcee84SDoug Anderson_default_settings = { 24a1dcee84SDoug Anderson "u-boot": {}, 25a1dcee84SDoug Anderson "linux": { 26a1dcee84SDoug Anderson "process_tags": "False", 27a1dcee84SDoug Anderson } 28a1dcee84SDoug Anderson} 29a1dcee84SDoug Anderson 30a1dcee84SDoug Andersonclass _ProjectConfigParser(ConfigParser.SafeConfigParser): 31a1dcee84SDoug Anderson """ConfigParser that handles projects. 32a1dcee84SDoug Anderson 33a1dcee84SDoug Anderson There are two main goals of this class: 34a1dcee84SDoug Anderson - Load project-specific default settings. 35a1dcee84SDoug Anderson - Merge general default settings/aliases with project-specific ones. 36a1dcee84SDoug Anderson 37a1dcee84SDoug Anderson # Sample config used for tests below... 38f5d44b9bSPaul Burton >>> try: 39f5d44b9bSPaul Burton ... from StringIO import StringIO 40f5d44b9bSPaul Burton ... except ImportError: 41f5d44b9bSPaul Burton ... from io import StringIO 42a1dcee84SDoug Anderson >>> sample_config = ''' 43a1dcee84SDoug Anderson ... [alias] 44a1dcee84SDoug Anderson ... me: Peter P. <likesspiders@example.com> 45a1dcee84SDoug Anderson ... enemies: Evil <evil@example.com> 46a1dcee84SDoug Anderson ... 47a1dcee84SDoug Anderson ... [sm_alias] 48a1dcee84SDoug Anderson ... enemies: Green G. <ugly@example.com> 49a1dcee84SDoug Anderson ... 50a1dcee84SDoug Anderson ... [sm2_alias] 51a1dcee84SDoug Anderson ... enemies: Doc O. <pus@example.com> 52a1dcee84SDoug Anderson ... 53a1dcee84SDoug Anderson ... [settings] 54a1dcee84SDoug Anderson ... am_hero: True 55a1dcee84SDoug Anderson ... ''' 56a1dcee84SDoug Anderson 57a1dcee84SDoug Anderson # Check to make sure that bogus project gets general alias. 58a1dcee84SDoug Anderson >>> config = _ProjectConfigParser("zzz") 59f5d44b9bSPaul Burton >>> config.readfp(StringIO(sample_config)) 60a1dcee84SDoug Anderson >>> config.get("alias", "enemies") 61*ec9e0f47SSimon Glass u'Evil <evil@example.com>' 62a1dcee84SDoug Anderson 63a1dcee84SDoug Anderson # Check to make sure that alias gets overridden by project. 64a1dcee84SDoug Anderson >>> config = _ProjectConfigParser("sm") 65f5d44b9bSPaul Burton >>> config.readfp(StringIO(sample_config)) 66a1dcee84SDoug Anderson >>> config.get("alias", "enemies") 67*ec9e0f47SSimon Glass u'Green G. <ugly@example.com>' 68a1dcee84SDoug Anderson 69a1dcee84SDoug Anderson # Check to make sure that settings get merged with project. 70a1dcee84SDoug Anderson >>> config = _ProjectConfigParser("linux") 71f5d44b9bSPaul Burton >>> config.readfp(StringIO(sample_config)) 72a1dcee84SDoug Anderson >>> sorted(config.items("settings")) 73*ec9e0f47SSimon Glass [(u'am_hero', u'True'), (u'process_tags', u'False')] 74a1dcee84SDoug Anderson 75a1dcee84SDoug Anderson # Check to make sure that settings works with unknown project. 76a1dcee84SDoug Anderson >>> config = _ProjectConfigParser("unknown") 77f5d44b9bSPaul Burton >>> config.readfp(StringIO(sample_config)) 78a1dcee84SDoug Anderson >>> sorted(config.items("settings")) 79*ec9e0f47SSimon Glass [(u'am_hero', u'True')] 80a1dcee84SDoug Anderson """ 81a1dcee84SDoug Anderson def __init__(self, project_name): 82a1dcee84SDoug Anderson """Construct _ProjectConfigParser. 83a1dcee84SDoug Anderson 84a1dcee84SDoug Anderson In addition to standard SafeConfigParser initialization, this also loads 85a1dcee84SDoug Anderson project defaults. 86a1dcee84SDoug Anderson 87a1dcee84SDoug Anderson Args: 88a1dcee84SDoug Anderson project_name: The name of the project. 89a1dcee84SDoug Anderson """ 90a1dcee84SDoug Anderson self._project_name = project_name 91a1dcee84SDoug Anderson ConfigParser.SafeConfigParser.__init__(self) 92a1dcee84SDoug Anderson 93a1dcee84SDoug Anderson # Update the project settings in the config based on 94a1dcee84SDoug Anderson # the _default_settings global. 95a1dcee84SDoug Anderson project_settings = "%s_settings" % project_name 96a1dcee84SDoug Anderson if not self.has_section(project_settings): 97a1dcee84SDoug Anderson self.add_section(project_settings) 98a1dcee84SDoug Anderson project_defaults = _default_settings.get(project_name, {}) 99c9eac38aSPaul Burton for setting_name, setting_value in project_defaults.items(): 100a1dcee84SDoug Anderson self.set(project_settings, setting_name, setting_value) 101a1dcee84SDoug Anderson 102*ec9e0f47SSimon Glass def _to_unicode(self, val): 103*ec9e0f47SSimon Glass """Make sure a value is of type 'unicode' 104*ec9e0f47SSimon Glass 105*ec9e0f47SSimon Glass Args: 106*ec9e0f47SSimon Glass val: string or unicode object 107*ec9e0f47SSimon Glass 108*ec9e0f47SSimon Glass Returns: 109*ec9e0f47SSimon Glass unicode version of val 110*ec9e0f47SSimon Glass """ 111*ec9e0f47SSimon Glass return val if isinstance(val, unicode) else val.decode('utf-8') 112*ec9e0f47SSimon Glass 113a1dcee84SDoug Anderson def get(self, section, option, *args, **kwargs): 114a1dcee84SDoug Anderson """Extend SafeConfigParser to try project_section before section. 115a1dcee84SDoug Anderson 116a1dcee84SDoug Anderson Args: 117a1dcee84SDoug Anderson See SafeConfigParser. 118a1dcee84SDoug Anderson Returns: 119a1dcee84SDoug Anderson See SafeConfigParser. 120a1dcee84SDoug Anderson """ 121a1dcee84SDoug Anderson try: 122*ec9e0f47SSimon Glass val = ConfigParser.SafeConfigParser.get( 123a1dcee84SDoug Anderson self, "%s_%s" % (self._project_name, section), option, 124a1dcee84SDoug Anderson *args, **kwargs 125a1dcee84SDoug Anderson ) 126a1dcee84SDoug Anderson except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): 127*ec9e0f47SSimon Glass val = ConfigParser.SafeConfigParser.get( 128a1dcee84SDoug Anderson self, section, option, *args, **kwargs 129a1dcee84SDoug Anderson ) 130*ec9e0f47SSimon Glass return self._to_unicode(val) 131a1dcee84SDoug Anderson 132a1dcee84SDoug Anderson def items(self, section, *args, **kwargs): 133a1dcee84SDoug Anderson """Extend SafeConfigParser to add project_section to section. 134a1dcee84SDoug Anderson 135a1dcee84SDoug Anderson Args: 136a1dcee84SDoug Anderson See SafeConfigParser. 137a1dcee84SDoug Anderson Returns: 138a1dcee84SDoug Anderson See SafeConfigParser. 139a1dcee84SDoug Anderson """ 140a1dcee84SDoug Anderson project_items = [] 141a1dcee84SDoug Anderson has_project_section = False 142a1dcee84SDoug Anderson top_items = [] 143a1dcee84SDoug Anderson 144a1dcee84SDoug Anderson # Get items from the project section 145a1dcee84SDoug Anderson try: 146a1dcee84SDoug Anderson project_items = ConfigParser.SafeConfigParser.items( 147a1dcee84SDoug Anderson self, "%s_%s" % (self._project_name, section), *args, **kwargs 148a1dcee84SDoug Anderson ) 149a1dcee84SDoug Anderson has_project_section = True 150a1dcee84SDoug Anderson except ConfigParser.NoSectionError: 151a1dcee84SDoug Anderson pass 152a1dcee84SDoug Anderson 153a1dcee84SDoug Anderson # Get top-level items 154a1dcee84SDoug Anderson try: 155a1dcee84SDoug Anderson top_items = ConfigParser.SafeConfigParser.items( 156a1dcee84SDoug Anderson self, section, *args, **kwargs 157a1dcee84SDoug Anderson ) 158a1dcee84SDoug Anderson except ConfigParser.NoSectionError: 159a1dcee84SDoug Anderson # If neither section exists raise the error on... 160a1dcee84SDoug Anderson if not has_project_section: 161a1dcee84SDoug Anderson raise 162a1dcee84SDoug Anderson 163a1dcee84SDoug Anderson item_dict = dict(top_items) 164a1dcee84SDoug Anderson item_dict.update(project_items) 165*ec9e0f47SSimon Glass return {(self._to_unicode(item), self._to_unicode(val)) 166*ec9e0f47SSimon Glass for item, val in item_dict.iteritems()} 167a1dcee84SDoug Anderson 1680d24de9dSSimon Glassdef ReadGitAliases(fname): 1690d24de9dSSimon Glass """Read a git alias file. This is in the form used by git: 1700d24de9dSSimon Glass 1710d24de9dSSimon Glass alias uboot u-boot@lists.denx.de 1720d24de9dSSimon Glass alias wd Wolfgang Denk <wd@denx.de> 1730d24de9dSSimon Glass 1740d24de9dSSimon Glass Args: 1750d24de9dSSimon Glass fname: Filename to read 1760d24de9dSSimon Glass """ 1770d24de9dSSimon Glass try: 1780d24de9dSSimon Glass fd = open(fname, 'r') 1790d24de9dSSimon Glass except IOError: 180a920a17bSPaul Burton print("Warning: Cannot find alias file '%s'" % fname) 1810d24de9dSSimon Glass return 1820d24de9dSSimon Glass 1830d24de9dSSimon Glass re_line = re.compile('alias\s+(\S+)\s+(.*)') 1840d24de9dSSimon Glass for line in fd.readlines(): 1850d24de9dSSimon Glass line = line.strip() 1860d24de9dSSimon Glass if not line or line[0] == '#': 1870d24de9dSSimon Glass continue 1880d24de9dSSimon Glass 1890d24de9dSSimon Glass m = re_line.match(line) 1900d24de9dSSimon Glass if not m: 191a920a17bSPaul Burton print("Warning: Alias file line '%s' not understood" % line) 1920d24de9dSSimon Glass continue 1930d24de9dSSimon Glass 1940d24de9dSSimon Glass list = alias.get(m.group(1), []) 1950d24de9dSSimon Glass for item in m.group(2).split(','): 1960d24de9dSSimon Glass item = item.strip() 1970d24de9dSSimon Glass if item: 1980d24de9dSSimon Glass list.append(item) 1990d24de9dSSimon Glass alias[m.group(1)] = list 2000d24de9dSSimon Glass 2010d24de9dSSimon Glass fd.close() 2020d24de9dSSimon Glass 20387d65558SVikram Narayanandef CreatePatmanConfigFile(config_fname): 20487d65558SVikram Narayanan """Creates a config file under $(HOME)/.patman if it can't find one. 20587d65558SVikram Narayanan 20687d65558SVikram Narayanan Args: 20787d65558SVikram Narayanan config_fname: Default config filename i.e., $(HOME)/.patman 20887d65558SVikram Narayanan 20987d65558SVikram Narayanan Returns: 21087d65558SVikram Narayanan None 21187d65558SVikram Narayanan """ 21287d65558SVikram Narayanan name = gitutil.GetDefaultUserName() 21387d65558SVikram Narayanan if name == None: 21487d65558SVikram Narayanan name = raw_input("Enter name: ") 21587d65558SVikram Narayanan 21687d65558SVikram Narayanan email = gitutil.GetDefaultUserEmail() 21787d65558SVikram Narayanan 21887d65558SVikram Narayanan if email == None: 21987d65558SVikram Narayanan email = raw_input("Enter email: ") 22087d65558SVikram Narayanan 22187d65558SVikram Narayanan try: 22287d65558SVikram Narayanan f = open(config_fname, 'w') 22387d65558SVikram Narayanan except IOError: 224a920a17bSPaul Burton print("Couldn't create patman config file\n") 22587d65558SVikram Narayanan raise 22687d65558SVikram Narayanan 227ad893140SSimon Glass print('''[alias] 228ad893140SSimon Glassme: %s <%s> 229ad893140SSimon Glass 230ad893140SSimon Glass[bounces] 231ad893140SSimon Glassnxp = Zhikang Zhang <zhikang.zhang@nxp.com> 232ad893140SSimon Glass''' % (name, email), file=f) 23387d65558SVikram Narayanan f.close(); 23487d65558SVikram Narayanan 2358568baedSDoug Andersondef _UpdateDefaults(parser, config): 2368568baedSDoug Anderson """Update the given OptionParser defaults based on config. 2378568baedSDoug Anderson 2388568baedSDoug Anderson We'll walk through all of the settings from the parser 2398568baedSDoug Anderson For each setting we'll look for a default in the option parser. 2408568baedSDoug Anderson If it's found we'll update the option parser default. 2418568baedSDoug Anderson 2428568baedSDoug Anderson The idea here is that the .patman file should be able to update 2438568baedSDoug Anderson defaults but that command line flags should still have the final 2448568baedSDoug Anderson say. 2458568baedSDoug Anderson 2468568baedSDoug Anderson Args: 2478568baedSDoug Anderson parser: An instance of an OptionParser whose defaults will be 2488568baedSDoug Anderson updated. 249a1dcee84SDoug Anderson config: An instance of _ProjectConfigParser that we will query 2508568baedSDoug Anderson for settings. 2518568baedSDoug Anderson """ 2528568baedSDoug Anderson defaults = parser.get_default_values() 2538568baedSDoug Anderson for name, val in config.items('settings'): 2548568baedSDoug Anderson if hasattr(defaults, name): 2558568baedSDoug Anderson default_val = getattr(defaults, name) 2568568baedSDoug Anderson if isinstance(default_val, bool): 2578568baedSDoug Anderson val = config.getboolean('settings', name) 2588568baedSDoug Anderson elif isinstance(default_val, int): 2598568baedSDoug Anderson val = config.getint('settings', name) 2608568baedSDoug Anderson parser.set_default(name, val) 2618568baedSDoug Anderson else: 262a920a17bSPaul Burton print("WARNING: Unknown setting %s" % name) 2638568baedSDoug Anderson 2648895b3e1SSimon Glassdef _ReadAliasFile(fname): 2658895b3e1SSimon Glass """Read in the U-Boot git alias file if it exists. 2668895b3e1SSimon Glass 2678895b3e1SSimon Glass Args: 2688895b3e1SSimon Glass fname: Filename to read. 2698895b3e1SSimon Glass """ 2708895b3e1SSimon Glass if os.path.exists(fname): 2718895b3e1SSimon Glass bad_line = None 2728895b3e1SSimon Glass with open(fname) as fd: 2738895b3e1SSimon Glass linenum = 0 2748895b3e1SSimon Glass for line in fd: 2758895b3e1SSimon Glass linenum += 1 2768895b3e1SSimon Glass line = line.strip() 2778895b3e1SSimon Glass if not line or line.startswith('#'): 2788895b3e1SSimon Glass continue 279b8a48fbcSAdam Sampson words = line.split(None, 2) 2808895b3e1SSimon Glass if len(words) < 3 or words[0] != 'alias': 2818895b3e1SSimon Glass if not bad_line: 2828895b3e1SSimon Glass bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum, 2838895b3e1SSimon Glass line) 2848895b3e1SSimon Glass continue 2858895b3e1SSimon Glass alias[words[1]] = [s.strip() for s in words[2].split(',')] 2868895b3e1SSimon Glass if bad_line: 287a920a17bSPaul Burton print(bad_line) 2888895b3e1SSimon Glass 289e11aa602SChris Packhamdef _ReadBouncesFile(fname): 290e11aa602SChris Packham """Read in the bounces file if it exists 291e11aa602SChris Packham 292e11aa602SChris Packham Args: 293e11aa602SChris Packham fname: Filename to read. 294e11aa602SChris Packham """ 295e11aa602SChris Packham if os.path.exists(fname): 296e11aa602SChris Packham with open(fname) as fd: 297e11aa602SChris Packham for line in fd: 298e11aa602SChris Packham if line.startswith('#'): 299e11aa602SChris Packham continue 300e11aa602SChris Packham bounces.add(line.strip()) 301e11aa602SChris Packham 302ad893140SSimon Glassdef GetItems(config, section): 303ad893140SSimon Glass """Get the items from a section of the config. 304ad893140SSimon Glass 305ad893140SSimon Glass Args: 306ad893140SSimon Glass config: _ProjectConfigParser object containing settings 307ad893140SSimon Glass section: name of section to retrieve 308ad893140SSimon Glass 309ad893140SSimon Glass Returns: 310ad893140SSimon Glass List of (name, value) tuples for the section 311ad893140SSimon Glass """ 312ad893140SSimon Glass try: 313ad893140SSimon Glass return config.items(section) 314ad893140SSimon Glass except ConfigParser.NoSectionError as e: 315ad893140SSimon Glass return [] 316ad893140SSimon Glass except: 317ad893140SSimon Glass raise 318ad893140SSimon Glass 319a1dcee84SDoug Andersondef Setup(parser, project_name, config_fname=''): 3200d24de9dSSimon Glass """Set up the settings module by reading config files. 3210d24de9dSSimon Glass 3220d24de9dSSimon Glass Args: 3238568baedSDoug Anderson parser: The parser to update 324a1dcee84SDoug Anderson project_name: Name of project that we're working on; we'll look 325a1dcee84SDoug Anderson for sections named "project_section" as well. 3260d24de9dSSimon Glass config_fname: Config filename to read ('' for default) 3270d24de9dSSimon Glass """ 3288895b3e1SSimon Glass # First read the git alias file if available 3298895b3e1SSimon Glass _ReadAliasFile('doc/git-mailrc') 330a1dcee84SDoug Anderson config = _ProjectConfigParser(project_name) 3310d24de9dSSimon Glass if config_fname == '': 3322b36c75dSVikram Narayanan config_fname = '%s/.patman' % os.getenv('HOME') 33387d65558SVikram Narayanan 33487d65558SVikram Narayanan if not os.path.exists(config_fname): 335a920a17bSPaul Burton print("No config file found ~/.patman\nCreating one...\n") 33687d65558SVikram Narayanan CreatePatmanConfigFile(config_fname) 33787d65558SVikram Narayanan 3388568baedSDoug Anderson config.read(config_fname) 3390d24de9dSSimon Glass 340ad893140SSimon Glass for name, value in GetItems(config, 'alias'): 3410d24de9dSSimon Glass alias[name] = value.split(',') 3420d24de9dSSimon Glass 343e11aa602SChris Packham _ReadBouncesFile('doc/bounces') 344ad893140SSimon Glass for name, value in GetItems(config, 'bounces'): 345e11aa602SChris Packham bounces.add(value) 346e11aa602SChris Packham 3478568baedSDoug Anderson _UpdateDefaults(parser, config) 3480d24de9dSSimon Glass 3490d24de9dSSimon Glass# These are the aliases we understand, indexed by alias. Each member is a list. 3500d24de9dSSimon Glassalias = {} 351e11aa602SChris Packhambounces = set() 352a1dcee84SDoug Anderson 353a1dcee84SDoug Andersonif __name__ == "__main__": 354a1dcee84SDoug Anderson import doctest 355a1dcee84SDoug Anderson 356a1dcee84SDoug Anderson doctest.testmod() 357