From 413c8fa6f28991304d6f7f2d8053363010530074 Mon Sep 17 00:00:00 2001 From: Laurence Gonsalves Date: Mon, 26 Mar 2018 15:24:42 -0700 Subject: [PATCH] Fix Issue #47 If an option looks at the value of another option it can see the current value. If no value has been set a MissingValueException is thrown. --- .../com/xenomachina/argparser/ArgParser.kt | 78 ++++++++++--------- .../xenomachina/argparser/ParsingDelegate.kt | 2 +- .../xenomachina/argparser/ArgParserTest.kt | 37 +++++++++ 3 files changed, 80 insertions(+), 37 deletions(-) diff --git a/src/main/kotlin/com/xenomachina/argparser/ArgParser.kt b/src/main/kotlin/com/xenomachina/argparser/ArgParser.kt index 6c02469..0e1da38 100644 --- a/src/main/kotlin/com/xenomachina/argparser/ArgParser.kt +++ b/src/main/kotlin/com/xenomachina/argparser/ArgParser.kt @@ -443,19 +443,20 @@ class ArgParser( * @throws SystemExitException if parsing or validation failed. */ fun force() { - if (!finished) { - parseOptions - if (!inValidation) { - inValidation = true - try { - for (delegate in delegates) delegate.checkHasValue() - for (delegate in delegates) delegate.validate() - } finally { - inValidation = false + if (!inParse) { + if (!finished) { + parseOptions + if (!inValidation) { + inValidation = true + try { + for (delegate in delegates) delegate.checkHasValue() + for (delegate in delegates) delegate.validate() + } finally { + inValidation = false + } } } } - finished = true } /** @@ -470,44 +471,49 @@ class ArgParser( return provided } - private var parseStarted = false + private var inParse = false internal fun checkNotParsed() { - if (parseStarted) throw IllegalStateException("arguments have already been parsed") + if (inParse || finished) throw IllegalStateException("arguments have already been parsed") } private val parseOptions by lazy { val positionalArguments = mutableListOf() - parseStarted = true - var i = 0 - optionLoop@ while (i < args.size) { - val arg = args[i] - i += when { - arg == "--" -> { - i++ - break@optionLoop - } - arg.startsWith("--") -> - parseLongOpt(i, args) - arg.startsWith("-") -> - parseShortOpts(i, args) - else -> { - positionalArguments.add(arg) - when (mode) { - Mode.GNU -> 1 - Mode.POSIX -> { - i++ - break@optionLoop + inParse = true + try { + var i = 0 + optionLoop@ while (i < args.size) { + val arg = args[i] + i += when { + arg == "--" -> { + i++ + break@optionLoop + } + arg.startsWith("--") -> + parseLongOpt(i, args) + arg.startsWith("-") -> + parseShortOpts(i, args) + else -> { + positionalArguments.add(arg) + when (mode) { + Mode.GNU -> 1 + Mode.POSIX -> { + i++ + break@optionLoop + } } } } } - } - // Collect remaining arguments as positional-only arguments - positionalArguments.addAll(args.slice(i..args.size - 1)) + // Collect remaining arguments as positional-only arguments + positionalArguments.addAll(args.slice(i..args.size - 1)) - parsePositionalArguments(positionalArguments) + parsePositionalArguments(positionalArguments) + finished = true + } finally { + inParse = false + } } private fun parsePositionalArguments(args: List) { diff --git a/src/main/kotlin/com/xenomachina/argparser/ParsingDelegate.kt b/src/main/kotlin/com/xenomachina/argparser/ParsingDelegate.kt index d21f731..e1cb4e3 100644 --- a/src/main/kotlin/com/xenomachina/argparser/ParsingDelegate.kt +++ b/src/main/kotlin/com/xenomachina/argparser/ParsingDelegate.kt @@ -38,7 +38,7 @@ internal abstract class ParsingDelegate( override val value: T get() { parser.force() - // checkHasValue should have ensured that this is non-null + checkHasValue() return holder!!.value } diff --git a/src/test/kotlin/com/xenomachina/argparser/ArgParserTest.kt b/src/test/kotlin/com/xenomachina/argparser/ArgParserTest.kt index c8838cd..53f8ea7 100644 --- a/src/test/kotlin/com/xenomachina/argparser/ArgParserTest.kt +++ b/src/test/kotlin/com/xenomachina/argparser/ArgParserTest.kt @@ -1571,3 +1571,40 @@ class Issue18Test_DefaultThenValidator : Test({ val x = Args(parserOf()).x x shouldEqual 0 }) + +class Issue47Test : Test({ + class Args(parser: ArgParser) { + val caseInsensitive by parser.flagging("-c", "--case_insensitive", help = TEST_HELP) + + val includedExtensions by parser.adding("-e", "--include_ext", help = TEST_HELP) { + extensionCheckCaseInsensitive() + } + + private fun String.extensionCheckCaseInsensitive() = + if (caseInsensitive) this.toLowerCase() else this + } + + val includeExtensions = Args(parserOf("-e", "Foo", "-c", "-e", "Bar")).includedExtensions + includeExtensions shouldEqual listOf("Foo", "bar") +}) + +class DependentArgs(parser: ArgParser) { + val suffix by parser.storing(TEST_HELP) + + val x by parser.adding(TEST_HELP) { + "$this:$suffix" + } +} + +class DependentArgsTest_orderMatters : Test({ + val result = DependentArgs(parserOf("--suffix", "bar", "-x", "foo", "-x", "dry", "--suffix", "fish", "-x", "cat")).x + result shouldEqual listOf("foo:bar", "dry:bar", "cat:fish") +}) + +class DependentArgsTest_unsetThrowsMissingValueException : Test({ + shouldThrow { + DependentArgs(parserOf("-x", "foo", "-x", "dry", "--suffix", "fish", "-x", "cat")).x + }.run { + message shouldEqual "missing SUFFIX" + } +})