changeset 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
files src/main/kotlin/name/blackcap/passman/CreateSubcommand.kt src/main/kotlin/name/blackcap/passman/DeleteSubcommand.kt src/main/kotlin/name/blackcap/passman/ExportSubcommand.kt src/main/kotlin/name/blackcap/passman/HelpSubcommand.kt src/main/kotlin/name/blackcap/passman/ListSubcommand.kt src/main/kotlin/name/blackcap/passman/Main.kt
diffstat 6 files changed, 87 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- a/src/main/kotlin/name/blackcap/passman/CreateSubcommand.kt	Tue Jul 02 11:27:39 2024 -0700
+++ b/src/main/kotlin/name/blackcap/passman/CreateSubcommand.kt	Tue Jul 02 17:34:52 2024 -0700
@@ -12,7 +12,7 @@
     }
     private lateinit var commandLine: CommandLine
 
-    override fun run(args: Array<String>): {
+    override fun run(args: Array<String>) {
         val options = Options().apply {
             addOption("g", GENERATE, false, "Use password generator.")
             addOption("h", HELP, false, "Print this help message.")
--- a/src/main/kotlin/name/blackcap/passman/DeleteSubcommand.kt	Tue Jul 02 11:27:39 2024 -0700
+++ b/src/main/kotlin/name/blackcap/passman/DeleteSubcommand.kt	Tue Jul 02 17:34:52 2024 -0700
@@ -1,13 +1,13 @@
 package name.blackcap.passman
 
 class DeleteSubcommand(): Subcommand() {
-    override fun run(args: Array<String>): {
+    override fun run(args: Array<String>) {
         if (args.isEmpty()) {
             throw SubcommandException(message = "expecting a site name", status = 2)
         }
         if (args[0] == "-h" || args[0].startsWith("--h")) {
             println("usage: passman delete name [...]")
-            return 0
+            return
         }
         val db = Database.default
         var errors = 0
--- a/src/main/kotlin/name/blackcap/passman/ExportSubcommand.kt	Tue Jul 02 11:27:39 2024 -0700
+++ b/src/main/kotlin/name/blackcap/passman/ExportSubcommand.kt	Tue Jul 02 17:34:52 2024 -0700
@@ -18,7 +18,7 @@
     private val options = ImportExportArguments()
     private lateinit var csvFile: String
 
-    override fun run(args: Array<String>): {
+    override fun run(args: Array<String>) {
         parseArguments(args)
         db = Database.default
         try {
--- a/src/main/kotlin/name/blackcap/passman/HelpSubcommand.kt	Tue Jul 02 11:27:39 2024 -0700
+++ b/src/main/kotlin/name/blackcap/passman/HelpSubcommand.kt	Tue Jul 02 17:34:52 2024 -0700
@@ -12,6 +12,7 @@
         println("list         List records.")
         println("merge        Merge passwords in from another PassMan database.")
         println("password     Change database encryption key.")
+        println("quit         Exit from interactive mode.")
         println("read         Retrieve data from existing record.")
         println("rename       Rename existing record.")
         println("update       Update existing record.")
--- a/src/main/kotlin/name/blackcap/passman/ListSubcommand.kt	Tue Jul 02 11:27:39 2024 -0700
+++ b/src/main/kotlin/name/blackcap/passman/ListSubcommand.kt	Tue Jul 02 17:34:52 2024 -0700
@@ -60,7 +60,7 @@
         }
         if (commandLine.hasOption(HELP)) {
             HelpFormatter().printHelp("$SHORTNAME list [options]", options)
-            return
+            throw SubcommandException(status = 0)
         }
         if (commandLine.args.isNotEmpty()) {
             throw SubcommandException(message = "unexpected trailing arguments", status = 2)
--- 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!!)
+    }
+}