summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authorLi Haoyi <haoyi.sg@gmail.com>2018-02-17 20:43:02 -0800
committerLi Haoyi <haoyi.sg@gmail.com>2018-02-17 20:43:02 -0800
commit3990e125dbfcfa759ebe7d564f51ae3fa8681ea9 (patch)
treeda2061a51cc6669b10a24b284e55676b6e24570d /main
parent09b37096e7effc117947c29c205fb3f32386bf8e (diff)
downloadmill-3990e125dbfcfa759ebe7d564f51ae3fa8681ea9.tar.gz
mill-3990e125dbfcfa759ebe7d564f51ae3fa8681ea9.tar.bz2
mill-3990e125dbfcfa759ebe7d564f51ae3fa8681ea9.zip
improve error reporting when you mistype a selector and it can't be resolved
Diffstat (limited to 'main')
-rw-r--r--main/src/mill/main/Resolve.scala53
1 files changed, 45 insertions, 8 deletions
diff --git a/main/src/mill/main/Resolve.scala b/main/src/mill/main/Resolve.scala
index 992484bc..fdbccfce 100644
--- a/main/src/mill/main/Resolve.scala
+++ b/main/src/mill/main/Resolve.scala
@@ -3,6 +3,7 @@ package mill.main
import mill.define._
import mill.define.TaskModule
import ammonite.util.Res
+import mill.main.ResolveMetadata.singleModuleMeta
import mill.util.Router.EntryPoint
import mill.util.{Router, Scripts}
@@ -44,17 +45,49 @@ object ResolveMetadata extends Resolve[String]{
}
else if (last == "_") Right(direct.toList)
else direct.find(_.split('.').last == last) match{
- case None =>
- Left(
- "Unable to resolve " +
- Segments((Segment.Label(last) :: revSelectorsSoFar).reverse: _*).render
- )
+ case None => Resolve.errorMsg(direct, last, revSelectorsSoFar)
case Some(s) => Right(List(s))
}
}
}
object Resolve extends Resolve[NamedTask[Any]]{
+ def minimum(i1: Int, i2: Int, i3: Int)= math.min(math.min(i1, i2), i3)
+
+ /**
+ * Short Levenshtein distance algorithm, based on
+ *
+ * https://rosettacode.org/wiki/Levenshtein_distance#Scala
+ */
+ def editDistance(s1: String, s2: String) = {
+ val dist = Array.tabulate(s2.length+1, s1.length+1){(j, i) => if(j==0) i else if (i==0) j else 0}
+
+ for(j <- 1 to s2.length; i <- 1 to s1.length)
+ dist(j)(i) = if(s2(j - 1) == s1(i-1)) dist(j - 1)(i-1)
+ else minimum(dist(j - 1)(i) + 1, dist(j)(i - 1) + 1, dist(j - 1)(i - 1) + 1)
+
+ dist(s2.length)(s1.length)
+ }
+
+ def errorMsg(direct: Seq[String], last: String, revSelectorsSoFar: List[Segment]) = {
+ val similar =
+ direct
+ .map(d => (d, Resolve.editDistance(d.split('.').last, last)))
+ .filter(_._2 < 3)
+ .sortBy(_._2)
+ val hint = similar match{
+ case Nil =>
+ val search = Segments((Segment.Label("_") :: revSelectorsSoFar).reverse: _*).render
+ s" Try `mill resolve $search` to see what's available."
+ case items => " Did you mean " + items.head._1 + "?"
+ }
+ Left(
+ "Unable to resolve " +
+ Segments((Segment.Label(last) :: revSelectorsSoFar).reverse: _*).render +
+ "." + hint
+ )
+ }
+
def endResolve(obj: Module,
revSelectorsSoFar: List[Segment],
last: String,
@@ -108,9 +141,13 @@ object Resolve extends Resolve[NamedTask[Any]]{
val command = invokeCommand(obj, last).headOption
command orElse target orElse runDefault.flatten.headOption match {
- case None => Left("Cannot resolve " +
- Segments((Segment.Label(last) :: revSelectorsSoFar).reverse: _*).render
- )
+ case None =>
+ Resolve.errorMsg(
+ singleModuleMeta(obj, discover, revSelectorsSoFar.isEmpty),
+ last,
+ revSelectorsSoFar
+ )
+
// Contents of `either` *must* be a `Task`, because we only select
// methods returning `Task` in the discovery process
case Some(either) => either.right.map(Seq(_))