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