Skip to content

Commit c0475d9

Browse files
committed
Factor out help logic
1 parent f79bef6 commit c0475d9

File tree

1 file changed

+89
-88
lines changed

1 file changed

+89
-88
lines changed

library/src/scala/annotation/newMain.scala

+89-88
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ final class newMain extends MainAnnotation[FromString, Any]:
7777

7878
private inline val maxUsageLineLength = 120
7979

80-
private var info: Info = _ // TODO remove this var
80+
private var help: Help = _
8181

8282
private def getAliases(param: Parameter): Seq[String] =
8383
param.annotations.collect{ case a: Alias => a }.flatMap(_.aliases)
@@ -107,27 +107,19 @@ final class newMain extends MainAnnotation[FromString, Any]:
107107
getAliases(param).filter(name => !nameIsValid(name) && !shortNameIsValid(name))
108108

109109
def command(info: Info, args: Seq[String]): Option[Seq[String]] =
110-
this.info = info
110+
help = new Help(info)
111+
val canonicalNames = CanonicalNames(info)
111112

112113
val errors = new mutable.ArrayBuffer[String]
113114

114115
def error(msg: String): Unit = {
115116
errors += msg
116117
}
117118

118-
val canonicalNames = CanonicalNames(info)
119-
120-
val helpIsOverridden = canonicalNames.getName(helpArg).isDefined
121-
val shortHelpIsOverridden = canonicalNames.getShortName(shortHelpArg).isDefined
122-
123-
val displayHelp =
124-
(!helpIsOverridden && args.contains(getNameWithMarker(helpArg))) ||
125-
(!shortHelpIsOverridden && args.contains(getNameWithMarker(shortHelpArg)))
126-
127-
if displayHelp then
128-
usage()
119+
if Help.hasHelpArg(canonicalNames, args) then
120+
help.printUsage()
129121
println()
130-
explain()
122+
help.printExplain()
131123
None
132124
else
133125
val (positionalArgs, byNameArgs, invalidByNameArgs) = {
@@ -212,94 +204,103 @@ final class newMain extends MainAnnotation[FromString, Any]:
212204

213205
if errors.nonEmpty then
214206
for msg <- errors do println(s"Error: $msg")
215-
usage()
207+
help.printUsage()
216208
None
217209
else
218210
Some(argStrings.flatten)
219211
end if
220212
end command
221213

222-
private def usage(): Unit =
223-
def argsUsage: Seq[String] =
224-
for (infos <- info.parameters)
225-
yield {
226-
val canonicalName = getNameWithMarker(infos.name)
227-
val shortNames = getShortNames(infos).map(getNameWithMarker)
228-
val alternativeNames = getAlternativeNames(infos).map(getNameWithMarker)
229-
val namesPrint = (canonicalName +: alternativeNames ++: shortNames).mkString("[", " | ", "]")
230-
val shortTypeName = infos.typeName.split('.').last
231-
if infos.isVarargs then s"[<$shortTypeName> [<$shortTypeName> [...]]]"
232-
else if infos.hasDefault then s"[$namesPrint <$shortTypeName>]"
233-
else s"$namesPrint <$shortTypeName>"
234-
}
235-
236-
def wrapArgumentUsages(argsUsage: Seq[String], maxLength: Int): Seq[String] = {
237-
def recurse(args: Seq[String], currentLine: String, acc: Vector[String]): Seq[String] =
238-
(args, currentLine) match {
239-
case (Nil, "") => acc
240-
case (Nil, l) => (acc :+ l)
241-
case (arg +: t, "") => recurse(t, arg, acc)
242-
case (arg +: t, l) if l.length + 1 + arg.length <= maxLength => recurse(t, s"$l $arg", acc)
243-
case (arg +: t, l) => recurse(t, arg, acc :+ l)
214+
private class Help(info: Info):
215+
216+
def printUsage(): Unit =
217+
def argsUsage: Seq[String] =
218+
for (infos <- info.parameters)
219+
yield {
220+
val canonicalName = getNameWithMarker(infos.name)
221+
val shortNames = getShortNames(infos).map(getNameWithMarker)
222+
val alternativeNames = getAlternativeNames(infos).map(getNameWithMarker)
223+
val namesPrint = (canonicalName +: alternativeNames ++: shortNames).mkString("[", " | ", "]")
224+
val shortTypeName = infos.typeName.split('.').last
225+
if infos.isVarargs then s"[<$shortTypeName> [<$shortTypeName> [...]]]"
226+
else if infos.hasDefault then s"[$namesPrint <$shortTypeName>]"
227+
else s"$namesPrint <$shortTypeName>"
244228
}
245229

246-
recurse(argsUsage, "", Vector()).toList
247-
}
230+
def wrapArgumentUsages(argsUsage: Seq[String], maxLength: Int): Seq[String] = {
231+
def recurse(args: Seq[String], currentLine: String, acc: Vector[String]): Seq[String] =
232+
(args, currentLine) match {
233+
case (Nil, "") => acc
234+
case (Nil, l) => (acc :+ l)
235+
case (arg +: t, "") => recurse(t, arg, acc)
236+
case (arg +: t, l) if l.length + 1 + arg.length <= maxLength => recurse(t, s"$l $arg", acc)
237+
case (arg +: t, l) => recurse(t, arg, acc :+ l)
238+
}
248239

249-
val usageBeginning = s"Usage: ${info.name} "
250-
val argsOffset = usageBeginning.length
251-
val usages = wrapArgumentUsages(argsUsage, maxUsageLineLength - argsOffset)
240+
recurse(argsUsage, "", Vector()).toList
241+
}
252242

253-
println(usageBeginning + usages.mkString("\n" + " " * argsOffset))
254-
end usage
243+
val printUsageBeginning = s"Usage: ${info.name} "
244+
val argsOffset = printUsageBeginning.length
245+
val printUsages = wrapArgumentUsages(argsUsage, maxUsageLineLength - argsOffset)
255246

256-
private def explain(): Unit =
257-
inline def shiftLines(s: Seq[String], shift: Int): String = s.map(" " * shift + _).mkString("\n")
247+
println(printUsageBeginning + printUsages.mkString("\n" + " " * argsOffset))
248+
end printUsage
258249

259-
def wrapLongLine(line: String, maxLength: Int): List[String] = {
260-
def recurse(s: String, acc: Vector[String]): Seq[String] =
261-
val lastSpace = s.trim.nn.lastIndexOf(' ', maxLength)
262-
if ((s.length <= maxLength) || (lastSpace < 0))
263-
acc :+ s
264-
else {
265-
val (shortLine, rest) = s.splitAt(lastSpace)
266-
recurse(rest.trim.nn, acc :+ shortLine)
267-
}
250+
def printExplain(): Unit =
251+
def shiftLines(s: Seq[String], shift: Int): String = s.map(" " * shift + _).mkString("\n")
268252

269-
recurse(line, Vector()).toList
270-
}
253+
def wrapLongLine(line: String, maxLength: Int): List[String] = {
254+
def recurse(s: String, acc: Vector[String]): Seq[String] =
255+
val lastSpace = s.trim.nn.lastIndexOf(' ', maxLength)
256+
if ((s.length <= maxLength) || (lastSpace < 0))
257+
acc :+ s
258+
else {
259+
val (shortLine, rest) = s.splitAt(lastSpace)
260+
recurse(rest.trim.nn, acc :+ shortLine)
261+
}
271262

272-
if (info.documentation.nonEmpty)
273-
println(wrapLongLine(info.documentation, maxUsageLineLength).mkString("\n"))
274-
if (info.parameters.nonEmpty) {
275-
val argNameShift = 2
276-
val argDocShift = argNameShift + 2
277-
278-
println("Arguments:")
279-
for infos <- info.parameters do
280-
val canonicalName = getNameWithMarker(infos.name)
281-
val shortNames = getShortNames(infos).map(getNameWithMarker)
282-
val alternativeNames = getAlternativeNames(infos).map(getNameWithMarker)
283-
val otherNames = (alternativeNames ++: shortNames) match {
284-
case Seq() => ""
285-
case names => names.mkString("(", ", ", ") ")
286-
}
287-
val argDoc = StringBuilder(" " * argNameShift)
288-
argDoc.append(s"$canonicalName $otherNames- ${infos.typeName.split('.').last}")
289-
if infos.isVarargs then argDoc.append(" (vararg)")
290-
else if infos.hasDefault then argDoc.append(" (optional)")
291-
292-
if (infos.documentation.nonEmpty) {
293-
val shiftedDoc =
294-
infos.documentation.split("\n").nn
295-
.map(line => shiftLines(wrapLongLine(line.nn, maxUsageLineLength - argDocShift), argDocShift))
296-
.mkString("\n")
297-
argDoc.append("\n").append(shiftedDoc)
298-
}
263+
recurse(line, Vector()).toList
264+
}
299265

300-
println(argDoc)
301-
}
302-
end explain
266+
if (info.documentation.nonEmpty)
267+
println(wrapLongLine(info.documentation, maxUsageLineLength).mkString("\n"))
268+
if (info.parameters.nonEmpty) {
269+
val argNameShift = 2
270+
val argDocShift = argNameShift + 2
271+
272+
println("Arguments:")
273+
for infos <- info.parameters do
274+
val canonicalName = getNameWithMarker(infos.name)
275+
val shortNames = getShortNames(infos).map(getNameWithMarker)
276+
val alternativeNames = getAlternativeNames(infos).map(getNameWithMarker)
277+
val otherNames = (alternativeNames ++: shortNames) match {
278+
case Seq() => ""
279+
case names => names.mkString("(", ", ", ") ")
280+
}
281+
val argDoc = StringBuilder(" " * argNameShift)
282+
argDoc.append(s"$canonicalName $otherNames- ${infos.typeName.split('.').last}")
283+
if infos.isVarargs then argDoc.append(" (vararg)")
284+
else if infos.hasDefault then argDoc.append(" (optional)")
285+
286+
if (infos.documentation.nonEmpty) {
287+
val shiftedDoc =
288+
infos.documentation.split("\n").nn
289+
.map(line => shiftLines(wrapLongLine(line.nn, maxUsageLineLength - argDocShift), argDocShift))
290+
.mkString("\n")
291+
argDoc.append("\n").append(shiftedDoc)
292+
}
293+
294+
println(argDoc)
295+
}
296+
end printExplain
297+
298+
private object Help:
299+
def hasHelpArg(canonicalNames: CanonicalNames, args: Seq[String]): Boolean =
300+
val helpIsOverridden = canonicalNames.getName(helpArg).isDefined
301+
val shortHelpIsOverridden = canonicalNames.getShortName(shortHelpArg).isDefined
302+
(!helpIsOverridden && args.contains(getNameWithMarker(helpArg))) ||
303+
(!shortHelpIsOverridden && args.contains(getNameWithMarker(shortHelpArg)))
303304

304305
def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = {
305306
if arg.nonEmpty then parse[T](param, arg)
@@ -330,7 +331,7 @@ final class newMain extends MainAnnotation[FromString, Any]:
330331
def run(execProgram: () => Any): Unit = {
331332
if parseErrors.nonEmpty then
332333
for msg <- parseErrors do println(s"Error: $msg")
333-
usage()
334+
help.printUsage()
334335
else
335336
execProgram()
336337
}

0 commit comments

Comments
 (0)