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 '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 '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 [('am_hero', 'True'), ('process_tags', '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 [('am_hero', '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 get(self, section, option, *args, **kwargs): 103 """Extend SafeConfigParser to try project_section before section. 104 105 Args: 106 See SafeConfigParser. 107 Returns: 108 See SafeConfigParser. 109 """ 110 try: 111 return ConfigParser.SafeConfigParser.get( 112 self, "%s_%s" % (self._project_name, section), option, 113 *args, **kwargs 114 ) 115 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): 116 return ConfigParser.SafeConfigParser.get( 117 self, section, option, *args, **kwargs 118 ) 119 120 def items(self, section, *args, **kwargs): 121 """Extend SafeConfigParser to add project_section to section. 122 123 Args: 124 See SafeConfigParser. 125 Returns: 126 See SafeConfigParser. 127 """ 128 project_items = [] 129 has_project_section = False 130 top_items = [] 131 132 # Get items from the project section 133 try: 134 project_items = ConfigParser.SafeConfigParser.items( 135 self, "%s_%s" % (self._project_name, section), *args, **kwargs 136 ) 137 has_project_section = True 138 except ConfigParser.NoSectionError: 139 pass 140 141 # Get top-level items 142 try: 143 top_items = ConfigParser.SafeConfigParser.items( 144 self, section, *args, **kwargs 145 ) 146 except ConfigParser.NoSectionError: 147 # If neither section exists raise the error on... 148 if not has_project_section: 149 raise 150 151 item_dict = dict(top_items) 152 item_dict.update(project_items) 153 return item_dict.items() 154 155def ReadGitAliases(fname): 156 """Read a git alias file. This is in the form used by git: 157 158 alias uboot u-boot@lists.denx.de 159 alias wd Wolfgang Denk <wd@denx.de> 160 161 Args: 162 fname: Filename to read 163 """ 164 try: 165 fd = open(fname, 'r') 166 except IOError: 167 print("Warning: Cannot find alias file '%s'" % fname) 168 return 169 170 re_line = re.compile('alias\s+(\S+)\s+(.*)') 171 for line in fd.readlines(): 172 line = line.strip() 173 if not line or line[0] == '#': 174 continue 175 176 m = re_line.match(line) 177 if not m: 178 print("Warning: Alias file line '%s' not understood" % line) 179 continue 180 181 list = alias.get(m.group(1), []) 182 for item in m.group(2).split(','): 183 item = item.strip() 184 if item: 185 list.append(item) 186 alias[m.group(1)] = list 187 188 fd.close() 189 190def CreatePatmanConfigFile(config_fname): 191 """Creates a config file under $(HOME)/.patman if it can't find one. 192 193 Args: 194 config_fname: Default config filename i.e., $(HOME)/.patman 195 196 Returns: 197 None 198 """ 199 name = gitutil.GetDefaultUserName() 200 if name == None: 201 name = raw_input("Enter name: ") 202 203 email = gitutil.GetDefaultUserEmail() 204 205 if email == None: 206 email = raw_input("Enter email: ") 207 208 try: 209 f = open(config_fname, 'w') 210 except IOError: 211 print("Couldn't create patman config file\n") 212 raise 213 214 print('''[alias] 215me: %s <%s> 216 217[bounces] 218nxp = Zhikang Zhang <zhikang.zhang@nxp.com> 219''' % (name, email), file=f) 220 f.close(); 221 222def _UpdateDefaults(parser, config): 223 """Update the given OptionParser defaults based on config. 224 225 We'll walk through all of the settings from the parser 226 For each setting we'll look for a default in the option parser. 227 If it's found we'll update the option parser default. 228 229 The idea here is that the .patman file should be able to update 230 defaults but that command line flags should still have the final 231 say. 232 233 Args: 234 parser: An instance of an OptionParser whose defaults will be 235 updated. 236 config: An instance of _ProjectConfigParser that we will query 237 for settings. 238 """ 239 defaults = parser.get_default_values() 240 for name, val in config.items('settings'): 241 if hasattr(defaults, name): 242 default_val = getattr(defaults, name) 243 if isinstance(default_val, bool): 244 val = config.getboolean('settings', name) 245 elif isinstance(default_val, int): 246 val = config.getint('settings', name) 247 parser.set_default(name, val) 248 else: 249 print("WARNING: Unknown setting %s" % name) 250 251def _ReadAliasFile(fname): 252 """Read in the U-Boot git alias file if it exists. 253 254 Args: 255 fname: Filename to read. 256 """ 257 if os.path.exists(fname): 258 bad_line = None 259 with open(fname) as fd: 260 linenum = 0 261 for line in fd: 262 linenum += 1 263 line = line.strip() 264 if not line or line.startswith('#'): 265 continue 266 words = line.split(None, 2) 267 if len(words) < 3 or words[0] != 'alias': 268 if not bad_line: 269 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum, 270 line) 271 continue 272 alias[words[1]] = [s.strip() for s in words[2].split(',')] 273 if bad_line: 274 print(bad_line) 275 276def _ReadBouncesFile(fname): 277 """Read in the bounces file if it exists 278 279 Args: 280 fname: Filename to read. 281 """ 282 if os.path.exists(fname): 283 with open(fname) as fd: 284 for line in fd: 285 if line.startswith('#'): 286 continue 287 bounces.add(line.strip()) 288 289def GetItems(config, section): 290 """Get the items from a section of the config. 291 292 Args: 293 config: _ProjectConfigParser object containing settings 294 section: name of section to retrieve 295 296 Returns: 297 List of (name, value) tuples for the section 298 """ 299 try: 300 return config.items(section) 301 except ConfigParser.NoSectionError as e: 302 return [] 303 except: 304 raise 305 306def Setup(parser, project_name, config_fname=''): 307 """Set up the settings module by reading config files. 308 309 Args: 310 parser: The parser to update 311 project_name: Name of project that we're working on; we'll look 312 for sections named "project_section" as well. 313 config_fname: Config filename to read ('' for default) 314 """ 315 # First read the git alias file if available 316 _ReadAliasFile('doc/git-mailrc') 317 config = _ProjectConfigParser(project_name) 318 if config_fname == '': 319 config_fname = '%s/.patman' % os.getenv('HOME') 320 321 if not os.path.exists(config_fname): 322 print("No config file found ~/.patman\nCreating one...\n") 323 CreatePatmanConfigFile(config_fname) 324 325 config.read(config_fname) 326 327 for name, value in GetItems(config, 'alias'): 328 alias[name] = value.split(',') 329 330 _ReadBouncesFile('doc/bounces') 331 for name, value in GetItems(config, 'bounces'): 332 bounces.add(value) 333 334 _UpdateDefaults(parser, config) 335 336# These are the aliases we understand, indexed by alias. Each member is a list. 337alias = {} 338bounces = set() 339 340if __name__ == "__main__": 341 import doctest 342 343 doctest.testmod() 344