diff src/main/kotlin/name/blackcap/passman/ImportSubcommand.kt @ 16:7a74ae668665

Add export subcommand.
author David Barts <n5jrn@me.com>
date Sun, 05 Feb 2023 10:50:39 -0800
parents 4dae7a15ee48
children ea65ab890f66
line wrap: on
line diff
--- a/src/main/kotlin/name/blackcap/passman/ImportSubcommand.kt	Fri Feb 03 18:48:13 2023 -0800
+++ b/src/main/kotlin/name/blackcap/passman/ImportSubcommand.kt	Sun Feb 05 10:50:39 2023 -0800
@@ -3,37 +3,21 @@
 import com.opencsv.CSVParserBuilder
 import com.opencsv.CSVReaderBuilder
 import com.opencsv.exceptions.CsvException
-import org.apache.commons.cli.*
-import java.io.FileReader
+import org.apache.commons.cli.ParseException
+import java.io.FileInputStream
 import java.io.IOException
+import java.io.InputStreamReader
 import java.text.SimpleDateFormat
 import java.util.*
-import kotlin.system.exitProcess
 
 class ImportSubcommand(): Subcommand() {
     private companion object {
-        val CSV_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").apply {
-            timeZone = TimeZone.getTimeZone("UTC")
-            isLenient = false
-        }
-        const val ESCAPE = "escape"
-        const val FORCE = "force"
-        const val HELP = "help"
-        const val IGNORE = "ignore"
-        const val QUOTE = "quote"
-        const val SEPARATOR = "separator"
-        const val SKIP = "skip"
         const val NFIELDS = 7
-
     }
-    private lateinit var commandLine: CommandLine
+    private lateinit var csvDateFormat: SimpleDateFormat
     private lateinit var db: Database
-
-    /* default option values */
-    private var escape = '\\'
-    private var quote = '"'
-    private var separator = ','
-
+    private val options = ImportExportArguments()
+    private lateinit var csvFile: String
     private var line = 0
 
     override fun run(args: Array<String>) {
@@ -50,58 +34,32 @@
     }
 
     private fun parseArguments(args: Array<String>) {
-        val options = Options().apply {
-            addOption("e", ImportSubcommand.ESCAPE, true, "CSV escape character (default $escape).")
-            addOption("f", ImportSubcommand.FORCE, false, "Do not ask before overwriting.")
-            addOption("h", ImportSubcommand.HELP, false, "Print this help message.")
-            addOption("i", ImportSubcommand.IGNORE, false, "Ignore white space before quoted strings.")
-            addOption("q", ImportSubcommand.QUOTE, true, "CSV string-quoting character (default $quote).")
-            addOption("s", ImportSubcommand.SEPARATOR, true, "CSV separator character (default $separator).")
-            addOption("k", ImportSubcommand.SKIP, false, "Skip first line of input.")
-        }
-        try {
-            commandLine = DefaultParser().parse(options, args)
-        } catch (e: ParseException) {
-            die(e.message ?: "syntax error", 2)
-        }
-        if (commandLine.hasOption(ImportSubcommand.HELP)) {
-            HelpFormatter().printHelp("$SHORTNAME import [options] csv_file", options)
-            exitProcess(0)
+        val params = parseInto("import", args, options)
+        when (params.size) {
+            0 -> die("expecting CSV file name", 2)
+            1 -> csvFile = params[0]
+            else -> die("unexpected trailing arguments", 2)
         }
-        if (commandLine.args.isEmpty()) {
-            die("expecting other CSV file name", 2)
-        }
-        if (commandLine.args.size > 1) {
-            die("unexpected trailing arguments", 2)
+        csvDateFormat = SimpleDateFormat(options.format).apply {
+            timeZone = TimeZone.getTimeZone(options.zone)
+            isLenient = false
         }
-        escape = getOptionChar(ImportSubcommand.ESCAPE, escape)
-        quote = getOptionChar(ImportSubcommand.QUOTE, quote)
-        separator = getOptionChar(ImportSubcommand.SEPARATOR, separator)
-    }
-
-    private fun getOptionChar(optionName: String, defaultValue: Char): Char {
-        val optionValue = commandLine.getOptionValue(optionName) ?: return defaultValue
-        val ret = optionValue.firstOrNull()
-        if (ret == null) {
-            die("--$optionName value must not be empty")
-        }
-        return ret!!
     }
 
     private fun doImport() {
         val csvParser = CSVParserBuilder()
-            .withEscapeChar(escape)
-            .withQuoteChar(quote)
-            .withSeparator(separator)
-            .withIgnoreLeadingWhiteSpace(commandLine.hasOption(ImportSubcommand.IGNORE))
+            .withEscapeChar(options.escape)
+            .withQuoteChar(options.quote)
+            .withSeparator(options.separator)
+            .withIgnoreLeadingWhiteSpace(options.ignore)
             .build()
 
-        val csvReader = CSVReaderBuilder(FileReader(commandLine.args[0]))
+        val csvReader = CSVReaderBuilder(InputStreamReader(FileInputStream(csvFile), options.charset))
             .withCSVParser(csvParser)
             .build()
 
         csvReader.use {
-            if (commandLine.hasOption(ImportSubcommand.SKIP)) {
+            if (options.skip) {
                 line++
                 it.skip(1)
             }
@@ -145,7 +103,7 @@
             return null
         }
         try {
-            return CSV_DATE_FORMAT.parse(unparsed)
+            return csvDateFormat.parse(unparsed)
         } catch (e: ParseException) {
             die("${see(unparsed)} - invalid date/time string")
             throw e /* kotlin is too stupid to realize this never happens */
@@ -153,7 +111,7 @@
     }
 
     private fun okToChange(thisEntry: Entry?, otherEntry: Entry): Boolean =
-        thisEntry == null || commandLine.hasOption(FORCE) || askUserIfOkToOverwrite(thisEntry, otherEntry)
+        thisEntry == null || options.force || askUserIfOkToOverwrite(thisEntry, otherEntry)
 
     private fun saysNull(string: String) = string.lowercase() == "null"