Move SSD box type CSV parsing to typed Kotlin

ober

f1ca6d76cca854af5ac72e4e44ed9a702ca2b5e0

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 40a9e27..ed93785 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -112,7 +112,19 @@
                 (boxTypeIsWeapon
                   (resolvedBoxTypeNameOr
                     (boxTypesResolve context text)
-                    text)))))))
+                       text)))))))
+
+    (typed-kotlin-file "com/sfb/ssdreview/BoxTypeCsv.kt"
+      (typed-library (com sfb ssdreview)
+        (export parseBoxTypeCsvLine)
+        (def (parseBoxTypeCsvLine (line : String)) : (Nullable BoxType)
+          (if (not (string-matches-regex? line "^[^,]+,.+$"))
+              (nullable-none BoxType)
+              (let ((id (string-trim (string-substring-before line ",")))
+                    (name (string-trim (string-replace-regex line "^[^,]*," ""))))
+                (if (or (string-blank? id) (string-blank? name))
+                    (nullable-none BoxType)
+                    (nullable-some (make-BoxType id name))))))))
 
     (typed-kotlin-file "com/sfb/ssdreview/OcrWord.kt"
       (typed-library (com sfb ssdreview)
@@ -2967,14 +2979,7 @@
        "        val items = mutableListOf<BoxType>()"
        "        context.assets.open(\"box_types.csv\").bufferedReader().useLines { lines ->"
        "            lines.drop(1).forEach { line ->"
-       "                val comma = line.indexOf(',')"
-       "                if (comma > 0) {"
-       "                    val id = line.substring(0, comma).trim()"
-       "                    val name = line.substring(comma + 1).trim()"
-       "                    if (id.isNotBlank() && name.isNotBlank()) {"
-       "                        items.add(BoxType(id, name))"
-       "                    }"
-       "                }"
+       "                parseBoxTypeCsvLine(line)?.let { items.add(it) }"
        "            }"
        "        }"
        "        cache = items"