@@ -96,89 +96,17 @@ final class newMain extends MainAnnotation[FromString, Any]:
96
96
private def shortNameWithMarker (name : String ): String = shortArgMarker + name
97
97
98
98
def command (info : Info , args : Seq [String ]): Option [Seq [String ]] =
99
- checkNames(info)
100
-
101
99
val canonicalNames = CanonicalNames (info)
102
-
103
- var hasError : Boolean = false
104
- def error (msg : String ): Unit = {
105
- hasError = true
106
- println(s " Error: $msg" )
107
- }
108
-
100
+ canonicalNames.checkNames()
109
101
if Help .hasHelpArg(canonicalNames, args) then
110
- val help = new Help (info)
111
- help.printUsage()
112
- println()
113
- help.printExplain()
102
+ Help .printUsage(info)
103
+ Help .printExplain(info)
114
104
None
115
105
else
116
- val (positionalArgs, byNameArgs) = {
117
- def recurse (remainingArgs : Seq [String ], pa : mutable.Queue [String ], bna : Seq [(String , String )]): (mutable.Queue [String ], Seq [(String , String )]) =
118
- remainingArgs match {
119
- case Seq () =>
120
- (pa, bna)
121
- case (argName @ longArgRegex()) +: argValue +: rest =>
122
- canonicalNames.getName(argName) match
123
- case Some (canonicalName) => recurse(rest, pa, bna :+ (canonicalName -> argValue))
124
- case None =>
125
- error(s " unknown argument name: $argName" )
126
- recurse(rest, pa, bna)
127
- case (argName @ shortArgRegex()) +: argValue +: rest =>
128
- canonicalNames.getShortName(argName) match
129
- case Some (canonicalName) => recurse(rest, pa, bna :+ (canonicalName -> argValue))
130
- case None =>
131
- error(s " unknown argument name: $argName" )
132
- recurse(rest, pa, bna)
133
- case arg +: rest =>
134
- recurse(rest, pa :+ arg, bna)
135
- }
136
-
137
- val (pa, bna) = recurse(args.toSeq, mutable.Queue .empty, Vector ())
138
- val nameToArgValues : Map [String , Seq [String ]] = if bna.isEmpty then Map .empty else bna.groupMapReduce(_._1)(p => List (p._2))(_ ++ _)
139
- (pa, nameToArgValues)
140
- }
141
-
142
- val argStrings : Seq [Seq [String ]] =
143
- for paramInfo <- info.parameters yield {
144
- if (paramInfo.isVarargs) {
145
- val byNameGetters = byNameArgs.getOrElse(paramInfo.name, Seq ())
146
- val positionalGetters = positionalArgs.removeAll()
147
- // First take arguments passed by name, then those passed by position
148
- byNameGetters ++ positionalGetters
149
- } else {
150
- byNameArgs.get(paramInfo.name) match
151
- case Some (Nil ) =>
152
- throw AssertionError (s " ${paramInfo.name} present in byNameArgs, but it has no argument value " )
153
- case Some (argValues) =>
154
- if argValues.length > 1 then
155
- // Do not accept multiple values
156
- // Remove this test to take last given argument
157
- error(s " more than one value for ${paramInfo.name}: ${argValues.mkString(" , " )}" )
158
- Nil
159
- else
160
- List (argValues.last)
161
- case None =>
162
- if positionalArgs.length > 0 then
163
- List (positionalArgs.dequeue())
164
- else if paramInfo.hasDefault then
165
- List (" " )
166
- else
167
- error(s " missing argument for ${paramInfo.name}" )
168
- Nil
169
- }
170
- }
171
-
172
- // Handle unused and invalid args
173
- for (remainingArg <- positionalArgs) error(s " unused argument: $remainingArg" )
174
-
175
- if hasError then
176
- val help = new Help (info)
177
- help.printUsage()
106
+ preProcessArgs(info, canonicalNames, args).orElse {
107
+ Help .printUsage(info)
178
108
None
179
- else
180
- Some (argStrings.flatten)
181
- end if
109
+ }
182
110
end command
183
111
184
112
def argGetter [T ](param : Parameter , arg : String , defaultArgument : Option [() => T ])(using p : FromString [T ]): () => T = {
@@ -197,6 +125,71 @@ final class newMain extends MainAnnotation[FromString, Any]:
197
125
def run (execProgram : () => Any ): Unit =
198
126
if ! hasParseErrors then execProgram()
199
127
128
+ private def preProcessArgs (info : Info , canonicalNames : CanonicalNames , args : Seq [String ]): Option [Seq [String ]] =
129
+ var hasError : Boolean = false
130
+ def error (msg : String ): Unit = {
131
+ hasError = true
132
+ println(s " Error: $msg" )
133
+ }
134
+
135
+ val (positionalArgs, byNameArgsMap) =
136
+ val positionalArgs = List .newBuilder[String ]
137
+ val byNameArgs = List .newBuilder[(String , String )]
138
+ var i = 0
139
+ while i < args.length do
140
+ args(i) match
141
+ case name @ (longArgRegex() | shortArgRegex()) =>
142
+ if i == args.length - 1 then // last argument -x ot --xyz
143
+ error(s " missing argument for ${name}" )
144
+ else args(i + 1 ) match
145
+ case longArgRegex() | shortArgRegex() =>
146
+ error(s " missing argument for ${name}" )
147
+ case value =>
148
+ canonicalNames.getName(name) match
149
+ case Some (canonicalName) =>
150
+ byNameArgs += ((canonicalName, value))
151
+ case None =>
152
+ error(s " unknown argument name: $name" )
153
+ i += 1 // consume `value`
154
+ case value =>
155
+ positionalArgs += value
156
+ i += 1
157
+ end while
158
+ (positionalArgs.result(), byNameArgs.result().groupMap(_._1)(_._2))
159
+
160
+ // List of arguments in the order they should be passed to the main function
161
+ val orderedArgs : List [String ] =
162
+ def rec (params : List [Parameter ], acc : List [String ], remainingArgs : List [String ]): List [String ] =
163
+ params match
164
+ case Nil =>
165
+ for (remainingArg <- remainingArgs) error(s " unused argument: $remainingArg" )
166
+ acc.reverse
167
+ case param :: tailParams =>
168
+ if param.isVarargs then // also last arguments
169
+ byNameArgsMap.get(param.name) match
170
+ case Some (byNameVarargs) => acc ::: byNameVarargs.toList ::: remainingArgs
171
+ case None => acc ::: remainingArgs
172
+ else byNameArgsMap.get(param.name) match
173
+ case Some (argValues) =>
174
+ assert(argValues.nonEmpty, s " ${param.name} present in byNameArgsMap, but it has no argument value " )
175
+ if argValues.length > 1 then
176
+ error(s " more than one value for ${param.name}: ${argValues.mkString(" , " )}" )
177
+ rec(tailParams, argValues.last :: acc, remainingArgs)
178
+
179
+ case None =>
180
+ remainingArgs match
181
+ case arg :: rest =>
182
+ rec(tailParams, arg :: acc, rest)
183
+ case Nil =>
184
+ if ! param.hasDefault then
185
+ error(s " missing argument for ${param.name}" )
186
+ rec(tailParams, " " :: acc, Nil )
187
+ rec(info.parameters.toList, Nil , positionalArgs)
188
+
189
+ if hasError then None
190
+ else Some (orderedArgs)
191
+ end preProcessArgs
192
+
200
193
private var hasParseErrors : Boolean = false
201
194
202
195
/** Issue an error, and return an uncallable getter */
@@ -210,31 +203,22 @@ final class newMain extends MainAnnotation[FromString, Any]:
210
203
case Some (t) => () => t
211
204
case None => parseError(s " could not parse argument for ` ${param.name}` of type ${param.typeName.split('.' ).last}: $arg" )
212
205
213
- private def checkNames (info : Info ): Unit =
214
- def checkDuplicateNames () =
215
- val nameAndCanonicalName = info.parameters.flatMap { paramInfo =>
216
- (getNameWithMarker(paramInfo.name) +: paramInfo.longAliases ++: paramInfo.shortAliases).map(_ -> paramInfo.name)
217
- }
218
- val nameToCanonicalNames = nameAndCanonicalName.groupMap(_._1)(_._2)
219
- for (name, canonicalNames) <- nameToCanonicalNames if canonicalNames.length > 1 do
220
- throw IllegalArgumentException (s " $name is used for multiple parameters: ${canonicalNames.mkString(" , " )}" )
221
- def checkValidNames () =
222
- def isValidArgName (name : String ): Boolean =
223
- longArgNameRegex.matches(name) || shortArgNameRegex.matches(name)
224
- for param <- info.parameters do
225
- if ! isValidArgName(param.name) then
226
- throw IllegalArgumentException (s " The following argument name is invalid: ${param.name}" )
227
- for alias <- param.aliasNames if ! isValidArgName(alias) do
228
- throw IllegalArgumentException (s " The following alias is invalid: $alias" )
229
-
230
- checkValidNames()
231
- checkDuplicateNames()
232
-
233
- private class Help (info : Info ):
206
+
207
+ private object Help :
208
+
209
+ /** The name of the special argument to display the method's help.
210
+ * If one of the method's parameters is called the same, will be ignored.
211
+ */
212
+ private inline val helpArg = " help"
213
+
214
+ /** The short name of the special argument to display the method's help.
215
+ * If one of the method's parameters uses the same short name, will be ignored.
216
+ */
217
+ private inline val shortHelpArg = 'h'
234
218
235
219
private inline val maxUsageLineLength = 120
236
220
237
- def printUsage (): Unit =
221
+ def printUsage (info : Info ): Unit =
238
222
def argsUsage : Seq [String ] =
239
223
for (param <- info.parameters)
240
224
yield {
@@ -266,7 +250,7 @@ final class newMain extends MainAnnotation[FromString, Any]:
266
250
println(printUsageBeginning + printUsages.mkString(" \n " + " " * argsOffset))
267
251
end printUsage
268
252
269
- def printExplain (): Unit =
253
+ def printExplain (info : Info ): Unit =
270
254
def shiftLines (s : Seq [String ], shift : Int ): String = s.map(" " * shift + _).mkString(" \n " )
271
255
272
256
def wrapLongLine (line : String , maxLength : Int ): List [String ] = {
@@ -282,6 +266,8 @@ final class newMain extends MainAnnotation[FromString, Any]:
282
266
recurse(line, Vector ()).toList
283
267
}
284
268
269
+ println()
270
+
285
271
if (info.documentation.nonEmpty)
286
272
println(wrapLongLine(info.documentation, maxUsageLineLength).mkString(" \n " ))
287
273
if (info.parameters.nonEmpty) {
@@ -312,25 +298,16 @@ final class newMain extends MainAnnotation[FromString, Any]:
312
298
}
313
299
end printExplain
314
300
315
- private object Help :
316
- /** The name of the special argument to display the method's help.
317
- * If one of the method's parameters is called the same, will be ignored.
318
- */
319
- private inline val helpArg = " help"
320
-
321
- /** The short name of the special argument to display the method's help.
322
- * If one of the method's parameters uses the same short name, will be ignored.
323
- */
324
- private inline val shortHelpArg = 'h'
325
-
326
301
def hasHelpArg (canonicalNames : CanonicalNames , args : Seq [String ]): Boolean =
327
302
val helpIsOverridden = canonicalNames.getName(argMarker + helpArg).isDefined
328
303
val shortHelpIsOverridden = canonicalNames.getShortName(shortArgMarker + shortHelpArg).isDefined
329
304
(! helpIsOverridden && args.contains(longNameWithMarker(helpArg))) ||
330
305
(! shortHelpIsOverridden && args.contains(shortNameWithMarker(shortHelpArg.toString)))
306
+
331
307
end Help
332
308
333
309
private class CanonicalNames (info : Info ):
310
+
334
311
private val namesToCanonicalName : Map [String , String ] = info.parameters.flatMap(
335
312
param =>
336
313
val names = param.longAliases.map(_.drop(2 ))
@@ -347,10 +324,32 @@ final class newMain extends MainAnnotation[FromString, Any]:
347
324
else names.map(_ -> canonicalName)
348
325
).toMap
349
326
350
- def getName (name : String ): Option [String ] = namesToCanonicalName.get(name.drop(2 ))
327
+ def getName (name : String ): Option [String ] = namesToCanonicalName.get(name.drop(2 )).orElse(getShortName(name))
351
328
352
329
def getShortName (name : String ): Option [String ] = shortNamesToCanonicalName.get(name.drop(1 ))
353
330
331
+ override def toString (): String =
332
+ s " CanonicalNames( $namesToCanonicalName, $shortNamesToCanonicalName) "
333
+
334
+ def checkNames (): Unit =
335
+ def checkDuplicateNames () =
336
+ val nameAndCanonicalName = info.parameters.flatMap { paramInfo =>
337
+ (getNameWithMarker(paramInfo.name) +: paramInfo.longAliases ++: paramInfo.shortAliases).map(_ -> paramInfo.name)
338
+ }
339
+ val nameToCanonicalNames = nameAndCanonicalName.groupMap(_._1)(_._2)
340
+ for (name, canonicalNames) <- nameToCanonicalNames if canonicalNames.length > 1 do
341
+ throw IllegalArgumentException (s " $name is used for multiple parameters: ${canonicalNames.mkString(" , " )}" )
342
+ def checkValidNames () =
343
+ def isValidArgName (name : String ): Boolean =
344
+ longArgNameRegex.matches(name) || shortArgNameRegex.matches(name)
345
+ for param <- info.parameters do
346
+ if ! isValidArgName(param.name) then
347
+ throw IllegalArgumentException (s " The following argument name is invalid: ${param.name}" )
348
+ for alias <- param.aliasNames if ! isValidArgName(alias) do
349
+ throw IllegalArgumentException (s " The following alias is invalid: $alias" )
350
+
351
+ checkValidNames()
352
+ checkDuplicateNames()
354
353
end CanonicalNames
355
354
356
355
end newMain
0 commit comments