# HG changeset patch # User David Barts # Date 1719966892 25200 # Node ID 07406c4af4a53a67849496e16d812405d012d013 # Parent ea65ab890f66bdebe37abc79a99ad9b76b1a3e4a More interactive mode stuff. diff -r ea65ab890f66 -r 07406c4af4a5 src/main/kotlin/name/blackcap/passman/CreateSubcommand.kt --- 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): { + override fun run(args: Array) { val options = Options().apply { addOption("g", GENERATE, false, "Use password generator.") addOption("h", HELP, false, "Print this help message.") diff -r ea65ab890f66 -r 07406c4af4a5 src/main/kotlin/name/blackcap/passman/DeleteSubcommand.kt --- 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): { + override fun run(args: Array) { 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 diff -r ea65ab890f66 -r 07406c4af4a5 src/main/kotlin/name/blackcap/passman/ExportSubcommand.kt --- 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): { + override fun run(args: Array) { parseArguments(args) db = Database.default try { diff -r ea65ab890f66 -r 07406c4af4a5 src/main/kotlin/name/blackcap/passman/HelpSubcommand.kt --- 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.") diff -r ea65ab890f66 -r 07406c4af4a5 src/main/kotlin/name/blackcap/passman/ListSubcommand.kt --- 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) diff -r ea65ab890f66 -r 07406c4af4a5 src/main/kotlin/name/blackcap/passman/Main.kt --- 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) { + val NOPASSWORD = setOf("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): 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("password") + val QUIT = setOf("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): 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!!) + } +}