args
Find a file
2025-06-24 22:35:27 -04:00
gradle/wrapper args 2025-03-05 00:49:32 -05:00
src change license 2025-06-24 22:35:27 -04:00
.gitignore gitignore gradle.properties 2025-03-05 22:25:19 -05:00
build.gradle.kts change license 2025-06-24 22:35:27 -04:00
gradlew args 2025-03-05 00:49:32 -05:00
gradlew.bat args 2025-03-05 00:49:32 -05:00
LICENSE change license 2025-06-24 22:35:27 -04:00
README.MD change license 2025-06-24 22:35:27 -04:00
settings.gradle.kts args 2025-03-05 00:49:32 -05:00

Args

Args is a simple library for extracting well-defined key-value style arguments from your runtime program arguments.

Motivation

JVM-based applications do not have a standardized method to extract program arguments passed-in at application runtime boot; they are simply shipped to your main function as an array of strings.

The Args library allows application developers to create well-defined named arguments that have well-defined value types. These arguments and their specified values are parsed by Args and stored in a type-safe map.

Usage

Here is a sample application that uses Args to create well-defined arguments:

class TestSample {
	val nameArg = Argument.Text("name", 'n', Regex("(?:[A-Z][A-Za-z\\-'_]* ?)+"))
	val ageArg = Argument.Integral("age", 'a', 0..120)
	val favoriteSeasonArg = Argument.Text("favoriteSeason", 's', Regex("spring|summer|autumn|winter"))
	val percentGremlinArg = Argument.Decimal("percentGremlin", 'G', 0.0..100.0)
	val isFurryArg = Argument.Flag("isFurry", 'F')
	val boopedArg = Argument.Presence("booped", 'b')

	class MyArgsDelegate(backing: ArgsMap) {
		val name: String? by backing
		val age: Long? by backing
		val favoriteSeason: String? by backing
		val percentGremlin: Double? by backing
		val isFurry: Boolean? by backing
		val booped: Unit? by backing
	}

	val MyArgs = Arguments(nameArg, ageArg, favoriteSeasonArg, percentGremlinArg, isFurryArg, boopedArg)

	fun parseMyArgs(vararg args: CharSequence): MyArgsDelegate =
		MyArgsDelegate(MyArgs.parse(args))

	@Test fun `parse sample with good args`() {
		val delegate = parseMyArgs(
			"--name", "Vivi",
			"--age", "5", // in fox years
			"+s", "autumn",
			"+G", "98.17",
			"+Fb"
		)

		assertEquals("Vivi", delegate.name)
		assertEquals(5L, delegate.age)
		assertEquals("autumn", delegate.favoriteSeason)
		assertEquals(99.0, delegate.percentGremlin!!, 1.0)
		assertNotNull(delegate.booped)
		assertTrue(delegate.isFurry)
	}
}

Declaring Named Arguments

All arguments are defined using one of the Argument subclasses. Common to all argument types, they have an associated "long name", a case-insensitive alphanumeric string that is passed to your application with a -- or ++ prefix; and an optional "short name", a case-sensitive alphanumeric character that is passed to your application with a - or + prefix.

Parsing Input Arguments

Once all desired Argument types are defined, you may package them into an Arguments set for parsing and processing.

On instantiation, Arguments will throw an exception if there are any argument name conflicts. All argument long names must be unique, case-insensitively. Short names, when not null (meaning that argument has no short name), must be unique, case-sensitively. (Flag-type arguments have an additional restriction in that, because the capitalization of its short name is pertinent to its state, their short names must be unique case-insensitively.)

When you receive input application arguments (e.g. those passed to your fun main(vararg args: String) function by your CLI), you may pass them to Arguments.parse (as an Array<CharSequence>) and receive an ArgsMap in return. Note that the parse function does not manage argument term delimiters like spaces; terms in the array must be delimited and assigned to sequential indices in the input Array externally (e.g. as handled by the CLI).

Arguments have a value associated with them. Generally, arguments receive an explicit value in the input term immediately following the argument declaration term in the input Array.

