settings.py 9.6 KB

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