From 79629ae68d215a5a2cde862a11ded3539b1bb37c Mon Sep 17 00:00:00 2001 From: Infendro Date: Sat, 27 Dec 2025 17:30:05 +0100 Subject: [PATCH] refactor argument system - arguments can have multiple names simultaneously - argument values can be passed using spaces (e.g., --arg value) - single-character arguments can be passed using a single dash (e.g., --a -> -a) - multiple single-character arguments can be combined (e.g., -a -b -> -ab) - simplify custom arguments using generic classes --- .../com/infendro/cli/argument/Argument.kt | 75 +++++----- .../com/infendro/cli/argument/Boolean.kt | 90 +++--------- .../kotlin/com/infendro/cli/argument/Int.kt | 93 +++---------- .../com/infendro/cli/argument/String.kt | 89 +++--------- .../com/infendro/cli/command/Command.kt | 131 ++++-------------- .../com/infendro/cli/command/Execution.kt | 90 ++++++++++-- .../infendro/cli/exception/CliException.kt | 3 + .../build/NoArgumentNameException.kt | 3 + .../exception/run/ArgumentCountException.kt | 7 - .../run/DuplicateArgumentException.kt | 10 ++ .../exception/run/InvalidArgumentException.kt | 5 - .../run/MalformedArgumentException.kt | 10 ++ .../exception/run/MissingArgumentException.kt | 10 ++ .../cli/exception/run/ParseException.kt | 11 ++ .../exception/run/UnknownArgumentException.kt | 7 +- .../exception/run/UnknownCommandException.kt | 7 +- .../kotlin/com/infendro/cli/util/Regex.kt | 5 +- 17 files changed, 258 insertions(+), 388 deletions(-) create mode 100644 src/commonMain/kotlin/com/infendro/cli/exception/CliException.kt create mode 100644 src/commonMain/kotlin/com/infendro/cli/exception/build/NoArgumentNameException.kt delete mode 100644 src/commonMain/kotlin/com/infendro/cli/exception/run/ArgumentCountException.kt create mode 100644 src/commonMain/kotlin/com/infendro/cli/exception/run/DuplicateArgumentException.kt delete mode 100644 src/commonMain/kotlin/com/infendro/cli/exception/run/InvalidArgumentException.kt create mode 100644 src/commonMain/kotlin/com/infendro/cli/exception/run/MalformedArgumentException.kt create mode 100644 src/commonMain/kotlin/com/infendro/cli/exception/run/MissingArgumentException.kt create mode 100644 src/commonMain/kotlin/com/infendro/cli/exception/run/ParseException.kt diff --git a/src/commonMain/kotlin/com/infendro/cli/argument/Argument.kt b/src/commonMain/kotlin/com/infendro/cli/argument/Argument.kt index 4e0ce36..540b622 100644 --- a/src/commonMain/kotlin/com/infendro/cli/argument/Argument.kt +++ b/src/commonMain/kotlin/com/infendro/cli/argument/Argument.kt @@ -1,51 +1,44 @@ package com.infendro.cli.argument -sealed class Argument { - abstract val name: String - abstract val min: Int - abstract val max: Int +import com.infendro.cli.exception.build.NoArgumentNameException - abstract class Single : Argument() { - final override val min: Int - get() = 1 - final override val max: Int - get() = 1 +sealed class Argument( + val names: List, + val parser: Parser, +) { + abstract val required: Boolean - abstract val parser: Parser - } + val name: String + get() = names[0] - abstract class SingleOrElse : Argument() { - abstract val other: T - - final override val min: Int - get() = 0 - final override val max: Int - get() = 1 - - abstract val parser: Parser - } - - abstract class SingleOrNull : Argument() { - final override val min: Int - get() = 0 - final override val max: Int - get() = 1 - - abstract val parser: Parser - } - - abstract class Multiple : Argument>() { - override val min: Int - get() = 0 - override val max: Int - get() = Int.MAX_VALUE - - abstract val parser: Parser + init { + if (names.isEmpty()) + throw NoArgumentNameException() } interface Parser { - fun parse( - text: String?, - ): T + fun parse(text: String?): T + } + + class Single( + names: List, + parser: Parser, + ) : Argument(names, parser) { + override val required: Boolean = true + } + + class SingleOrElse( + names: List, + parser: Parser, + val other: T, + ) : Argument(names, parser) { + override val required: Boolean = false + } + + class SingleOrNull( + names: List, + parser: Parser, + ) : Argument(names, parser) { + override val required: Boolean = false } } diff --git a/src/commonMain/kotlin/com/infendro/cli/argument/Boolean.kt b/src/commonMain/kotlin/com/infendro/cli/argument/Boolean.kt index 7343bad..776275e 100644 --- a/src/commonMain/kotlin/com/infendro/cli/argument/Boolean.kt +++ b/src/commonMain/kotlin/com/infendro/cli/argument/Boolean.kt @@ -1,84 +1,30 @@ package com.infendro.cli.argument import com.infendro.cli.command.Command +import com.infendro.cli.exception.run.ParseException -private object BooleanParser : Argument.Parser { - override fun parse( - text: String?, - ): Boolean { - return text == null || text.toBooleanStrict() +object BooleanParser : Argument.Parser { + override fun parse(text: String?): Boolean = try { + text?.toBooleanStrict() ?: true + } catch (_: IllegalArgumentException) { + throw ParseException(text, "Boolean") } } -class BooleanArgument( - override val name: String, -) : Argument.Single() { - override val parser: Parser - get() = BooleanParser -} +fun boolean(vararg names: String) = + Argument.Single(names.asList(), BooleanParser) -fun boolean( - name: String, -) = BooleanArgument(name) +fun booleanOrElse(vararg names: String, other: Boolean) = + Argument.SingleOrElse(names.asList(), BooleanParser, other) -fun Command.Builder.boolean( - name: String, -) = BooleanArgument(name) - .also { argument(it) } +fun booleanOrNull(vararg names: String) = + Argument.SingleOrNull(names.asList(), BooleanParser) -class BooleanOrElseArgument( - override val name: String, - override val other: Boolean, -) : Argument.SingleOrElse() { - override val parser: Parser - get() = BooleanParser -} +fun Command.Builder.boolean(vararg names: String) = + Argument.Single(names.asList(), BooleanParser).also { argument(it) } -fun booleanOrElse( - name: String, - other: Boolean, -) = BooleanOrElseArgument(name, other) +fun Command.Builder.booleanOrElse(vararg names: String, other: Boolean) = + Argument.SingleOrElse(names.asList(), BooleanParser, other).also { argument(it) } -fun Command.Builder.booleanOrElse( - name: String, - other: Boolean, -) = BooleanOrElseArgument(name, other) - .also { argument(it) } - -class BooleanOrNullArgument( - override val name: String, -) : Argument.SingleOrNull() { - override val parser: Parser - get() = BooleanParser -} - -fun booleanOrNull( - name: String, -) = BooleanOrNullArgument(name) - -fun Command.Builder.booleanOrNull( - name: String, -) = BooleanOrNullArgument(name) - .also { argument(it) } - -class BooleansArgument( - override val name: String, - override val min: Int = 0, - override val max: Int = Int.MAX_VALUE, -) : Argument.Multiple() { - override val parser: Parser - get() = BooleanParser -} - -fun booleans( - name: String, - min: Int = 0, - max: Int = Int.MAX_VALUE, -) = BooleansArgument(name, min, max) - -fun Command.Builder.booleans( - name: String, - min: Int = 0, - max: Int = Int.MAX_VALUE, -) = BooleansArgument(name, min, max) - .also { argument(it) } +fun Command.Builder.booleanOrNull(vararg names: String) = + Argument.SingleOrNull(names.asList(), BooleanParser).also { argument(it) } diff --git a/src/commonMain/kotlin/com/infendro/cli/argument/Int.kt b/src/commonMain/kotlin/com/infendro/cli/argument/Int.kt index 962e3cd..eb7f060 100644 --- a/src/commonMain/kotlin/com/infendro/cli/argument/Int.kt +++ b/src/commonMain/kotlin/com/infendro/cli/argument/Int.kt @@ -1,84 +1,33 @@ package com.infendro.cli.argument import com.infendro.cli.command.Command +import com.infendro.cli.exception.run.ParseException -private object IntParser : Argument.Parser { - override fun parse( - text: String?, - ): Int { - return text!!.toInt() +object IntParser : Argument.Parser { + override fun parse(text: String?): Int = try { + text!!.toInt() + } catch (e: Exception) { + when (e) { + is NullPointerException, is IllegalArgumentException -> throw ParseException(text, "Int") + else -> throw e + } } } -class IntArgument( - override val name: String, -) : Argument.Single() { - override val parser: Parser - get() = IntParser -} +fun int(vararg names: String) = + Argument.Single(names.asList(), IntParser) -fun int( - name: String, -) = IntArgument(name) +fun intOrElse(vararg names: String, other: Int) = + Argument.SingleOrElse(names.asList(), IntParser, other) -fun Command.Builder.int( - name: String, -) = IntArgument(name) - .also { argument(it) } +fun intOrNull(vararg names: String) = + Argument.SingleOrNull(names.asList(), IntParser) -class IntOrElseArgument( - override val name: String, - override val other: Int, -) : Argument.SingleOrElse() { - override val parser: Parser - get() = IntParser -} +fun Command.Builder.int(vararg names: String) = + Argument.Single(names.asList(), IntParser).also { argument(it) } -fun intOrElse( - name: String, - other: Int, -) = IntOrElseArgument(name, other) +fun Command.Builder.intOrElse(vararg names: String, other: Int) = + Argument.SingleOrElse(names.asList(), IntParser, other).also { argument(it) } -fun Command.Builder.intOrElse( - name: String, - other: Int, -) = IntOrElseArgument(name, other) - .also { argument(it) } - -class IntOrNullArgument( - override val name: String, -) : Argument.SingleOrNull() { - override val parser: Parser - get() = IntParser -} - -fun intOrNull( - name: String, -) = IntOrNullArgument(name) - -fun Command.Builder.intOrNull( - name: String, -) = IntOrNullArgument(name) - .also { argument(it) } - -class IntsArgument( - override val name: String, - override val min: Int = 0, - override val max: Int = Int.MAX_VALUE, -) : Argument.Multiple() { - override val parser: Parser - get() = IntParser -} - -fun ints( - name: String, - min: Int = 0, - max: Int = Int.MAX_VALUE, -) = IntsArgument(name, min, max) - -fun Command.Builder.ints( - name: String, - min: Int = 0, - max: Int = Int.MAX_VALUE, -) = IntsArgument(name, min, max) - .also { argument(it) } +fun Command.Builder.intOrNull(vararg names: String) = + Argument.SingleOrNull(names.asList(), IntParser).also { argument(it) } diff --git a/src/commonMain/kotlin/com/infendro/cli/argument/String.kt b/src/commonMain/kotlin/com/infendro/cli/argument/String.kt index a2756e9..5d835a9 100644 --- a/src/commonMain/kotlin/com/infendro/cli/argument/String.kt +++ b/src/commonMain/kotlin/com/infendro/cli/argument/String.kt @@ -1,84 +1,27 @@ package com.infendro.cli.argument import com.infendro.cli.command.Command +import com.infendro.cli.exception.run.ParseException -private object StringParser : Argument.Parser { - override fun parse( - text: String?, - ): String { - return text!! - } +object StringParser : Argument.Parser { + override fun parse(text: String?): String = + text ?: throw ParseException(text, "String") } -class StringArgument( - override val name: String, -) : Argument.Single() { - override val parser: Parser - get() = StringParser -} +fun string(vararg names: String) = + Argument.Single(names.asList(), StringParser) -fun string( - name: String, -) = StringArgument(name) +fun stringOrElse(vararg names: String, other: String) = + Argument.SingleOrElse(names.asList(), StringParser, other) -fun Command.Builder.string( - name: String, -) = StringArgument(name) - .also { argument(it) } +fun stringOrNull(vararg names: String) = + Argument.SingleOrNull(names.asList(), StringParser) -class StringOrElseArgument( - override val name: String, - override val other: String, -) : Argument.SingleOrElse() { - override val parser: Parser - get() = StringParser -} +fun Command.Builder.string(vararg names: String) = + Argument.Single(names.asList(), StringParser).also { argument(it) } -fun stringOrElse( - name: String, - other: String, -) = StringOrElseArgument(name, other) +fun Command.Builder.stringOrElse(vararg names: String, other: String) = + Argument.SingleOrElse(names.asList(), StringParser, other).also { argument(it) } -fun Command.Builder.stringOrElse( - name: String, - other: String, -) = StringOrElseArgument(name, other) - .also { argument(it) } - -class StringOrNullArgument( - override val name: String, -) : Argument.SingleOrNull() { - override val parser: Parser - get() = StringParser -} - -fun stringOrNull( - name: String, -) = StringOrNullArgument(name) - -fun Command.Builder.stringOrNull( - name: String, -) = StringOrNullArgument(name) - .also { argument(it) } - -class StringsArgument( - override val name: String, - override val min: Int = 0, - override val max: Int = Int.MAX_VALUE, -) : Argument.Multiple() { - override val parser: Parser - get() = StringParser -} - -fun strings( - name: String, - min: Int = 0, - max: Int = Int.MAX_VALUE, -) = StringsArgument(name, min, max) - -fun Command.Builder.strings( - name: String, - min: Int = 0, - max: Int = Int.MAX_VALUE, -) = StringsArgument(name, min, max) - .also { argument(it) } +fun Command.Builder.stringOrNull(vararg names: String) = + Argument.SingleOrNull(names.asList(), StringParser).also { argument(it) } diff --git a/src/commonMain/kotlin/com/infendro/cli/command/Command.kt b/src/commonMain/kotlin/com/infendro/cli/command/Command.kt index 6154854..47dd233 100644 --- a/src/commonMain/kotlin/com/infendro/cli/command/Command.kt +++ b/src/commonMain/kotlin/com/infendro/cli/command/Command.kt @@ -1,99 +1,37 @@ package com.infendro.cli.command import com.infendro.cli.argument.Argument +import com.infendro.cli.exception.CliException import com.infendro.cli.exception.build.* -import com.infendro.cli.exception.run.ArgumentCountException -import com.infendro.cli.exception.run.InvalidArgumentException -import com.infendro.cli.exception.run.UnknownArgumentException import com.infendro.cli.exception.run.UnknownCommandException -import com.infendro.cli.util.Regex.ARGUMENT import com.infendro.cli.util.Regex.ARGUMENT_NAME import com.infendro.cli.util.Regex.COMMAND_NAME class Command private constructor( - private val index: Int, - private val name: String?, - private val commands: List, - private val arguments: List>, + val name: String?, + val commands: List, + val arguments: List>, private val block: Execution.() -> Unit, ) { - private val nextIndex: Int - get() = index + 1 - - fun run( - args: Array, - ) { + fun run(args: Array) = try { val index = args.withIndex() - .find { (_, value) -> value.startsWith("--") } + .firstOrNull { (_, value) -> value.startsWith("-") } ?.index ?: args.size val cmd = args.slice(0.. println("""argument "${e.argument}" must appear ${e.min} times""") - e.max == Int.MAX_VALUE -> println("""argument "${e.argument}" must appear at least ${e.min} times""") - else -> println("""argument "${e.argument}" must appear between ${e.min} and ${e.max} times""") - } - } - } - - private fun run( - cmd: List, - arg: List, - ) { - if (index == cmd.lastIndex) { - Execution( - parseCmd(cmd), - parseArg(arg), - ).block() - } else { - val command = commands.firstOrNull() { it.name == cmd[nextIndex] || it.name == null } - ?: throw UnknownCommandException(cmd[nextIndex]) - command.run(cmd, arg) - } - } - - private fun parseCmd( - cmd: List, - ): List { - return cmd.map { Execution.Cmd(it) } - } - - private fun parseArg( - arg: List, - ): List { - val result = mutableListOf() - - for (argument in arg) { - val match = ARGUMENT.matchEntire(argument) - ?: throw InvalidArgumentException(argument) - - val name = match.groups["name"]!!.value - val value = match.groups["value"]?.value - - if (arguments.none { it.name == name }) - throw UnknownArgumentException(name) - - result += Execution.Arg(name, value) + var command = this + for (c in cmd) { + command = command.commands.firstOrNull { it.name == c || it.name == null } + ?: throw UnknownCommandException(c) } - arguments.forEach { argument -> - val count = result.count { it.name == argument.name } - if (count !in argument.min..argument.max) - throw ArgumentCountException(argument.name, argument.min, argument.max) - } - - return result + val execution = Execution.from(command, cmd, arg) + val execute = command.block + execution.execute() + } catch (e: CliException) { + println(e.message) } class Builder internal constructor( @@ -107,46 +45,37 @@ class Command private constructor( private val nextIndex: Int get() = index + 1 - fun command( - name: String? = null, - block: Builder.(Key) -> Unit, - ) { - if (name != null && !COMMAND_NAME.matches(name)) + fun command(name: String? = null, block: Builder.(Key) -> Unit) { + if (name != null && !name.matches(COMMAND_NAME)) throw IllegalCommandNameException(name) if (commands.any { it.name == name }) throw DuplicateCommandException(name) val builder = Builder(nextIndex, name) - builder.block(Key(nextIndex)) + val key = Key(nextIndex) + builder.block(key) val command = builder.build() commands.add(command) } - fun argument( - argument: Argument<*>, - ) { - if (!ARGUMENT_NAME.matches(argument.name)) - throw IllegalArgumentNameException(argument.name) + fun argument(argument: Argument<*>) { + for (name in argument.names) { + if (!name.matches(ARGUMENT_NAME)) + throw IllegalArgumentNameException(name) - if (arguments.any { it.name == argument.name }) - throw DuplicateArgumentException(argument.name) + if (arguments.any { name in it.names }) + throw DuplicateArgumentException(name) + } arguments.add(argument) } - fun arguments( - vararg arguments: Argument<*>, - ) { - for (argument in arguments) { - argument(argument) - } - } + fun arguments(vararg arguments: Argument<*>) = + arguments.forEach(::argument) - fun execute( - block: Execution.() -> Unit, - ) { + fun execute(block: Execution.() -> Unit) { if (this::block.isInitialized) throw DuplicateExecuteException() @@ -157,7 +86,7 @@ class Command private constructor( if (!this::block.isInitialized) throw NoExecuteException() - return Command(index, name, commands, arguments, block) + return Command(name, commands, arguments, block) } } diff --git a/src/commonMain/kotlin/com/infendro/cli/command/Execution.kt b/src/commonMain/kotlin/com/infendro/cli/command/Execution.kt index a6c305d..65ade04 100644 --- a/src/commonMain/kotlin/com/infendro/cli/command/Execution.kt +++ b/src/commonMain/kotlin/com/infendro/cli/command/Execution.kt @@ -1,20 +1,92 @@ package com.infendro.cli.command import com.infendro.cli.argument.Argument +import com.infendro.cli.exception.run.DuplicateArgumentException +import com.infendro.cli.exception.run.MalformedArgumentException +import com.infendro.cli.exception.run.MissingArgumentException +import com.infendro.cli.exception.run.UnknownArgumentException import kotlin.reflect.KProperty -class Execution( +class Execution private constructor( val commands: List, val arguments: List, ) { + companion object { + fun from( + command: Command, + cmd: List, + arg: List, + ): Execution { + val commands = Cmd.from(cmd) + val arguments = Arg.from(command, arg) + return Execution(commands, arguments) + } + } + class Cmd( val name: String, - ) + ) { + companion object { + fun from(cmd: List): List = cmd.map { Cmd(it) } + } + } class Arg( val name: String, val value: String?, - ) + ) { + companion object { + fun from(command: Command, arg: List): List = buildList { + var i = 0 + while (i < arg.size) { + val dashes = arg[i].takeWhile { it == '-' }.count() + val trimmed = arg[i].substring(dashes) + + if (dashes !in 1..2 || trimmed.isEmpty()) + throw MalformedArgumentException(arg[i]) + + val (name, value) = when { + trimmed.contains('=') -> { + val (name, value) = trimmed.split('=', limit = 2) + name to value + } + + else -> { + val next = arg.getOrNull(i + 1) + if (next != null && !next.startsWith('-')) { + i++ + trimmed to next + } else { + trimmed to null + } + } + } + + val names = when { + dashes == 1 -> name.map { "$it" } + else -> listOf(name) + } + + for (name in names) { + if (command.arguments.none { name in it.names }) + throw UnknownArgumentException(name) + + //TODO parse early + add(Arg(name, value)) + } + i++ + } + + for (argument in command.arguments) { + val count = count { it.name in argument.names } + when { + argument.required && count == 0 -> throw MissingArgumentException(argument.name) + count > 1 -> throw DuplicateArgumentException(argument.name) + } + } + } + } + } operator fun Command.Key.getValue( thisRef: Any?, @@ -24,24 +96,18 @@ class Execution( operator fun Argument.Single.getValue( thisRef: Any?, property: KProperty<*>, - ): T = arguments.single { it.name == name } + ): T = arguments.single { it.name in names } .let { parser.parse(it.value) } operator fun Argument.SingleOrElse.getValue( thisRef: Any?, property: KProperty<*>, - ): T = arguments.singleOrNull { it.name == name } + ): T = arguments.singleOrNull { it.name in names } ?.let { parser.parse(it.value) } ?: other operator fun Argument.SingleOrNull.getValue( thisRef: Any?, property: KProperty<*>, - ): T? = arguments.singleOrNull { it.name == name } + ): T? = arguments.singleOrNull { it.name in names } ?.let { parser.parse(it.value) } - - operator fun Argument.Multiple.getValue( - thisRef: Any?, - property: KProperty<*>, - ): List = arguments.filter { it.name == name } - .map { parser.parse(it.value) } } diff --git a/src/commonMain/kotlin/com/infendro/cli/exception/CliException.kt b/src/commonMain/kotlin/com/infendro/cli/exception/CliException.kt new file mode 100644 index 0000000..913de16 --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/cli/exception/CliException.kt @@ -0,0 +1,3 @@ +package com.infendro.cli.exception + +abstract class CliException : Exception() diff --git a/src/commonMain/kotlin/com/infendro/cli/exception/build/NoArgumentNameException.kt b/src/commonMain/kotlin/com/infendro/cli/exception/build/NoArgumentNameException.kt new file mode 100644 index 0000000..b8692ea --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/cli/exception/build/NoArgumentNameException.kt @@ -0,0 +1,3 @@ +package com.infendro.cli.exception.build + +class NoArgumentNameException : Exception() diff --git a/src/commonMain/kotlin/com/infendro/cli/exception/run/ArgumentCountException.kt b/src/commonMain/kotlin/com/infendro/cli/exception/run/ArgumentCountException.kt deleted file mode 100644 index bef1654..0000000 --- a/src/commonMain/kotlin/com/infendro/cli/exception/run/ArgumentCountException.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.infendro.cli.exception.run - -class ArgumentCountException( - val argument: String, - val min: Int, - val max: Int, -) : Exception() diff --git a/src/commonMain/kotlin/com/infendro/cli/exception/run/DuplicateArgumentException.kt b/src/commonMain/kotlin/com/infendro/cli/exception/run/DuplicateArgumentException.kt new file mode 100644 index 0000000..1e33ac3 --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/cli/exception/run/DuplicateArgumentException.kt @@ -0,0 +1,10 @@ +package com.infendro.cli.exception.run + +import com.infendro.cli.exception.CliException + +class DuplicateArgumentException( + val argument: String, +) : CliException() { + override val message: String + get() = """duplicate argument "$argument"""" +} diff --git a/src/commonMain/kotlin/com/infendro/cli/exception/run/InvalidArgumentException.kt b/src/commonMain/kotlin/com/infendro/cli/exception/run/InvalidArgumentException.kt deleted file mode 100644 index 01b73f6..0000000 --- a/src/commonMain/kotlin/com/infendro/cli/exception/run/InvalidArgumentException.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.infendro.cli.exception.run - -class InvalidArgumentException( - val argument: String, -) : Exception() diff --git a/src/commonMain/kotlin/com/infendro/cli/exception/run/MalformedArgumentException.kt b/src/commonMain/kotlin/com/infendro/cli/exception/run/MalformedArgumentException.kt new file mode 100644 index 0000000..50e39f4 --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/cli/exception/run/MalformedArgumentException.kt @@ -0,0 +1,10 @@ +package com.infendro.cli.exception.run + +import com.infendro.cli.exception.CliException + +class MalformedArgumentException( + val argument: String, +) : CliException() { + override val message: String + get() = """malformed argument "$argument"""" +} diff --git a/src/commonMain/kotlin/com/infendro/cli/exception/run/MissingArgumentException.kt b/src/commonMain/kotlin/com/infendro/cli/exception/run/MissingArgumentException.kt new file mode 100644 index 0000000..49fab78 --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/cli/exception/run/MissingArgumentException.kt @@ -0,0 +1,10 @@ +package com.infendro.cli.exception.run + +import com.infendro.cli.exception.CliException + +class MissingArgumentException( + val argument: String, +) : CliException() { + override val message: String + get() = """missing argument "$argument"""" +} diff --git a/src/commonMain/kotlin/com/infendro/cli/exception/run/ParseException.kt b/src/commonMain/kotlin/com/infendro/cli/exception/run/ParseException.kt new file mode 100644 index 0000000..b6966f0 --- /dev/null +++ b/src/commonMain/kotlin/com/infendro/cli/exception/run/ParseException.kt @@ -0,0 +1,11 @@ +package com.infendro.cli.exception.run + +import com.infendro.cli.exception.CliException + +class ParseException( + val text: String?, + val type: String, +) : CliException() { + override val message: String + get() = """"$text" cannot be converted to $type""" +} diff --git a/src/commonMain/kotlin/com/infendro/cli/exception/run/UnknownArgumentException.kt b/src/commonMain/kotlin/com/infendro/cli/exception/run/UnknownArgumentException.kt index 4c7d9ab..8cf98a2 100644 --- a/src/commonMain/kotlin/com/infendro/cli/exception/run/UnknownArgumentException.kt +++ b/src/commonMain/kotlin/com/infendro/cli/exception/run/UnknownArgumentException.kt @@ -1,5 +1,10 @@ package com.infendro.cli.exception.run +import com.infendro.cli.exception.CliException + class UnknownArgumentException( val argument: String, -) : Exception() +) : CliException() { + override val message: String + get() = """unknown argument "$argument"""" +} diff --git a/src/commonMain/kotlin/com/infendro/cli/exception/run/UnknownCommandException.kt b/src/commonMain/kotlin/com/infendro/cli/exception/run/UnknownCommandException.kt index 780f916..795a923 100644 --- a/src/commonMain/kotlin/com/infendro/cli/exception/run/UnknownCommandException.kt +++ b/src/commonMain/kotlin/com/infendro/cli/exception/run/UnknownCommandException.kt @@ -1,5 +1,10 @@ package com.infendro.cli.exception.run +import com.infendro.cli.exception.CliException + class UnknownCommandException( val command: String, -) : Exception() +) : CliException() { + override val message: String + get() = """unknown command "$command"""" +} diff --git a/src/commonMain/kotlin/com/infendro/cli/util/Regex.kt b/src/commonMain/kotlin/com/infendro/cli/util/Regex.kt index b0fc020..b7cd051 100644 --- a/src/commonMain/kotlin/com/infendro/cli/util/Regex.kt +++ b/src/commonMain/kotlin/com/infendro/cli/util/Regex.kt @@ -1,7 +1,6 @@ package com.infendro.cli.util internal object Regex { - val COMMAND_NAME = """[a-z]+(?:-[a-z]+)*""".toRegex() - val ARGUMENT_NAME = """[a-z]+(?:[\-.][a-z]+)*""".toRegex() - val ARGUMENT = """--(?[a-z]+(?:[\-.][a-z]+)*)(?:=(?.*))?""".toRegex() + val COMMAND_NAME = Regex("""[a-z]+(?:-[a-z]+)*""") + val ARGUMENT_NAME = Regex("""[a-z]+(?:[\-.][a-z]+)*""") }