diff --git a/src/filesystem/Filesystem.php b/src/filesystem/Filesystem.php --- a/src/filesystem/Filesystem.php +++ b/src/filesystem/Filesystem.php @@ -853,6 +853,48 @@ } /** + * Retrieve the relative location of the specified path. + * + * This function returns the relative path from the current path to a given + * absolute path. + * + * @param string An absolute path. + * @return string The relative path between the current path and the + * specified path. + */ + public static function relativePath($path, $root) { + // Some compatibility fixes for Windows paths + $from = is_dir($root) ? rtrim($root, '\/').'/' : $root; + $to = is_dir($path) ? rtrim($path, '\/').'/' : $path; + $from = str_replace('\\', '/', $from); + $to = str_replace('\\', '/', $to); + + $from = explode('/', $from); + $to = explode('/', $to); + + $rel_path = $to; + + foreach ($from as $depth => $dir) { + // Find first non-matching directory. + if ($dir === $to[$depth]) { + // Ignore this directory. + array_shift($rel_path); + } else { + // Get number of remaining directories to $from. + $remaining = count($from) - $depth; + if ($remaining > 1) { + // Add traversals up to first matching directory. + $pad_length = (count($rel_path) + $remaining - 1) * -1; + $rel_path = array_pad($rel_path, $pad_length, '..'); + break; + } + } + } + + return implode('/', $rel_path); + } + + /** * Test whether a path is descendant from some root path after resolving all * symlinks and removing artifacts. Both paths must exists for the relation * to obtain. A path is always a descendant of itself as long as it exists. diff --git a/src/filesystem/__tests__/FilesystemTestCase.php b/src/filesystem/__tests__/FilesystemTestCase.php --- a/src/filesystem/__tests__/FilesystemTestCase.php +++ b/src/filesystem/__tests__/FilesystemTestCase.php @@ -163,4 +163,32 @@ } } + public function testRelativePath() { + $test_cases = array( + array( + '/foo/bar/baz', + '/', + 'foo/bar/baz', + ), + array( + '/foo/bar/baz', + '/foo', + 'bar/baz', + ), + array( + '/foo/foobar', + '/foo/bar/baz', + '../foobar', + ), + ); + + foreach ($test_cases as $test_case) { + list($path, $root, $expected) = $test_case; + + $this->assertEqual( + $expected, + Filesystem::relativePath($path, $root)); + } + } + }