1# SPDX-License-Identifier: GPL-2.0+ 2# Copyright (c) 2011 The Chromium OS Authors. 3# 4 5try: 6 import configparser as ConfigParser 7except: 8 import ConfigParser 9 10import argparse 11import os 12import re 13 14from patman import command 15from patman import tools 16 17"""Default settings per-project. 18 19These are used by _ProjectConfigParser. Settings names should match 20the "dest" of the option parser from patman.py. 21""" 22_default_settings = { 23 "u-boot": {}, 24 "linux": { 25 "process_tags": "False", 26 }, 27 "gcc": { 28 "process_tags": "False", 29 "add_signoff": "False", 30 "check_patch": "False", 31 }, 32} 33 34class _ProjectConfigParser(ConfigParser.SafeConfigParser): 35 """ConfigParser that handles projects. 36 37 There are two main goals of this class: 38 - Load project-specific default settings. 39 - Merge general default settings/aliases with project-specific ones. 40 41 # Sample config used for tests below... 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 >>> str(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 >>> str(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((str(a), str(b)) for (a, b) in 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((str(a), str(b)) for (a, b) in 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 val = ConfigParser.SafeConfigParser.get( 113 self, "%s_%s" % (self._project_name, section), option, 114 *args, **kwargs 115 ) 116 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): 117 val = ConfigParser.SafeConfigParser.get( 118 self, section, option, *args, **kwargs 119 ) 120 return val 121 122 def items(self, section, *args, **kwargs): 123 """Extend SafeConfigParser to add project_section to section. 124 125 Args: 126 See SafeConfigParser. 127 Returns: 128 See SafeConfigParser. 129 """ 130 project_items = [] 131 has_project_section = False 132 top_items = [] 133 134 # Get items from the project section 135 try: 136 project_items = ConfigParser.SafeConfigParser.items( 137 self, "%s_%s" % (self._project_name, section), *args, **kwargs 138 ) 139 has_project_section = True 140 except ConfigParser.NoSectionError: 141 pass 142 143 # Get top-level items 144 try: 145 top_items = ConfigParser.SafeConfigParser.items( 146 self, section, *args, **kwargs 147 ) 148 except ConfigParser.NoSectionError: 149 # If neither section exists raise the error on... 150 if not has_project_section: 151 raise 152 153 item_dict = dict(top_items) 154 item_dict.update(project_items) 155 return {(item, val) for item, val in item_dict.items()} 156 157def ReadGitAliases(fname): 158 """Read a git alias file. This is in the form used by git: 159 160 alias uboot u-boot@lists.denx.de 161 alias wd Wolfgang Denk <wd@denx.de> 162 163 Args: 164 fname: Filename to read 165 """ 166 try: 167 fd = open(fname, 'r', encoding='utf-8') 168 except IOError: 169 print("Warning: Cannot find alias file '%s'" % fname) 170 return 171 172 re_line = re.compile('alias\s+(\S+)\s+(.*)') 173 for line in fd.readlines(): 174 line = line.strip() 175 if not line or line[0] == '#': 176 continue 177 178 m = re_line.match(line) 179 if not m: 180 print("Warning: Alias file line '%s' not understood" % line) 181 continue 182 183 list = alias.get(m.group(1), []) 184 for item in m.group(2).split(','): 185 item = item.strip() 186 if item: 187 list.append(item) 188 alias[m.group(1)] = list 189 190 fd.close() 191 192def CreatePatmanConfigFile(gitutil, config_fname): 193 """Creates a config file under $(HOME)/.patman if it can't find one. 194 195 Args: 196 config_fname: Default config filename i.e., $(HOME)/.patman 197 198 Returns: 199 None 200 """ 201 name = gitutil.GetDefaultUserName() 202 if name == None: 203 name = raw_input("Enter name: ") 204 205 email = gitutil.GetDefaultUserEmail() 206 207 if email == None: 208 email = raw_input("Enter email: ") 209 210 try: 211 f = open(config_fname, 'w') 212 except IOError: 213 print("Couldn't create patman config file\n") 214 raise 215 216 print('''[alias] 217me: %s <%s> 218 219[bounces] 220nxp = Zhikang Zhang <zhikang.zhang@nxp.com> 221''' % (name, email), file=f) 222 f.close(); 223 224def _UpdateDefaults(main_parser, config): 225 """Update the given OptionParser defaults based on config. 226 227 We'll walk through all of the settings from all parsers. 228 For each setting we'll look for a default in the option parser. 229 If it's found we'll update the option parser default. 230 231 The idea here is that the .patman file should be able to update 232 defaults but that command line flags should still have the final 233 say. 234 235 Args: 236 parser: An instance of an ArgumentParser whose defaults will be 237 updated. 238 config: An instance of _ProjectConfigParser that we will query 239 for settings. 240 """ 241 # Find all the parsers and subparsers 242 parsers = [main_parser] 243 parsers += [subparser for action in main_parser._actions 244 if isinstance(action, argparse._SubParsersAction) 245 for _, subparser in action.choices.items()] 246 247 # Collect the defaults from each parser 248 defaults = {} 249 for parser in parsers: 250 pdefs = parser.parse_known_args()[0] 251 defaults.update(vars(pdefs)) 252 253 # Go through the settings and collect defaults 254 for name, val in config.items('settings'): 255 if name in defaults: 256 default_val = defaults[name] 257 if isinstance(default_val, bool): 258 val = config.getboolean('settings', name) 259 elif isinstance(default_val, int): 260 val = config.getint('settings', name) 261 elif isinstance(default_val, str): 262 val = config.get('settings', name) 263 defaults[name] = val 264 else: 265 print("WARNING: Unknown setting %s" % name) 266 267 # Set all the defaults (this propagates through all subparsers) 268 main_parser.set_defaults(**defaults) 269 270def _ReadAliasFile(fname): 271 """Read in the U-Boot git alias file if it exists. 272 273 Args: 274 fname: Filename to read. 275 """ 276 if os.path.exists(fname): 277 bad_line = None 278 with open(fname, encoding='utf-8') as fd: 279 linenum = 0 280 for line in fd: 281 linenum += 1 282 line = line.strip() 283 if not line or line.startswith('#'): 284 continue 285 words = line.split(None, 2) 286 if len(words) < 3 or words[0] != 'alias': 287 if not bad_line: 288 bad_line = "%s:%d:Invalid line '%s'" % (fname, linenum, 289 line) 290 continue 291 alias[words[1]] = [s.strip() for s in words[2].split(',')] 292 if bad_line: 293 print(bad_line) 294 295def _ReadBouncesFile(fname): 296 """Read in the bounces file if it exists 297 298 Args: 299 fname: Filename to read. 300 """ 301 if os.path.exists(fname): 302 with open(fname) as fd: 303 for line in fd: 304 if line.startswith('#'): 305 continue 306 bounces.add(line.strip()) 307 308def GetItems(config, section): 309 """Get the items from a section of the config. 310 311 Args: 312 config: _ProjectConfigParser object containing settings 313 section: name of section to retrieve 314 315 Returns: 316 List of (name, value) tuples for the section 317 """ 318 try: 319 return config.items(section) 320 except ConfigParser.NoSectionError as e: 321 return [] 322 except: 323 raise 324 325def Setup(gitutil, parser, project_name, config_fname=''): 326 """Set up the settings module by reading config files. 327 328 Args: 329 parser: The parser to update 330 project_name: Name of project that we're working on; we'll look 331 for sections named "project_section" as well. 332 config_fname: Config filename to read ('' for default) 333 """ 334 # First read the git alias file if available 335 _ReadAliasFile('doc/git-mailrc') 336 config = _ProjectConfigParser(project_name) 337 if config_fname == '': 338 config_fname = '%s/.patman' % os.getenv('HOME') 339 340 if not os.path.exists(config_fname): 341 print("No config file found ~/.patman\nCreating one...\n") 342 CreatePatmanConfigFile(gitutil, config_fname) 343 344 config.read(config_fname) 345 346 for name, value in GetItems(config, 'alias'): 347 alias[name] = value.split(',') 348 349 _ReadBouncesFile('doc/bounces') 350 for name, value in GetItems(config, 'bounces'): 351 bounces.add(value) 352 353 _UpdateDefaults(parser, config) 354 355# These are the aliases we understand, indexed by alias. Each member is a list. 356alias = {} 357bounces = set() 358 359if __name__ == "__main__": 360 import doctest 361 362 doctest.testmod() 363