Move SSD import path splitting to typed Kotlin

ober

8e783306f2a6fe8cafc245225aecbd80235b7017

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 59f1a6b..0da57f9 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -5287,8 +5287,13 @@
       (typed-library (com sfb ssdreview)
         (export safePathComponent safeLeaf safeZipEntryName validSourceKey
                 validatedSourceKey treeFileMime
-                safeTreePathParts treePathLeaf treePathDirectoryParts)
+                safeTreePathParts treePathLeaf treePathDirectoryParts
+                safeImportLeafEntryName zipEntryRootName zipEntryLeafName)
         (type Int32)
+        (type Char)
+        (extern (stringSplitChar (text : String)
+                                 (separator : Char)) : (List String)
+          (kotlin-member-call split))
         (def (safePathComponent (name : String)) : Bool
           (let ((n (string-length name)))
             (and (and (>= n 1) (<= n 128))
@@ -5332,6 +5337,18 @@
                                (string-matches-regex?
                                  name
                                  "^(ground_truth|learned|events|reviews)/[A-Za-z0-9][A-Za-z0-9._-]*\\.(json|jsonl)$")))))))
+        (def (safeImportLeafEntryName (name : String)) : Bool
+          (and (safeZipEntryName name)
+               (not (string-ends-with? name "/"))))
+        (def (zipEntryParts (name : String)
+                            (separator : Char)) : (List String)
+          (stringSplitChar name separator))
+        (def (zipEntryRootName (name : String)
+                               (separator : Char)) : String
+          (list-ref (zipEntryParts name separator) (int32 0)))
+        (def (zipEntryLeafName (name : String)
+                               (separator : Char)) : String
+          (list-ref (zipEntryParts name separator) (int32 1)))
         (def (validSourceKey (key : String)) : Bool
           (let ((n (string-length key)))
             (and (and (>= n 1) (<= n 96))
@@ -8619,16 +8636,16 @@
        "    }"
        ""
        "    private fun containedImportPath(name: String): File {"
-       "        require(safeZipEntryName(name) && !name.endsWith('/')) { \"Unsafe import path\" }"
-       "        val parts = name.split('/')"
-       "        val directory = when (parts[0]) {"
+       "        require(safeImportLeafEntryName(name)) { \"Unsafe import path\" }"
+       "        val root = zipEntryRootName(name, '/')"
+       "        val directory = when (root) {"
        "            \"ground_truth\" -> groundTruthDir"
        "            \"learned\" -> learnedDir"
        "            \"events\" -> eventsDir"
        "            \"reviews\" -> reviewsDir"
        "            else -> throw IllegalArgumentException(\"Unknown import directory\")"
        "        }"
-       "        return containedLeaf(directory, parts[1])"
+       "        return containedLeaf(directory, zipEntryLeafName(name, '/'))"
        "    }"
        ""
        "    private fun readLocalText(file: File): String {"