Mercurial > cgi-bin > hgweb.cgi > PassMan
comparison 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 |
comparison
equal
deleted
inserted
replaced
15:0fc90892a3ae | 16:7a74ae668665 |
---|---|
1 package name.blackcap.passman | 1 package name.blackcap.passman |
2 | 2 |
3 import com.opencsv.CSVParserBuilder | 3 import com.opencsv.CSVParserBuilder |
4 import com.opencsv.CSVReaderBuilder | 4 import com.opencsv.CSVReaderBuilder |
5 import com.opencsv.exceptions.CsvException | 5 import com.opencsv.exceptions.CsvException |
6 import org.apache.commons.cli.* | 6 import org.apache.commons.cli.ParseException |
7 import java.io.FileReader | 7 import java.io.FileInputStream |
8 import java.io.IOException | 8 import java.io.IOException |
9 import java.io.InputStreamReader | |
9 import java.text.SimpleDateFormat | 10 import java.text.SimpleDateFormat |
10 import java.util.* | 11 import java.util.* |
11 import kotlin.system.exitProcess | |
12 | 12 |
13 class ImportSubcommand(): Subcommand() { | 13 class ImportSubcommand(): Subcommand() { |
14 private companion object { | 14 private companion object { |
15 val CSV_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").apply { | |
16 timeZone = TimeZone.getTimeZone("UTC") | |
17 isLenient = false | |
18 } | |
19 const val ESCAPE = "escape" | |
20 const val FORCE = "force" | |
21 const val HELP = "help" | |
22 const val IGNORE = "ignore" | |
23 const val QUOTE = "quote" | |
24 const val SEPARATOR = "separator" | |
25 const val SKIP = "skip" | |
26 const val NFIELDS = 7 | 15 const val NFIELDS = 7 |
27 | |
28 } | 16 } |
29 private lateinit var commandLine: CommandLine | 17 private lateinit var csvDateFormat: SimpleDateFormat |
30 private lateinit var db: Database | 18 private lateinit var db: Database |
31 | 19 private val options = ImportExportArguments() |
32 /* default option values */ | 20 private lateinit var csvFile: String |
33 private var escape = '\\' | |
34 private var quote = '"' | |
35 private var separator = ',' | |
36 | |
37 private var line = 0 | 21 private var line = 0 |
38 | 22 |
39 override fun run(args: Array<String>) { | 23 override fun run(args: Array<String>) { |
40 parseArguments(args) | 24 parseArguments(args) |
41 db = Database.open() | 25 db = Database.open() |
48 die("line $line, $message") | 32 die("line $line, $message") |
49 } | 33 } |
50 } | 34 } |
51 | 35 |
52 private fun parseArguments(args: Array<String>) { | 36 private fun parseArguments(args: Array<String>) { |
53 val options = Options().apply { | 37 val params = parseInto("import", args, options) |
54 addOption("e", ImportSubcommand.ESCAPE, true, "CSV escape character (default $escape).") | 38 when (params.size) { |
55 addOption("f", ImportSubcommand.FORCE, false, "Do not ask before overwriting.") | 39 0 -> die("expecting CSV file name", 2) |
56 addOption("h", ImportSubcommand.HELP, false, "Print this help message.") | 40 1 -> csvFile = params[0] |
57 addOption("i", ImportSubcommand.IGNORE, false, "Ignore white space before quoted strings.") | 41 else -> die("unexpected trailing arguments", 2) |
58 addOption("q", ImportSubcommand.QUOTE, true, "CSV string-quoting character (default $quote).") | |
59 addOption("s", ImportSubcommand.SEPARATOR, true, "CSV separator character (default $separator).") | |
60 addOption("k", ImportSubcommand.SKIP, false, "Skip first line of input.") | |
61 } | 42 } |
62 try { | 43 csvDateFormat = SimpleDateFormat(options.format).apply { |
63 commandLine = DefaultParser().parse(options, args) | 44 timeZone = TimeZone.getTimeZone(options.zone) |
64 } catch (e: ParseException) { | 45 isLenient = false |
65 die(e.message ?: "syntax error", 2) | |
66 } | 46 } |
67 if (commandLine.hasOption(ImportSubcommand.HELP)) { | |
68 HelpFormatter().printHelp("$SHORTNAME import [options] csv_file", options) | |
69 exitProcess(0) | |
70 } | |
71 if (commandLine.args.isEmpty()) { | |
72 die("expecting other CSV file name", 2) | |
73 } | |
74 if (commandLine.args.size > 1) { | |
75 die("unexpected trailing arguments", 2) | |
76 } | |
77 escape = getOptionChar(ImportSubcommand.ESCAPE, escape) | |
78 quote = getOptionChar(ImportSubcommand.QUOTE, quote) | |
79 separator = getOptionChar(ImportSubcommand.SEPARATOR, separator) | |
80 } | |
81 | |
82 private fun getOptionChar(optionName: String, defaultValue: Char): Char { | |
83 val optionValue = commandLine.getOptionValue(optionName) ?: return defaultValue | |
84 val ret = optionValue.firstOrNull() | |
85 if (ret == null) { | |
86 die("--$optionName value must not be empty") | |
87 } | |
88 return ret!! | |
89 } | 47 } |
90 | 48 |
91 private fun doImport() { | 49 private fun doImport() { |
92 val csvParser = CSVParserBuilder() | 50 val csvParser = CSVParserBuilder() |
93 .withEscapeChar(escape) | 51 .withEscapeChar(options.escape) |
94 .withQuoteChar(quote) | 52 .withQuoteChar(options.quote) |
95 .withSeparator(separator) | 53 .withSeparator(options.separator) |
96 .withIgnoreLeadingWhiteSpace(commandLine.hasOption(ImportSubcommand.IGNORE)) | 54 .withIgnoreLeadingWhiteSpace(options.ignore) |
97 .build() | 55 .build() |
98 | 56 |
99 val csvReader = CSVReaderBuilder(FileReader(commandLine.args[0])) | 57 val csvReader = CSVReaderBuilder(InputStreamReader(FileInputStream(csvFile), options.charset)) |
100 .withCSVParser(csvParser) | 58 .withCSVParser(csvParser) |
101 .build() | 59 .build() |
102 | 60 |
103 csvReader.use { | 61 csvReader.use { |
104 if (commandLine.hasOption(ImportSubcommand.SKIP)) { | 62 if (options.skip) { |
105 line++ | 63 line++ |
106 it.skip(1) | 64 it.skip(1) |
107 } | 65 } |
108 | 66 |
109 it.iterator().forEach { fields -> | 67 it.iterator().forEach { fields -> |
143 private fun parseCsvTime(unparsed: String): Date? { | 101 private fun parseCsvTime(unparsed: String): Date? { |
144 if (saysNull(unparsed)) { | 102 if (saysNull(unparsed)) { |
145 return null | 103 return null |
146 } | 104 } |
147 try { | 105 try { |
148 return CSV_DATE_FORMAT.parse(unparsed) | 106 return csvDateFormat.parse(unparsed) |
149 } catch (e: ParseException) { | 107 } catch (e: ParseException) { |
150 die("${see(unparsed)} - invalid date/time string") | 108 die("${see(unparsed)} - invalid date/time string") |
151 throw e /* kotlin is too stupid to realize this never happens */ | 109 throw e /* kotlin is too stupid to realize this never happens */ |
152 } | 110 } |
153 } | 111 } |
154 | 112 |
155 private fun okToChange(thisEntry: Entry?, otherEntry: Entry): Boolean = | 113 private fun okToChange(thisEntry: Entry?, otherEntry: Entry): Boolean = |
156 thisEntry == null || commandLine.hasOption(FORCE) || askUserIfOkToOverwrite(thisEntry, otherEntry) | 114 thisEntry == null || options.force || askUserIfOkToOverwrite(thisEntry, otherEntry) |
157 | 115 |
158 private fun saysNull(string: String) = string.lowercase() == "null" | 116 private fun saysNull(string: String) = string.lowercase() == "null" |
159 | 117 |
160 } | 118 } |