From eb7da2f6892fade0d71382a0932e8cff5c8193e3 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sat, 8 Feb 2014 10:23:31 -0500 Subject: [PATCH] move Nature-related classes to Natures.scala --- main/src/main/scala/sbt/AutoPlugin.scala | 114 ++--------------------- main/src/main/scala/sbt/Natures.scala | 105 +++++++++++++++++++++ 2 files changed, 112 insertions(+), 107 deletions(-) create mode 100644 main/src/main/scala/sbt/Natures.scala diff --git a/main/src/main/scala/sbt/AutoPlugin.scala b/main/src/main/scala/sbt/AutoPlugin.scala index 22701c5ca..a087d8982 100644 --- a/main/src/main/scala/sbt/AutoPlugin.scala +++ b/main/src/main/scala/sbt/AutoPlugin.scala @@ -1,9 +1,7 @@ package sbt - import logic.{Atom, Clause, Clauses, Formula, Literal, Logic, Negated} - import Logic.{CyclicNegation, InitialContradictions, InitialOverlap, LogicException} import Def.Setting - import Natures._ + import logic.Logic.LogicException /** Marks a top-level object so that sbt will wildcard import it for .sbt files, `consoleProject`, and `set`. */ trait AutoImport @@ -78,111 +76,13 @@ abstract class AutoPlugin /** An error that occurs when auto-plugins aren't configured properly. * It translates the error from the underlying logic system to be targeted at end users. */ -final class AutoPluginException(val origin: Option[LogicException], prefix: String) extends RuntimeException(prefix + Natures.translateMessage(origin)) +final class AutoPluginException private(val message: String, val origin: Option[LogicException]) extends RuntimeException(message) { /** Prepends `p` to the error message derived from `origin`. */ - def withPrefix(p: String) = new AutoPluginException(origin, p) + def withPrefix(p: String) = new AutoPluginException(p + message, origin) } - - -/** An expression that matches `Nature`s. */ -sealed trait Natures { - def && (o: Basic): Natures -} - -/** Represents a feature or conceptual group of settings. -* `label` is the unique ID for this nature. */ -final case class Nature(label: String) extends Basic { - /** Constructs a Natures matcher that excludes this Nature. */ - def unary_! : Basic = Exclude(this) - override def toString = label -} - -object Natures +object AutoPluginException { - // TODO: allow multiple AutoPlugins to provide the same Nature? - // TODO: translate error messages - /** Given the available auto plugins `defined`, returns a function that selects [[AutoPlugin]]s for the provided [[Nature]]s. - * The [[AutoPlugin]]s are topologically sorted so that a selected [[AutoPlugin]] comes before its selecting [[AutoPlugin]].*/ - def compile(defined: List[AutoPlugin]): Natures => Seq[AutoPlugin] = - if(defined.isEmpty) - Types.const(Nil) - else - { - val byAtom = defined.map(x => (Atom(x.provides.label), x)) - val byAtomMap = byAtom.toMap - if(byAtom.size != byAtomMap.size) duplicateProvidesError(byAtom) - val clauses = Clauses( defined.map(d => asClause(d)) ) - requestedNatures => - Logic.reduce(clauses, flatten(requestedNatures).toSet) match { - case Left(problem) => throw new AutoPluginException(problem, "") - case Right(results) => - // results includes the originally requested (positive) atoms, - // which won't have a corresponding AutoPlugin to map back to - results.ordered.flatMap(a => byAtomMap.get(a).toList) - } - } - - private[sbt] def translateMessage(e: LogicException) = e match { - case ic: InitialContradictions => s"Contradiction in selected natures. These natures were both included and excluded: ${literalsString(ic.literals.toSeq)}" - case io: InitialOverlap => s"Cannot directly enable plugins. Plugins are enabled when their required natures are satisifed. The directly selected plugins were: ${literalsString(io.literals.toSeq)}" - case cn: CyclicNegation => s"Cycles in plugin requirements cannot involve excludes. The problematic cycle is: ${literalsString(cn.cycle)}" - } - private[this] def literalsString(lits: Seq[Literal]): String = - lits map { case Atom(l) => l; case Negated(Atom(l)) => l } mkString(", ") - - private[this] def duplicateProvidesError(byAtom: Seq[(Atom, AutoPlugin)]) { - val dupsByAtom = defined.groupBy(_._1).mapValues(_._2) - val dupStrings = for( (atom, dups) <- dupsByAtom if dups.size > 1 ) yield - s"${atom.label} by ${dups.mkString(", ")} - val (ns, nl) = if(dupStrings > 1) ("s", "\n\t") else ("", " ") - val message = s"Nature$ns provided by multiple AutoPlugins:$nl${dupStrings.mkString(nl)}" - throw new AutoPluginException(None, message) - } - - /** [[Natures]] instance that doesn't require any [[Nature]]s. */ - def empty: Natures = Empty - private[sbt] final object Empty extends Natures { - def &&(o: Basic): Natures = o - override def toString = "" - } - - /** An included or excluded Nature. TODO: better name than Basic. */ - sealed abstract class Basic extends Natures { - def &&(o: Basic): Natures = And(this :: o :: Nil) - } - private[sbt] final case class Exclude(n: Nature) extends Basic { - def unary_! : Nature = n - override def toString = s"!$n" - } - private[sbt] final case class And(natures: List[Basic]) extends Natures { - def &&(o: Basic): Natures = And(o :: natures) - override def toString = natures.mkString(", ") - } - private[sbt] def and(a: Natures, b: Natures) = b match { - case Empty => a - case And(ns) => (a /: ns)(_ && _) - case b: Basic => a && b - } - - /** Defines a clause for `ap` such that the [[Nature]] provided by `ap` is the head and the selector for `ap` is the body. */ - private[sbt] def asClause(ap: AutoPlugin): Clause = - Clause( convert(ap.select), Set(Atom(ap.provides.label)) ) - - private[this] def flatten(n: Natures): Seq[Literal] = n match { - case And(ns) => convertAll(ns) - case b: Basic => convertBasic(b) :: Nil - case Empty => Nil - } - - private[this] def convert(n: Natures): Formula = n match { - case And(ns) => convertAll(ns).reduce[Formula](_ && _) - case b: Basic => convertBasic(b) - case Empty => Formula.True - } - private[this] def convertBasic(b: Basic): Literal = b match { - case Exclude(n) => !convertBasic(n) - case Nature(s) => Atom(s) - } - private[this] def convertAll(ns: Seq[Basic]): Seq[Literal] = ns map convertBasic -} \ No newline at end of file + def apply(msg: String): AutoPluginException = new AutoPluginException(msg, None) + def apply(origin: LogicException): AutoPluginException = new AutoPluginException(Natures.translateMessage(origin), Some(origin)) +} diff --git a/main/src/main/scala/sbt/Natures.scala b/main/src/main/scala/sbt/Natures.scala new file mode 100644 index 000000000..4d8f7095b --- /dev/null +++ b/main/src/main/scala/sbt/Natures.scala @@ -0,0 +1,105 @@ +package sbt + + import logic.{Atom, Clause, Clauses, Formula, Literal, Logic, Negated} + import Logic.{CyclicNegation, InitialContradictions, InitialOverlap, LogicException} + import Natures._ + +/** An expression that matches `Nature`s. */ +sealed trait Natures { + def && (o: Basic): Natures +} + +/** Represents a feature or conceptual group of settings. +* `label` is the unique ID for this nature. */ +final case class Nature(label: String) extends Basic { + /** Constructs a Natures matcher that excludes this Nature. */ + def unary_! : Basic = Exclude(this) + override def toString = label +} + +object Natures +{ + /** Given the available auto plugins `defined`, returns a function that selects [[AutoPlugin]]s for the provided [[Nature]]s. + * The [[AutoPlugin]]s are topologically sorted so that a selected [[AutoPlugin]] comes before its selecting [[AutoPlugin]].*/ + def compile(defined: List[AutoPlugin]): Natures => Seq[AutoPlugin] = + if(defined.isEmpty) + Types.const(Nil) + else + { + val byAtom = defined.map(x => (Atom(x.provides.label), x)) + val byAtomMap = byAtom.toMap + if(byAtom.size != byAtomMap.size) duplicateProvidesError(byAtom) + val clauses = Clauses( defined.map(d => asClause(d)) ) + requestedNatures => + Logic.reduce(clauses, flatten(requestedNatures).toSet) match { + case Left(problem) => throw AutoPluginException(problem) + case Right(results) => + // results includes the originally requested (positive) atoms, + // which won't have a corresponding AutoPlugin to map back to + results.ordered.flatMap(a => byAtomMap.get(a).toList) + } + } + + private[sbt] def translateMessage(e: LogicException) = e match { + case ic: InitialContradictions => s"Contradiction in selected natures. These natures were both included and excluded: ${literalsString(ic.literals.toSeq)}" + case io: InitialOverlap => s"Cannot directly enable plugins. Plugins are enabled when their required natures are satisifed. The directly selected plugins were: ${literalsString(io.literals.toSeq)}" + case cn: CyclicNegation => s"Cycles in plugin requirements cannot involve excludes. The problematic cycle is: ${literalsString(cn.cycle)}" + } + private[this] def literalsString(lits: Seq[Literal]): String = + lits map { case Atom(l) => l; case Negated(Atom(l)) => l } mkString(", ") + + private[this] def duplicateProvidesError(byAtom: Seq[(Atom, AutoPlugin)]) { + val dupsByAtom = byAtom.groupBy(_._1).mapValues(_.map(_._2)) + val dupStrings = for( (atom, dups) <- dupsByAtom if dups.size > 1 ) yield + s"${atom.label} by ${dups.mkString(", ")}" + val (ns, nl) = if(dupStrings.size > 1) ("s", "\n\t") else ("", " ") + val message = s"Nature$ns provided by multiple AutoPlugins:$nl${dupStrings.mkString(nl)}" + throw AutoPluginException(message) + } + + /** [[Natures]] instance that doesn't require any [[Nature]]s. */ + def empty: Natures = Empty + private[sbt] final object Empty extends Natures { + def &&(o: Basic): Natures = o + override def toString = "" + } + + /** An included or excluded Nature. TODO: better name than Basic. */ + sealed abstract class Basic extends Natures { + def &&(o: Basic): Natures = And(this :: o :: Nil) + } + private[sbt] final case class Exclude(n: Nature) extends Basic { + def unary_! : Nature = n + override def toString = s"!$n" + } + private[sbt] final case class And(natures: List[Basic]) extends Natures { + def &&(o: Basic): Natures = And(o :: natures) + override def toString = natures.mkString(", ") + } + private[sbt] def and(a: Natures, b: Natures) = b match { + case Empty => a + case And(ns) => (a /: ns)(_ && _) + case b: Basic => a && b + } + + /** Defines a clause for `ap` such that the [[Nature]] provided by `ap` is the head and the selector for `ap` is the body. */ + private[sbt] def asClause(ap: AutoPlugin): Clause = + Clause( convert(ap.select), Set(Atom(ap.provides.label)) ) + + private[this] def flatten(n: Natures): Seq[Literal] = n match { + case And(ns) => convertAll(ns) + case b: Basic => convertBasic(b) :: Nil + case Empty => Nil + } + + private[this] def convert(n: Natures): Formula = n match { + case And(ns) => convertAll(ns).reduce[Formula](_ && _) + case b: Basic => convertBasic(b) + case Empty => Formula.True + } + private[this] def convertBasic(b: Basic): Literal = b match { + case Exclude(n) => !convertBasic(n) + case Nature(s) => Atom(s) + } + private[this] def convertAll(ns: Seq[Basic]): Seq[Literal] = ns map convertBasic +} \ No newline at end of file