comparison mercurial/ui.py @ 33499:0407a51b9d8c

codemod: register core configitems using a script This is done by a script [2] using RedBaron [1], a tool designed for doing code refactoring. All "default" values are decided by the script and are strongly consistent with the existing code. There are 2 changes done manually to fix tests: [warn] mercurial/exchange.py: experimental.bundle2-output-capture: default needs manual removal [warn] mercurial/localrepo.py: experimental.hook-track-tags: default needs manual removal Since RedBaron is not confident about how to indent things [2]. [1]: https://github.com/PyCQA/redbaron [2]: https://github.com/PyCQA/redbaron/issues/100 [3]: #!/usr/bin/env python # codemod_configitems.py - codemod tool to fill configitems # # Copyright 2017 Facebook, Inc. # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. from __future__ import absolute_import, print_function import os import sys import redbaron def readpath(path): with open(path) as f: return f.read() def writepath(path, content): with open(path, 'w') as f: f.write(content) _configmethods = {'config', 'configbool', 'configint', 'configbytes', 'configlist', 'configdate'} def extractstring(rnode): """get the string from a RedBaron string or call_argument node""" while rnode.type != 'string': rnode = rnode.value return rnode.value[1:-1] # unquote, "'str'" -> "str" def uiconfigitems(red): """match *.ui.config* pattern, yield (node, method, args, section, name)""" for node in red.find_all('atomtrailers'): entry = None try: obj = node[-3].value method = node[-2].value args = node[-1] section = args[0].value name = args[1].value if (obj in ('ui', 'self') and method in _configmethods and section.type == 'string' and name.type == 'string'): entry = (node, method, args, extractstring(section), extractstring(name)) except Exception: pass else: if entry: yield entry def coreconfigitems(red): """match coreconfigitem(...) pattern, yield (node, args, section, name)""" for node in red.find_all('atomtrailers'): entry = None try: args = node[1] section = args[0].value name = args[1].value if (node[0].value == 'coreconfigitem' and section.type == 'string' and name.type == 'string'): entry = (node, args, extractstring(section), extractstring(name)) except Exception: pass else: if entry: yield entry def registercoreconfig(cfgred, section, name, defaultrepr): """insert coreconfigitem to cfgred AST section and name are plain string, defaultrepr is a string """ # find a place to insert the "coreconfigitem" item entries = list(coreconfigitems(cfgred)) for node, args, nodesection, nodename in reversed(entries): if (nodesection, nodename) < (section, name): # insert after this entry node.insert_after( 'coreconfigitem(%r, %r,\n' ' default=%s,\n' ')' % (section, name, defaultrepr)) return def main(argv): if not argv: print('Usage: codemod_configitems.py FILES\n' 'For example, FILES could be "{hgext,mercurial}/*/**.py"') dirname = os.path.dirname reporoot = dirname(dirname(dirname(os.path.abspath(__file__)))) # register configitems to this destination cfgpath = os.path.join(reporoot, 'mercurial', 'configitems.py') cfgred = redbaron.RedBaron(readpath(cfgpath)) # state about what to do registered = set((s, n) for n, a, s, n in coreconfigitems(cfgred)) toregister = {} # {(section, name): defaultrepr} coreconfigs = set() # {(section, name)}, whether it's used in core # first loop: scan all files before taking any action for i, path in enumerate(argv): print('(%d/%d) scanning %s' % (i + 1, len(argv), path)) iscore = ('mercurial' in path) and ('hgext' not in path) red = redbaron.RedBaron(readpath(path)) # find all repo.ui.config* and ui.config* calls, and collect their # section, name and default value information. for node, method, args, section, name in uiconfigitems(red): if section == 'web': # [web] section has some weirdness, ignore them for now continue defaultrepr = None key = (section, name) if len(args) == 2: if key in registered: continue if method == 'configlist': defaultrepr = 'list' elif method == 'configbool': defaultrepr = 'False' else: defaultrepr = 'None' elif len(args) >= 3 and (args[2].target is None or args[2].target.value == 'default'): # try to understand the "default" value dnode = args[2].value if dnode.type == 'name': if dnode.value in {'None', 'True', 'False'}: defaultrepr = dnode.value elif dnode.type == 'string': defaultrepr = repr(dnode.value[1:-1]) elif dnode.type in ('int', 'float'): defaultrepr = dnode.value # inconsistent default if key in toregister and toregister[key] != defaultrepr: defaultrepr = None # interesting to rewrite if key not in registered: if defaultrepr is None: print('[note] %s: %s.%s: unsupported default' % (path, section, name)) registered.add(key) # skip checking it again else: toregister[key] = defaultrepr if iscore: coreconfigs.add(key) # second loop: rewrite files given "toregister" result for path in argv: # reconstruct redbaron - trade CPU for memory red = redbaron.RedBaron(readpath(path)) changed = False for node, method, args, section, name in uiconfigitems(red): key = (section, name) defaultrepr = toregister.get(key) if defaultrepr is None or key not in coreconfigs: continue if len(args) >= 3 and (args[2].target is None or args[2].target.value == 'default'): try: del args[2] changed = True except Exception: # redbaron fails to do the rewrite due to indentation # see https://github.com/PyCQA/redbaron/issues/100 print('[warn] %s: %s.%s: default needs manual removal' % (path, section, name)) if key not in registered: print('registering %s.%s' % (section, name)) registercoreconfig(cfgred, section, name, defaultrepr) registered.add(key) if changed: print('updating %s' % path) writepath(path, red.dumps()) if toregister: print('updating configitems.py') writepath(cfgpath, cfgred.dumps()) if __name__ == "__main__": sys.exit(main(sys.argv[1:]))
author Jun Wu <quark@fb.com>
date Fri, 14 Jul 2017 14:22:40 -0700
parents d74141ccfd8b
children d90f9e4704de
comparison
equal deleted inserted replaced
33498:b7a75b9a3386 33499:0407a51b9d8c
406 self.verbose = self.debugflag or self.configbool('ui', 'verbose') 406 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
407 self.quiet = not self.debugflag and self.configbool('ui', 'quiet') 407 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
408 if self.verbose and self.quiet: 408 if self.verbose and self.quiet:
409 self.quiet = self.verbose = False 409 self.quiet = self.verbose = False
410 self._reportuntrusted = self.debugflag or self.configbool("ui", 410 self._reportuntrusted = self.debugflag or self.configbool("ui",
411 "report_untrusted", True) 411 "report_untrusted")
412 self.tracebackflag = self.configbool('ui', 'traceback', False) 412 self.tracebackflag = self.configbool('ui', 'traceback')
413 self.logblockedtimes = self.configbool('ui', 'logblockedtimes') 413 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
414 414
415 if section in (None, 'trusted'): 415 if section in (None, 'trusted'):
416 # update trust information 416 # update trust information
417 self._trustusers.update(self.configlist('trusted', 'users')) 417 self._trustusers.update(self.configlist('trusted', 'users'))
922 finally: 922 finally:
923 self._blockedtimes['stdio_blocked'] += \ 923 self._blockedtimes['stdio_blocked'] += \
924 (util.timer() - starttime) * 1000 924 (util.timer() - starttime) * 1000
925 925
926 def _isatty(self, fh): 926 def _isatty(self, fh):
927 if self.configbool('ui', 'nontty', False): 927 if self.configbool('ui', 'nontty'):
928 return False 928 return False
929 return util.isatty(fh) 929 return util.isatty(fh)
930 930
931 def disablepager(self): 931 def disablepager(self):
932 self._disablepager = True 932 self._disablepager = True
945 not "history, "summary" not "summ", etc. 945 not "history, "summary" not "summ", etc.
946 """ 946 """
947 if (self._disablepager 947 if (self._disablepager
948 or self.pageractive 948 or self.pageractive
949 or command in self.configlist('pager', 'ignore') 949 or command in self.configlist('pager', 'ignore')
950 or not self.configbool('ui', 'paginate', True) 950 or not self.configbool('ui', 'paginate')
951 or not self.configbool('pager', 'attend-' + command, True) 951 or not self.configbool('pager', 'attend-' + command, True)
952 # TODO: if we want to allow HGPLAINEXCEPT=pager, 952 # TODO: if we want to allow HGPLAINEXCEPT=pager,
953 # formatted() will need some adjustment. 953 # formatted() will need some adjustment.
954 or not self.formatted() 954 or not self.formatted()
955 or self.plain() 955 or self.plain()
1110 if self.plain(): 1110 if self.plain():
1111 return "text" 1111 return "text"
1112 1112
1113 # Default interface for all the features 1113 # Default interface for all the features
1114 defaultinterface = "text" 1114 defaultinterface = "text"
1115 i = self.config("ui", "interface", None) 1115 i = self.config("ui", "interface")
1116 if i in alldefaults: 1116 if i in alldefaults:
1117 defaultinterface = i 1117 defaultinterface = i
1118 1118
1119 choseninterface = defaultinterface 1119 choseninterface = defaultinterface
1120 f = self.config("ui", "interface.%s" % feature, None) 1120 f = self.config("ui", "interface.%s" % feature, None)
1184 This function always returns false when in plain mode, see `ui.plain()'. 1184 This function always returns false when in plain mode, see `ui.plain()'.
1185 ''' 1185 '''
1186 if self.plain(): 1186 if self.plain():
1187 return False 1187 return False
1188 1188
1189 i = self.configbool("ui", "formatted", None) 1189 i = self.configbool("ui", "formatted")
1190 if i is None: 1190 if i is None:
1191 # some environments replace stdout without implementing isatty 1191 # some environments replace stdout without implementing isatty
1192 # usually those are non-interactive 1192 # usually those are non-interactive
1193 return self._isatty(self.fout) 1193 return self._isatty(self.fout)
1194 1194
1462 1462
1463 @util.propertycache 1463 @util.propertycache
1464 def _progbar(self): 1464 def _progbar(self):
1465 """setup the progbar singleton to the ui object""" 1465 """setup the progbar singleton to the ui object"""
1466 if (self.quiet or self.debugflag 1466 if (self.quiet or self.debugflag
1467 or self.configbool('progress', 'disable', False) 1467 or self.configbool('progress', 'disable')
1468 or not progress.shouldprint(self)): 1468 or not progress.shouldprint(self)):
1469 return None 1469 return None
1470 return getprogbar(self) 1470 return getprogbar(self)
1471 1471
1472 def _progclear(self): 1472 def _progclear(self):