From 4bd98e124335d15679413b0a51c6bedab35eb8ac Mon Sep 17 00:00:00 2001 From: Murray Stevenson <50844517+murraystevenson@users.noreply.github.com> Date: Mon, 10 Aug 2026 16:24:41 -0700 Subject: [PATCH] PathMatcher : Add `in` and `containing` These provide PathMatcher with equivalent behaviour to Gaffer's `in` and `containing` set expression operators. This implementation of `containing()` is based on `PathMatcher::intersection()`'s existing `find()`-based approach, rather than Gaffer::SetExpressionAlgo's `.match() & ( PathMatcher::ExactMatch | PathMatcher::DescendantMatch )`. --- Changes | 3 ++ include/IECore/PathMatcher.h | 6 ++++ src/IECore/PathMatcher.cpp | 29 ++++++++++++++++ src/IECorePython/PathMatcherBinding.cpp | 2 ++ test/IECore/PathMatcherTest.py | 46 +++++++++++++++++++++++++ 5 files changed, 86 insertions(+) diff --git a/Changes b/Changes index 602c25159c..69d171b63a 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,10 @@ 10.7.x.x (relative to 10.7.0.0) ======== +Improvements +------------ +- PathMatcher : Added `in()` and `containing()` methods. 10.7.0.0 ======== diff --git a/include/IECore/PathMatcher.h b/include/IECore/PathMatcher.h index 5f039dd59e..d5006ac237 100644 --- a/include/IECore/PathMatcher.h +++ b/include/IECore/PathMatcher.h @@ -100,6 +100,12 @@ class IECORE_API PathMatcher /// Returns a PathMatcher for objects matching both this and the given PathMatcher PathMatcher intersection( const PathMatcher &paths ) const; + /// Returns a PathMatcher with only the paths in this PathMatcher that match or are + /// descendants of paths in the given PathMatcher. + PathMatcher in( const PathMatcher &paths ) const; + /// Returns a PathMatcher with only the paths in this PathMatcher that match or are + /// ancestors of paths in the given PathMatcher. + PathMatcher containing( const PathMatcher &paths ) const; /// Removes the specified path and all descendant paths. /// Returns true if something was removed, false otherwise. diff --git a/src/IECore/PathMatcher.cpp b/src/IECore/PathMatcher.cpp index 045bc6d25f..60a73ff1f4 100644 --- a/src/IECore/PathMatcher.cpp +++ b/src/IECore/PathMatcher.cpp @@ -451,6 +451,35 @@ PathMatcher PathMatcher::intersection( const PathMatcher &paths ) const return result; } +PathMatcher PathMatcher::in( const PathMatcher &paths ) const +{ + PathMatcher result = PathMatcher(); + for( Iterator it = paths.begin(); it != paths.end(); ++it ) + { + result.addPaths( (*this).subTree( *it ), *it ); + it.prune(); + } + return result; +} + +PathMatcher PathMatcher::containing( const PathMatcher &paths ) const +{ + PathMatcher result = PathMatcher(); + for( Iterator it = (*this).begin(); it != (*this).end(); ++it ) + { + RawIterator rit = paths.find( *it ); + if( rit == paths.end() ) + { + it.prune(); + } + else + { + result.addPath( *it ); + } + } + return result; +} + bool PathMatcher::prune( const std::string &path ) { if( path.empty() ) diff --git a/src/IECorePython/PathMatcherBinding.cpp b/src/IECorePython/PathMatcherBinding.cpp index 129433d2cb..37c5ad3bcb 100644 --- a/src/IECorePython/PathMatcherBinding.cpp +++ b/src/IECorePython/PathMatcherBinding.cpp @@ -352,6 +352,8 @@ void IECorePython::bindPathMatcher() .def( "addPaths", (bool (PathMatcher::*)( const PathMatcher &, const std::vector & ))&PathMatcher::addPaths ) .def( "removePaths", &PathMatcher::removePaths ) .def( "intersection", (PathMatcher ( PathMatcher::*)( const PathMatcher & ) const)&PathMatcher::intersection ) + .def( "in_", (PathMatcher ( PathMatcher::*)( const PathMatcher & ) const)&PathMatcher::in ) + .def( "containing", (PathMatcher ( PathMatcher::*)( const PathMatcher & ) const)&PathMatcher::containing ) .def( "prune", (bool (PathMatcher::*)( const std::vector & ))&PathMatcher::prune ) .def( "prune", (bool (PathMatcher::*)( const std::string & ))&PathMatcher::prune ) .def( "subTree", (PathMatcher ( PathMatcher::*)( const std::vector & ) const)&PathMatcher::subTree ) diff --git a/test/IECore/PathMatcherTest.py b/test/IECore/PathMatcherTest.py index 90a0f16893..d87ec82826 100644 --- a/test/IECore/PathMatcherTest.py +++ b/test/IECore/PathMatcherTest.py @@ -959,6 +959,52 @@ def testIntersection( self ) : self.assertEqual( m3.paths(), [ "/a/b/c/d/myTest" ] ) + def testIn( self ) : + + m1 = IECore.PathMatcher( [ "/a/b/c/d/myTest", "/a/b/c/myTest", "/a/b/c", "/a/b/c/d", "/a/b/d" ] ) + m2 = IECore.PathMatcher( [ "/a/b/c/d" ] ) + + self.assertEqual( m1.in_( m2 ), IECore.PathMatcher( [ "/a/b/c/d/myTest", "/a/b/c/d" ] ) ) + + m2 = IECore.PathMatcher( [ "/a/b/c" ] ) + self.assertEqual( m1.in_( m2 ), IECore.PathMatcher( [ "/a/b/c/d/myTest", "/a/b/c/myTest", "/a/b/c", "/a/b/c/d" ] ) ) + + m2 = IECore.PathMatcher( [ "/a/b/d" ] ) + self.assertEqual( m1.in_( m2 ), IECore.PathMatcher( [ "/a/b/d" ] ) ) + + m2 = IECore.PathMatcher( [ "/a/b/e" ] ) + self.assertEqual( m1.in_( m2 ), IECore.PathMatcher() ) + + m2 = IECore.PathMatcher( [ "/a" ] ) + self.assertEqual( m1.in_( m2 ), m1 ) + + self.assertEqual( m1.in_( m1 ), m1 ) + + self.assertEqual( m1.in_( IECore.PathMatcher() ), IECore.PathMatcher() ) + + def testContaining( self ) : + + m1 = IECore.PathMatcher( [ "/a/b/c/d/myTest", "/a/b/c/myTest", "/a/b/c", "/a/b/d" ] ) + m2 = IECore.PathMatcher( [ "/a/b/c/d/myTest" ] ) + + self.assertEqual( m1.containing( m2 ), IECore.PathMatcher( [ "/a/b/c", "/a/b/c/d/myTest" ] ) ) + + m2 = IECore.PathMatcher( [ "/a/b/c/d" ] ) + self.assertEqual( m1.containing( m2 ), IECore.PathMatcher( [ "/a/b/c" ] ) ) + + m2 = IECore.PathMatcher( [ "/a/b/d" ] ) + self.assertEqual( m1.containing( m2 ), IECore.PathMatcher( [ "/a/b/d" ] ) ) + + m2 = IECore.PathMatcher( [ "/a/b/e" ] ) + self.assertEqual( m1.containing( m2 ), IECore.PathMatcher() ) + + m2 = IECore.PathMatcher( [ "/a" ] ) + self.assertEqual( m1.containing( m2 ), IECore.PathMatcher() ) + + self.assertEqual( m1.containing( m1 ), m1 ) + + self.assertEqual( m1.containing( IECore.PathMatcher() ), IECore.PathMatcher() ) + def testSize( self ) : m = IECore.PathMatcher()