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