summaryrefslogtreecommitdiff
path: root/main/core/src/mill/util/ParseArgs.scala
diff options
context:
space:
mode:
Diffstat (limited to 'main/core/src/mill/util/ParseArgs.scala')
-rw-r--r--main/core/src/mill/util/ParseArgs.scala134
1 files changed, 134 insertions, 0 deletions
diff --git a/main/core/src/mill/util/ParseArgs.scala b/main/core/src/mill/util/ParseArgs.scala
new file mode 100644
index 00000000..ae3b1685
--- /dev/null
+++ b/main/core/src/mill/util/ParseArgs.scala
@@ -0,0 +1,134 @@
+package mill.util
+
+import fastparse.all._
+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 {
+ val plainChars =
+ P(CharsWhile(c => c != ',' && c != '{' && c != '}')).!.map(Fragment.Keep)
+
+ val toExpand: P[Fragment] =
+ P("{" ~ braceParser.rep(1).rep(sep = ",") ~ "}").map(
+ x => Fragment.Expand(x.toList.map(_.toList))
+ )
+
+ val braceParser = P(toExpand | plainChars)
+
+ val parser = P(braceParser.rep(1).rep(sep = ",") ~ End)
+ }
+
+ private def parseBraceExpansion(input: String) = {
+ 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
+ }
+ }
+ }
+
+ BraceExpansionParser.parser
+ .map { vss =>
+ val stringss = vss.map(x => Fragment.unfold(x.toList)).toList
+ unfold(stringss)
+ }
+ .parse(input)
+ }
+
+ 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 val identChars = ('a' to 'z') ++ ('A' to 'Z') ++ ('0' to '9') ++ Seq('_', '-')
+ private val ident = P( CharsWhileIn(identChars) ).!
+
+ def isLegalIdentifier(identifier: String): Boolean =
+ (Start ~ ident ~ End).parse(identifier).isInstanceOf[Parsed.Success[_]]
+
+ private def parseSelector(input: String) = {
+ val ident2 = P( CharsWhileIn(identChars ++ ".") ).!
+ val segment = P( ident ).map( Segment.Label)
+ val crossSegment = P("[" ~ ident2.rep(1, sep = ",") ~ "]").map(Segment.Cross)
+ val simpleQuery = P(segment ~ ("." ~ segment | crossSegment).rep).map {
+ case (h, rest) => Segments(h :: rest.toList:_*)
+ }
+ val query = P( simpleQuery ~ ("/" ~/ simpleQuery).?).map{
+ case (q, None) => (None, q)
+ case (q, Some(q2)) => (Some(q), q2)
+ }
+ query.parse(input)
+ }
+}