mercurial_extension_utils-1.5.0/0000775000175000017500000000000013562123467020121 5ustar marcinkmarcink00000000000000mercurial_extension_utils-1.5.0/.hgtags0000664000175000017500000000231013562123406021364 0ustar marcinkmarcink0000000000000057362b22fd15a5ebde9b03d28c12e0dc2a35bf2d 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 mercurial_extension_utils-1.5.0/mercurial_extension_utils.py0000664000175000017500000015006413562047710025774 0ustar marcinkmarcink00000000000000# -*- coding: utf-8 -*- # # mercurial extension utils: library supporting mercurial extensions # writing # # 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. """ 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. 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 returned paths are always /-separated. 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 ########################################################################### def normalize_path(path): """ Converts given path to absolute, /-separated form. That means: - expanding ~ - converting to absolute - (on Windows) converting backslash to slash - dropping final slash (if any) >>> 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' 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 = pycompat.bytestr( os.path.abspath(os.path.expanduser(path))) if os.name == 'nt': reply = reply.replace(b'\\', b'/') return pycompat.bytestr(reply.rstrip(b'/')) def belongs_to_tree(child, parent): """Checks whether child lies anywhere inside parent directory tree. Child should be absolute path, parent will be tlida expanded and converted to absolute path (this convention is caused by typical use case, where repo.root is compared against some user-specified directory). On match, matching parent is returned (it matters if it was sanitized). >>> belongs_to_tree("/tmp/sub/dir", "/tmp") '/tmp' >>> belongs_to_tree("/tmp", "/tmp") '/tmp' >>> belongs_to_tree("/tmp/sub", "/tmp/sub/dir/../..") '/tmp' On mismatch None is returned. >>> belongs_to_tree("/usr/sub", "/tmp") Tilda expressions are allowed in parent specification: >>> 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' 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) :return: expanded canonicalized parent on match, None on mismatch """ parent = normalize_path(parent) child = normalize_path(child) # os.path.commonprefix is case-sensitive, on Windows this makes things crazy # 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 + b'/') else: lower_child = child.lower() lower_parent = parent.lower() matches = lower_child == lower_parent or lower_child.startswith(lower_parent + b'/') return matches and parent or None def belongs_to_tree_group(child, parents): """ Similar to belongs_to_tree, but handles list of candidate parents. >>> 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"]) Returns longest match if more than one parent matches. >>> belongs_to_tree_group("/tmp/sub/dir", ["/tmp", "/bin", "/tmp", "/tmp/sub"]) '/tmp/sub' where length is considered after expansion >>> belongs_to_tree_group("/home/lordvader/src/apps", ["~/src", "/home/lordvader"]) '/home/lordvader/src' 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 = pycompat.bytestr(b'') for parent in parents: canon_path = belongs_to_tree(child, parent) if canon_path: if len(canon_path) > len(longest_parent): longest_parent = canon_path return longest_parent and longest_parent or None class DirectoryPattern(object): """ Represents directory name pattern, like ``~/src/{suffix}``, or ``/opt/repos/(group)/{suffix}``, and let's match agains such pattern. Pattern mini-language: - tildas (``~`` and ``~user``) are expanded - ``(something)`` matches any part which does not contain directory separator - ``{something}`` greedily matches anything, including directory separators Constructed pattern can be used to match against, succesfull match extracts all marked fragments. On Windows comparison is case-insensitive, on Unix/Linux case matters. >>> 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') 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(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 = {b'{': b'.+', b'(': b'[^/\\\\]+'} def __init__(self, pattern_text, ui=None): """Parses given pattern. Doesn't raise, in case of invalid patterns creates object which does not match anything and warns. :param pattern_text: parsed pattern :param ui: (optional) mercurial ui object, if given, used for debugging """ self.pattern_text = text = normalize_path(pattern_text) self._pattern_re = None # Will stay such if we fail somewhere here # Convert pattern to regexp rgxp_text = b'^' while text: match = self._re_pattern_lead.search(text) if match: prefix, open_char, text = match.group(1), match.group(2), match.group(3) match = self._re_closure[open_char].search(text) if not match: if ui: 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 += b'(?P<' + group_name + b'>' + self._re_match_snippet[open_char] + b')' else: rgxp_text += re_escape(text) text = b'' rgxp_text += b'$' if ui: 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(ui_string("Invalid directory pattern: %s\n", pattern_text)) def is_valid(self): """Can be used to check whether object was properly constructed""" return bool(self._pattern_re) def search(self, tested_path): """ Matches given directory against the pattern. On match, returns dictionary of all named elements. On mismatch, returns None :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 (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 dict((key, pycompat.maybebytestr(value)) for key, value in dict_iteritems(match.groupdict())) # return match.groupdict() else: return None ########################################################################### # Text substitution ########################################################################### class TextFiller(object): r''' Handle filling patterns like 'some/{thing}/{else}' with values. Comparing to standard library ``.format`` offers a bit different syntax, related to actual extension-writing problems, and different ways of error handling. In simplest form, it just replaces any ``{name}`` with value of ``name``, as-is >>> 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' Values can be repeated and unnecessary keys are ignored: >>> 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' It is also possible to perform simple substitutions, for example ``{name:/=-} takes value of ``name``, replaces all slashes with minuses, and fills output with the resulting value >>> tf = TextFiller('{prefix:_=___}/goto/{suffix:/=-}') >>> tf.fill(prefix='some_prefix', suffix='some/long/suffix') 'some___prefix/goto/some-long-suffix' Substitution can be also used to replace multi-character sequences, and replacement can be empty: >>> tf = TextFiller('{prefix:/home/=}/docs/{suffix:.txt=.html}') >>> tf.fill(prefix='/home/joe', suffix='some/document.txt') 'joe/docs/some/document.html' and chained to replace more than one thing: >>> 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/' 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')) 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: >>> tf = TextFiller('{some}/text/to/{fill}') >>> tf.fill(some='prefix', badfill='suffix') and of bad pattern: >>> 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') 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(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(b'^ : ([^{}:=]+) = ([^{}:=]*) (.*) $', re.VERBOSE) def __init__(self, fill_text, ui=None): def percent_escape(val): """Escape %-s in given text by doubling them.""" return pycompat.bytestr(val.replace(b'%', b'%%')) 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 # List of substitutions, tuples: # result synthetic name, # base field name # [(from, to), (from, to), ...] list of substitutions to make self._substitutions = [] replacement = pycompat.bytestr('') synth_idx = 0 while text: match = self._re_pattern_lead.search(text) if match: replacement += percent_escape(match.group(1)) text = pycompat.bytestr(match.group(2)) match = self._re_pattern_cont.search(text) if not match: if ui: ui.warn(ui_string("Bad replacement pattern: %s\n", fill_text)) return 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(ui_string("Bad replacement pattern: %s\n", fill_text)) return 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 = b"_" + pycompat.bytestr(str(synth_idx)) self._substitutions.append((synth, name, fixups)) name = synth replacement += b'%(' + name + b')s' else: replacement += percent_escape(text) text = b'' # Final save if ui: ui.debug(ui_string("meu: Replacement %s turned into expression %s\n", fill_text, replacement)) self._replacement = replacement def is_valid(self): """Returns whether object is in correct state, or broken""" return bool(self._replacement) def fill(self, **kwargs): """Fills text with given arguments. If something is broken (missing key, broken pattern) 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 = bstr_args[src_field] for src, dest in fixups: value = value.replace(src, dest) 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, 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 >>> 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 """ section = pycompat.bytestr(section) for key, value in dict_iteritems(items): setconfig_item(ui, section, key, value) def setconfig_list(ui, section, items): """ Alternative form of setting many configuration items with one call. Here items are given as list of key,value pairs. Contrary to setconfig_dict, this guarantees ordering. >>> import mercurial.ui; ui = mercurial.ui.ui() >>> 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, "sect22", [('v', 'vvv')]) >>> ui.config(b"sect11", b'a') 8 >>> 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: setconfig_item(ui, section, key, value) def rgxp_config_items(ui, section, rgxp): r''' Yields items from given config section which match given regular expression. >>> 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(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 :param rgxp: tested regexp, should contain single (group) :return: yields pairs (group-match, value) for all matching items ''' for key, value in ui.configitems(pycompat.bytestr(section)): match = rgxp.search(pycompat.bytestr(key)) if match: yield pycompat.maybebytestr(match.group(1)), pycompat.maybebytestr(value) def rgxp_configlist_items(ui, section, rgxp): r''' Similar to rgxp_config_items, but returned values are read using ui.configlist, so returned as lists. >>> 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(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) 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 pycompat.maybebytestr(match.group(1)), ui.configlist(section, key) def rgxp_configbool_items(ui, section, rgxp): r''' Similar to rgxp_config_items, but returned values are read using ui.configbool, so returned as booleans. >>> 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(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 :param rgxp: tested regexp, should contain single (group) :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 pycompat.maybebytestr(match.group(1)), ui.configbool(section, key) def suffix_config_items(ui, section, suffix): """ Yields items from given config section which match pattern '«sth».suffix' >>> 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) ... 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 :param suffix: expected suffix (without a dot) :return: yields pairs (prefix, value) for all matching items, values are lists """ 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 def suffix_configlist_items(ui, section, suffix): """ Similar to suffix_config_items, but returned values are read using ui.configlist, so returned as lists. >>> import mercurial.ui; ui = mercurial.ui.ui() >>> 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, 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) 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 :param suffix: expected suffix (without a dot) :return: yields pairs (group-match, value-as-list) for all matching items, values are boolean """ 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 def suffix_configbool_items(ui, section, suffix): """ Similar to suffix_config_items, but returned values are read using ui.configbool, so returned as booleans. >>> 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)) b'true' True b'false' False b'one' True b'zero' False b'yes' True b'no' False >>> >>> 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 :param suffix: expected suffix (without a dot) :return: yields pairs (group-match, value) for all matching items """ 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 ########################################################################### # Monkeypatching ########################################################################### def monkeypatch_method(cls, fname=None): """ Monkey-patches some method, replacing it with another implementation. Original method is preserved on ``.orig`` attribute. >>> 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 It is also possible to use different name >>> 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 :param cls: Class being modified :param fname: Name of method being monkey-patched (if not given, name of decorated function is used) """ def decorator(func): local_fname = fname if local_fname is None: local_fname = func.__name__ setattr(func, "orig", getattr(cls, local_fname, None)) setattr(cls, local_fname, func) return func return decorator def monkeypatch_function(module, fname=None): """ Monkey-patches some function, replacing it with another implementation. Original function is preserved on ``.orig`` attribute. >>> 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) 6 >>> 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 :param module: Module being modified :param fname: Name of function being monkey-patched (if not given, name of decorated function is used) """ # In fact implementation is the same. But it is more readable # to use two names return monkeypatch_method(module, fname) ########################################################################### # Locating repositories ########################################################################### def find_repositories_below(path, check_inside=False): """Finds all Mercurial repositories in given directory tree. Works as generator, yielding full paths of all repositories found, ordered alphabetically. If initial path is itself some repository, it is included. By default function does not look for embedded repositories (if we scan from ~/src and both ~/src/somerepo and ~/src/somerepo/subrepo are repositories, only the former will be yielded). This can be changed by check_inside param :param path: Initial path :param check_inside: Shall we look for embedded repos? :return: generator of full repo paths (paths are absolute and even on Windows /-separated) """ # Impl. note: we do not use os.walk as it can be very costly # if some repo is big and deep. pending = deque([normalize_path(path)]) while pending: checked = pending.popleft() if os.path.isdir(checked + b'/.hg'): yield checked if not check_inside: continue try: names = os.listdir(checked) except OSError: # Things like permission errors (say, on lost+found) # Let's ignorre this, better to process whatever we can names = [] 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)] pending.extendleft(sorted(dir_paths, reverse=True)) ########################################################################### # Compatibility layers ########################################################################### def command(cmdtable): """ 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 (adding norepo, optionalrepo and inferrepo args which are missing there). Usage: just call ``meu.command(cmdtable)`` instead of ``cmdutil.command(cmdtable)``. For example: >>> 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]') Below is uninteresting test that it really works in various mecurials: >>> 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 """ from mercurial import cmdutil, commands import inspect try: from mercurial import registrar command = registrar.command(cmdtable) return command except (ImportError, AttributeError): command = cmdutil.command(cmdtable) spec = inspect.getargspec(command) if 'norepo' in spec[0]: # Looks like modern mercurial with correct api, keeping # it's implementation return command # Old mecurial with only name, options, synopsis in data, # patching to get full signature. This is more or less copy # of current implementation, sans docs. def parsealiases(cmd): return cmd.lstrip("^").split("|") def fixed_cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False, inferrepo=False): def decorator(func): if synopsis: cmdtable[name] = func, list(options), synopsis else: cmdtable[name] = func, list(options) if norepo: commands.norepo += ' %s' % ' '.join(parsealiases(name)) if optionalrepo: commands.optionalrepo += ' %s' % ' '.join(parsealiases(name)) if inferrepo: commands.inferrepo += ' %s' % ' '.join(parsealiases(name)) return func return decorator return fixed_cmd ########################################################################### # Demandimport workarounds and other import-related functions ########################################################################### def direct_import(module_name, blocked_modules=None): """ Imports given module, working around Mercurial demandimport (so recursively imported modules are properly imported) >>> re = direct_import("re") >>> re.__name__ 're' >>> re.search("^(.)", "Ala").group(1) 'A' Allows to block some modules from demandimport machinery, so they are not accidentally misloaded: >>> k = direct_import("dbm", ["dbm.gnu", "dbm.ndbm", "dbm.dumb"]) >>> k.__name__ 'dbm' :param module_name: name of imported module :param blocked_modules: names of modules to be blocked from demandimport (list) :return: imported module """ return direct_import_ext(module_name, blocked_modules)[0] def direct_import_ext(module_name, blocked_modules=None): """ Like direct_import, but returns info whether module was just imported, or already loaded. >>> 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 :param module_name: name of imported module :param blocked_modules: names of modules to be blocked from demandimport (list) :return: (imported module, was-it-imported-now?) """ if module_name in sys.modules: return sys.modules[module_name], False from mercurial import demandimport if blocked_modules: for blocked_module in blocked_modules: if hasattr(demandimport, 'IGNORES'): # Mercurial 4.7 introduced set demandimport.IGNORES demandimport.IGNORES.add(blocked_module) else: # Earlier it was demandimport.ignore, a list if blocked_module not in demandimport.ignore: demandimport.ignore.append(blocked_module) # Various attempts to define is_demandimport_enabled try: # Since Mercurial 2.9.1 is_demandimport_enabled = demandimport.isenabled except AttributeError: def is_demandimport_enabled(): """Checks whether demandimport is enabled at the moment""" return __import__ == demandimport._demandimport # pylint: disable=protected-access # Temporarily disable demandimport to make the need of extending # the list above less likely. if is_demandimport_enabled(): demandimport.disable() __import__(module_name) demandimport.enable() else: __import__(module_name) return sys.modules[module_name], True def disable_logging(module_name): """ Shut up warning about initialized logging which happens if some imported module logs (mercurial does not setup logging machinery) >>> disable_logging("keyring") :param module_name: Name of logger to disable """ import logging if hasattr(logging, 'NullHandler'): null_handler = logging.NullHandler() else: class NullHandler(logging.Handler): """Emergency null handler""" def handle(self, record): pass def emit(self, record): pass def createLock(self): self.lock = None null_handler = NullHandler() logging.getLogger(module_name).addHandler(null_handler) ########################################################################### # Context detection ########################################################################### def inside_tortoisehg(): """Detects tortoisehg presence - returning True if the function is called by some code which has TortoiseHg in the caller stack. This may be needed in some cases to accomodate specific TortoiseHg main loop behaviours (see enable_hook below for example).""" import inspect for item in inspect.stack(): # item has 6 elems: the frame object, the filename, the line number of the current line, the function name, a list of lines of context from the source code, and the index of the current line within that list. module = inspect.getmodule(item[0]) if module.__name__.startswith("tortoisehg."): return True return False ########################################################################### # Hook support ########################################################################### def enable_hook(ui, hook_name, hook_function): """ Enables given (dynamic) hook. At the moment this is a simple wrapper for ui.setconfig, with the only exception: it checks whether the same function is already configured by name, and if so, doesn't do anything (so it may be used for *dynamically install hook unless it is already statically enabled* cases). :param hook_name: string like "pre-tag.my_function" (hook placement and symbolic name) :param hook_function: proper callable. To handle presence detection, it should be top-level module function (not method, 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 = 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(ui_string("meu: Hook already statically installed, skipping %s: %s\n", hook_name, hook_function_name)) return if value == hook_function: ui.debug(ui_string("meu: Hook already dynamically installed, skipping %s: %s\n", hook_name, hook_function_name)) return ui.debug(ui_string("meu: Enabling dynamic hook %s: %s\n", hook_name, hook_function_name)) # Standard way of hook enabling 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()) mercurial_extension_utils-1.5.0/mercurial_extension_utils_loader.py0000664000175000017500000000030413042373652027311 0ustar marcinkmarcink00000000000000 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) mercurial_extension_utils-1.5.0/drone.sh0000664000175000017500000000056013042373652021561 0ustar marcinkmarcink00000000000000# 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 mercurial_extension_utils-1.5.0/tox.ini0000664000175000017500000000303313562044650021427 0ustar marcinkmarcink00000000000000 [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 mercurial_extension_utils-1.5.0/.hgignore0000664000175000017500000000023513502644306021716 0ustar marcinkmarcink00000000000000syntax: regexp \.pyc$ \.pyo$ ~$ ^\.\# ^\.(project|pydevproject)$ ^\.settings/ ^build/ ^dist/ \.egg-info/ ^README\.html ^\.tox/ syntax: glob .pytest_cache mercurial_extension_utils-1.5.0/setup.py0000664000175000017500000000245713562123406021634 0ustar marcinkmarcink00000000000000# pylint:disable=missing-docstring from setuptools import setup, find_packages VERSION = '1.5.0' LONG_DESCRIPTION = open("README.txt").read() INSTALL_REQUIRES = [] setup( name="mercurial_extension_utils", version=VERSION, author='Marcin Kasperski', author_email='Marcin.Kasperski@mekk.waw.pl', url='http://bitbucket.org/Mekk/mercurial-extension_utils', description='Mercurial Extension Utils', long_description=LONG_DESCRIPTION, license='BSD', py_modules=['mercurial_extension_utils'], keywords="mercurial hg extension", install_requires=INSTALL_REQUIRES, # Giving up on python setup.py test, discovery too problematic # tests_require=TEST_REQUIRES, # test_suite='tests', classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Console', 'Intended Audience :: Developers', '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', # 'Topic :: Software Development :: Version Control :: Mercurial', ], zip_safe=True) mercurial_extension_utils-1.5.0/setup.cfg0000664000175000017500000000004613562123467021742 0ustar marcinkmarcink00000000000000[egg_info] tag_build = tag_date = 0 mercurial_extension_utils-1.5.0/tests/0000775000175000017500000000000013562123467021263 5ustar marcinkmarcink00000000000000mercurial_extension_utils-1.5.0/tests/manual_find_repositories_below.py0000664000175000017500000000027513042373652030111 0ustar marcinkmarcink00000000000000 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 mercurial_extension_utils-1.5.0/tests/py2_doctests_mercurial_extension_utils.py0000644000175000017500000003335713562047751031650 0ustar marcinkmarcink00000000000000# -*- 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") ''' mercurial_extension_utils-1.5.0/tests/py2win_doctests_mercurial_extension_utils.py0000664000175000017500000002574513042373652032365 0ustar marcinkmarcink00000000000000# -*- 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 ''' mercurial_extension_utils-1.5.0/tests/test_find_repositories_below.py0000664000175000017500000000663013561560553027620 0ustar marcinkmarcink00000000000000 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() mercurial_extension_utils-1.5.0/tests/test_doctest.py0000664000175000017500000000471613467767471024366 0ustar marcinkmarcink00000000000000# -*- 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() mercurial_extension_utils-1.5.0/README-TESTING.txt0000664000175000017500000000031113042373652022721 0ustar marcinkmarcink00000000000000Various 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) mercurial_extension_utils-1.5.0/HISTORY.txt0000664000175000017500000000655413562054174022033 0ustar marcinkmarcink000000000000001.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`` mercurial_extension_utils-1.5.0/PKG-INFO0000664000175000017500000002114213562123467021216 0ustar marcinkmarcink00000000000000Metadata-Version: 1.1 Name: mercurial_extension_utils Version: 1.5.0 Summary: Mercurial Extension Utils Home-page: http://bitbucket.org/Mekk/mercurial-extension_utils Author: Marcin Kasperski Author-email: Marcin.Kasperski@mekk.waw.pl License: BSD Description: .. -*- mode: rst -*- ==================================== Mercurial Extension Utils ==================================== This module contains group of reusable functions, which I found useful while writing Mercurial extensions. .. contents:: :local: :depth: 2 .. sectnum:: For Mercurial users =========================== This module is of no direct use to you, but some extensions you may wish to use (for example `Dynamic Username`_ or `Path Pattern`_) need it to work. In normal cases it should be installed automatically with the actual extension, read below for more tricky cases (in particular, for information about installation on Windows). .. note:: This document uses `Dynamic Username`_ in examples, but the same method should work for any other extension which requires ``mercurial_extension_utils``. Installing on Linux/Unix ------------------------------------------------------- In typical case ``mercurial_extension_utils`` should be installed automatically, without requiring your attention, by commands like ``pip install mercurial_dynamic_username``. If for some reason it did not work, just install from PyPi with:: pip install --user mercurial_extension_utils or system-wide with:: sudo pip install mercurial_extension_utils If you don't have ``pip``, try:: sudo easy_install mercurial_extension_utils Upgrade to newer version using the same commands with ``--upgrade`` option added, for example:: pip install --user --upgrade mercurial_extension_utils If you miss both ``pip``, and ``easy_install``, follow recipe from `Installing for development`_ section. Installing on Windows ------------------------------------------------------- Windows Mercurial distributions (including most popular - and well deserving that - TortoiseHg_) are not using system Python (in fact, one may use Mercurial without installing Python at all), and installing into bundled Python path is uneasy. To remedy that, extensions utilizing this module handle additional methods of locating it. The following two methods of installation are available: 1. If you have some Python installed, you may still install both this module, and extension using it, from PyPi. For example:: pip install mercurial_extension_utils pip install mercurial_dynamic_username This will not (yet) make the module visible to your Mercurial, but you will get all the necessary files installed on your computer. Then activate the actual extension in charge by specifying it's path, for example by writing in your ``Mercurial.ini``:: [extensions] mercurial_dynamic_username = C:/Python27/Lib/site-packages/mercurial_dynamic_username.py .. note:: This works because ``mercurial_dynamic_username.py`` checks for ``mercurial_extension_utils.py`` saved in the same directory (and ``pip`` installs both modules in the same place). You can get the same effect by manually downloading all files into the same directory (using ``pip`` is more handy as it tracks dependencies and supports upgrades). Upgrade with ``pip`` by adding ``--upgrade`` to it's options. 2. If you don't have any Python, clone both the extension(s) repository and ``mercurial_extension_utils``` and put them in the same place, for example:: cd c:\MercurialPlugins hg clone https://bitbucket.org/Mekk/mercurial-extension_utils/ hg clone https://bitbucket.org/Mekk/mercurial-dynamic_username/ Update repositories to newest tagged versions (untagged versions may be unstable or not working). Activate the actual extension by specifying it's path, for example by writing in ``Mercurial.ini``:: [extensions] mercurial_dynamic_username = C:/MercurialPlugins/mercurial-dynamic_username/mercurial_dynamic_username.py .. note:: Directory names matter. This works because ``mercurial_dynamic_username.py`` checks for ``mercurial_extension_utils.py`` in ``../mercurial_extension_utils`` and ``../extension_utils`` (relative to it's own location). To upgrade to new version, simply pull and update to newer tag. Installing for development ------------------------------------------------------- On Windows use second variant from the previous chapter (clone and activate by path). On Linux/Unix do the same. Clone all the necessary repositories, for example:: cd ~/sources/ hg clone https://bitbucket.org/Mekk/mercurial-extension_utils/ hg clone https://bitbucket.org/Mekk/mercurial-dynamic_username/ then either make it visible to Python by repeating in every repo:: pip install --user -e . or activate the extension(s) by full path, by writing in ``~/.hgrc`` something like:: [extensions] mercurial_dynamic_username = ~/sources/mercurial-dynamic_username/mercurial_dynamic_username.py For Mercurial extensions developers ==================================== Contained functions are mostly tiny utilities related to configuration processing or location matching. They either extend Mercurial APIs a bit (like function to iterate config items which match regexp), or support tasks which aren't strictly Mercurial related, but happen repeatably during extension writing (like matching repository root against set of paths defined in configuration). See docstrings for details. History ================================================== See `HISTORY.txt`_ Development, bug reports, enhancement suggestions =================================================== Development is tracked on BitBucket, see http://bitbucket.org/Mekk/mercurial-extension_utils/ Use BitBucket issue tracker for bug reports and enhancement suggestions. Additional notes ==================================================== Check also `Mercurial extensions I wrote`_. .. _Mercurial extensions I wrote: http://mekk.bitbucket.io/mercurial.html .. _Mercurial: http://mercurial.selenic.com .. _Dynamic Username: http://bitbucket.org/Mekk/mercurial-dynamic_username/ .. _Path Pattern: http://bitbucket.org/Mekk/mercurial-path_pattern/ .. _HISTORY.txt: http://bitbucket.org/Mekk/mercurial-extension_utils/src/tip/HISTORY.txt .. _TortoiseHg: http://tortoisehg.bitbucket.org/ .. |drone-badge| image:: https://drone.io/bitbucket.org/Mekk/mercurial-extension_utils/status.png :target: https://drone.io/bitbucket.org/Mekk/mercurial-extension_utils/latest :align: middle Keywords: mercurial hg extension Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Intended Audience :: Developers 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 mercurial_extension_utils-1.5.0/TODO.txt0000664000175000017500000000013513042373652021422 0ustar marcinkmarcink00000000000000 → Finishing mercurial_extension_utils_loader and publishing it as install alternative. mercurial_extension_utils-1.5.0/mercurial_extension_utils.egg-info/0000775000175000017500000000000013562123467027112 5ustar marcinkmarcink00000000000000mercurial_extension_utils-1.5.0/mercurial_extension_utils.egg-info/dependency_links.txt0000664000175000017500000000000113562123466033157 0ustar marcinkmarcink00000000000000 mercurial_extension_utils-1.5.0/mercurial_extension_utils.egg-info/SOURCES.txt0000664000175000017500000000112613562123467030776 0ustar marcinkmarcink00000000000000.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 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.pymercurial_extension_utils-1.5.0/mercurial_extension_utils.egg-info/zip-safe0000664000175000017500000000000113042374177030541 0ustar marcinkmarcink00000000000000 mercurial_extension_utils-1.5.0/mercurial_extension_utils.egg-info/top_level.txt0000664000175000017500000000003213562123466031636 0ustar marcinkmarcink00000000000000mercurial_extension_utils mercurial_extension_utils-1.5.0/mercurial_extension_utils.egg-info/PKG-INFO0000664000175000017500000002114213562123466030206 0ustar marcinkmarcink00000000000000Metadata-Version: 1.1 Name: mercurial-extension-utils Version: 1.5.0 Summary: Mercurial Extension Utils Home-page: http://bitbucket.org/Mekk/mercurial-extension_utils Author: Marcin Kasperski Author-email: Marcin.Kasperski@mekk.waw.pl License: BSD Description: .. -*- mode: rst -*- ==================================== Mercurial Extension Utils ==================================== This module contains group of reusable functions, which I found useful while writing Mercurial extensions. .. contents:: :local: :depth: 2 .. sectnum:: For Mercurial users =========================== This module is of no direct use to you, but some extensions you may wish to use (for example `Dynamic Username`_ or `Path Pattern`_) need it to work. In normal cases it should be installed automatically with the actual extension, read below for more tricky cases (in particular, for information about installation on Windows). .. note:: This document uses `Dynamic Username`_ in examples, but the same method should work for any other extension which requires ``mercurial_extension_utils``. Installing on Linux/Unix ------------------------------------------------------- In typical case ``mercurial_extension_utils`` should be installed automatically, without requiring your attention, by commands like ``pip install mercurial_dynamic_username``. If for some reason it did not work, just install from PyPi with:: pip install --user mercurial_extension_utils or system-wide with:: sudo pip install mercurial_extension_utils If you don't have ``pip``, try:: sudo easy_install mercurial_extension_utils Upgrade to newer version using the same commands with ``--upgrade`` option added, for example:: pip install --user --upgrade mercurial_extension_utils If you miss both ``pip``, and ``easy_install``, follow recipe from `Installing for development`_ section. Installing on Windows ------------------------------------------------------- Windows Mercurial distributions (including most popular - and well deserving that - TortoiseHg_) are not using system Python (in fact, one may use Mercurial without installing Python at all), and installing into bundled Python path is uneasy. To remedy that, extensions utilizing this module handle additional methods of locating it. The following two methods of installation are available: 1. If you have some Python installed, you may still install both this module, and extension using it, from PyPi. For example:: pip install mercurial_extension_utils pip install mercurial_dynamic_username This will not (yet) make the module visible to your Mercurial, but you will get all the necessary files installed on your computer. Then activate the actual extension in charge by specifying it's path, for example by writing in your ``Mercurial.ini``:: [extensions] mercurial_dynamic_username = C:/Python27/Lib/site-packages/mercurial_dynamic_username.py .. note:: This works because ``mercurial_dynamic_username.py`` checks for ``mercurial_extension_utils.py`` saved in the same directory (and ``pip`` installs both modules in the same place). You can get the same effect by manually downloading all files into the same directory (using ``pip`` is more handy as it tracks dependencies and supports upgrades). Upgrade with ``pip`` by adding ``--upgrade`` to it's options. 2. If you don't have any Python, clone both the extension(s) repository and ``mercurial_extension_utils``` and put them in the same place, for example:: cd c:\MercurialPlugins hg clone https://bitbucket.org/Mekk/mercurial-extension_utils/ hg clone https://bitbucket.org/Mekk/mercurial-dynamic_username/ Update repositories to newest tagged versions (untagged versions may be unstable or not working). Activate the actual extension by specifying it's path, for example by writing in ``Mercurial.ini``:: [extensions] mercurial_dynamic_username = C:/MercurialPlugins/mercurial-dynamic_username/mercurial_dynamic_username.py .. note:: Directory names matter. This works because ``mercurial_dynamic_username.py`` checks for ``mercurial_extension_utils.py`` in ``../mercurial_extension_utils`` and ``../extension_utils`` (relative to it's own location). To upgrade to new version, simply pull and update to newer tag. Installing for development ------------------------------------------------------- On Windows use second variant from the previous chapter (clone and activate by path). On Linux/Unix do the same. Clone all the necessary repositories, for example:: cd ~/sources/ hg clone https://bitbucket.org/Mekk/mercurial-extension_utils/ hg clone https://bitbucket.org/Mekk/mercurial-dynamic_username/ then either make it visible to Python by repeating in every repo:: pip install --user -e . or activate the extension(s) by full path, by writing in ``~/.hgrc`` something like:: [extensions] mercurial_dynamic_username = ~/sources/mercurial-dynamic_username/mercurial_dynamic_username.py For Mercurial extensions developers ==================================== Contained functions are mostly tiny utilities related to configuration processing or location matching. They either extend Mercurial APIs a bit (like function to iterate config items which match regexp), or support tasks which aren't strictly Mercurial related, but happen repeatably during extension writing (like matching repository root against set of paths defined in configuration). See docstrings for details. History ================================================== See `HISTORY.txt`_ Development, bug reports, enhancement suggestions =================================================== Development is tracked on BitBucket, see http://bitbucket.org/Mekk/mercurial-extension_utils/ Use BitBucket issue tracker for bug reports and enhancement suggestions. Additional notes ==================================================== Check also `Mercurial extensions I wrote`_. .. _Mercurial extensions I wrote: http://mekk.bitbucket.io/mercurial.html .. _Mercurial: http://mercurial.selenic.com .. _Dynamic Username: http://bitbucket.org/Mekk/mercurial-dynamic_username/ .. _Path Pattern: http://bitbucket.org/Mekk/mercurial-path_pattern/ .. _HISTORY.txt: http://bitbucket.org/Mekk/mercurial-extension_utils/src/tip/HISTORY.txt .. _TortoiseHg: http://tortoisehg.bitbucket.org/ .. |drone-badge| image:: https://drone.io/bitbucket.org/Mekk/mercurial-extension_utils/status.png :target: https://drone.io/bitbucket.org/Mekk/mercurial-extension_utils/latest :align: middle Keywords: mercurial hg extension Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Intended Audience :: Developers 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 mercurial_extension_utils-1.5.0/README.txt0000644000175000017500000001451213302111730021576 0ustar marcinkmarcink00000000000000.. -*- mode: rst -*- ==================================== Mercurial Extension Utils ==================================== This module contains group of reusable functions, which I found useful while writing Mercurial extensions. .. contents:: :local: :depth: 2 .. sectnum:: For Mercurial users =========================== This module is of no direct use to you, but some extensions you may wish to use (for example `Dynamic Username`_ or `Path Pattern`_) need it to work. In normal cases it should be installed automatically with the actual extension, read below for more tricky cases (in particular, for information about installation on Windows). .. note:: This document uses `Dynamic Username`_ in examples, but the same method should work for any other extension which requires ``mercurial_extension_utils``. Installing on Linux/Unix ------------------------------------------------------- In typical case ``mercurial_extension_utils`` should be installed automatically, without requiring your attention, by commands like ``pip install mercurial_dynamic_username``. If for some reason it did not work, just install from PyPi with:: pip install --user mercurial_extension_utils or system-wide with:: sudo pip install mercurial_extension_utils If you don't have ``pip``, try:: sudo easy_install mercurial_extension_utils Upgrade to newer version using the same commands with ``--upgrade`` option added, for example:: pip install --user --upgrade mercurial_extension_utils If you miss both ``pip``, and ``easy_install``, follow recipe from `Installing for development`_ section. Installing on Windows ------------------------------------------------------- Windows Mercurial distributions (including most popular - and well deserving that - TortoiseHg_) are not using system Python (in fact, one may use Mercurial without installing Python at all), and installing into bundled Python path is uneasy. To remedy that, extensions utilizing this module handle additional methods of locating it. The following two methods of installation are available: 1. If you have some Python installed, you may still install both this module, and extension using it, from PyPi. For example:: pip install mercurial_extension_utils pip install mercurial_dynamic_username This will not (yet) make the module visible to your Mercurial, but you will get all the necessary files installed on your computer. Then activate the actual extension in charge by specifying it's path, for example by writing in your ``Mercurial.ini``:: [extensions] mercurial_dynamic_username = C:/Python27/Lib/site-packages/mercurial_dynamic_username.py .. note:: This works because ``mercurial_dynamic_username.py`` checks for ``mercurial_extension_utils.py`` saved in the same directory (and ``pip`` installs both modules in the same place). You can get the same effect by manually downloading all files into the same directory (using ``pip`` is more handy as it tracks dependencies and supports upgrades). Upgrade with ``pip`` by adding ``--upgrade`` to it's options. 2. If you don't have any Python, clone both the extension(s) repository and ``mercurial_extension_utils``` and put them in the same place, for example:: cd c:\MercurialPlugins hg clone https://bitbucket.org/Mekk/mercurial-extension_utils/ hg clone https://bitbucket.org/Mekk/mercurial-dynamic_username/ Update repositories to newest tagged versions (untagged versions may be unstable or not working). Activate the actual extension by specifying it's path, for example by writing in ``Mercurial.ini``:: [extensions] mercurial_dynamic_username = C:/MercurialPlugins/mercurial-dynamic_username/mercurial_dynamic_username.py .. note:: Directory names matter. This works because ``mercurial_dynamic_username.py`` checks for ``mercurial_extension_utils.py`` in ``../mercurial_extension_utils`` and ``../extension_utils`` (relative to it's own location). To upgrade to new version, simply pull and update to newer tag. Installing for development ------------------------------------------------------- On Windows use second variant from the previous chapter (clone and activate by path). On Linux/Unix do the same. Clone all the necessary repositories, for example:: cd ~/sources/ hg clone https://bitbucket.org/Mekk/mercurial-extension_utils/ hg clone https://bitbucket.org/Mekk/mercurial-dynamic_username/ then either make it visible to Python by repeating in every repo:: pip install --user -e . or activate the extension(s) by full path, by writing in ``~/.hgrc`` something like:: [extensions] mercurial_dynamic_username = ~/sources/mercurial-dynamic_username/mercurial_dynamic_username.py For Mercurial extensions developers ==================================== Contained functions are mostly tiny utilities related to configuration processing or location matching. They either extend Mercurial APIs a bit (like function to iterate config items which match regexp), or support tasks which aren't strictly Mercurial related, but happen repeatably during extension writing (like matching repository root against set of paths defined in configuration). See docstrings for details. History ================================================== See `HISTORY.txt`_ Development, bug reports, enhancement suggestions =================================================== Development is tracked on BitBucket, see http://bitbucket.org/Mekk/mercurial-extension_utils/ Use BitBucket issue tracker for bug reports and enhancement suggestions. Additional notes ==================================================== Check also `Mercurial extensions I wrote`_. .. _Mercurial extensions I wrote: http://mekk.bitbucket.io/mercurial.html .. _Mercurial: http://mercurial.selenic.com .. _Dynamic Username: http://bitbucket.org/Mekk/mercurial-dynamic_username/ .. _Path Pattern: http://bitbucket.org/Mekk/mercurial-path_pattern/ .. _HISTORY.txt: http://bitbucket.org/Mekk/mercurial-extension_utils/src/tip/HISTORY.txt .. _TortoiseHg: http://tortoisehg.bitbucket.org/ .. |drone-badge| image:: https://drone.io/bitbucket.org/Mekk/mercurial-extension_utils/status.png :target: https://drone.io/bitbucket.org/Mekk/mercurial-extension_utils/latest :align: middle