xdevs23
xdevs23

Reputation: 3994

How to check if a path is inside another path using Okio?

I am using Okio in Kotlin/Native and I would like to check if one path is inside another path.

Although there is a equal/greater than/less than operator, it looks like it only compares the length.

Example:

"/a/b/c/d".toPath().startsWith("/a/b/c".toPath()) // should return true
"/a/b/d/d".toPath().startsWith("/a/b/c".toPath()) // should return false

But startsWith does not exist.

Kotlin/JVM supports this via Java: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/java.io.-file/starts-with.html

Upvotes: 0

Views: 203

Answers (2)

xdevs23
xdevs23

Reputation: 3994

I have created this extension function which implements startsWith as described in the question:

fun Path.startsWith(other: Path) = normalized().run {
    other.normalized().let { normalizedOther ->
        normalizedOther.segments.size <= segments.size &&
                segments
                    .slice(0 until normalizedOther.segments.size)
                    .filterIndexed { index, s -> normalizedOther.segments[index] != s }
                    .isEmpty()
    }
}

It first checks if the other path has more segments (or components) than this path which would already mean they don't match since /a/b/c can never start with /a/b/c/d (or even /1/2/3/4).

If the segment count of other is the same or less, it proceeds with slicing this into as many segments as other has so that any sub-entries are ignored.

Then, it filters the sliced segments of this that don't match by using the same index for accessing the segments of other.

Now we have a list of segments that don't match on the same index. By checking if the list isEmpty(), we now have the conclusion of whether this startsWith other (you can turn this into an infix if you want.).

Passing test:

import okio.Path.Companion.toPath
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue

class PathsTests {

    @Test
    fun testStartsWith() {
        listOf(
            "/a/b/c/d" to "/a/b/c",
            "/a/b/c/d" to "/a/b/c/",
            "/A/B/C/D" to "/A/B/C",
            "/a/b/c/d" to "/a/b//c/",
            "/a/b/c/d" to "/a/b/../b/c",
            "/a/b/c/d" to "/a/../a/./b/../b///c",
            "\\a\\b\\c\\d" to "/a/../a/./b/../b///c",
            "/home/user/.config/test" to "/home/user",
            "/var/www/html/app" to "/var/www/html",
            "/home/user" to "/",
            "/" to "/",
            "////////////////////////" to "/",
            "/" to "////////////////////////",
            "/home/user" to "/home/user",
            "/home/user/./" to "/home/user",
            "/home/user" to "/home/user/./",
            "/./var" to "/var",
            "." to ".",
            "./" to ".",
            ".." to "..",
            "../.." to "../..",
            "./.." to "../.",
            "../." to "./..",
            "./../." to ".././.",
            "/." to "/.",
            "./" to ".",
            "/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z" to "/a/b/c",
            "/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a" to "/a/a/a"
        ).forEach { (pathString, otherPathString) ->
            assertTrue(
                pathString.toPath().startsWith(otherPathString.toPath()),
                "$pathString should start with $otherPathString"
            )
        }

        listOf(
            "/a/b/c" to "/a/b/c/d/",
            "/a/b/c/" to "/a/b/c/d",
            "/a/b/d/d" to "/a/b/c",
            "/a/b/d/d" to "/a/b/ce",
            "/a/b/ce" to "/a/b/c",
            "/a/b/c" to "/a/b/ce",
            "/abcd" to "/a/b/c/d",
            "/a/../b/c" to "/a/b/c",
            "/a/b/" to "/a/b//c",
            "/a/b/c/d" to "/a/b/../c",
            "/a/b/c/d" to "/a/./a/../b/./b///c",
            "/a/b/c" to "/c/b/a",
            "/a/a/a/a" to "/a/a/a/a/a",
            "\\a\\b\\d\\d" to "\\a\\b\\c",
            "\\a\\b\\d\\d" to "/a/b/c",
            "/home/user/.config/test" to "/home/user2",
            "/var/www/html/app" to "/var/local/www/html/app",
            "/home/user" to ".",
            "/" to "./",
            "/home/user" to "/home/user2",
            "/home/user/./" to "/home/user2",
            "/home/user2" to "/home/user/./",
            "../var" to "/var",
            "." to "..",
            "./" to "..",
            ".." to ".",
            "/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z" to "/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/z",
            "/a/a/a" to "/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a",
            "/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a" to "/A",
        ).forEach { (pathString, otherPathString) ->
            assertFalse(
                pathString.toPath().startsWith(otherPathString.toPath()),
                "$pathString should not start with $otherPathString"
            )
        }
    }

}

Upvotes: 1

oldergod
oldergod

Reputation: 15010

You could .toString() the Paths to do this operation.

You could otherwise use relativeTo and check the result. That's not exhaustively correct but something like the following could be a start:

  @Test fun testingInclusion() {
    println("/a/b/c/d".toPath().contains("/a/b/c".toPath()))
    println("/a/b/d/d".toPath().contains("/a/b/c".toPath()))
  }

  private fun Path.contains(other: Path): Boolean {
    return !this.relativeTo(other).toString().contains("..")
  }

Upvotes: 0

Related Questions