Skip to content

Commit e4d43bf

Browse files
committed
Split ArgParser.kt into separate files
Also move sources to package-named dirs.
1 parent ca753b8 commit e4d43bf

File tree

12 files changed

+701
-515
lines changed

12 files changed

+701
-515
lines changed

src/main/kotlin/ArgParser.kt renamed to src/main/kotlin/com/xenomachina/argparser/ArgParser.kt

Lines changed: 16 additions & 500 deletions
Large diffs are not rendered by default.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright © 2016 Laurence Gonsalves
2+
//
3+
// This file is part of kotlin-argparser, a library which can be found at
4+
// http://github.com/xenomachina/kotlin-argparser
5+
//
6+
// This library is free software; you can redistribute it and/or modify it
7+
// under the terms of the GNU Lesser General Public License as published by the
8+
// Free Software Foundation; either version 2.1 of the License, or (at your
9+
// option) any later version.
10+
//
11+
// This library is distributed in the hope that it will be useful, but WITHOUT
12+
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
14+
// for more details.
15+
//
16+
// You should have received a copy of the GNU Lesser General Public License
17+
// along with this library; if not, see http://www.gnu.org/licenses/
18+
19+
package com.xenomachina.argparser
20+
21+
import com.xenomachina.common.Holder
22+
23+
/**
24+
* Returns a new `DelegateProvider` with the specified default value.
25+
*
26+
* @param newDefault the default value for the resulting [ArgParser.Delegate]
27+
*/
28+
fun <T> ArgParser.DelegateProvider<T>.default(newDefault: T): ArgParser.DelegateProvider<T> {
29+
return ArgParser.DelegateProvider(ctor = ctor, defaultHolder = Holder(newDefault))
30+
}
31+
32+
/**
33+
* Returns a new `Delegate` with the specified default value.
34+
*
35+
* @param newDefault the default value for the resulting [ArgParser.Delegate]
36+
*/
37+
fun <T> ArgParser.Delegate<T>.default(defaultValue: T): ArgParser.Delegate<T> {
38+
val inner = this
39+
40+
return object : ArgParser.Delegate<T>() {
41+
override fun toHelpFormatterValue(): HelpFormatter.Value = inner.toHelpFormatterValue().copy(isRequired = false)
42+
43+
override fun validate() {
44+
inner.validate()
45+
}
46+
47+
override val parser: ArgParser
48+
get() = inner.parser
49+
50+
override val value: T
51+
get() {
52+
inner.parser.force()
53+
return if (inner.hasValue) inner.value else defaultValue
54+
}
55+
56+
override val hasValue: Boolean
57+
get() = true
58+
59+
override val errorName: String
60+
get() = inner.errorName
61+
62+
override val help: String
63+
get() = inner.help
64+
65+
override fun addValidator(validator: ArgParser.Delegate<T>.() -> Unit): ArgParser.Delegate<T> =
66+
inner.addValidator() { validator(inner) }
67+
68+
override fun registerLeaf(root: ArgParser.Delegate<*>) {
69+
inner.registerLeaf(root)
70+
}
71+
}
72+
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
// Copyright © 2016 Laurence Gonsalves
2+
//
3+
// This file is part of kotlin-argparser, a library which can be found at
4+
// http://github.com/xenomachina/kotlin-argparser
5+
//
6+
// This library is free software; you can redistribute it and/or modify it
7+
// under the terms of the GNU Lesser General Public License as published by the
8+
// Free Software Foundation; either version 2.1 of the License, or (at your
9+
// option) any later version.
10+
//
11+
// This library is distributed in the hope that it will be useful, but WITHOUT
12+
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
14+
// for more details.
15+
//
16+
// You should have received a copy of the GNU Lesser General Public License
17+
// along with this library; if not, see http://www.gnu.org/licenses/
18+
19+
package com.xenomachina.argparser
20+
21+
import com.xenomachina.text.NBSP_CODEPOINT
22+
import com.xenomachina.text.term.codePointWidth
23+
import com.xenomachina.text.term.columnize
24+
import com.xenomachina.text.term.wrapText
25+
26+
/**
27+
* Default implementation of [HelpFormatter]. Output is modelled after that of common UNIX utilities and looks
28+
* something like this:
29+
*
30+
* ```
31+
* usage: program_name [-h] [-n] [-I INCLUDE]... -o OUTPUT
32+
* [-v]... SOURCE... DEST
33+
*
34+
* Does something really useful.
35+
*
36+
* required arguments:
37+
* -o OUTPUT, directory in which all output should
38+
* --output OUTPUT be generated
39+
*
40+
* optional arguments:
41+
* -h, --help show this help message and exit
42+
*
43+
* -n, --dry-run don't do anything
44+
*
45+
* -I INCLUDE, search in this directory for header
46+
* --include INCLUDE files
47+
*
48+
* -v, --verbose increase verbosity
49+
*
50+
* positional arguments:
51+
* SOURCE source file
52+
*
53+
* DEST destination file
54+
*
55+
* More info is available at http://program-name.example.com/
56+
* ```
57+
*
58+
* @property prologue Text that should appear near the beginning of the help, immediately after the usage summary.
59+
* @property epilogue Text that should appear at the end of the help.
60+
*/
61+
class DefaultHelpFormatter(
62+
val prologue: String? = null,
63+
val epilogue: String? = null
64+
) : HelpFormatter {
65+
val indent = " "
66+
val indentWidth = indent.codePointWidth()
67+
68+
override fun format(
69+
progName: String?,
70+
columns: Int,
71+
values: List<HelpFormatter.Value>
72+
): String {
73+
val sb = StringBuilder()
74+
appendUsage(sb, columns, progName, values)
75+
sb.append("\n")
76+
77+
if (!prologue.isNullOrEmpty()) {
78+
sb.append("\n")
79+
// we just checked that prologue is non-null
80+
sb.append(prologue!!.wrapText(columns))
81+
sb.append("\n")
82+
}
83+
84+
val required = mutableListOf<HelpFormatter.Value>()
85+
val optional = mutableListOf<HelpFormatter.Value>()
86+
val positional = mutableListOf<HelpFormatter.Value>()
87+
88+
for (value in values) {
89+
when {
90+
value.isPositional -> positional
91+
value.isRequired -> required
92+
else -> optional
93+
}.add(value)
94+
}
95+
// Make left column as narrow as possible without wrapping any of the individual usages, though no wider than
96+
// half the screen.
97+
val usageColumns = (2 * indentWidth - 1 + (
98+
values.map { usageText(it).split(" ").map { it.length }.max() ?: 0 }.max() ?: 0)
99+
).coerceAtMost(columns / 2)
100+
appendSection(sb, usageColumns, columns, "required", required)
101+
appendSection(sb, usageColumns, columns, "optional", optional)
102+
appendSection(sb, usageColumns, columns, "positional", positional)
103+
104+
if (!epilogue?.trim().isNullOrEmpty()) {
105+
sb.append("\n")
106+
// we just checked that epilogue is non-null
107+
sb.append(epilogue!!.trim().wrapText(columns))
108+
sb.append("\n")
109+
}
110+
111+
return sb.toString()
112+
}
113+
114+
private fun appendSection(
115+
sb: StringBuilder,
116+
usageColumns: Int,
117+
columns: Int,
118+
name: String,
119+
values: List<HelpFormatter.Value>
120+
) {
121+
122+
if (!values.isEmpty()) {
123+
sb.append("\n")
124+
sb.append("$name arguments:\n")
125+
for (value in values) {
126+
val left = usageText(value).wrapText(usageColumns - indentWidth).prependIndent(indent)
127+
val right = value.help.wrapText(columns - usageColumns - 2 * indentWidth).prependIndent(indent)
128+
sb.append(columnize(left, right, minWidths = intArrayOf(usageColumns)))
129+
sb.append("\n\n")
130+
}
131+
}
132+
}
133+
134+
private fun usageText(value: HelpFormatter.Value) =
135+
value.usages.map { it.replace(' ', '\u00a0') }.joinToString(", ")
136+
137+
private fun appendUsage(sb: StringBuilder, columns: Int, progName: String?, values: List<HelpFormatter.Value>) {
138+
var usageStart = USAGE_PREFIX + (if (progName != null) " $progName" else "")
139+
140+
val valueSB = StringBuilder()
141+
for (value in values) value.run {
142+
if (!usages.isEmpty()) {
143+
val usage = usages[0].replace(' ', NBSP_CODEPOINT.toChar())
144+
if (isRequired) {
145+
valueSB.append(" $usage")
146+
} else {
147+
valueSB.append(" [$usage]")
148+
}
149+
if (isRepeating) {
150+
valueSB.append("...")
151+
}
152+
}
153+
}
154+
155+
if (usageStart.length > columns / 2) {
156+
sb.append(usageStart)
157+
sb.append("\n")
158+
val valueIndent = (USAGE_PREFIX + " " + indent).codePointWidth()
159+
val valueColumns = columns - valueIndent
160+
sb.append(valueSB.toString().wrapText(valueColumns).prependIndent(" ".repeat(valueIndent)))
161+
} else {
162+
usageStart += " "
163+
val valueColumns = columns - usageStart.length
164+
sb.append(columnize(usageStart, valueSB.toString().wrapText(valueColumns)))
165+
}
166+
}
167+
}
168+
169+
private const val USAGE_PREFIX = "usage:"
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright © 2016 Laurence Gonsalves
2+
//
3+
// This file is part of kotlin-argparser, a library which can be found at
4+
// http://github.com/xenomachina/kotlin-argparser
5+
//
6+
// This library is free software; you can redistribute it and/or modify it
7+
// under the terms of the GNU Lesser General Public License as published by the
8+
// Free Software Foundation; either version 2.1 of the License, or (at your
9+
// option) any later version.
10+
//
11+
// This library is distributed in the hope that it will be useful, but WITHOUT
12+
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
14+
// for more details.
15+
//
16+
// You should have received a copy of the GNU Lesser General Public License
17+
// along with this library; if not, see http://www.gnu.org/licenses/
18+
19+
package com.xenomachina.argparser
20+
21+
import java.io.Writer
22+
23+
/**
24+
* Indicates that the user requested that help should be shown (with the
25+
* `--help` option, for example).
26+
*/
27+
class ShowHelpException internal constructor(
28+
private val helpFormatter: HelpFormatter,
29+
private val delegates: List<ArgParser.Delegate<*>>
30+
) : SystemExitException("Help was requested", 0) {
31+
override fun printUserMessage(writer: Writer, progName: String?, columns: Int) {
32+
writer.write(helpFormatter.format(progName, columns, delegates.map { it.toHelpFormatterValue() }))
33+
}
34+
}
35+
36+
/**
37+
* Indicates that an unrecognized option was supplied.
38+
*
39+
* @property optName the name of the option
40+
*/
41+
open class UnrecognizedOptionException(val optName: String) :
42+
SystemExitException("unrecognized option '$optName'", 2)
43+
44+
/**
45+
* Indicates that a value is missing after parsing has completed.
46+
*
47+
* @property valueName the name of the missing value
48+
*/
49+
open class MissingValueException(val valueName: String) :
50+
SystemExitException("missing $valueName", 2)
51+
52+
/**
53+
* Indicates that the value of a supplied argument is invalid.
54+
*/
55+
open class InvalidArgumentException(message: String) : SystemExitException(message, 2)
56+
57+
/**
58+
* Indicates that a required option argument was not supplied.
59+
*
60+
* @property optName the name of the option
61+
* @property argName the name of the missing argument, or null
62+
*/
63+
open class OptionMissingRequiredArgumentException(val optName: String, val argName: String? = null) :
64+
SystemExitException("option '$optName' is missing "
65+
+ (if (argName == null) "a required argument" else "the required argument $argName"),
66+
2)
67+
68+
/**
69+
* Indicates that a required positional argument was not supplied.
70+
*
71+
* @property argName the name of the positional argument
72+
*/
73+
open class MissingRequiredPositionalArgumentException(val argName: String) :
74+
SystemExitException("missing $argName operand", 2)
75+
76+
/**
77+
* Indicates that an argument was forced upon an option that does not take one.
78+
*
79+
* For example, if the arguments contained "--foo=bar" and the "--foo" option does not consume any arguments.
80+
*
81+
* @property optName the name of the option
82+
*/
83+
open class UnexpectedOptionArgumentException(val optName: String) :
84+
SystemExitException("option '$optName' doesn't allow an argument", 2)
85+
86+
/**
87+
* Indicates that there is an unhandled positional argument.
88+
*
89+
* @property valueName the name of the missing value
90+
*/
91+
open class UnexpectedPositionalArgumentException(val valueName: String?) :
92+
SystemExitException("unexpected argument${if (valueName == null) "" else " after $valueName"}", 2)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright © 2016 Laurence Gonsalves
2+
//
3+
// This file is part of kotlin-argparser, a library which can be found at
4+
// http://github.com/xenomachina/kotlin-argparser
5+
//
6+
// This library is free software; you can redistribute it and/or modify it
7+
// under the terms of the GNU Lesser General Public License as published by the
8+
// Free Software Foundation; either version 2.1 of the License, or (at your
9+
// option) any later version.
10+
//
11+
// This library is distributed in the hope that it will be useful, but WITHOUT
12+
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
14+
// for more details.
15+
//
16+
// You should have received a copy of the GNU Lesser General Public License
17+
// along with this library; if not, see http://www.gnu.org/licenses/
18+
19+
package com.xenomachina.argparser
20+
21+
/**
22+
* Formats help for an [ArgParser].
23+
*/
24+
interface HelpFormatter {
25+
/**
26+
* Formats a help message.
27+
*
28+
* @param progName name of the program as it should appear in usage information, or null if
29+
* program name is unknown.
30+
* @param columns width of display help should be formatted for, measured in character cells.
31+
* @param values [Value] objects describing the arguments types available.
32+
*/
33+
fun format(progName: String?, columns: Int, values: List<Value>): String
34+
35+
/**
36+
* An option or positional argument type which should be formatted for help
37+
*
38+
* @param usages possible usage strings for this argument type
39+
* @param isRequired indicates whether this is required
40+
* @param isRepeating indicates whether it makes sense to repeat this argument
41+
* @param isPositional indicates whether this is a positional argument
42+
* @param help help text provided at Delegate construction time
43+
*/
44+
data class Value(
45+
val usages: List<String>,
46+
val isRequired: Boolean,
47+
val isRepeating: Boolean,
48+
val isPositional: Boolean,
49+
val help: String)
50+
}

0 commit comments

Comments
 (0)