diff -Nru mercurial-extension-utils-1.3.6/debian/changelog mercurial-extension-utils-1.5.0/debian/changelog --- mercurial-extension-utils-1.3.6/debian/changelog 2019-12-17 12:36:33.000000000 +0000 +++ mercurial-extension-utils-1.5.0/debian/changelog 2020-01-03 09:03:46.000000000 +0000 @@ -1,8 +1,19 @@ -mercurial-extension-utils (1.3.6-1build1) focal; urgency=medium +mercurial-extension-utils (1.5.0-1) unstable; urgency=medium - * No-change rebuild to generate dependencies on python2. + [ Christoph Mathys ] + * New upstream release. + * Convert to Python 3. + * Update Standards Version and compat. + * Add Upstream-Contact to d/copyright. - -- Matthias Klose Tue, 17 Dec 2019 12:36:33 +0000 + [ Andrej Shadura ] + * Temporarily reenable Python 2 to ease the transition. + * Update copyright dates. + * Add Vcs-* fields. + * Trim trailing whitespace. + * Use secure URI in Homepage field. + + -- Andrej Shadura Fri, 03 Jan 2020 10:03:46 +0100 mercurial-extension-utils (1.3.6-1) unstable; urgency=medium diff -Nru mercurial-extension-utils-1.3.6/debian/compat mercurial-extension-utils-1.5.0/debian/compat --- mercurial-extension-utils-1.3.6/debian/compat 2018-10-02 19:58:02.000000000 +0000 +++ mercurial-extension-utils-1.5.0/debian/compat 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -11 diff -Nru mercurial-extension-utils-1.3.6/debian/control mercurial-extension-utils-1.5.0/debian/control --- mercurial-extension-utils-1.3.6/debian/control 2018-10-02 19:58:02.000000000 +0000 +++ mercurial-extension-utils-1.5.0/debian/control 2020-01-03 09:03:46.000000000 +0000 @@ -2,16 +2,17 @@ Section: python Priority: optional Maintainer: Christoph Mathys -Build-Depends: debhelper (>= 11), python-all (>= 2.6.6-3~), - python-setuptools, dh-python -Standards-Version: 4.2.1 -Homepage: http://pypi.python.org/pypi/mercurial_extension_utils -X-Python-Version: all -XB-Python-Version: ${python:Versions} +Build-Depends: debhelper-compat (= 12), python3-all, + python3-setuptools, dh-python, + python-all, python-setuptools +Standards-Version: 4.4.1 +Homepage: https://pypi.python.org/pypi/mercurial_extension_utils +Vcs-Browser: https://salsa.debian.org/python-team/modules/mercurial-extension-utils +Vcs-Git: https://salsa.debian.org/python-team/modules/mercurial-extension-utils.git Package: mercurial-extension-utils Architecture: all -Depends: ${python:Depends}, ${misc:Depends}, mercurial +Depends: ${python:Depends}, ${python3:Depends}, ${misc:Depends}, mercurial Description: Contains functions for writing Mercurial extensions Contains functions used by Mercurial extension mercurial-keyring. They are mostly tiny utilities related to configuration processing or location diff -Nru mercurial-extension-utils-1.3.6/debian/copyright mercurial-extension-utils-1.5.0/debian/copyright --- mercurial-extension-utils-1.3.6/debian/copyright 2018-10-02 19:58:02.000000000 +0000 +++ mercurial-extension-utils-1.5.0/debian/copyright 2020-01-03 09:03:46.000000000 +0000 @@ -1,13 +1,14 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: mercurial-extension-utils +Upstream-Contact: Marcin Kasperski Source: http://pypi.python.org/pypi/mercurial_extension-utils Files: * -Copyright: 2015 Marcin Kasperski +Copyright: 2015—2019 Marcin Kasperski License: BSD-3-clause Files: debian/* -Copyright: 2016 Christoph Mathys +Copyright: 2016—2019 Christoph Mathys License: BSD-3-clause License: BSD-3-clause diff -Nru mercurial-extension-utils-1.3.6/debian/rules mercurial-extension-utils-1.5.0/debian/rules --- mercurial-extension-utils-1.3.6/debian/rules 2018-10-02 19:58:02.000000000 +0000 +++ mercurial-extension-utils-1.5.0/debian/rules 2020-01-03 09:03:46.000000000 +0000 @@ -4,4 +4,4 @@ export PYBUILD_NAME="mercurial-keyring" %: - dh $@ --with python2 --buildsystem=pybuild + dh $@ --with python2,python3 --buildsystem=pybuild diff -Nru mercurial-extension-utils-1.3.6/drone.sh mercurial-extension-utils-1.5.0/drone.sh --- mercurial-extension-utils-1.3.6/drone.sh 1970-01-01 00:00:00.000000000 +0000 +++ mercurial-extension-utils-1.5.0/drone.sh 2017-01-26 13:20:42.000000000 +0000 @@ -0,0 +1,14 @@ +# Copy of test recipe used on drone.io +# +# Configuration: +# Language: Python2.7 +# No Database +# No Environment Variables +# Work dir: /home/ubuntu/src/bitbucket.org/Mekk/mercurial-extension_utils +# (can't change) + +pip install Mercurial --use-mirrors +python -m unittest discover tests + +pip install tox +tox -e py27-hg27,py27-hg29,py27-hg32,py27-hg33 diff -Nru mercurial-extension-utils-1.3.6/.hgignore mercurial-extension-utils-1.5.0/.hgignore --- mercurial-extension-utils-1.3.6/.hgignore 1970-01-01 00:00:00.000000000 +0000 +++ mercurial-extension-utils-1.5.0/.hgignore 2019-06-20 08:50:14.000000000 +0000 @@ -0,0 +1,17 @@ +syntax: regexp + +\.pyc$ +\.pyo$ +~$ +^\.\# +^\.(project|pydevproject)$ +^\.settings/ +^build/ +^dist/ +\.egg-info/ +^README\.html +^\.tox/ + +syntax: glob + +.pytest_cache diff -Nru mercurial-extension-utils-1.3.6/.hgtags mercurial-extension-utils-1.5.0/.hgtags --- mercurial-extension-utils-1.3.6/.hgtags 1970-01-01 00:00:00.000000000 +0000 +++ mercurial-extension-utils-1.5.0/.hgtags 2019-11-11 00:10:46.000000000 +0000 @@ -0,0 +1,26 @@ +57362b22fd15a5ebde9b03d28c12e0dc2a35bf2d 0.6.0 +57362b22fd15a5ebde9b03d28c12e0dc2a35bf2d 0.6.0 +e54bee893d66e23ee68a1b21f695706a9aa2f5c4 0.6.0 +bb58a4178ffc887911168dd23d588efa0be4b015 0.6.1 +3dc85253b779b8b64db5b639c18c8c1cc360f0ea 0.7.0 +78990a2b31c10737d1dbf7b50beee47040ff8f71 0.8.0 +c208ad3d955f9049f92775d31fd0e439ae055dfe 0.8.1 +6b8053838226d0c615f167c05eb4cadf08edc8e1 0.9.0 +9ae993c639d822ccf214cc5b55960eb50f1acdb5 0.10.0 +39089a39a53194f95ab08a3072dbb6defd39706d 0.11.0 +65624c55a18f99861142cc3be5b210ac359f5e02 1.0.0 +b1df1e710f03cdba58a3a4433dfd5d9a547ec993 1.0.1 +27bdda476e4fe97e04ae6595be8112a0bffb6edb 1.1.0 +8942af39a234280b167350c16249d6f8eb343d21 1.1.1 +254d849ee46b39dcb7b1228c70a08bcdcda000fd 1.1.2 +3044deffd6201a9323ecfe594ed898b5766209c8 1.2.0 +7a91a4f5c179dd78decc924d13fa5d99860f98d3 1.3.0 +8759af69e0c8194015b79c0592e0bf5c2d140126 1.3.1 +feef8221d9da41f448d0ff978d6420cf18fffad7 1.3.2 +d04b7635b0b12a74976a88dd6ca4ace7a86b2dc0 1.3.3 +45ba5bd5b1d25287b141f64a0acfbf848100cdf0 1.3.4 +3f3951195f8f223bcfcd6b03ef85182b9e6edc69 1.3.5 +d6c91f955d2c4f06d3c71eabddc4a257a9a59536 1.3.6 +9c2241c231bb47acb902aa790fb36ee2c7a98370 1.3.7 +78671ef929e60d53f458c83c4332cf501f7081cb 1.4.0 +5144b5a15ea46208adb496d4436dbf47e1847f8a 1.5.0 diff -Nru mercurial-extension-utils-1.3.6/HISTORY.txt mercurial-extension-utils-1.5.0/HISTORY.txt --- mercurial-extension-utils-1.3.6/HISTORY.txt 1970-01-01 00:00:00.000000000 +0000 +++ mercurial-extension-utils-1.5.0/HISTORY.txt 2019-11-10 18:35:40.000000000 +0000 @@ -0,0 +1,166 @@ +1.5.0 +~~~~~~~~~~~~ + +Polished support for python3 (tested with py 3.5-3.7 and with +mercurial 5.0-5.2). Seems to work as expected, yet to be verified +against all extensions. + +Added ui_string function, helper to safely format argument +for ui.debug, ui.status etc. + +1.4.0 +~~~~~~~~~~~~ + +Preliminary support for python3 (with mercurial 5.0). +Exact APIs are yet to be verified (bstr/str decisions) +but (adapted) tests pass. + +Tested against hg 5.0 and hg 4.9. + +1.3.7 +~~~~~~~~~~~~ + +Tested against hg 4.8 (no changes needed). + +1.3.6 +~~~~~~~~~~~~ + +Fixed problems with hg 4.7 (accomodating changed demandimport APIs). + +1.3.5 +~~~~~~~~~~~~ + +Formally tested with hg 4.5 and 4.6. + +Dropping test badges which don't work anymore from docs. + +1.3.4 +~~~~~~~~~~~~~ + +In-advance preparation for cmdutil.commands → registrar.commands +migration in core Mercurial API (see 46ba2cdda476 in hg-stable, likely +to be released in 4.3). + +1.3.3 +~~~~~~~~~~~~~ + +Updated links after bitbucket changes. + +hg 4.1 and 4.2 added to tested versions. + +1.3.2 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +find_repositories_below doesn't fail in case some subdirectory is +unreadable. Instead, it simply skips it and continues to work +(realistic use-case: lost+found doesn't crash it anymore, but is +skipped…) + +1.3.1 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Some tests were failing on mercurial 3.8, even more on 4.0 +(actual code worked properly, just tests were faiing). + +1.3.0 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Added enable_hook function (which detects whether hook is already +installed and withdraws in such a case). + +Added inside_tortoisehg function (detecting that „we're running under +Tortoise”). + +1.2.0 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Added meu.command (compatibility wrapper for cmdutil.command). + + +1.1.2 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Added setconfig_list. + +Various test improvements (including tox tests configured +to check various mercurial versions) + + +1.1.1 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests should work on any machine. Started Drone.io autotests. +Added some requirement.s + +1.1.0 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +New functions: direct_import, direct_import_ext, and disable_logging. +Mostly taken from mercurial_keyring, but improved: +- imports handle dotted.modules +- disable_logging actually works for py2.6 + +1.0.1 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Test fixes, minor code cleanups. + +1.0.0 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Documentation updates. + +0.11.0 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Works on Windows (and handles normalizing paths to /-separator) + +0.10.0 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +find_repositories_below + +0.9.0 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +monkeypatch_method and monkeypatch_function + +0.8.1 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Bugfix: TextFiller was hanging if run on pattern +not ending with {item}. Effectively mercurial hanged +while loading path patterns, for example. + +0.8.0 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ``rgxp_configbool_items`` +- ``suffix_configbool_items`` + +0.7.0 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ``setconfig_dict``, +- ``DirectoryPattern`` +- ``TextFiller`` + +Actually used to simplify and improve ``mercurial_path_pattern``. + +0.6.1 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Extra config support: +- ``suffix_config_items``, +- ``suffix_configlist_items``. + +Actually used to simplify ``mercurial_dynamic_username``. + +0.6.0 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +First public release: +- ``belongs_to_tree``, +- ``belongs_to_tree_group``, +- ``rgxp_config_items``, +- ``rgxp_configlist_items`` diff -Nru mercurial-extension-utils-1.3.6/mercurial_extension_utils.egg-info/PKG-INFO mercurial-extension-utils-1.5.0/mercurial_extension_utils.egg-info/PKG-INFO --- mercurial-extension-utils-1.3.6/mercurial_extension_utils.egg-info/PKG-INFO 2018-08-03 21:06:36.000000000 +0000 +++ mercurial-extension-utils-1.5.0/mercurial_extension_utils.egg-info/PKG-INFO 2019-11-11 00:11:34.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: mercurial-extension-utils -Version: 1.3.6 +Version: 1.5.0 Summary: Mercurial Extension Utils Home-page: http://bitbucket.org/Mekk/mercurial-extension_utils Author: Marcin Kasperski @@ -206,6 +206,7 @@ Classifier: License :: DFSG approved Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 3 Classifier: Operating System :: OS Independent Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Version Control diff -Nru mercurial-extension-utils-1.3.6/mercurial_extension_utils.egg-info/SOURCES.txt mercurial-extension-utils-1.5.0/mercurial_extension_utils.egg-info/SOURCES.txt --- mercurial-extension-utils-1.3.6/mercurial_extension_utils.egg-info/SOURCES.txt 2018-08-03 21:06:36.000000000 +0000 +++ mercurial-extension-utils-1.5.0/mercurial_extension_utils.egg-info/SOURCES.txt 2019-11-11 00:11:35.000000000 +0000 @@ -1,8 +1,21 @@ +.hgignore +.hgtags +HISTORY.txt +README-TESTING.txt README.txt +TODO.txt +drone.sh mercurial_extension_utils.py +mercurial_extension_utils_loader.py setup.py +tox.ini mercurial_extension_utils.egg-info/PKG-INFO mercurial_extension_utils.egg-info/SOURCES.txt mercurial_extension_utils.egg-info/dependency_links.txt mercurial_extension_utils.egg-info/top_level.txt -mercurial_extension_utils.egg-info/zip-safe \ No newline at end of file +mercurial_extension_utils.egg-info/zip-safe +tests/manual_find_repositories_below.py +tests/py2_doctests_mercurial_extension_utils.py +tests/py2win_doctests_mercurial_extension_utils.py +tests/test_doctest.py +tests/test_find_repositories_below.py \ No newline at end of file diff -Nru mercurial-extension-utils-1.3.6/mercurial_extension_utils_loader.py mercurial-extension-utils-1.5.0/mercurial_extension_utils_loader.py --- mercurial-extension-utils-1.3.6/mercurial_extension_utils_loader.py 1970-01-01 00:00:00.000000000 +0000 +++ mercurial-extension-utils-1.5.0/mercurial_extension_utils_loader.py 2017-01-26 13:20:42.000000000 +0000 @@ -0,0 +1,11 @@ + +import os, sys + +THE_DIR = os.path.dirname(os.path.abspath(__file__)) + +sys.path.append(THE_DIR) + +# This makes this dir a winner (but later) +# +# def extsetup(ui): +# sys.path.insert(0, THE_DIR) diff -Nru mercurial-extension-utils-1.3.6/mercurial_extension_utils.py mercurial-extension-utils-1.5.0/mercurial_extension_utils.py --- mercurial-extension-utils-1.3.6/mercurial_extension_utils.py 2018-08-03 20:28:06.000000000 +0000 +++ mercurial-extension-utils-1.5.0/mercurial_extension_utils.py 2019-11-10 17:58:32.000000000 +0000 @@ -30,30 +30,176 @@ # # See README.txt for more details. -"""Utility functions useful during Mercurial extension writing +""" +Utility functions useful during Mercurial extension writing + +Mostly related to configuration processing, path matching and similar +activities. I extracted this module once I noticed a couple of my +extensions need the same or similar functions. -Mostly related to configuration processing, path matching and -similar activities. I extracted this module once I noticed a couple -of my extensions need the same or similar functions. +Part of this module is about wrapping some incompatibilities between +various Mercurial versions. Note: file-related functions defined here use / as path separator, -even on Windows. Backslashes in params should usually work too, but +even on Windows. Backslashes in params should usually work too, but returned paths are always /-separated. -Documentation examples in this module use Unix paths, see -file mercurial_extension_utils_win.py for windows doctests. +Documentation examples in this module use Unix paths and Python3 +syntax, see tests/*doctests.py for versions with Python2 and/or +Windows examples. + +On Python3 we mostly follow Mercurial's own idea of using binary +strings, wrapped as bytestr where useful. """ +from __future__ import print_function # For doctests from mercurial.i18n import _ - import re import os import sys +import types from collections import deque # pylint: disable=line-too-long,invalid-name ########################################################################### +# Tiny py2/py3 compatibility layer (used internally) +########################################################################### + +# We mostly defer to Mercurial's own compatibility layer - if we are +# on py3, it exists (elsewhere no chances for working hg, versions +# without meecurial.pycompat don't install on py3), if we are on py2 +# it may exist or not depending on Mercurial version. It it doesn't or +# is incomplete, we fix missing parts. + +# Trick to avoid separate file craetion +_compat_name = 'mercurial_extension_utils.pycompat' +pycompat = types.ModuleType(_compat_name) +# pycompat = imp.new_module(_compat_name) +sys.modules[_compat_name] = pycompat + +pycompat.identity = lambda a: a + +try: + from mercurial import pycompat as _pycompat + + for func in [ # py2 / py3 + 'ispy3', # False / True + 'bytestr', # str / bytes subclass with some glue to make it more py2str-like + # and smart constructor which accepts bytestr, bytes and str + 'unicode', # unicode / str + 'bytechr', # chr / chr(x).encode() (more efficient equiv) + 'maybebytestr', # identity / upgrade bytes to bytestr, on nonbytes identity + 'sysbytes', # identity / x.encode(utf-8) + 'sysstr', # identity / make it native str, safely (convert bytes to str, leave str) + 'strkwargs', # identity / if any key is bytes, make it str + 'byteskwargs', # identity / if nay key is str, make it bytes + ]: + if hasattr(_pycompat, func): + setattr(pycompat, func, getattr(_pycompat, func)) +except ImportError: + pass + +if not hasattr(pycompat, 'ispy3'): + pycompat.ispy3 = (sys.version_info[0] >= 3) +if not pycompat.ispy3: + # Only on py2 we can be on old mercurial where pycompat doesn't exist, + # or has less functions. Here we fix missing bits (that's mostly copy + # and paste from pycompat in modern mercurial). + if not hasattr(pycompat, 'bytechr'): + pycompat.bytechr = chr + if not hasattr(pycompat, 'bytestr'): + pycompat.bytestr = str + if not hasattr(pycompat, 'maybebytestr'): + pycompat.maybebytestr = pycompat.identity + if not hasattr(pycompat, 'iterbytestr'): + pycompat.iterbytestr = iter + if not hasattr(pycompat, 'strkwargs'): + pycompat.strkwargs = pycompat.identity + if not hasattr(pycompat, 'byteskwargs'): + pycompat.byteskwargs = pycompat.identity + if not hasattr(pycompat, 'sysbytes'): + pycompat.sysbytes = pycompat.identity + if not hasattr(pycompat, 'sysstr'): + pycompat.sysstr = pycompat.identity + if not hasattr(pycompat, 'unicode'): + pycompat.unicode = unicode + +if pycompat.ispy3: + + def dict_iteritems(dic): + return dic.items() + + if sys.version_info < (3, 7): + + def re_escape(txt): + # Pythons 3.5 and 3.6 fail on bytestrings. Let's fix it similarly to 3.7 fix + if isinstance(txt, str): + return re.escape(txt) + else: + v = str(txt, 'latin1') + return re.escape(v).encode('latin1') + + else: + + def re_escape(txt): + return re.escape(txt) + +else: + + def dict_iteritems(dic): + return dic.iteritems() + + def re_escape(txt): + return re.escape(txt) + + +########################################################################### +# Logging support +########################################################################### + +if pycompat.ispy3: + def _log_normalize(args): + return tuple(pycompat.bytestr(arg) for arg in args) +else: + _log_normalize = pycompat.identity + + +def ui_string(message, *args): + """ + Idiomatically equivalent to:: + + return _(message) % args + + but handles idiosyncracies of mercurial.ui logging under py3 where + bytestring and byteargs are expected, and None's and normal strings + cause problems. + + Typical use (replace debug with any other method): + + >>> import mercurial.ui; ui = mercurial.ui.ui() + >>> ui.debug(ui_string("Simple text")) + >>> ui.debug(ui_string(b"Simple binary")) + >>> ui.debug(ui_string("This is %s and %s and %s", b'bin', u'txt', None)) + >>> ui.debug(ui_string(b"This is %s and %s and %s", b'bin', u'txt', None)) + + This works because we reasonably create binary strings, as ui expects: + + >>> ui_string("Simple text") + b'Simple text' + >>> ui_string(b"Simple binary") + b'Simple binary' + >>> ui_string("This is %s and %s and %s", b'bin', u'txt', None) + b'This is bin and txt and None' + >>> ui_string(b"This is %s and %s and %s", b'bin', u'txt', None) + b'This is bin and txt and None' + """ + # _ seems to handle normalization, so we don't have to. + return _(message) % _log_normalize(args) + + + +########################################################################### # Directory matching in various shapes ########################################################################### @@ -74,11 +220,27 @@ '/some/where' >>> normalize_path("../../../some/where") '/home/lordvader/some/where' + + In case of python3, result is also forced to bytestr if not in such + form yet: + + >>> type(normalize_path("~/src")) + + >>> type(normalize_path(b"~/src")) + + + This way bytes input is also properly handled: + + >>> normalize_path(b"~/src") + '/home/lordvader/src' + >>> normalize_path(b"/some/where/") + '/some/where' """ - reply = os.path.abspath(os.path.expanduser(path)) + reply = pycompat.bytestr( + os.path.abspath(os.path.expanduser(path))) if os.name == 'nt': - reply = reply.replace('\\', '/') - return reply.rstrip('/') + reply = reply.replace(b'\\', b'/') + return pycompat.bytestr(reply.rstrip(b'/')) def belongs_to_tree(child, parent): @@ -114,6 +276,23 @@ Note: even on Windows, / is used as path separator (both on input, and on output). + On Python3 both kinds of strings are handled as long as arguments are compatible + and result is forced to bytestr: + + >>> x = belongs_to_tree(b"/tmp/sub/dir", b"/tmp") + >>> x + '/tmp' + >>> type(x) + + >>> x = belongs_to_tree(b"/usr/sub", b"/tmp") + >>> x + + >>> x = belongs_to_tree(b"/home/lordvader/devel/webapps", b"~lordvader/devel") + >>> x + '/home/lordvader/devel' + >>> type(x) + + :param child: tested directory (preferably absolute path) :param parent: tested parent (will be tilda-expanded, so things like ~/work are OK) @@ -126,11 +305,11 @@ # pfx = normalize_path(os.path.commonprefix([child, parent])) # return pfx == true_parent and true_parent or None if os.name != 'nt': - matches = child == parent or child.startswith(parent + '/') + matches = child == parent or child.startswith(parent + b'/') else: lower_child = child.lower() lower_parent = parent.lower() - matches = lower_child == lower_parent or lower_child.startswith(lower_parent + '/') + matches = lower_child == lower_parent or lower_child.startswith(lower_parent + b'/') return matches and parent or None @@ -157,12 +336,23 @@ Note: even on Windows, / is used as path separator (both on input, and on output). + On Py3 both kinds of strings are handled as long as arguments are compatible: + + >>> x = belongs_to_tree_group(b"/tmp/sub/dir", [b"/bin", b"/tmp"]) + >>> x + '/tmp' + >>> type(x) + + + >>> belongs_to_tree_group(b"/home/lordvader/src/apps", [b"~/src", b"/home/lordvader"]) + '/home/lordvader/src' + :param child: tested directory (preferably absolute path) :param parents: tested parents (list or tuple of directories to test, will be tilda-expanded) """ child = normalize_path(child) - longest_parent = '' + longest_parent = pycompat.bytestr(b'') for parent in parents: canon_path = belongs_to_tree(child, parent) if canon_path: @@ -230,17 +420,46 @@ {} >>> pat.search('/home/lordvader/dev/acme/subdir') >>> pat.search('/home/lordvader/dev') + + On Py3 binary strings can be used as well (and results are wrapped + as bytestr):: + + >>> pat = DirectoryPattern(b'~/src/{suffix}') + >>> pat.is_valid() + True + >>> pat.search(b"/opt/repos/abcd") + >>> x = pat.search(b"~/src/repos/in/tree") + >>> x + {'suffix': 'repos/in/tree'} + >>> type(x['suffix']) + + >>> [type(t) for t in x.keys()] + [] + + >>> pat.search(b"/home/lordvader/src/repos/here" if os.system != 'nt' else b"c:/users/lordvader/src/repos/here") + {'suffix': 'repos/here'} + >>> pat.search(b"/home/lordvader/src") + + >>> pat = DirectoryPattern(b'~lordvader/devel/(item)') + >>> pat.search(b"/opt/repos/abcd") + >>> pat.search(b"~/devel/libxuza") + {'item': 'libxuza'} + >>> pat.search(b"~/devel/libs/libxuza") + >>> pat.search(b"/home/lordvader/devel/webapp") + {'item': 'webapp'} + >>> pat.search(b"/home/lordvader/devel") + """ # Regexps used during pattern parsing - _re_pattern_lead = re.compile(r' ^ ([^{}()]*) ([({]) (.*) $', re.VERBOSE) - _re_closure = {'{': re.compile(r'^ ([a-zA-Z_]+) [}] (.*) $', re.VERBOSE), - '(': re.compile(r'^ ([a-zA-Z_]+) [)] (.*) $', re.VERBOSE)} + _re_pattern_lead = re.compile(b' ^ ([^{}()]*) ([({]) (.*) $', re.VERBOSE) + _re_closure = {b'{': re.compile(b'^ ([a-zA-Z_]+) [}] (.*) $', re.VERBOSE), + b'(': re.compile(b'^ ([a-zA-Z_]+) [)] (.*) $', re.VERBOSE)} # (text inside (braces) or {braces} is restricted as it is used within regexp # Regexp snippets used to match escaped parts - _re_match_snippet = {'{': r'.+', - '(': r'[^/\\]+'} + _re_match_snippet = {b'{': b'.+', + b'(': b'[^/\\\\]+'} def __init__(self, pattern_text, ui=None): """Parses given pattern. Doesn't raise, in case of invalid patterns @@ -253,7 +472,7 @@ self._pattern_re = None # Will stay such if we fail somewhere here # Convert pattern to regexp - rgxp_text = '^' + rgxp_text = b'^' while text: match = self._re_pattern_lead.search(text) if match: @@ -261,22 +480,25 @@ match = self._re_closure[open_char].search(text) if not match: if ui: - ui.warn(_("Invalid directory pattern: %s") % pattern_text) + ui.warn(ui_string("Invalid directory pattern: %s\n", + pattern_text)) return group_name, text = match.group(1), match.group(2) - rgxp_text += re.escape(prefix) - rgxp_text += '(?P<' + group_name + '>' + self._re_match_snippet[open_char] + ')' + rgxp_text += re_escape(prefix) + rgxp_text += b'(?P<' + group_name + b'>' + self._re_match_snippet[open_char] + b')' else: - rgxp_text += re.escape(text) - text = '' - rgxp_text += '$' + rgxp_text += re_escape(text) + text = b'' + rgxp_text += b'$' if ui: - ui.debug(_("Pattern %s translated into regexp %s\n") % (pattern_text, rgxp_text)) + ui.debug(ui_string("meu: Pattern %s translated into regexp %s\n", + pattern_text, rgxp_text)) try: self._pattern_re = re.compile(rgxp_text, os.name == 'nt' and re.IGNORECASE or 0) except: # pylint:disable=bare-except if ui: - ui.warn(_("Invalid directory pattern: %s") % pattern_text) + ui.warn(ui_string("Invalid directory pattern: %s\n", + pattern_text)) def is_valid(self): """Can be used to check whether object was properly constructed""" @@ -291,14 +513,16 @@ :param tested_path: path to check, will be tilda-expanded and converted to abspath before comparison :return: Dictionary mapping all ``{brace}`` and ``(paren)`` parts to matched - items + items (on py3 represented as bytestr). """ if not self._pattern_re: return exp_tested_path = normalize_path(tested_path) match = self._pattern_re.search(exp_tested_path) if match: - return match.groupdict() + return dict((key, pycompat.maybebytestr(value)) + for key, value in dict_iteritems(match.groupdict())) + # return match.groupdict() else: return None @@ -360,8 +584,8 @@ The same parameter can be used in various substitutions: >>> tf = TextFiller(r'http://go.to/{item:/=-}, G:{item:/=\}, name: {item}') - >>> print tf.fill(item='so/me/thing') - http://go.to/so-me-thing, G:so\me\thing, name: so/me/thing + >>> print(tf.fill(item='so/me/thing')) + b'http://go.to/so-me-thing, G:so\\me\\thing, name: so/me/thing' Errors are handled by returning None (and warning if ui is given), both in case of missing key: @@ -380,23 +604,40 @@ >>> tf.is_valid() False >>> tf.fill(some='prefix', fill='suffix') + + On Py3 binary strings are in fact used and preferred: + + >>> tf = TextFiller(b'{some}/text/to/{fill}') + >>> tf.fill(some=b'prefix', fill=b'suffix') + 'prefix/text/to/suffix' + >>> tf.fill(some=b'/ab/c/d', fill=b'x') + '/ab/c/d/text/to/x' + + >>> tf = TextFiller(b'{some}/text/to/{some}') + >>> tf.is_valid() + True + >>> tf.fill(some=b'val') + 'val/text/to/val' + >>> tf.fill(some=b'ab/c/d', fill=b'x') + 'ab/c/d/text/to/ab/c/d' + ''' # Regexps used during parsing - _re_pattern_lead = re.compile(r' ^ ([^{}]*) [{] (.*) $', re.VERBOSE) - _re_pattern_cont = re.compile(r''' + _re_pattern_lead = re.compile(b' ^ ([^{}]*) [{] (.*) $', re.VERBOSE) + _re_pattern_cont = re.compile(b''' ^ ([a-zA-Z][a-zA-Z0-9_]*) # name (leading _ disallowed on purpose) ((?: : [^{}:=]+ = [^{}:=]* )*) # :sth=else substitutions [}] (.*) $ ''', re.VERBOSE) - _re_sub = re.compile(r'^ : ([^{}:=]+) = ([^{}:=]*) (.*) $', re.VERBOSE) + _re_sub = re.compile(b'^ : ([^{}:=]+) = ([^{}:=]*) (.*) $', re.VERBOSE) def __init__(self, fill_text, ui=None): def percent_escape(val): """Escape %-s in given text by doubling them.""" - return val.replace('%', '%%') + return pycompat.bytestr(val.replace(b'%', b'%%')) - text = self.fill_text = fill_text + text = self.fill_text = pycompat.bytestr(fill_text) # Replacement text. That's just percent 'some %(abc)s text' (we use % not '{}' to # leave chances of working on python 2.5). Empty value means I am broken self._replacement = None @@ -406,40 +647,43 @@ # [(from, to), (from, to), ...] list of substitutions to make self._substitutions = [] - replacement = '' + replacement = pycompat.bytestr('') synth_idx = 0 while text: match = self._re_pattern_lead.search(text) if match: replacement += percent_escape(match.group(1)) - text = match.group(2) + text = pycompat.bytestr(match.group(2)) match = self._re_pattern_cont.search(text) if not match: if ui: - ui.warn(_("Bad replacement pattern: %s") % fill_text) + ui.warn(ui_string("Bad replacement pattern: %s\n", + fill_text)) return - name, substs, text = match.group(1), match.group(2), match.group(3) + name, substs, text = pycompat.bytestr(match.group(1)), pycompat.bytestr(match.group(2)), pycompat.bytestr(match.group(3)) if substs: fixups = [] while substs: match = self._re_sub.search(substs) if not match: if ui: - ui.warn(_("Bad replacement pattern: %s") % fill_text) + ui.warn(ui_string("Bad replacement pattern: %s\n", + fill_text)) return - src, dest, substs = match.group(1), match.group(2), match.group(3) + src, dest, substs = pycompat.bytestr(match.group(1)), pycompat.bytestr(match.group(2)), pycompat.bytestr(match.group(3)) fixups.append((src, dest)) synth_idx += 1 - synth = "_" + str(synth_idx) + synth = b"_" + pycompat.bytestr(str(synth_idx)) self._substitutions.append((synth, name, fixups)) name = synth - replacement += '%(' + name + ')s' + replacement += b'%(' + name + b')s' else: replacement += percent_escape(text) - text = '' + text = b'' # Final save if ui: - ui.debug(_("Replacement %s turned into expression %s") % (fill_text, replacement)) + ui.debug(ui_string("meu: Replacement %s turned into expression %s\n", + fill_text, replacement)) self._replacement = replacement def is_valid(self): @@ -451,39 +695,92 @@ returns None""" if not self._replacement: return None + + # TODO: maybe byteskwargs? + bstr_args = dict((pycompat.bytestr(key), pycompat.bytestr(value)) + for key, value in dict_iteritems(kwargs)) + try: for made_field, src_field, fixups in self._substitutions: - value = kwargs[src_field] + value = bstr_args[src_field] for src, dest in fixups: value = value.replace(src, dest) - kwargs[made_field] = value - return self._replacement % kwargs + bstr_args[made_field] = value + return pycompat.bytestr(self._replacement % bstr_args) except: # pylint:disable=bare-except return None + ########################################################################### # Config support ########################################################################### +def setconfig_item(ui, section, name, value): + """ + Mostly equivalent to ```ui.setconfig(section, name, value)```, but + under Py3 avoids errors raised if any of the params is unicode. + + >>> import mercurial.ui; ui = mercurial.ui.ui() + >>> setconfig_item(ui, b"s1", b'a', b'va') + >>> setconfig_item(ui, b"s1", b'b', 7) + >>> setconfig_item(ui, "s2", 'a', 'v2a') + >>> setconfig_item(ui, "s2", 'b', 8) + + >>> ui.config(b"s1", b'a') + b'va' + >>> ui.config(b"s1", b'b') + 7 + >>> x = ui.config(b"s2", b'a') + >>> x + 'v2a' + >>> type(x) + + >>> ui.config(b"s2", b'b') + 8 + """ + if isinstance(value, pycompat.unicode): + value = pycompat.bytestr(value) + ui.setconfig(pycompat.bytestr(section), + pycompat.bytestr(name), + value) + def setconfig_dict(ui, section, items): """ Set's many configuration items with one call. Defined mostly to make some code (including doctests below) a bit more readable. + Note that binary strings are mostly used, in sync with Mercurial 5.0 + decision to use those as section names and keys in ui.config… + >>> import mercurial.ui; ui = mercurial.ui.ui() - >>> setconfig_dict(ui, "sect1", {'a': 7, 'bbb': 'xxx', 'c': '-'}) - >>> setconfig_dict(ui, "sect2", {'v': 'vvv'}) - >>> ui.config("sect1", 'a') + >>> setconfig_dict(ui, b"sect1", {b'a': 7, b'bbb': 'xxx', b'c': b'-'}) + >>> setconfig_dict(ui, b"sect2", {b'v': 'vvv'}) + >>> ui.config(b"sect1", b'a') 7 - >>> ui.config("sect2", 'v') + >>> x = ui.config(b"sect2", b'v') + >>> x 'vvv' + >>> type(x) + + + … but we support some reasonable conversions: + + >>> setconfig_dict(ui, "sect11", {'aa': 7, 'bbbb': 'xxx', 'cc': '-'}) + >>> setconfig_dict(ui, "sect22", {'vv': 'vvv'}) + >>> ui.config(b"sect11", b'aa') + 7 + >>> x = ui.config(b"sect22", b'vv') + >>> x, type(x) + ('vvv', ) :param section: configuration section tofill :param items: dictionary of items to set """ - for key, value in items.iteritems(): - ui.setconfig(section, key, value) + section = pycompat.bytestr(section) + + for key, value in dict_iteritems(items): + setconfig_item(ui, section, key, value) def setconfig_list(ui, section, items): @@ -493,19 +790,35 @@ setconfig_dict, this guarantees ordering. >>> import mercurial.ui; ui = mercurial.ui.ui() - >>> setconfig_list(ui, "sect1", + >>> setconfig_list(ui, b"sect1", + ... [(b'a', 7), (b'bbb', b'xxx'), (b'c', b'-'), (b'a', 8)]) + >>> setconfig_list(ui, b"sect2", [(b'v', b'vvv')]) + >>> ui.config(b"sect1", b'a') + 8 + >>> ui.config(b"sect2", b'v') + b'vvv' + + We also support normal strings to some degree: + + >>> import mercurial.ui; ui = mercurial.ui.ui() + >>> setconfig_list(ui, "sect11", ... [('a', 7), ('bbb', 'xxx'), ('c', '-'), ('a', 8)]) - >>> setconfig_list(ui, "sect2", [('v', 'vvv')]) - >>> ui.config("sect1", 'a') + >>> setconfig_list(ui, "sect22", [('v', 'vvv')]) + >>> ui.config(b"sect11", b'a') 8 - >>> ui.config("sect2", 'v') + >>> x = ui.config(b"sect22", b'v') + >>> x 'vvv' + >>> type(x) + :param section: configuration section tofill :param items: dictionary of items to set """ + section = pycompat.bytestr(section) + for key, value in items: - ui.setconfig(section, key, value) + setconfig_item(ui, section, key, value) def rgxp_config_items(ui, section, rgxp): @@ -526,10 +839,10 @@ ... ]) >>> >>> for name, value in rgxp_config_items( - ... ui, "foo", re.compile(r'^pfx-(\w+)-sfx$')): - ... print name, value - some ala, ma kota - other 4 + ... ui, "foo", re.compile(b'^pfx-(\\w+)-sfx$')): + ... print(name, value) + b'some' b'ala, ma kota' + b'other' 4 :param ui: mercurial ui, used to access config :param section: config section name @@ -537,10 +850,10 @@ :return: yields pairs (group-match, value) for all matching items ''' - for key, value in ui.configitems(section): - match = rgxp.search(key) + for key, value in ui.configitems(pycompat.bytestr(section)): + match = rgxp.search(pycompat.bytestr(key)) if match: - yield match.group(1), value + yield pycompat.maybebytestr(match.group(1)), pycompat.maybebytestr(value) def rgxp_configlist_items(ui, section, rgxp): @@ -561,22 +874,24 @@ ... ]) >>> >>> for name, value in rgxp_configlist_items( - ... ui, "foo", re.compile(r'^pfx-(\w+)-sfx$')): - ... print name, value - some ['ala', 'ma', 'kota'] - other ['sth'] + ... ui, "foo", re.compile(b'^pfx-(\\w+)-sfx$')): + ... print(name, value) + b'some' [b'ala', b'ma', b'kota'] + b'other' [b'sth'] :param ui: mercurial ui, used to access config :param section: config section name - :param rgxp: tested regexp, should contain single (group) + :param rgxp: tested regexp, should contain single (group) and be binary-string based :return: yields pairs (group-match, value-as-list) for all matching items ''' + section = pycompat.bytestr(section) for key, _unneeded_value in ui.configitems(section): + key = pycompat.bytestr(key) match = rgxp.search(key) if match: - yield match.group(1), ui.configlist(section, key) + yield pycompat.maybebytestr(match.group(1)), ui.configlist(section, key) def rgxp_configbool_items(ui, section, rgxp): @@ -597,10 +912,10 @@ ... }) >>> >>> for name, value in rgxp_configbool_items( - ... ui, "foo", re.compile(r'^pfx-(\w+)-sfx$')): - ... print name, value - some True - other False + ... ui, "foo", re.compile(b'^pfx-(\\w+)-sfx$')): + ... print(name, value) + b'some' True + b'other' False :param ui: mercurial ui, used to access config :param section: config section name @@ -609,10 +924,12 @@ :return: yields pairs (group-match, value-as-list) for all matching items ''' + section = pycompat.bytestr(section) for key, _unneeded_value in ui.configitems(section): + key = pycompat.bytestr(key) match = rgxp.search(key) if match: - yield match.group(1), ui.configbool(section, key) + yield pycompat.maybebytestr(match.group(1)), ui.configbool(section, key) def suffix_config_items(ui, section, suffix): @@ -633,9 +950,22 @@ >>> >>> for name, value in suffix_config_items( ... ui, "foo", 'item'): - ... print name, value - some ala, ma kota - other 4 + ... print(name, value) + ... print(type(name), type(value)) + b'some' b'ala, ma kota' + + b'other' 4 + + >>> + >>> for name, value in suffix_config_items( + ... ui, b"foo", b'item'): + ... print(name, value) + ... print(type(name), type(value)) + b'some' b'ala, ma kota' + + b'other' 4 + + :param ui: mercurial ui, used to access config :param section: config section name @@ -643,7 +973,8 @@ :return: yields pairs (prefix, value) for all matching items, values are lists """ - rgxp = re.compile(r'^(\w+)\.' + re.escape(suffix)) + esc_suffix = pycompat.bytestr(re_escape(suffix)) + rgxp = re.compile(b'^(\\w+)\\.' + esc_suffix) for key, value in rgxp_config_items(ui, section, rgxp): yield key, value @@ -654,22 +985,30 @@ ui.configlist, so returned as lists. >>> import mercurial.ui; ui = mercurial.ui.ui() - >>> setconfig_list(ui, "foo", [ - ... ("some.item", "ala, ma kota"), - ... ("some.nonitem", "bela nie"), - ... ("x", "yes"), - ... ("other.item", "kazimira"), + >>> setconfig_list(ui, b"foo", [ + ... (b"some.item", b"ala, ma kota"), + ... (b"some.nonitem", b"bela nie"), + ... (b"x", b"yes"), + ... (b"other.item", b"kazimira"), ... ]) - >>> setconfig_dict(ui, "notfoo", { - ... "some.item": "bad", - ... "also.item": "too", + >>> setconfig_dict(ui, b"notfoo", { + ... b"some.item": "bad", + ... b"also.item": "too", ... }) >>> >>> for name, value in suffix_configlist_items( + ... ui, b"foo", b"item"): + ... print(name, value) + b'some' [b'ala', b'ma', b'kota'] + b'other' [b'kazimira'] + + Attempts to handle native strings: + + >>> for name, value in suffix_configlist_items( ... ui, "foo", "item"): - ... print name, value - some ['ala', 'ma', 'kota'] - other ['kazimira'] + ... print(name, value) + b'some' [b'ala', b'ma', b'kota'] + b'other' [b'kazimira'] :param ui: mercurial ui, used to access config :param section: config section name @@ -678,7 +1017,8 @@ :return: yields pairs (group-match, value-as-list) for all matching items, values are boolean """ - rgxp = re.compile(r'^(\w+)\.' + re.escape(suffix)) + esc_suffix = pycompat.bytestr(re_escape(suffix)) + rgxp = re.compile(b'^(\\w+)\\.' + esc_suffix) for key, value in rgxp_configlist_items(ui, section, rgxp): yield key, value @@ -706,22 +1046,22 @@ >>> >>> for name, value in suffix_configbool_items( ... ui, "foo", "item"): - ... print name, str(value) - true True - false False - one True - zero False - yes True - no False + ... print(name, str(value)) + b'true' True + b'false' False + b'one' True + b'zero' False + b'yes' True + b'no' False >>> - >>> ui.setconfig("foo", "text.item", "something") - >>> for name, value in suffix_configbool_items( - ... ui, "foo", "item"): - ... print name, str(value) - Traceback (most recent call last): - File "/usr/lib/python2.7/dist-packages/mercurial/ui.py", line 237, in configbool - % (section, name, v)) - ConfigError: foo.text.item is not a boolean ('something') + >>> ui.setconfig(b"foo", b"text.item", b"something") + >>> try: + ... for name, value in suffix_configbool_items(ui, "foo", "item"): + ... x = (name, str(value)) + ... print("Strange, no error") + ... except Exception as err: + ... print(repr(err)[:58]) # :58 augments py36-py37 diff + ConfigError(b"foo.text.item is not a boolean ('something') :param ui: mercurial ui, used to access config :param section: config section name @@ -730,7 +1070,9 @@ :return: yields pairs (group-match, value) for all matching items """ - rgxp = re.compile(r'^(\w+)\.' + re.escape(suffix)) + section = pycompat.bytestr(section) + esc_suffix = pycompat.bytestr(re_escape(suffix)) + rgxp = re.compile(b'^(\\w+)\\.' + esc_suffix) for key, value in rgxp_configbool_items(ui, section, rgxp): yield key, value @@ -754,7 +1096,7 @@ ... return "Patched: " + meth.orig(self, arg) >>> >>> obj = SomeClass() - >>> print obj.meth("some param") + >>> print(obj.meth("some param")) Patched: Original: some param It is also possible to use different name @@ -768,7 +1110,7 @@ ... return "Patched: " + another_meth.orig(self, arg) >>> >>> obj = SomeClass() - >>> print obj.meth("some param") + >>> print(obj.meth("some param")) Patched: Original: some param :param cls: Class being modified @@ -794,19 +1136,19 @@ >>> import random >>> @monkeypatch_function(random) ... def seed(x=None): - ... print "Forcing random to seed with 0 instead of", x + ... print("Forcing random to seed with 0 instead of", x) ... return seed.orig(0) >>> >>> random.seed() Forcing random to seed with 0 instead of None >>> random.randint(0, 10) - 9 + 6 >>> import random >>> @monkeypatch_function(random, 'choice') ... def choice_first(sequence): ... return sequence[0] - >>> for x in range(0, 4): print random.choice("abcdefgh") + >>> for x in range(0, 4): print(random.choice("abcdefgh")) a a a @@ -847,7 +1189,7 @@ pending = deque([normalize_path(path)]) while pending: checked = pending.popleft() - if os.path.isdir(checked + '/.hg'): + if os.path.isdir(checked + b'/.hg'): yield checked if not check_inside: continue @@ -857,7 +1199,7 @@ # Things like permission errors (say, on lost+found) # Let's ignorre this, better to process whatever we can names = [] - paths = [checked + '/' + name + paths = [pycompat.bytestr(checked + b'/' + pycompat.bytestr(name)) for name in names if name != '.hg'] dir_paths = [item for item in paths if os.path.isdir(item)] @@ -872,6 +1214,8 @@ """ Compatibility layer for mercurial.cmdtutil.command. + For Mercurials >= 3.8 it's registrar.command + For Mercurials >= 3.1 it's just synonym for cmdutil.command. For Mercurials <= 3.0 it returns upward compatible function @@ -906,9 +1250,9 @@ >>> # Syntax changed in hg3.8, trying to accomodate >>> commands.norepo if hasattr(commands, 'norepo') else ' othercmd' # doctest: +ELLIPSIS '... othercmd' - >>> othercmd.__dict__['norepo'] if othercmd.__dict__ else True + >>> othercmd.__dict__['norepo'] if othercmd.__dict__ else True True - >>> mycmd.__dict__['norepo'] if mycmd.__dict__ else False + >>> mycmd.__dict__['norepo'] if mycmd.__dict__ else False False """ @@ -970,9 +1314,9 @@ Allows to block some modules from demandimport machinery, so they are not accidentally misloaded: - >>> k = direct_import("anydbm", ["dbhash", "gdbm", "dbm", "bsddb.db"]) + >>> k = direct_import("dbm", ["dbm.gnu", "dbm.ndbm", "dbm.dumb"]) >>> k.__name__ - 'anydbm' + 'dbm' :param module_name: name of imported module :param blocked_modules: names of modules to be blocked from demandimport @@ -1040,9 +1384,8 @@ def disable_logging(module_name): """ - Shut up warning about initialized logging which happens - if some imported module logs (mercurial does not setup logging - machinery) + Shut up warning about initialized logging which happens if some + imported module logs (mercurial does not setup logging machinery) >>> disable_logging("keyring") @@ -1104,29 +1447,49 @@ not lambda, not local function embedded inside another function). """ + hook_name = pycompat.bytestr(hook_name) + # Detecting function name, and checking whether it seems publically # importable and callable from global module level if hook_function.__class__.__name__ == 'function' \ and not hook_function.__name__.startswith('<') \ and not hook_function.__module__.startswith('__'): - hook_function_name = '{module}.{name}'.format( - module=hook_function.__module__, name=hook_function.__name__) - hook_activator = 'python:' + hook_function_name + hook_function_name = pycompat.bytestr('{module}.{name}'.format( + module=hook_function.__module__, name=hook_function.__name__)) + hook_activator = pycompat.bytestr(b'python:' + hook_function_name) for key, value in ui.configitems("hooks"): if key == hook_name: if value == hook_activator: - ui.debug(_("Hook already statically installed, skipping %s: %s\n") % ( - hook_name, hook_function_name)) + ui.debug(ui_string("meu: Hook already statically installed, skipping %s: %s\n", + hook_name, hook_function_name)) return if value == hook_function: - ui.debug(_("Hook already dynamically installed, skipping %s: %s\n") % ( - hook_name, hook_function_name)) + ui.debug(ui_string("meu: Hook already dynamically installed, skipping %s: %s\n", + hook_name, hook_function_name)) return - ui.debug(_("Enabling dynamic hook %s: %s.%s\n") % ( - hook_name, hook_function.__module__, hook_function.__name__)) + ui.debug(ui_string("meu: Enabling dynamic hook %s: %s\n", + hook_name, hook_function_name)) # Standard way of hook enabling - ui.setconfig("hooks", hook_name, hook_function) + setconfig_item(ui, b"hooks", hook_name, hook_function) + + +########################################################################### +# Manual test support +########################################################################### + +if __name__ == "__main__": + import doctest + # doctest.testmod() + # doctest.run_docstring_examples(setconfig_dict, globals()) + # doctest.run_docstring_examples(setconfig_list, globals()) + # doctest.run_docstring_examples(rgxp_config_items, globals()) + # doctest.run_docstring_examples(suffix_configlist_items, globals()) + # doctest.run_docstring_examples(normalize_path, globals()) + doctest.run_docstring_examples(rgxp_configbool_items, globals()) + doctest.run_docstring_examples(rgxp_configlist_items, globals()) + doctest.run_docstring_examples(suffix_config_items, globals()) + doctest.run_docstring_examples(suffix_configbool_items, globals()) diff -Nru mercurial-extension-utils-1.3.6/PKG-INFO mercurial-extension-utils-1.5.0/PKG-INFO --- mercurial-extension-utils-1.3.6/PKG-INFO 2018-08-03 21:06:36.000000000 +0000 +++ mercurial-extension-utils-1.5.0/PKG-INFO 2019-11-11 00:11:35.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: mercurial_extension_utils -Version: 1.3.6 +Version: 1.5.0 Summary: Mercurial Extension Utils Home-page: http://bitbucket.org/Mekk/mercurial-extension_utils Author: Marcin Kasperski @@ -206,6 +206,7 @@ Classifier: License :: DFSG approved Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 3 Classifier: Operating System :: OS Independent Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Version Control diff -Nru mercurial-extension-utils-1.3.6/README-TESTING.txt mercurial-extension-utils-1.5.0/README-TESTING.txt --- mercurial-extension-utils-1.3.6/README-TESTING.txt 1970-01-01 00:00:00.000000000 +0000 +++ mercurial-extension-utils-1.5.0/README-TESTING.txt 2017-01-26 13:20:42.000000000 +0000 @@ -0,0 +1,13 @@ +Various ways of pre-release testing: + +- manual, current install + + python -m unittest discover tests/ + +- cross-version + + tox + +- automatic + + (active on drone.io, script saved in drone.sh) diff -Nru mercurial-extension-utils-1.3.6/setup.py mercurial-extension-utils-1.5.0/setup.py --- mercurial-extension-utils-1.3.6/setup.py 2018-08-03 21:06:17.000000000 +0000 +++ mercurial-extension-utils-1.5.0/setup.py 2019-11-11 00:10:46.000000000 +0000 @@ -2,7 +2,7 @@ from setuptools import setup, find_packages -VERSION = '1.3.6' +VERSION = '1.5.0' LONG_DESCRIPTION = open("README.txt").read() INSTALL_REQUIRES = [] @@ -28,6 +28,7 @@ 'License :: DFSG approved', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 3', 'Operating System :: OS Independent', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Version Control', diff -Nru mercurial-extension-utils-1.3.6/tests/manual_find_repositories_below.py mercurial-extension-utils-1.5.0/tests/manual_find_repositories_below.py --- mercurial-extension-utils-1.3.6/tests/manual_find_repositories_below.py 1970-01-01 00:00:00.000000000 +0000 +++ mercurial-extension-utils-1.5.0/tests/manual_find_repositories_below.py 2017-01-26 13:20:42.000000000 +0000 @@ -0,0 +1,6 @@ + +import mercurial_extension_utils as meu + +#for repo_path in meu.find_repositories_below("~/DEV_hg/mercurial"): +for repo_path in meu.find_repositories_below("~/DEV_hg"): + print repo_path diff -Nru mercurial-extension-utils-1.3.6/tests/py2_doctests_mercurial_extension_utils.py mercurial-extension-utils-1.5.0/tests/py2_doctests_mercurial_extension_utils.py --- mercurial-extension-utils-1.3.6/tests/py2_doctests_mercurial_extension_utils.py 1970-01-01 00:00:00.000000000 +0000 +++ mercurial-extension-utils-1.5.0/tests/py2_doctests_mercurial_extension_utils.py 2019-11-10 17:59:05.000000000 +0000 @@ -0,0 +1,456 @@ +# -*- coding: utf-8 -*- +# +# mercurial extension utils: Python 2 doctests +# +# Copyright (c) 2015-2019 Marcin Kasperski +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# See README.txt for more details. + +r''' +This module exists solely to give some examples (and doctest) +of mercurial_extension_utils styled for Python2 syntax. +Tests are copied from mercurial_extension_utils.py (before +switch to py3 syntax in doctests). + + >>> import mercurial.ui; ui = mercurial.ui.ui() + >>> ui.debug(ui_string("Simple text")) + >>> ui.debug(ui_string(b"Simple binary")) + >>> ui.debug(ui_string("This is %s and %s and %s", b'bin', u'txt', None)) + >>> ui.debug(ui_string(b"This is %s and %s and %s", b'bin', u'txt', None)) + + This works because we reasonably create binary strings, as ui expects: + + >>> ui_string("Simple text") + 'Simple text' + >>> ui_string(b"Simple binary") + 'Simple binary' + >>> ui_string("This is %s and %s and %s", b'bin', u'txt', None) + u'This is bin and txt and None' + >>> ui_string(b"This is %s and %s and %s", b'bin', u'txt', None) + u'This is bin and txt and None' + + >>> normalize_path("~/src") + '/home/lordvader/src' + >>> normalize_path("/some/where") + '/some/where' + >>> normalize_path("/some/where/") + '/some/where' + >>> normalize_path("../../../some/where") + '/home/lordvader/some/where' + + + >>> belongs_to_tree("/tmp/sub/dir", "/tmp") + '/tmp' + >>> belongs_to_tree("/tmp", "/tmp") + '/tmp' + >>> belongs_to_tree("/tmp/sub", "/tmp/sub/dir/../..") + '/tmp' + + + >>> belongs_to_tree("/usr/sub", "/tmp") + + + >>> home_work_src = os.path.join(os.environ["HOME"], "work", "src") + >>> belongs_to_tree(home_work_src, "~/work") + '/home/lordvader/work' + >>> belongs_to_tree("/home/lordvader/devel/webapps", "~lordvader/devel") + '/home/lordvader/devel' + + + >>> belongs_to_tree_group("/tmp/sub/dir", ["/bin", "/tmp"]) + '/tmp' + >>> belongs_to_tree_group("/tmp", ["/tmp"]) + '/tmp' + >>> belongs_to_tree_group("/tmp/sub/dir", ["/bin", "~/src"]) + + + >>> belongs_to_tree_group("/tmp/sub/dir", ["/tmp", "/bin", "/tmp", "/tmp/sub"]) + '/tmp/sub' + + + >>> belongs_to_tree_group("/home/lordvader/src/apps", ["~/src", "/home/lordvader"]) + '/home/lordvader/src' + + + >>> pat = DirectoryPattern('~/src/{suffix}') + >>> pat.is_valid() + True + >>> pat.search("/opt/repos/abcd") + >>> pat.search("~/src/repos/in/tree") + {'suffix': 'repos/in/tree'} + >>> pat.search("/home/lordvader/src/repos/here" if os.system != 'nt' else "c:/users/lordvader/src/repos/here") + {'suffix': 'repos/here'} + >>> pat.search("/home/lordvader/src") + + >>> pat = DirectoryPattern('~lordvader/devel/(item)') + >>> pat.search("/opt/repos/abcd") + >>> pat.search("~/devel/libxuza") + {'item': 'libxuza'} + >>> pat.search("~/devel/libs/libxuza") + >>> pat.search("/home/lordvader/devel/webapp") + {'item': 'webapp'} + >>> pat.search("/home/lordvader/devel") + + >>> from pprint import pprint # Help pass doctests below + + >>> pat = DirectoryPattern('/opt/repos/(group)/{suffix}') + >>> pat.search("/opt/repos/abcd") + >>> pprint(pat.search("/opt/repos/libs/abcd")) + {'group': 'libs', 'suffix': 'abcd'} + >>> pprint(pat.search("/opt/repos/apps/mini/webby")) + {'group': 'apps', 'suffix': 'mini/webby'} + + >>> pat = DirectoryPattern('/opt/repos/(group/{suffix}') + >>> pat.is_valid() + False + >>> pat.search('/opt/repos/some/where') + + Fixed strings can also be used and work reasonably: + + >>> pat = DirectoryPattern('~/dev/acme') + >>> pat.is_valid() + True + >>> pat.search('/home/lordvader/dev/acme') + {} + >>> pat.search('/home/lordvader/dev/acme/') + {} + >>> pat.search('/home/lordvader/dev/acme/subdir') + >>> pat.search('/home/lordvader/dev') + + + >>> tf = TextFiller('{some}/text/to/{fill}') + >>> tf.fill(some='prefix', fill='suffix') + 'prefix/text/to/suffix' + >>> tf.fill(some='/ab/c/d', fill='x') + '/ab/c/d/text/to/x' + + + >>> tf = TextFiller('{some}/text/to/{some}') + >>> tf.is_valid() + True + >>> tf.fill(some='val') + 'val/text/to/val' + >>> tf.fill(some='ab/c/d', fill='x') + 'ab/c/d/text/to/ab/c/d' + + + >>> tf = TextFiller('{prefix:_=___}/goto/{suffix:/=-}') + >>> tf.fill(prefix='some_prefix', suffix='some/long/suffix') + 'some___prefix/goto/some-long-suffix' + + + >>> tf = TextFiller('{prefix:/home/=}/docs/{suffix:.txt=.html}') + >>> tf.fill(prefix='/home/joe', suffix='some/document.txt') + 'joe/docs/some/document.html' + + + >>> tf = TextFiller(r'/goto/{item:/=-:\=_}/') + >>> tf.fill(item='this/is/slashy') + '/goto/this-is-slashy/' + >>> tf.fill(item=r'this\is\back') + '/goto/this_is_back/' + >>> tf.fill(item=r'this/is\mixed') + '/goto/this-is_mixed/' + + + >>> tf = TextFiller(r'http://go.to/{item:/=-}, G:{item:/=\}, name: {item}') + >>> print(tf.fill(item='so/me/thing')) + http://go.to/so-me-thing, G:so\me\thing, name: so/me/thing + + + >>> tf = TextFiller('{some}/text/to/{fill}') + >>> tf.fill(some='prefix', badfill='suffix') + + + >>> tf = TextFiller('{some/text/to/{fill}') + >>> tf.is_valid() + False + >>> tf.fill(some='prefix', fill='suffix') + + >>> tf = TextFiller('{some}/text/to/{fill:}') + >>> tf.is_valid() + False + >>> tf.fill(some='prefix', fill='suffix') + + + >>> import mercurial.ui; ui = mercurial.ui.ui() + >>> setconfig_dict(ui, "sect1", {'a': 7, 'bbb': 'xxx', 'c': '-'}) + >>> setconfig_dict(ui, "sect2", {'v': 'vvv'}) + >>> ui.config("sect1", 'a') + 7 + >>> ui.config("sect2", 'v') + 'vvv' + + + >>> import mercurial.ui; ui = mercurial.ui.ui() + >>> setconfig_list(ui, "sect1", + ... [('a', 7), ('bbb', 'xxx'), ('c', '-'), ('a', 8)]) + >>> setconfig_list(ui, "sect2", [('v', 'vvv')]) + >>> ui.config("sect1", 'a') + 8 + >>> ui.config("sect2", 'v') + 'vvv' + + + >>> import mercurial.ui; ui = mercurial.ui.ui() + >>> setconfig_list(ui, "foo", [ + ... ("pfx-some-sfx", "ala, ma kota"), + ... ("some.nonitem", "bela nie"), + ... ("x", "yes"), + ... ("pfx-other-sfx", 4) + ... ]) + >>> setconfig_list(ui, "notfoo", [ + ... ("pfx-some-sfx", "bad"), + ... ("pfx-also-sfx", "too"), + ... ]) + >>> + >>> for name, value in rgxp_config_items( + ... ui, "foo", re.compile(r'^pfx-(\w+)-sfx$')): + ... print(name, value) + some ala, ma kota + other 4 + + + >>> import mercurial.ui; ui = mercurial.ui.ui() + >>> setconfig_list(ui, "foo", [ + ... ("pfx-some-sfx", "ala, ma kota"), + ... ("some.nonitem", "bela nie"), + ... ("x", "yes"), + ... ("pfx-other-sfx", "sth"), + ... ]) + >>> setconfig_list(ui, "notfoo", [ + ... ("pfx-some-sfx", "bad"), + ... ("pfx-also-sfx", "too"), + ... ]) + >>> + >>> for name, value in rgxp_configlist_items( + ... ui, "foo", re.compile(r'^pfx-(\w+)-sfx$')): + ... print(name, value) + some ['ala', 'ma', 'kota'] + other ['sth'] + + + >>> import mercurial.ui; ui = mercurial.ui.ui() + >>> setconfig_list(ui, "foo", [ + ... ("pfx-some-sfx", "true"), + ... ("some.nonitem", "bela nie"), + ... ("x", "yes"), + ... ("pfx-other-sfx", "false"), + ... ]) + >>> setconfig_dict(ui, "notfoo", { + ... "pfx-some-sfx": "1", + ... "pfx-also-sfx": "0", + ... }) + >>> + >>> for name, value in rgxp_configbool_items( + ... ui, "foo", re.compile(r'^pfx-(\w+)-sfx$')): + ... print(name, value) + some True + other False + + + >>> import mercurial.ui; ui = mercurial.ui.ui() + >>> setconfig_list(ui, "foo", [ + ... ("some.item", "ala, ma kota"), + ... ("some.nonitem", "bela nie"), + ... ("x", "yes"), + ... ("other.item", 4), + ... ]) + >>> setconfig_dict(ui, "notfoo", { + ... "some.item": "bad", + ... "also.item": "too", + ... }) + >>> + >>> for name, value in suffix_config_items( + ... ui, "foo", 'item'): + ... print(name, value) + some ala, ma kota + other 4 + + + >>> import mercurial.ui; ui = mercurial.ui.ui() + >>> setconfig_list(ui, "foo", [ + ... ("some.item", "ala, ma kota"), + ... ("some.nonitem", "bela nie"), + ... ("x", "yes"), + ... ("other.item", "kazimira"), + ... ]) + >>> setconfig_dict(ui, "notfoo", { + ... "some.item": "bad", + ... "also.item": "too", + ... }) + >>> + >>> for name, value in suffix_configlist_items( + ... ui, "foo", "item"): + ... print(name, value) + some ['ala', 'ma', 'kota'] + other ['kazimira'] + + + >>> import mercurial.ui; ui = mercurial.ui.ui() + >>> setconfig_list(ui, "foo", [ + ... ("true.item", "true"), + ... ("false.item", "false"), + ... ("one.item", "1"), + ... ("zero.item", "0"), + ... ("yes.item", "yes"), + ... ("no.item", "no"), + ... ("some.nonitem", "1"), + ... ("x", "yes"), + ... ]) + >>> setconfig_dict(ui, "notfoo", { + ... "some.item": "0", + ... "also.item": "too", + ... }) + >>> + >>> for name, value in suffix_configbool_items( + ... ui, "foo", "item"): + ... print(name, str(value)) + true True + false False + one True + zero False + yes True + no False + >>> + >>> ui.setconfig("foo", "text.item", "something") + >>> for name, value in suffix_configbool_items( + ... ui, "foo", "item"): + ... print(name, str(value)) + Traceback (most recent call last): + File "/usr/lib/python2.7/dist-packages/mercurial/ui.py", line 237, in configbool + % (section, name, v)) + ConfigError: foo.text.item is not a boolean ('something') + + + >>> class SomeClass(object): + ... def meth(self, arg): + ... return "Original: " + arg + >>> + >>> @monkeypatch_method(SomeClass) + ... def meth(self, arg): + ... return "Patched: " + meth.orig(self, arg) + >>> + >>> obj = SomeClass() + >>> print(obj.meth("some param")) + Patched: Original: some param + + + >>> class SomeClass(object): + ... def meth(self, arg): + ... return "Original: " + arg + >>> + >>> @monkeypatch_method(SomeClass, "meth") + ... def another_meth(self, arg): + ... return "Patched: " + another_meth.orig(self, arg) + >>> + >>> obj = SomeClass() + >>> print(obj.meth("some param")) + Patched: Original: some param + + + >>> import random + >>> @monkeypatch_function(random) + ... def seed(x=None): + ... print("Forcing random to seed with 0 instead of", x) + ... return seed.orig(0) + >>> + >>> random.seed() + Forcing random to seed with 0 instead of None + >>> random.randint(0, 10) + 9 + + >>> import random + >>> @monkeypatch_function(random, 'choice') + ... def choice_first(sequence): + ... return sequence[0] + >>> for x in range(0, 4): print(random.choice("abcdefgh")) + a + a + a + a + + + >>> cmdtable = {} + >>> cmd = command(cmdtable) + >>> + >>> @cmd("somecmd", [], "somecmd") + ... def mycmd(ui, repo, sth, **opts): + ... pass + >>> + >>> @cmd("othercmd", [ + ... ('l', 'list', None, 'List widgets'), + ... ('p', 'pagesize', 10, 'Page size'), + ... ], "othercmd [-l] [-p 20]", norepo=True) + ... def othercmd(ui, sth, **opts): + ... pass + >>> + >>> sorted(cmdtable.keys()) + ['othercmd', 'somecmd'] + >>> cmdtable['othercmd'] # doctest: +ELLIPSIS + (, [('l', 'list', None, 'List widgets'), ('p', 'pagesize', 10, 'Page size')], 'othercmd [-l] [-p 20]') + + + >>> from mercurial import commands + >>> # Syntax changed in hg3.8, trying to accomodate + >>> commands.norepo if hasattr(commands, 'norepo') else ' othercmd' # doctest: +ELLIPSIS + '... othercmd' + >>> othercmd.__dict__['norepo'] if othercmd.__dict__ else True + True + >>> mycmd.__dict__['norepo'] if mycmd.__dict__ else False + False + + + >>> re = direct_import("re") + >>> re.__name__ + 're' + >>> re.search("^(.)", "Ala").group(1) + 'A' + + + >>> k = direct_import("anydbm", ["dbhash", "gdbm", "dbm", "bsddb.db"]) + >>> k.__name__ + 'anydbm' + + + >>> m1, loaded = direct_import_ext("xml.sax.handler") + >>> m1.__name__, loaded + ('xml.sax.handler', True) + + >>> m2, loaded = direct_import_ext("xml.sax.handler") + >>> m2.__name__, loaded + ('xml.sax.handler', False) + + >>> m1 == m2 + True + + + >>> disable_logging("keyring") + + + + +''' diff -Nru mercurial-extension-utils-1.3.6/tests/py2win_doctests_mercurial_extension_utils.py mercurial-extension-utils-1.5.0/tests/py2win_doctests_mercurial_extension_utils.py --- mercurial-extension-utils-1.3.6/tests/py2win_doctests_mercurial_extension_utils.py 1970-01-01 00:00:00.000000000 +0000 +++ mercurial-extension-utils-1.5.0/tests/py2win_doctests_mercurial_extension_utils.py 2017-01-26 13:20:42.000000000 +0000 @@ -0,0 +1,351 @@ +# -*- coding: utf-8 -*- +# +# mercurial extension utils: Windows doctests +# +# Copyright (c) 2015 Marcin Kasperski +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# See README.txt for more details. + +r''' +This module exists solely to give some examples (and doctest) +of mercurial_extension_utils behaviour on Windows. Structure +mimics that of mercurial_extension_utils. + + + + >>> normalize_path("~/src") + 'C:/Users/lordvader/src' + >>> normalize_path("/some/where") + 'c:/some/where' + >>> normalize_path("/some/where/") + 'c:/some/where' + >>> normalize_path("../../../some/where") + 'c:/Users/lordvader/some/where' + >>> normalize_path(r'C:\Users\Joe\source files') + 'C:/Users/Joe/source files' + + + + >>> belongs_to_tree("/tmp/sub/dir", "/tmp") + 'c:/tmp' + >>> belongs_to_tree("/tmp", "/tmp") + 'c:/tmp' + >>> belongs_to_tree("/tmp/sub", "/tmp/sub/dir/../..") + 'c:/tmp' + + >>> belongs_to_tree("/usr/sub", "/tmp") + + >>> home_work_src = os.path.join(os.environ["HOME"], "work", "src") + >>> belongs_to_tree(home_work_src, "~/work") + 'C:/Users/lordvader/work' + >>> belongs_to_tree("/home/lordvader/devel/webapps" if os.name != 'nt' else "c:/users/lordvader/devel/webapps", + ... "~lordvader/devel") + 'C:/Users/lordvader/devel' + + + + >>> belongs_to_tree_group("/tmp/sub/dir", ["/bin", "/tmp"]) + 'c:/tmp' + >>> belongs_to_tree_group("/tmp", ["/tmp"]) + 'c:/tmp' + >>> belongs_to_tree_group("/tmp/sub/dir", ["/bin", "~/src"]) + + >>> belongs_to_tree_group("/tmp/sub/dir", ["/tmp", "/bin", "/tmp", "/tmp/sub"]) + 'c:/tmp/sub' + + >>> belongs_to_tree_group("C:/Users/lordvader/src/apps", ["~/src", "C:/Users/lordvader"]) + 'C:/Users/lordvader/src' + + + + >>> pat = DirectoryPattern('~/src/{suffix}') + >>> pat.is_valid() + True + >>> pat.search("/opt/repos/abcd") + >>> pat.search("~/src/repos/in/tree") + {'suffix': 'repos/in/tree'} + >>> pat.search("c:/users/lordvader/src/repos/here") + {'suffix': 'repos/here'} + >>> pat.search("C:/Users/lordvader/src/repos/here") + {'suffix': 'repos/here'} + >>> pat.search("/home/lordvader/src") + + >>> pat = DirectoryPattern('~lordvader/devel/(item)') + >>> pat.search("/opt/repos/abcd") + >>> pat.search("~/devel/libxuza") + {'item': 'libxuza'} + >>> pat.search("~/devel/libs/libxuza") + >>> pat.search("C:/Users/lordvader/devel/webapp") + {'item': 'webapp'} + >>> pat.search("/users/lordvader/devel/webapp") + {'item': 'webapp'} + >>> pat.search("/home/lordvader/devel") + + >>> pat = DirectoryPattern('/opt/repos/(group)/{suffix}') + >>> pat.search("/opt/repos/abcd") + >>> pat.search("/opt/repos/libs/abcd") + {'group': 'libs', 'suffix': 'abcd'} + >>> pat.search("/opt/repos/apps/mini/webby") + {'group': 'apps', 'suffix': 'mini/webby'} + + >>> pat = DirectoryPattern('/opt/repos/(group/{suffix}') + >>> pat.is_valid() + False + >>> pat.search('/opt/repos/some/where') + + + + >>> tf = TextFiller('{some}/text/to/{fill}') + >>> tf.fill(some='prefix', fill='suffix') + 'prefix/text/to/suffix' + >>> tf.fill(some='/ab/c/d', fill='x') + '/ab/c/d/text/to/x' + + >>> tf = TextFiller('{some}/text/to/{some}') + >>> tf.is_valid() + True + >>> tf.fill(some='val') + 'val/text/to/val' + >>> tf.fill(some='ab/c/d', fill='x') + 'ab/c/d/text/to/ab/c/d' + + >>> tf = TextFiller('{prefix:_=___}/goto/{suffix:/=-}') + >>> tf.fill(prefix='some_prefix', suffix='some/long/suffix') + 'some___prefix/goto/some-long-suffix' + + >>> tf = TextFiller('{prefix:/home/=}/docs/{suffix:.txt=.html}') + >>> tf.fill(prefix='/home/joe', suffix='some/document.txt') + 'joe/docs/some/document.html' + + >>> tf = TextFiller(r'/goto/{item:/=-:\=_}/') + >>> tf.fill(item='this/is/slashy') + '/goto/this-is-slashy/' + >>> tf.fill(item=r'this\is\back') + '/goto/this_is_back/' + >>> tf.fill(item=r'this/is\mixed') + '/goto/this-is_mixed/' + + >>> tf = TextFiller(r'http://go.to/{item:/=-}, G:{item:/=\}, name: {item}') + >>> print tf.fill(item='so/me/thing') + http://go.to/so-me-thing, G:so\me\thing, name: so/me/thing + + >>> tf = TextFiller('{some}/text/to/{fill}') + >>> tf.fill(some='prefix', badfill='suffix') + + >>> tf = TextFiller('{some/text/to/{fill}') + >>> tf.is_valid() + False + >>> tf.fill(some='prefix', fill='suffix') + + >>> tf = TextFiller('{some}/text/to/{fill:}') + >>> tf.is_valid() + False + >>> tf.fill(some='prefix', fill='suffix') + + + + >>> import mercurial.ui; ui = mercurial.ui.ui() + >>> setconfig_dict(ui, "sect1", {'a': 7, 'bbb': 'xxx', 'c': '-'}) + >>> setconfig_dict(ui, "sect2", {'v': 'vvv'}) + >>> ui.config("sect1", 'a') + 7 + >>> ui.config("sect2", 'v') + 'vvv' + + + >>> import mercurial.ui; ui = mercurial.ui.ui() + >>> setconfig_dict(ui, "foo", { + ... "pfx-some-sfx": "ala, ma kota", + ... "some.nonitem": "bela nie", + ... "x": "yes", + ... "pfx-other-sfx": 4}) + >>> setconfig_dict(ui, "notfoo", { + ... "pfx-some-sfx": "bad", + ... "pfx-also-sfx": "too", + ... }) + >>> + >>> for name, value in rgxp_config_items( + ... ui, "foo", re.compile(r'^pfx-(\w+)-sfx$')): + ... print name, value + some ala, ma kota + other 4 + + + >>> import mercurial.ui; ui = mercurial.ui.ui() + >>> setconfig_dict(ui, "foo", { + ... "pfx-some-sfx": "ala, ma kota", + ... "some.nonitem": "bela nie", + ... "x": "yes", + ... "pfx-other-sfx": "sth"}) + >>> setconfig_dict(ui, "notfoo", { + ... "pfx-some-sfx": "bad", + ... "pfx-also-sfx": "too", + ... }) + >>> + >>> for name, value in rgxp_configlist_items( + ... ui, "foo", re.compile(r'^pfx-(\w+)-sfx$')): + ... print name, value + some ['ala', 'ma', 'kota'] + other ['sth'] + + + >>> import mercurial.ui; ui = mercurial.ui.ui() + >>> setconfig_dict(ui, "foo", { + ... "pfx-some-sfx": "true", + ... "some.nonitem": "bela nie", + ... "x": "yes", + ... "pfx-other-sfx": "false"}) + >>> setconfig_dict(ui, "notfoo", { + ... "pfx-some-sfx": "1", + ... "pfx-also-sfx": "0", + ... }) + >>> + >>> for name, value in rgxp_configbool_items( + ... ui, "foo", re.compile(r'^pfx-(\w+)-sfx$')): + ... print name, value + some True + other False + + + >>> import mercurial.ui; ui = mercurial.ui.ui() + >>> setconfig_dict(ui, "foo", { + ... "some.item": "ala, ma kota", + ... "some.nonitem": "bela nie", + ... "x": "yes", + ... "other.item": 4}) + >>> setconfig_dict(ui, "notfoo", { + ... "some.item": "bad", + ... "also.item": "too", + ... }) + >>> + >>> for name, value in suffix_config_items( + ... ui, "foo", 'item'): + ... print name, value + some ala, ma kota + other 4 + + + >>> import mercurial.ui; ui = mercurial.ui.ui() + >>> setconfig_dict(ui, "foo", { + ... "some.item": "ala, ma kota", + ... "some.nonitem": "bela nie", + ... "x": "yes", + ... "other.item": "kazimira"}) + >>> setconfig_dict(ui, "notfoo", { + ... "some.item": "bad", + ... "also.item": "too", + ... }) + >>> + >>> for name, value in suffix_configlist_items( + ... ui, "foo", "item"): + ... print name, value + some ['ala', 'ma', 'kota'] + other ['kazimira'] + + + >>> import mercurial.ui; ui = mercurial.ui.ui() + >>> setconfig_dict(ui, "foo", { + ... "true.item": "true", + ... "false.item": "false", + ... "one.item": "1", + ... "zero.item": "0", + ... "yes.item": "yes", + ... "no.item": "no", + ... "some.nonitem": "1", + ... "x": "yes"}) + >>> setconfig_dict(ui, "notfoo", { + ... "some.item": "0", + ... "also.item": "too", + ... }) + >>> + >>> for name, value in suffix_configbool_items( + ... ui, "foo", "item"): + ... print name, str(value) + zero False + yes True + one True + true True + no False + false False + >>> + >>> ui.setconfig("foo", "text.item", "something") + >>> for name, value in suffix_configbool_items( + ... ui, "foo", "item"): + ... print name, str(value) + Traceback (most recent call last): + File "/usr/lib/python2.7/dist-packages/mercurial/ui.py", line 237, in configbool + % (section, name, v)) + ConfigError: foo.text.item is not a boolean ('something') + + + >>> class SomeClass(object): + ... def meth(self, arg): + ... return "Original: " + arg + >>> + >>> @monkeypatch_method(SomeClass) + ... def meth(self, arg): + ... return "Patched: " + meth.orig(self, arg) + >>> + >>> obj = SomeClass() + >>> print obj.meth("some param") + Patched: Original: some param + + + >>> class SomeClass(object): + ... def meth(self, arg): + ... return "Original: " + arg + >>> + >>> @monkeypatch_method(SomeClass, "meth") + ... def another_meth(self, arg): + ... return "Patched: " + another_meth.orig(self, arg) + >>> + >>> obj = SomeClass() + >>> print obj.meth("some param") + Patched: Original: some param + + + >>> import random + >>> @monkeypatch_function(random) + ... def seed(x=None): + ... print "Forcing random to seed with 0 instead of", x + ... return seed.orig(0) + >>> + >>> random.seed() + Forcing random to seed with 0 instead of None + >>> random.randint(0, 10) + 9 + + >>> import random + >>> @monkeypatch_function(random, 'choice') + ... def choice_first(sequence): + ... return sequence[0] + >>> for x in range(0, 4): print random.choice("abcdefgh") + a + a + a + a + +''' diff -Nru mercurial-extension-utils-1.3.6/tests/test_doctest.py mercurial-extension-utils-1.5.0/tests/test_doctest.py --- mercurial-extension-utils-1.3.6/tests/test_doctest.py 1970-01-01 00:00:00.000000000 +0000 +++ mercurial-extension-utils-1.5.0/tests/test_doctest.py 2019-05-18 11:40:41.000000000 +0000 @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- + +# pylint: disable=missing-docstring,unused-argument,too-many-arguments + +import os +import unittest +import doctest +import getpass +import mercurial_extension_utils +import sys + +USING_PY3 = sys.version_info >= (3, 0, 0) + +# IMPORTANT NOTE: +# +# As I wanted doctests to be readable, most of them assume +# specific paths and names (for example some tests assume /home/lordvader +# as home directory). This is on purpose, +# +# >>> normalize_path("~/src") +# '/home/lordvader/src' +# +# is readable and fulfills documentation role well, whatever I could write +# instead to handle various accounts, would be unreadable mess. +# +# To make running tests possible, below we adapt docstrings +# before executing them. + + +class FixingUpDocTestParser(doctest.DocTestParser): # pylint: disable=no-init + + PATTERN_HOME = '/home/lordvader' + PATTERN_NAME = 'lordvader' + TRUE_HOME = os.path.expanduser("~") + TRUE_NAME = getpass.getuser() + REL_TO_HOME = os.path.relpath(TRUE_HOME) + + def get_doctest(self, string, globs, name, filename, lineno): + # Replace /home/lordvader with whatever true home is + # (and similar) + string = string \ + .replace(self.PATTERN_HOME, self.TRUE_HOME) \ + .replace(self.PATTERN_NAME, self.TRUE_NAME) + # Special fixup for ../../.. pointing at home + string = string.replace('"../../..', '"' + self.REL_TO_HOME) + return doctest.DocTestParser.get_doctest( + self, string, globs, name, filename, lineno) + + +def load_tests(loader, tests, pattern): + if os.name != 'nt': + if USING_PY3: + finder = doctest.DocTestFinder(parser=FixingUpDocTestParser()) + suite = doctest.DocTestSuite( + mercurial_extension_utils, + test_finder=finder) + else: + suite = doctest.DocFileSuite( + "py2_doctests_mercurial_extension_utils.py", + module_relative=True, + globs=mercurial_extension_utils.__dict__, + parser=FixingUpDocTestParser()) + else: + if USING_PY3: + raise Exception("TODO: py3 tests for Windows") + else: + suite = doctest.DocFileSuite( + "py2win_doctests_mercurial_extension_utils.py", + module_relative=True, + globs=mercurial_extension_utils.__dict__, + parser=FixingUpDocTestParser()) + tests.addTests(suite) + return tests + + +if __name__ == "__main__": + unittest.main() + diff -Nru mercurial-extension-utils-1.3.6/tests/test_find_repositories_below.py mercurial-extension-utils-1.5.0/tests/test_find_repositories_below.py --- mercurial-extension-utils-1.3.6/tests/test_find_repositories_below.py 1970-01-01 00:00:00.000000000 +0000 +++ mercurial-extension-utils-1.5.0/tests/test_find_repositories_below.py 2019-11-09 15:55:23.000000000 +0000 @@ -0,0 +1,101 @@ + +import mercurial_extension_utils as meu +import os +import tempfile +import shutil +import subprocess +import unittest + + +class RepoBuffer(object): + def __init__(self): + self.location = tempfile.mkdtemp() + self.setup_repos() + + def __del__(self): + shutil.rmtree(self.location) + + def setup_repos(self): + self._exec_in_top("hg", "init", "c/c1/c11-repo") + self._exec_in_top("hg", "init", "a-repo") + self._exec_in_top("hg", "init", "b/b3/b3a-repo") + self._exec_in_top("hg", "init", "b/b1-repo") + self._exec_in_top("hg", "init", "b/b2-repo") + self._exec_in_top("hg", "init", "b/b3/b3b-repo") + self._exec_in_top("hg", "init", "a-repo/a1-subrepo") + self._exec_in_top("hg", "init", "b/b1-repo/b11-subrepo") + + def _exec_in_top(self, *args): + status = subprocess.Popen(args, cwd=self.location).wait() + if status: + raise subprocess.CalledProcessError(status, args[0]) + + +class TestFindRepositories(unittest.TestCase): + + buffer = RepoBuffer() + + def _check_path_and_fix(self, repo_path, where): + self.assertTrue(os.path.isdir(repo_path)) + self.assertTrue(os.path.isabs(repo_path)) + self.assertTrue(os.path.isdir(os.path.join(repo_path, b".hg"))) + norm_where = meu.normalize_path(where) + fixed_path = meu.pycompat.bytestr(repo_path.replace(norm_where, b"/xxx")) + if fixed_path == repo_path: + self.fail("Failed to normalize path, repo_path %s, where %s" % (repo_path, norm_where)) + return fixed_path + + def test_std(self): + where = self.buffer.location + items = [] + for repo_path in meu.find_repositories_below(where): + items.append(self._check_path_and_fix(repo_path, where)) + self.assertEqual(items, [meu.pycompat.bytestr(x) for x in [ + "/xxx/a-repo", + "/xxx/b/b1-repo", + "/xxx/b/b2-repo", + "/xxx/b/b3/b3a-repo", + "/xxx/b/b3/b3b-repo", + "/xxx/c/c1/c11-repo", + ]]) + + def test_std_check_inside(self): + where = self.buffer.location + items = [] + for repo_path in meu.find_repositories_below(where, check_inside=True): + items.append(self._check_path_and_fix(repo_path, where)) + self.assertEqual(items, [meu.pycompat.bytestr(x) for x in [ + "/xxx/a-repo", + "/xxx/a-repo/a1-subrepo", + "/xxx/b/b1-repo", + "/xxx/b/b1-repo/b11-subrepo", + "/xxx/b/b2-repo", + "/xxx/b/b3/b3a-repo", + "/xxx/b/b3/b3b-repo", + "/xxx/c/c1/c11-repo", + ]]) + + def test_from_repo(self): + where = self.buffer.location + items = [] + for repo_path in meu.find_repositories_below( + os.path.join(where, "a-repo")): + items.append(self._check_path_and_fix(repo_path, where)) + self.assertEqual(items, [meu.pycompat.bytestr(x) for x in [ + "/xxx/a-repo", + ]]) + + def test_from_repo_check_inside(self): + where = self.buffer.location + items = [] + for repo_path in meu.find_repositories_below( + os.path.join(where, "a-repo"), check_inside=True): + items.append(self._check_path_and_fix(repo_path, where)) + self.assertEqual(items, [meu.pycompat.bytestr(x) for x in [ + "/xxx/a-repo", + "/xxx/a-repo/a1-subrepo", + ]]) + + +if __name__ == "__main__": + unittest.main() diff -Nru mercurial-extension-utils-1.3.6/TODO.txt mercurial-extension-utils-1.5.0/TODO.txt --- mercurial-extension-utils-1.3.6/TODO.txt 1970-01-01 00:00:00.000000000 +0000 +++ mercurial-extension-utils-1.5.0/TODO.txt 2017-01-26 13:20:42.000000000 +0000 @@ -0,0 +1,3 @@ + +→ Finishing mercurial_extension_utils_loader + and publishing it as install alternative. diff -Nru mercurial-extension-utils-1.3.6/tox.ini mercurial-extension-utils-1.5.0/tox.ini --- mercurial-extension-utils-1.3.6/tox.ini 1970-01-01 00:00:00.000000000 +0000 +++ mercurial-extension-utils-1.5.0/tox.ini 2019-11-10 17:32:24.000000000 +0000 @@ -0,0 +1,55 @@ + +[tox] +minversion = 1.8 +toxworkdir = {homedir}/.tox/work/mercurial/extension_utils +distshare = {homedir}/.tox/distshare +envlist = py{35,36,37}-hg{52,51,50},py27-hg{52,51,50,49,48,45,44,41,38,37,33,30,29,27} +skip_missing_interpreters = true +;; We don't test python3.5, it's old and causes regexp problems (and some apis +;; actually fail). +;; +;; Installing custom pythons: +;; sudo add-apt-repository ppa:deadsnakes/ppa + +[testenv] +passenv = HOME +setenv = + HGRCPATH = {toxworkdir}/hgrc + py35: HGPYTHON3 = 1 + py36: HGPYTHON3 = 1 + py37: HGPYTHON3 = 1 +deps = + py26: unittest2 + hg27: Mercurial>=2.7,<2.8 + hg28: Mercurial>=2.8,<2.9 + hg29: Mercurial>=2.9,<3.0 + hg30: Mercurial>=3.0,<3.1 + hg31: Mercurial>=3.1,<3.2 + hg32: Mercurial>=3.2,<3.3 + hg33: Mercurial>=3.3,<3.4 + hg34: Mercurial>=3.4,<3.5 + hg35: Mercurial>=3.5,<3.6 + hg36: Mercurial>=3.6,<3.7 + hg37: Mercurial>=3.7,<3.8 + hg38: Mercurial>=3.8,<3.9 + hg40: Mercurial>=4.0,<4.1 + hg41: Mercurial>=4.1,<4.2 + hg42: Mercurial>=4.2,<4.3 + hg43: Mercurial>=4.3,<4.4 + hg44: Mercurial>=4.4,<4.5 + hg45: Mercurial>=4.5,<4.6 + hg46: Mercurial>=4.6,<4.7 + hg47: Mercurial>=4.7,<4.8 + hg48: Mercurial>=4.8,<4.9 + hg49: Mercurial>=4.9,<4.10 + hg50: Mercurial>=5.0,<5.1 + hg51: Mercurial>=5.1,<5.2 + hg52: Mercurial>=5.2,<5.3 +commands = + py26: unit2 discover tests + py27: python -m unittest discover tests + py35: python -m unittest discover tests + py36: python -m unittest discover tests + py37: python -m unittest discover tests + +