@@ -27,11 +27,11 @@ import annotation._
27
27
* The parameters of a main function may be passed either by position, or by name. Passing an argument positionaly
28
28
* means that you give the arguments in the same order as the function's signature. Passing an argument by name means
29
29
* that you give the argument right after giving its name. Considering the function
30
- * `@main def foo(i: Int, s : String)`, we may have arguments passed:
30
+ * `@main def foo(i: Int, str : String)`, we may have arguments passed:
31
31
* - by position: `scala foo 1 abc`,
32
- * - by name: `scala foo -- i 1 --s abc` or `scala foo --s abc - -i 1`.
32
+ * - by name: `scala foo -i 1 --str abc` or `scala foo --str abc -i 1`.
33
33
*
34
- * A mixture of both is also possible: `scala foo --s abc 1` is equivalent to all previous examples.
34
+ * A mixture of both is also possible: `scala foo --str abc 1` is equivalent to all previous examples.
35
35
*
36
36
* Note that main function overloading is not currently supported, i.e. you cannot define two main methods that have
37
37
* the same name in the same project.
@@ -41,18 +41,15 @@ import annotation._
41
41
* associated with the function, more precisely its description and the description of the parameters documented with
42
42
* `@param`.
43
43
*
44
- *
45
44
* Parameters may be given annotations to add functionalities to the main function:
46
- * - `main.ShortName` adds a short name to a parameter. For example, if a parameter `node` has as short name `n`, it
47
- * may be addressed using either `--node` or `-n`,
48
- * - `main.Name` adds another name to a parameter. For example, if a parameter `node` has as alternative name
49
- * `otherNode`, it may be addressed using either `--node` or `--otherNode`.
45
+ * - `main.Alias` adds other names to a parameter. For example, if a parameter `node` has as aliases
46
+ * `otherNode` and `n`, it may be addressed using `--node`, `--otherNode` or `-n`.
50
47
*
51
48
* Here is an example of a main function with annotated parameters:
52
- * `@main def foo(@main.ShortName('x' ) number: Int, @main.Name ("explanation") s: String)`. The following commands are
49
+ * `@main def foo(@main.Alias("x" ) number: Int, @main.Alias ("explanation") s: String)`. The following commands are
53
50
* equivalent:
54
- * - `scala foo --number 1 -- s abc`
55
- * - `scala foo -x 1 -- s abc`
51
+ * - `scala foo --number 1 -s abc`
52
+ * - `scala foo -x 1 -s abc`
56
53
* - `scala foo --number 1 --explanation abc`
57
54
* - `scala foo -x 1 --explanation abc`
58
55
*
@@ -125,8 +122,20 @@ final class main(maxLineLength: Int) extends MainAnnotation:
125
122
private val nameToParameterInfos : Map [String , ParameterInfos ] = parameterInfoss.map(infos => infos.name -> infos).toMap
126
123
127
124
private val (positionalArgs, byNameArgs, invalidByNameArgs) = {
128
- val namesToCanonicalName : Map [String , String ] = parameterInfoss.flatMap(infos => (infos.name +: getAlternativeNames(infos)).map(_ -> infos.name)).toMap
129
- val shortNamesToCanonicalName : Map [Char , String ] = parameterInfoss.flatMap(infos => getShortNames(infos).map(_ -> infos.name)).toMap
125
+ val namesToCanonicalName : Map [String , String ] = parameterInfoss.flatMap(
126
+ infos =>
127
+ var names = getAlternativeNames(infos)
128
+ val canonicalName = infos.name
129
+ if nameIsValid(canonicalName) then names = canonicalName +: names
130
+ names.map(_ -> canonicalName)
131
+ ).toMap
132
+ val shortNamesToCanonicalName : Map [Char , String ] = parameterInfoss.flatMap(
133
+ infos =>
134
+ var names = getShortNames(infos)
135
+ val canonicalName = infos.name
136
+ if shortNameIsValid(canonicalName) then names = canonicalName(0 ) +: names
137
+ names.map(_ -> canonicalName)
138
+ ).toMap
130
139
131
140
def getCanonicalArgName (arg : String ): Option [String ] =
132
141
if arg.startsWith(argMarker) && arg.length > argMarker.length then
@@ -138,7 +147,7 @@ final class main(maxLineLength: Int) extends MainAnnotation:
138
147
139
148
def isArgName (arg : String ): Boolean =
140
149
val isFullName = arg.startsWith(argMarker)
141
- val isShortName = arg.startsWith(shortArgMarker) && arg.length == 2 && shortNameIsValid(arg(1 ))
150
+ val isShortName = arg.startsWith(shortArgMarker) && arg.length == shortArgMarker.length + 1 && shortNameIsValid(arg(shortArgMarker.length ))
142
151
isFullName || isShortName
143
152
144
153
def recurse (remainingArgs : Seq [String ], pa : mutable.Queue [String ], bna : Seq [(String , String )], ia : Seq [String ]): (mutable.Queue [String ], Seq [(String , String )], Seq [String ]) =
@@ -170,12 +179,21 @@ final class main(maxLineLength: Int) extends MainAnnotation:
170
179
errors += msg
171
180
() => throw new AssertionError (" trying to get invalid argument" )
172
181
173
- private def nameIsValid (name : String ): Boolean =
174
- name.length > 0 // TODO add more checks for illegal characters
182
+ private inline def nameIsValid (name : String ): Boolean =
183
+ name.length > 1 // TODO add more checks for illegal characters
184
+
185
+ private inline def shortNameIsValid (name : String ): Boolean =
186
+ name.length == 1 && shortNameIsValid(name(0 ))
175
187
176
- private def shortNameIsValid (shortName : Char ): Boolean =
188
+ private inline def shortNameIsValid (shortName : Char ): Boolean =
177
189
('A' <= shortName && shortName <= 'Z' ) || ('a' <= shortName && shortName <= 'z' )
178
190
191
+ private def getNameWithMarker (name : String | Char ): String = name match {
192
+ case c : Char => shortArgMarker + c
193
+ case s : String if shortNameIsValid(s) => shortArgMarker + s
194
+ case s => argMarker + s
195
+ }
196
+
179
197
private def convert [T ](argName : String , arg : String , p : ArgumentParser [T ]): () => T =
180
198
p.fromStringOption(arg) match
181
199
case Some (t) => () => t
@@ -185,9 +203,9 @@ final class main(maxLineLength: Int) extends MainAnnotation:
185
203
def argsUsage : Seq [String ] =
186
204
for ((infos, kind) <- parameterInfoss.zip(argKinds))
187
205
yield {
188
- val canonicalName = argMarker + infos.name
189
- val shortNames = getShortNames(infos).map(shortArgMarker + _ )
190
- val alternativeNames = getAlternativeNames(infos).map(argMarker + _ )
206
+ val canonicalName = getNameWithMarker( infos.name)
207
+ val shortNames = getShortNames(infos).map(getNameWithMarker )
208
+ val alternativeNames = getAlternativeNames(infos).map(getNameWithMarker )
191
209
val namesPrint = (canonicalName +: alternativeNames ++: shortNames).mkString(" [" , " | " , " ]" )
192
210
193
211
kind match {
@@ -241,9 +259,9 @@ final class main(maxLineLength: Int) extends MainAnnotation:
241
259
242
260
println(" Arguments:" )
243
261
for ((infos, kind) <- parameterInfoss.zip(argKinds))
244
- val canonicalName = argMarker + infos.name
245
- val shortNames = getShortNames(infos).map(shortArgMarker + _ )
246
- val alternativeNames = getAlternativeNames(infos).map(argMarker + _ )
262
+ val canonicalName = getNameWithMarker( infos.name)
263
+ val shortNames = getShortNames(infos).map(getNameWithMarker )
264
+ val alternativeNames = getAlternativeNames(infos).map(getNameWithMarker )
247
265
val otherNames = (alternativeNames ++: shortNames) match {
248
266
case Seq () => " "
249
267
case names => names.mkString(" (" , " , " , " ) " )
@@ -271,28 +289,30 @@ final class main(maxLineLength: Int) extends MainAnnotation:
271
289
}
272
290
end explain
273
291
292
+ private def getAliases (paramInfos : ParameterInfos ): Seq [String ] =
293
+ paramInfos.annotations.collect{ case a : Alias => a }.flatMap(_.aliases)
294
+
274
295
private def getAlternativeNames (paramInfos : ParameterInfos ): Seq [String ] =
275
- val (valid, invalid) =
276
- paramInfos.annotations.collect{ case annot : Name => annot.name }.partition(nameIsValid)
277
- if invalid.nonEmpty then
278
- throw IllegalArgumentException (s " invalid names ${invalid.mkString(" , " )} for parameter ${paramInfos.name}" )
279
- valid
296
+ getAliases(paramInfos).filter(nameIsValid(_))
280
297
281
298
private def getShortNames (paramInfos : ParameterInfos ): Seq [Char ] =
282
- val (valid, invalid) =
283
- paramInfos.annotations.collect{ case annot : ShortName => annot.shortName }.partition(shortNameIsValid)
284
- if invalid.nonEmpty then
285
- throw IllegalArgumentException (s " invalid short names ${invalid.mkString(" , " )} for parameter ${paramInfos.name}" )
286
- valid
299
+ getAliases(paramInfos).filter(shortNameIsValid(_)).map(_(0 ))
300
+
301
+ private def getInvalidNames (paramInfos : ParameterInfos ): Seq [String | Char ] =
302
+ getAliases(paramInfos).filter(name => ! nameIsValid(name) && ! shortNameIsValid(name))
303
+
304
+ private def checkAliasesValidity (): Unit =
305
+ val problematicNames = nameToParameterInfos.toList.flatMap((_, infos) => getInvalidNames(infos))
306
+ if problematicNames.length > 0 then throw IllegalArgumentException (s " The following aliases are invalid: ${problematicNames.mkString(" , " )}" )
287
307
288
- private def checkNamesUnicity (): Unit =
308
+ private def checkAliasesUnicity (): Unit =
289
309
val nameAndCanonicalName = nameToParameterInfos.toList.flatMap {
290
310
case (canonicalName, infos) => (canonicalName +: getAlternativeNames(infos) ++: getShortNames(infos)).map(_ -> canonicalName)
291
311
}
292
312
val nameToCanonicalNames = nameAndCanonicalName.groupMap(_._1)(_._2)
293
313
294
314
for (name, canonicalNames) <- nameToCanonicalNames if canonicalNames.length > 1
295
- do throw AssertionError (s " $name is used for multiple parameters: ${canonicalNames.mkString(" , " )}" )
315
+ do throw IllegalArgumentException (s " $name is used for multiple parameters: ${canonicalNames.mkString(" , " )}" )
296
316
297
317
override def argGetter [T ](name : String , optDefaultGetter : Option [() => T ])(using p : ArgumentParser [T ]): () => T =
298
318
argKinds += (if optDefaultGetter.nonEmpty then ArgumentKind .OptionalArgument else ArgumentKind .SimpleArgument )
@@ -327,9 +347,10 @@ final class main(maxLineLength: Int) extends MainAnnotation:
327
347
() => (byNameGetters ++ positionalGetters).map(_())
328
348
329
349
override def run (f : => MainResultType ): Unit =
350
+ checkAliasesUnicity()
351
+ checkAliasesValidity()
330
352
for (remainingArg <- positionalArgs) error(s " unused argument: $remainingArg" )
331
353
for (invalidArg <- invalidByNameArgs) error(s " unknown argument name: $invalidArg" )
332
- checkNamesUnicity()
333
354
334
355
if args.contains(s " ${argMarker}help " ) then
335
356
usage()
@@ -345,6 +366,5 @@ final class main(maxLineLength: Int) extends MainAnnotation:
345
366
end main
346
367
347
368
object main :
348
- final class ShortName (val shortName : Char ) extends MainAnnotation .ParameterAnnotation
349
- final class Name (val name : String ) extends MainAnnotation .ParameterAnnotation
369
+ final class Alias (val aliases : String * ) extends MainAnnotation .ParameterAnnotation
350
370
end main
0 commit comments