From 2b831e5988e2e6ba2aaaae9bda4cd0e700b2b9c8 Mon Sep 17 00:00:00 2001 From: Ethan Atkins Date: Mon, 7 Jan 2019 20:17:51 -0800 Subject: [PATCH] Add LabeledFunctions to repo I noticed that debugging settings that return functions is annoying because often the setting is initialized as an anonymous function with a useless toString method. To improve the debugging for users, I'm adding a number of wrapper classes for functions that override the default toString with a provided label. I then used these functions to label all of the anonymous functions in Watched.scala. --- main-command/src/main/scala/sbt/Watched.scala | 22 ++- .../scala/sbt/internal/LabeledFunctions.scala | 163 ++++++++++++++++++ 2 files changed, 178 insertions(+), 7 deletions(-) create mode 100644 main-command/src/main/scala/sbt/internal/LabeledFunctions.scala diff --git a/main-command/src/main/scala/sbt/Watched.scala b/main-command/src/main/scala/sbt/Watched.scala index 082f4374d..aa3164e3e 100644 --- a/main-command/src/main/scala/sbt/Watched.scala +++ b/main-command/src/main/scala/sbt/Watched.scala @@ -17,6 +17,7 @@ import sbt.BasicCommandStrings.{ continuousDetail } import sbt.BasicCommands.otherCommandParser +import sbt.internal.LabeledFunctions._ import sbt.internal.LegacyWatched import sbt.internal.inc.Stamper import sbt.internal.io.{ EventMonitor, Source, WatchState } @@ -159,23 +160,30 @@ object Watched { private[this] val reRun = if (isWin) "" else " or 'r' to re-run the command" private def waitMessage(project: String): String = s"Waiting for source changes$project... (press enter to interrupt$reRun)" - val defaultStartWatch: Int => Option[String] = count => Some(s"$count. ${waitMessage("")}") + val defaultStartWatch: Int => Option[String] = + ((count: Int) => Some(s"$count. ${waitMessage("")}")).label("Watched.defaultStartWatch") @deprecated("Use defaultStartWatch in conjunction with the watchStartMessage key", "1.3.0") - val defaultWatchingMessage: WatchState => String = ws => defaultStartWatch(ws.count).get + val defaultWatchingMessage: WatchState => String = + ((ws: WatchState) => defaultStartWatch(ws.count).get).label("Watched.defaultWatchingMessage") def projectWatchingMessage(projectId: String): WatchState => String = - ws => projectOnWatchMessage(projectId)(ws.count).get + ((ws: WatchState) => projectOnWatchMessage(projectId)(ws.count).get) + .label("Watched.projectWatchingMessage") def projectOnWatchMessage(project: String): Int => Option[String] = - count => Some(s"$count. ${waitMessage(s" in project $project")}") + ((count: Int) => Some(s"$count. ${waitMessage(s" in project $project")}")) + .label("Watched.projectOnWatchMessage") - val defaultOnTriggerMessage: Int => Option[String] = _ => None + val defaultOnTriggerMessage: Int => Option[String] = + ((_: Int) => None).label("Watched.defaultOnTriggerMessage") @deprecated( "Use defaultOnTriggerMessage in conjunction with the watchTriggeredMessage key", "1.3.0" ) - val defaultTriggeredMessage: WatchState => String = const("") + val defaultTriggeredMessage: WatchState => String = + const("").label("Watched.defaultTriggeredMessage") val clearOnTrigger: Int => Option[String] = _ => Some(clearScreen) @deprecated("Use clearOnTrigger in conjunction with the watchTriggeredMessage key", "1.3.0") - val clearWhenTriggered: WatchState => String = const(clearScreen) + val clearWhenTriggered: WatchState => String = + const(clearScreen).label("Watched.clearWhenTriggered") def clearScreen: String = "\u001b[2J\u001b[0;0H" object WatchSource { diff --git a/main-command/src/main/scala/sbt/internal/LabeledFunctions.scala b/main-command/src/main/scala/sbt/internal/LabeledFunctions.scala new file mode 100644 index 000000000..bb196462d --- /dev/null +++ b/main-command/src/main/scala/sbt/internal/LabeledFunctions.scala @@ -0,0 +1,163 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt.internal + +/** + * Provides extension methods that allow us to wrap FunctionN instances with another FunctionN that + * delegates to the input FunctionN, but overrides the toString method with a user specified + * label. Anonymous functions have the default java toString which is just the class name. Because + * the anonymous class is generated implicitly, it can be impossible to tell which anonymous + * class you are looking at if there are multiple anonymous functions of the same type in a + * given object/class/package. We can overcome this limitation by explicitly setting the toString + * of the anonymous function instances. This is a fairly crude solution to the problem that + * is not ready to be exposed to users. Until the api matures or we decide that it's worth + * exposing to users, these should remain sbt package private. + */ +private[sbt] object LabeledFunctions { + + /** + * Adds extension methods to a zero argument function. + * @param f the function to extend + * @tparam R the function result type + */ + private[sbt] implicit class Function0Ops[R](val f: () => R) extends AnyVal { + + /** + * Add a label to the function. + * @param string the new toString method for the function + * @return a wrapped function with an overridden toString method. + */ + def label(string: String): () => R = new LabeledFunction0(f, string) + } + + /** + * Adds extension methods to a single argument function. + * @param f the function to extend + * @tparam T the input parameter + * @tparam R the function result type + */ + private[sbt] implicit class Function1Ops[T, R](val f: T => R) extends AnyVal { + + /** + * Add a label to the function. + * @param string the new toString method for the function + * @return a wrapped function with an overridden toString method. + */ + def label(string: String): T => R = new LabeledFunction1(f, string) + } + + /** + * Adds extension methods to a two argument function. + * @param f the function to extend + * @tparam T1 the first function input parameter + * @tparam T2 the second function input parameter + * @tparam R the function result type + */ + private[sbt] implicit class Function2Ops[T1, T2, R](val f: (T1, T2) => R) extends AnyVal { + + /** + * Add a label to the function. + * @param string the new toString method for the function + * @return a wrapped function with an overridden toString method. + */ + def label(string: String): (T1, T2) => R = new LabeledFunction2(f, string) + } + + /** + * Adds extension methods to a three argument function. + * @param f the function to extend + * @tparam T1 the first function input parameter + * @tparam T2 the second function input parameter + * @tparam T3 the third function input parameter + * @tparam R the function result type + */ + private[sbt] implicit class Function3Ops[T1, T2, T3, R](val f: (T1, T2, T3) => R) extends AnyVal { + + /** + * Add a label to the function. + * @param string the new toString method for the function + * @return a wrapped function with an overridden toString method. + */ + def label(string: String): (T1, T2, T3) => R = new LabeledFunction3(f, string) + } + + /** + * Adds extension methods to a three argument function. + * @param f the function to extend + * @tparam T1 the first function input parameter + * @tparam T2 the second function input parameter + * @tparam T3 the third function input parameter + * @tparam T4 the fourth function input parameter + * @tparam R the function result type + */ + private[sbt] implicit class Function4Ops[T1, T2, T3, T4, R](val f: (T1, T2, T3, T4) => R) + extends AnyVal { + + /** + * Add a label to the function. + * @param string the new toString method for the function + * @return a wrapped function with an overridden toString method. + */ + def label(string: String): (T1, T2, T3, T4) => R = new LabeledFunction4(f, string) + } + private class LabeledFunction0[+R](private val f: () => R, label: String) extends (() => R) { + override def apply(): R = f() + override def toString: String = label + override def equals(o: Any): Boolean = o match { + case that: LabeledFunction0[R] @unchecked => this.f == that.f + case that: (() => R) @unchecked => this.f == that + case _ => false + } + override def hashCode: Int = f.hashCode + } + private class LabeledFunction1[-T, +R](private val f: T => R, label: String) extends (T => R) { + override def apply(t: T): R = f(t) + override def toString: String = label + override def equals(o: Any): Boolean = o match { + case that: LabeledFunction1[T, R] @unchecked => this.f == that.f + case that: (T => R) @unchecked => this.f == that + case _ => false + } + override def hashCode: Int = f.hashCode + } + private class LabeledFunction2[-T1, -T2, +R](private val f: (T1, T2) => R, label: String) + extends ((T1, T2) => R) { + override def apply(t1: T1, t2: T2): R = f(t1, t2) + override def toString: String = label + override def equals(o: Any): Boolean = o match { + case that: LabeledFunction2[T1, T2, R] @unchecked => this.f == that.f + case that: ((T1, T2) => R) @unchecked => this.f == that + case _ => false + } + override def hashCode: Int = f.hashCode + } + private class LabeledFunction3[-T1, -T2, -T3, +R](private val f: (T1, T2, T3) => R, label: String) + extends ((T1, T2, T3) => R) { + override def apply(t1: T1, t2: T2, t3: T3): R = f(t1, t2, t3) + override def toString: String = label + override def equals(o: Any): Boolean = o match { + case that: LabeledFunction3[T1, T2, T3, R] @unchecked => this.f == that.f + case that: ((T1, T2, T3) => R) @unchecked => this.f == that + case _ => false + } + override def hashCode: Int = f.hashCode + } + private class LabeledFunction4[-T1, -T2, -T3, T4, +R]( + private val f: (T1, T2, T3, T4) => R, + label: String + ) extends ((T1, T2, T3, T4) => R) { + override def apply(t1: T1, t2: T2, t3: T3, t4: T4): R = f(t1, t2, t3, t4) + override def toString: String = label + override def equals(o: Any): Boolean = o match { + case that: LabeledFunction4[T1, T2, T3, T4, R] @unchecked => this.f == that.f + case that: ((T1, T2, T3, T4) => R) @unchecked => this.f == that + case _ => false + } + override def hashCode: Int = f.hashCode + } +}