diff src/main/kotlin/name/blackcap/passman/Main.kt @ 22:07406c4af4a5

More interactive mode stuff.
author David Barts <n5jrn@me.com>
date Tue, 02 Jul 2024 17:34:52 -0700
parents ea65ab890f66
children af86b8e0b88c
line wrap: on
line diff
--- a/src/main/kotlin/name/blackcap/passman/Main.kt	Tue Jul 02 11:27:39 2024 -0700
+++ b/src/main/kotlin/name/blackcap/passman/Main.kt	Tue Jul 02 17:34:52 2024 -0700
@@ -9,20 +9,87 @@
 import kotlin.system.exitProcess
 
 fun main(args: Array<String>) {
+    val NOPASSWORD = setOf<String>("help")
     if (args.isEmpty()) {
-        error("expecting subcommand")
+        openDatabase()
+        runInteractive()
+    }
+    val subcommand = args[0]
+    val scArgs = args.sliceArray(1 until args.size)
+    if (subcommand !in NOPASSWORD) {
+        openDatabase()
+    }
+    runNonInteractive(subcommand, scArgs)
+}
+
+fun openDatabase() {
+    try {
+        Database.default = Database.open()
+    } catch (e: DatabaseException) {
+        handleMessagedException(e)
         exitProcess(2)
     }
-    val subcommand = args[0];
-    val scArgs = args.sliceArray(1 until args.size)
-    Database.default = Database.open()
-    runSubcommand(subcommand, scArgs)
+}
+
+fun runNonInteractive(subcommand: String, scArgs: Array<String>): Unit {
+    try {
+        runSubcommand(subcommand, scArgs)
+    } catch (e: SubcommandException) {
+        handleSubcommandException(e)
+        exitProcess(e.status)
+    } catch (e: MessagedException) {
+        handleMessagedException(e)
+        exitProcess(1)
+    }
+    exitProcess(0)
+}
+
+fun runInteractive() {
+    val DISALLOWED = setOf<String>("password")
+    val QUIT = setOf<String>("exit", "quit")
+    var lastStatus = 0
+    while (true) {
+        val rawLine = System.console()?.readLine("passman> ") ?: break
+        val s = Shplitter()
+        s.feed(rawLine)
+        if (!s.complete) {
+            error("unterminated quoting")
+            lastStatus = 1
+            continue
+        }
+        val line = s.split().toList()
+        if (line.isEmpty()) {
+            continue
+        }
+        val subcommand = line.first()
+        val scArgs = line.drop(1).toTypedArray()
+        if (subcommand in QUIT) {
+            break
+        }
+        if (subcommand in DISALLOWED) {
+            error("$subcommand subcommand not allowed in interactive mode")
+            lastStatus = 2
+            continue
+        }
+        try {
+            runSubcommand(subcommand, scArgs)
+            lastStatus = 0
+        } catch(e: SubcommandException) {
+            handleSubcommandException(e)
+            lastStatus = e.status
+        } catch(e: MessagedException) {
+            handleMessagedException(e)
+            lastStatus = 1
+        }
+    }
+    println()  // ensure shell prompt comes out on a line of its own
+    exitProcess(lastStatus)
 }
 
 fun runSubcommand(name: String, args: Array<String>): Unit {
     val instance = getInstanceForClass(getClassForSubcommand(name))
     if (instance == null) {
-        die("${see(name)} - unknown subcommand", 2)
+        throw SubcommandException(message = "${see(name)} - unknown subcommand", status = 2)
     } else {
         instance.run(args)
     }
@@ -42,3 +109,11 @@
 } catch (e: ReflectiveOperationException) {
     null
 }
+
+fun handleMessagedException(e: MessagedException)  = error(e.message)
+
+fun handleSubcommandException(e: SubcommandException): Unit {
+    if (e.message != null) {
+        error(e.message!!)
+    }
+}