feat(optional-option): added optional option
This commit is contained in:
@@ -4,7 +4,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "org.kargs"
|
group = "org.kargs"
|
||||||
version = "1.0.3"
|
version = "1.0.4"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@@ -12,8 +12,8 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation(kotlin("test"))
|
testImplementation(kotlin("test"))
|
||||||
implementation(kotlin("stdlib"))
|
// implementation(kotlin("stdlib"))
|
||||||
implementation(kotlin("reflect"))
|
// implementation(kotlin("reflect"))
|
||||||
|
|
||||||
// JUnit 5 testing dependencies
|
// JUnit 5 testing dependencies
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
|
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
|
||||||
@@ -68,3 +68,4 @@ mavenPublishing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,15 @@ sealed class ArgType<T>(val typeName: String) {
|
|||||||
override fun getValidationDescription(): String = "one of: ${choices.joinToString(", ")}"
|
override fun getValidationDescription(): String = "one of: ${choices.joinToString(", ")}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional value type - can be used as flag or with value
|
||||||
|
*/
|
||||||
|
class OptionalValue(val defaultWhenPresent: String = "true") : ArgType<String>("OptionalValue") {
|
||||||
|
override fun convert(value: String): String = value
|
||||||
|
|
||||||
|
override fun getValidationDescription(): String = "optional value or flag"
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val String: ArgType<kotlin.String> = StringType
|
val String: ArgType<kotlin.String> = StringType
|
||||||
val Int: ArgType<kotlin.Int> = IntType
|
val Int: ArgType<kotlin.Int> = IntType
|
||||||
@@ -90,5 +99,10 @@ sealed class ArgType<T>(val typeName: String) {
|
|||||||
* Create a choice type from valid options
|
* Create a choice type from valid options
|
||||||
*/
|
*/
|
||||||
fun choice(vararg options: kotlin.String) = Choice(options.toList())
|
fun choice(vararg options: kotlin.String) = Choice(options.toList())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an optional value or flag
|
||||||
|
*/
|
||||||
|
fun optionalValue(defaultWhenPresent: String = "true") = OptionalValue(defaultWhenPresent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
37
src/main/kotlin/org/kargs/OptionalOption.kt
Normal file
37
src/main/kotlin/org/kargs/OptionalOption.kt
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package org.kargs
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option that can be used as a flag or with a value
|
||||||
|
*/
|
||||||
|
class OptionalOption(
|
||||||
|
val longName: String,
|
||||||
|
val shortName: String? = null,
|
||||||
|
description: String? = null,
|
||||||
|
private val defaultWhenPresent: String = "true"
|
||||||
|
) : KargsProperty<String>(description) {
|
||||||
|
private var wasExplicitlySet = false
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(longName.isNotBlank()) { "Long name cannot be blank" }
|
||||||
|
require(!longName.startsWith("-")) { "Long name should not start with dashes" }
|
||||||
|
shortName?.let {
|
||||||
|
require(it.length == 1) { "Short name must be exactly one character" }
|
||||||
|
require(!it.startsWith("-")) { "Short name should not start with dashes" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun parseValue(str: String) {
|
||||||
|
value = str
|
||||||
|
wasExplicitlySet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set as flag (no value provided)
|
||||||
|
*/
|
||||||
|
fun setAsFlag() {
|
||||||
|
value = defaultWhenPresent
|
||||||
|
wasExplicitlySet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isSet(): Boolean = wasExplicitlySet
|
||||||
|
}
|
||||||
@@ -108,6 +108,7 @@ class Parser(
|
|||||||
private fun parseLongOption(cmd: Subcommand, key: String, args: Array<String>, index: Int): Int {
|
private fun parseLongOption(cmd: Subcommand, key: String, args: Array<String>, index: Int): Int {
|
||||||
val option = cmd.options.firstOrNull { it.longName == key }
|
val option = cmd.options.firstOrNull { it.longName == key }
|
||||||
val flag = cmd.flags.firstOrNull { it.longName == key }
|
val flag = cmd.flags.firstOrNull { it.longName == key }
|
||||||
|
val optionalOption = cmd.optionalOptions.firstOrNull { it.longName == key }
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
option != null -> {
|
option != null -> {
|
||||||
@@ -122,6 +123,18 @@ class Parser(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
optionalOption != null -> {
|
||||||
|
// Check if next arg exists and doesn't start with -
|
||||||
|
if (index + 1 < args.size && !args[index + 1].startsWith("-")) {
|
||||||
|
// Has value
|
||||||
|
optionalOption.parseValue(args[index + 1])
|
||||||
|
index + 1
|
||||||
|
} else {
|
||||||
|
// Used as flag
|
||||||
|
optionalOption.setAsFlag()
|
||||||
|
index
|
||||||
|
}
|
||||||
|
}
|
||||||
flag != null -> {
|
flag != null -> {
|
||||||
flag.setFlag()
|
flag.setFlag()
|
||||||
index
|
index
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ abstract class Subcommand(
|
|||||||
private val _options = mutableListOf<Option<*>>()
|
private val _options = mutableListOf<Option<*>>()
|
||||||
private val _flags = mutableListOf<Flag>()
|
private val _flags = mutableListOf<Flag>()
|
||||||
private val _arguments = mutableListOf<Argument<*>>()
|
private val _arguments = mutableListOf<Argument<*>>()
|
||||||
|
private val _optionalOptions = mutableListOf<OptionalOption>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
require(name.isNotBlank()) { "Subcommand name cannot be blank" }
|
require(name.isNotBlank()) { "Subcommand name cannot be blank" }
|
||||||
@@ -30,6 +31,7 @@ abstract class Subcommand(
|
|||||||
is Option<*> -> _options += prop
|
is Option<*> -> _options += prop
|
||||||
is Flag -> _flags += prop
|
is Flag -> _flags += prop
|
||||||
is Argument<*> -> _arguments += prop
|
is Argument<*> -> _arguments += prop
|
||||||
|
is OptionalOption -> _optionalOptions += prop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +39,7 @@ abstract class Subcommand(
|
|||||||
val options: List<Option<*>> get() = _options
|
val options: List<Option<*>> get() = _options
|
||||||
val flags: List<Flag> get() = _flags
|
val flags: List<Flag> get() = _flags
|
||||||
val arguments: List<Argument<*>> get() = _arguments
|
val arguments: List<Argument<*>> get() = _arguments
|
||||||
|
val optionalOptions: List<OptionalOption> get() = _optionalOptions
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute this subcommand - must be implemented by subclasses
|
* Execute this subcommand - must be implemented by subclasses
|
||||||
@@ -70,6 +73,18 @@ abstract class Subcommand(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (optionalOptions.isNotEmpty()) {
|
||||||
|
println()
|
||||||
|
println("Optional Value Options:")
|
||||||
|
optionalOptions.forEach { option ->
|
||||||
|
val shortName = option.shortName?.let { "-$it, " } ?: " "
|
||||||
|
println(" $shortName--${option.longName} [value]")
|
||||||
|
option.description?.let { desc ->
|
||||||
|
println(" $desc (can be used as flag or with value)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (flags.isNotEmpty()) {
|
if (flags.isNotEmpty()) {
|
||||||
println()
|
println()
|
||||||
println("Flags:")
|
println("Flags:")
|
||||||
@@ -117,12 +132,13 @@ abstract class Subcommand(
|
|||||||
*/
|
*/
|
||||||
private fun getTypeInfo(type: ArgType<*>): String {
|
private fun getTypeInfo(type: ArgType<*>): String {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
is ArgType.StringType -> ""
|
is ArgType.StringType -> " <string>"
|
||||||
is ArgType.IntType -> " <int>"
|
is ArgType.IntType -> " <int>"
|
||||||
is ArgType.DoubleType -> " <double>"
|
is ArgType.DoubleType -> " <double>"
|
||||||
is ArgType.BooleanType -> " <bool>"
|
is ArgType.BooleanType -> " <bool>"
|
||||||
is ArgType.IntRange -> " <${type.min}-${type.max}>"
|
is ArgType.IntRange -> " <${type.min}-${type.max}>"
|
||||||
is ArgType.Choice -> " <${type.choices.joinToString("|")}>"
|
is ArgType.Choice -> " <${type.choices.joinToString("|")}>"
|
||||||
|
is ArgType.OptionalValue -> " [string]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user