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