summaryrefslogtreecommitdiff
path: root/main/core/src/util/ParseArgs.scala
diff options
context:
space:
mode:
Diffstat (limited to 'main/core/src/util/ParseArgs.scala')
-rw-r--r--main/core/src/util/ParseArgs.scala137
1 files changed, 137 insertions, 0 deletions
diff --git a/main/core/src/util/ParseArgs.scala b/main/core/src/util/ParseArgs.scala
new file mode 100644
index 00000000..fc1a8ab3
--- /dev/null
+++ b/main/core/src/util/ParseArgs.scala
@@ -0,0 +1,137 @@
+package mill.util
+
+import fastparse._, NoWhitespace._
+import mill.define.{Segment, Segments}
+
+object ParseArgs {
+
+ def apply(scriptArgs: Seq[String],
+ multiSelect: Boolean): Either[String, (List[(Option[Segments], Segments)], Seq[String])] = {
+ val (selectors, args) = extractSelsAndArgs(scriptArgs, multiSelect)
+ for {
+ _ <- validateSelectors(selectors)
+ expandedSelectors <- EitherOps
+ .sequence(selectors.map(expandBraces))
+ .map(_.flatten)
+ selectors <- EitherOps.sequence(expandedSelectors.map(extractSegments))
+ } yield (selectors.toList, args)
+ }
+
+ def extractSelsAndArgs(scriptArgs: Seq[String],
+ multiSelect: Boolean): (Seq[String], Seq[String]) = {
+
+ if (multiSelect) {
+ val dd = scriptArgs.indexOf("--")
+ val selectors = if (dd == -1) scriptArgs else scriptArgs.take(dd)
+ val args = if (dd == -1) Seq.empty else scriptArgs.drop(dd + 1)
+
+ (selectors, args)
+ } else {
+ (scriptArgs.take(1), scriptArgs.drop(1))
+ }
+ }
+
+ private def validateSelectors(selectors: Seq[String]): Either[String, Unit] = {
+ if (selectors.isEmpty || selectors.exists(_.isEmpty))
+ Left("Selector cannot be empty")
+ else Right(())
+ }
+
+ def expandBraces(selectorString: String): Either[String, List[String]] = {
+ parseBraceExpansion(selectorString) match {
+ case f: Parsed.Failure => Left(s"Parsing exception ${f.msg}")
+ case Parsed.Success(expanded, _) => Right(expanded.toList)
+ }
+ }
+
+ private sealed trait Fragment
+ private object Fragment {
+ case class Keep(value: String) extends Fragment
+ case class Expand(values: List[List[Fragment]]) extends Fragment
+
+ def unfold(fragments: List[Fragment]): Seq[String] = {
+ fragments match {
+ case head :: rest =>
+ val prefixes = head match {
+ case Keep(v) => Seq(v)
+ case Expand(Nil) => Seq("{}")
+ case Expand(List(vs)) => unfold(vs).map("{" + _ + "}")
+ case Expand(vss) => vss.flatMap(unfold)
+ }
+ for {
+ prefix <- prefixes
+ suffix <- unfold(rest)
+ } yield prefix + suffix
+
+ case Nil => Seq("")
+ }
+ }
+ }
+
+ private object BraceExpansionParser {
+ def plainChars[_: P] =
+ P(CharsWhile(c => c != ',' && c != '{' && c != '}')).!.map(Fragment.Keep)
+
+ def toExpand[_: P]: P[Fragment] =
+ P("{" ~ braceParser.rep(1).rep(sep = ",") ~ "}").map(
+ x => Fragment.Expand(x.toList.map(_.toList))
+ )
+
+ def braceParser[_: P] = P(toExpand | plainChars)
+
+ def parser[_: P] = P(braceParser.rep(1).rep(sep = ",") ~ End).map { vss =>
+ def unfold(vss: List[Seq[String]]): Seq[String] = {
+ vss match {
+ case Nil => Seq("")
+ case head :: rest =>
+ for {
+ str <- head
+ r <- unfold(rest)
+ } yield
+ r match {
+ case "" => str
+ case _ => str + "," + r
+ }
+ }
+ }
+
+ val stringss = vss.map(x => Fragment.unfold(x.toList)).toList
+ unfold(stringss)
+ }
+ }
+
+ private def parseBraceExpansion(input: String) = {
+
+
+ parse(
+ input,
+ BraceExpansionParser.parser(_)
+ )
+ }
+
+ def extractSegments(selectorString: String): Either[String, (Option[Segments], Segments)] =
+ parseSelector(selectorString) match {
+ case f: Parsed.Failure => Left(s"Parsing exception ${f.msg}")
+ case Parsed.Success(selector, _) => Right(selector)
+ }
+
+ private def ident[_: P] = P( CharsWhileIn("a-zA-Z0-9_\\-") ).!
+
+ def standaloneIdent[_: P] = P(Start ~ ident ~ End )
+ def isLegalIdentifier(identifier: String): Boolean =
+ parse(identifier, standaloneIdent(_)).isInstanceOf[Parsed.Success[_]]
+
+ private def parseSelector(input: String) = {
+ def ident2[_: P] = P( CharsWhileIn("a-zA-Z0-9_\\-.") ).!
+ def segment[_: P] = P( ident ).map( Segment.Label)
+ def crossSegment[_: P] = P("[" ~ ident2.rep(1, sep = ",") ~ "]").map(Segment.Cross)
+ def simpleQuery[_: P] = P(segment ~ ("." ~ segment | crossSegment).rep).map {
+ case (h, rest) => Segments(h :: rest.toList:_*)
+ }
+ def query[_: P] = P( simpleQuery ~ ("/" ~/ simpleQuery).?).map{
+ case (q, None) => (None, q)
+ case (q, Some(q2)) => (Some(q), q2)
+ }
+ parse(input, query(_))
+ }
+}