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