Skip to content

Commit d50e119

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

File tree

1 file changed

+90
-87
lines changed

1 file changed

+90
-87
lines changed

library/src/scala/annotation/newMain.scala

Lines changed: 90 additions & 87 deletions
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,105 @@ 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-
}
214+
private class Help(info: Info):
215+
235216

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)
217+
218+
def printUsage(): Unit =
219+
def argsUsage: Seq[String] =
220+
for (infos <- info.parameters)
221+
yield {
222+
val canonicalName = getNameWithMarker(infos.name)
223+
val shortNames = getShortNames(infos).map(getNameWithMarker)
224+
val alternativeNames = getAlternativeNames(infos).map(getNameWithMarker)
225+
val namesPrint = (canonicalName +: alternativeNames ++: shortNames).mkString("[", " | ", "]")
226+
val shortTypeName = infos.typeName.split('.').last
227+
if infos.isVarargs then s"[<$shortTypeName> [<$shortTypeName> [...]]]"
228+
else if infos.hasDefault then s"[$namesPrint <$shortTypeName>]"
229+
else s"$namesPrint <$shortTypeName>"
244230
}
245231

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

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

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

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

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-
}
252+
def printExplain(): Unit =
253+
def shiftLines(s: Seq[String], shift: Int): String = s.map(" " * shift + _).mkString("\n")
268254

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

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-
}
265+
recurse(line, Vector()).toList
266+
}
299267

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

304307
def argGetter[T](param: Parameter, arg: String, defaultArgument: Option[() => T])(using p: FromString[T]): () => T = {
305308
if arg.nonEmpty then parse[T](param, arg)
@@ -330,7 +333,7 @@ final class newMain extends MainAnnotation[FromString, Any]:
330333
def run(execProgram: () => Any): Unit = {
331334
if parseErrors.nonEmpty then
332335
for msg <- parseErrors do println(s"Error: $msg")
333-
usage()
336+
help.printUsage()
334337
else
335338
execProgram()
336339
}

0 commit comments

Comments
 (0)