If the input arguments do not conform to the restrictions of the Args specification - e.g., arguments with long/short names not registered with your Arguments set; values not attached to a respective named argument; missing values for argument types that require explicit values; etc. - an exception will be thrown by the parse invocation.

ArgsMap

ArgsMap is a simple type-safe map, using Argument objects as keys, and their resolved associated input values as values. For a given arg: Argument<T>, a call to argsMap.get(arg) (or argsMap[arg]) will return a non-null element of type T if that argument was present in the input, or null if that argument is absent in the input.

Argument Types

Presence (<Unit>)

val boopedArg = Argument.Presence("booped", 'b') //usage: parse("--booped"), parse("-b")

class Presence(longName: String, shortName: Char? = null): Argument<Unit>

Presence-type arguments signal whether or not they were present in the input arguments set. If present, the argument will yield Unit as value.

When specifying input Presence argument by short names, you may group them in one argument term. For example, to signal presence of three Presence arguments with short names 'a', 'b', 'c', you may pass one term "-abc" over input.

Flag (<Boolean>)

val isFurryArg = Argument.Flag("isFurry", 'F')

class Flag(longName: String, shortName: Char? = null): Argument<Boolean>

Flag-type arguments yield a true or false boolean value.

Flag arguments may receive an optional explicit value which always takes precedence over implicit rules: -F true, --isFurry false

When an explicit value is not provided, the following rules govern the flag's implicit value:

  • When specified by long name:
    • A ++ prefix implies true (++isFurry).
    • A -- prefix implies false (--isFurry).
  • When specified by short name:
    • Flags have a "Direct" specification variant (when specified in uppercase or numeric), and an "Inverse" variant (when specified in lowercase).
    • In Direct Specification:
      • A + prefix implies true (+F).
      • A - prefix implies false (-F).
    • In Inverse Specification:
      • A + prefix implies false (+f).
      • A - prefix implies true (-f).

Like Presence arguments, Flag arguments can be specified in short name groups, following the implicit rules above; for instance, +abC would set the values of arguments with short names 'a' and 'b' to false, and 'c' to true.

Integral (<Long>)

val ageArg = Argument.Integral("age", 'a', 0..120)

class Integral(
	longName: String,
	shortName: Char? = null,
	val range: LongRange = Long.MIN_VALUE..Long.MAX_VALUE
): Argument<Long>

Integral-type arguments yield a Long integer value. The argument may restrict the range of input values (inclusive) that are allowed to be assigned to its value. If the input value is out of range, Arguments.parse will throw an exception. Integral arguments require an explicit value to be provided by input.

The value may use any format compatible with String.toLongLenient. If it returns null (malformed input), Arguments.parse will throw an exception.

Decimal (<Double>)

val percentGremlinArg = Argument.Decimal("percentGremlin", 'G', 0.0..100.0)

class Decimal(
	longName: String,
	shortName: Char? = null,
	val range: ClosedFloatingPointRange<Double> = Double.NEGATIVE_INFINITY..Double.POSITIVE_INFINITY
): Argument<Double>

Decimal-type arguments yield a Double floating point value. The argument may restrict the range of input values (inclusive) that are allowed to be assigned to its value. If the input value is out of range, Arguments.parse will throw an exception. Decimal arguments require an explicit value to be provided by input.

The value may use any format compatible with String.toDouble. Exceptions thrown by toDouble caused by malformed input will be rethrown by Arguments.parse.

Text (<String>)

val nameArg = Argument.Text("name", 'n', Regex("(?:[A-Z][A-Za-z\\-'_]* ?)+"))

class Text(
	longName: String,
	shortName: Char? = null,
	val regexRestriction: Regex? = null
): Argument<String>

Text-type arguments yield a String value (i.e., it will return the raw contents of the next index of terms). Text arguments may optionally specify a regexRestriction, which will cause Arguments.parse to throw an exception if the value does not match the Regex pattern.

License

Args by Archandle Studio is marked CC0 1.0.

To view a copy of this mark, please read the LICENSE file or visit https://creativecommons.org/publicdomain/zero/1.0/