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