extras: re-use Projection from jaraco.collections
authorJason R. Coombs <jaraco@jaraco.com>
Tue, 21 Mar 2023 20:47:30 -0400
changeset 50365 82e5a9b1ef1e
parent 50364 e8f1e0e295bb
child 50370 b47a9316050a
extras: re-use Projection from jaraco.collections
contrib/import-checker.py
hgext/rebase.py
mercurial/thirdparty/jaraco/__init__.py
mercurial/thirdparty/jaraco/collections.py
setup.py
--- a/contrib/import-checker.py	Tue Mar 21 17:21:45 2023 -0400
+++ b/contrib/import-checker.py	Tue Mar 21 20:47:30 2023 -0400
@@ -44,6 +44,7 @@
     # third-party imports should be directly imported
     'mercurial.thirdparty',
     'mercurial.thirdparty.attr',
+    'mercurial.thirdparty.jaraco.collections',
     'mercurial.thirdparty.zope',
     'mercurial.thirdparty.zope.interface',
     'typing',
--- a/hgext/rebase.py	Tue Mar 21 17:21:45 2023 -0400
+++ b/hgext/rebase.py	Tue Mar 21 20:47:30 2023 -0400
@@ -24,6 +24,7 @@
     wdirrev,
 )
 from mercurial.pycompat import open
+from mercurial.thirdparty.jaraco.collections import Projection
 from mercurial import (
     bookmarks,
     cmdutil,
@@ -52,6 +53,7 @@
     util,
 )
 
+
 # The following constants are used throughout the rebase module. The ordering of
 # their values must be maintained.
 
@@ -93,19 +95,8 @@
     yield b'intermediate-source'
 
 
-def _project(orig, names):
-    """Project a subset of names from orig."""
-    names_saved = tuple(names)
-    values = (orig.get(name, None) for name in names_saved)
-    return {
-        name: value
-        for name, value in zip(names_saved, values)
-        if value is not None
-    }
-
-
 def _save_extras(ctx, extra):
-    extra.update(_project(ctx.extra(), retained_extras()))
+    extra.update(Projection(retained_extras(), ctx.extra()))
 
 
 def _savebranch(ctx, extra):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/thirdparty/jaraco/collections.py	Tue Mar 21 20:47:30 2023 -0400
@@ -0,0 +1,56 @@
+# adapted from jaraco.collections 3.9
+
+import collections
+
+
+class Projection(collections.abc.Mapping):
+    """
+    Project a set of keys over a mapping
+
+    >>> sample = {'a': 1, 'b': 2, 'c': 3}
+    >>> prj = Projection(['a', 'c', 'd'], sample)
+    >>> prj == {'a': 1, 'c': 3}
+    True
+
+    Keys should only appear if they were specified and exist in the space.
+
+    >>> sorted(list(prj.keys()))
+    ['a', 'c']
+
+    Attempting to access a key not in the projection
+    results in a KeyError.
+
+    >>> prj['b']
+    Traceback (most recent call last):
+    ...
+    KeyError: 'b'
+
+    Use the projection to update another dict.
+
+    >>> target = {'a': 2, 'b': 2}
+    >>> target.update(prj)
+    >>> target == {'a': 1, 'b': 2, 'c': 3}
+    True
+
+    Also note that Projection keeps a reference to the original dict, so
+    if you modify the original dict, that could modify the Projection.
+
+    >>> del sample['a']
+    >>> dict(prj)
+    {'c': 3}
+    """
+
+    def __init__(self, keys, space):
+        self._keys = tuple(keys)
+        self._space = space
+
+    def __getitem__(self, key):
+        if key not in self._keys:
+            raise KeyError(key)
+        return self._space[key]
+
+    def __iter__(self):
+        return iter(set(self._keys).intersection(self._space))
+
+    def __len__(self):
+        return len(tuple(iter(self)))
--- a/setup.py	Tue Mar 21 17:21:45 2023 -0400
+++ b/setup.py	Tue Mar 21 20:47:30 2023 -0400
@@ -1302,6 +1302,7 @@
     'mercurial.templates',
     'mercurial.thirdparty',
     'mercurial.thirdparty.attr',
+    'mercurial.thirdparty.jaraco',
     'mercurial.thirdparty.zope',
     'mercurial.thirdparty.zope.interface',
     'mercurial.upgrade_utils',