package scala.tools
package nsc
package settings
import io.{ AbstractFile, Path, PlainFile, VirtualDirectory }
import scala.tools.util.StringOps
import scala.collection.mutable.ListBuffer
import scala.io.Source
class MutableSettings(val errorFn: String => Unit) extends AbsSettings with ScalaSettings with Mutable {
type ResultOfTryToSet = List[String]
def processArguments(arguments: List[String], processAll: Boolean): (Boolean, List[String]) = {
def loop(args: List[String], residualArgs: List[String]): (Boolean, List[String]) = args match {
case Nil =>
(checkDependencies, residualArgs)
case "--" :: xs =>
(checkDependencies, xs)
case x :: xs =>
val isOpt = x startsWith "-"
if (isOpt) {
val newArgs = parseParams(args)
if (args eq newArgs) {
errorFn("bad option: '" + x + "'")
(false, args)
}
else if (x == "") {
loop(xs, residualArgs)
}
else lookupSetting(x) match {
case Some(s) if s.shouldStopProcessing => (checkDependencies, newArgs)
case _ => loop(newArgs, residualArgs)
}
}
else {
if (processAll) loop(xs, residualArgs :+ x)
else (checkDependencies, args)
}
}
loop(arguments, Nil)
}
def processArgumentString(params: String) = processArguments(splitParams(params), true)
def copy(): Settings = {
val s = new Settings()
val xs = userSetSettings flatMap (_.unparse)
s.processArguments(xs.toList, true)
s
}
lazy val outputDirs = new OutputDirs
lazy val prefixSettings = allSettings collect { case x: PrefixSetting => x }
def splitParams(line: String) = cmd.Parser.tokenize(line, errorFn)
protected def parseParams(args: List[String]): List[String] = {
def tryToSetIfExists(
cmd: String,
args: List[String],
setter: (Setting) => (List[String] => Option[List[String]])
): Option[List[String]] =
lookupSetting(cmd) match {
case None => None
case Some(cmd) => setter(cmd)(args)
}
def parseColonArg(s: String): Option[List[String]] = {
val (p, args) = StringOps.splitWhere(s, _ == ':', true) getOrElse (return None)
tryToSetIfExists(p, args split "," toList, (s: Setting) => s.tryToSetColon _)
}
def parseNormalArg(p: String, args: List[String]): Option[List[String]] =
tryToSetIfExists(p, args, (s: Setting) => s.tryToSet _)
args match {
case Nil => Nil
case arg :: rest =>
if (!arg.startsWith("-")) {
errorFn("Argument '" + arg + "' does not start with '-'.")
args
}
else if (arg == "-") {
errorFn("'-' is not a valid argument.")
args
}
else {
val prefix = prefixSettings find (_ respondsTo arg)
if (prefix.isDefined) {
prefix.get tryToSet args
rest
}
else if (arg contains ":") parseColonArg(arg) match {
case Some(_) => rest
case None => args
}
else parseNormalArg(arg, rest) match {
case Some(xs) => xs
case None => args
}
}
}
}
def embeddedDefaults[T: Manifest]: Unit =
embeddedDefaults(implicitly[Manifest[T]].erasure.getClassLoader)
def embeddedDefaults(loader: ClassLoader) {
explicitParentLoader = Option(loader)
getClasspath("app", loader) foreach { classpath.value = _ }
getClasspath("boot", loader) foreach { bootclasspath append _ }
}
private[nsc] var explicitParentLoader: Option[ClassLoader] = None
private def getClasspath(id: String, loader: ClassLoader): Option[String] =
Option(loader).flatMap(ld => Option(ld.getResource(id + ".class.path"))).map { cp =>
Source.fromURL(cp).mkString
}
private def add[T <: Setting](s: T): T = {
allSettings += s
s
}
def BooleanSetting(name: String, descr: String) = add(new BooleanSetting(name, descr))
def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String) =
add(new ChoiceSetting(name, helpArg, descr, choices, default))
def IntSetting(name: String, descr: String, default: Int, range: Option[(Int, Int)], parser: String => Option[Int]) = add(new IntSetting(name, descr, default, range, parser))
def MultiStringSetting(name: String, arg: String, descr: String) = add(new MultiStringSetting(name, arg, descr))
def OutputSetting(outputDirs: OutputDirs, default: String) = add(new OutputSetting(outputDirs, default))
def PhasesSetting(name: String, descr: String) = add(new PhasesSetting(name, descr))
def StringSetting(name: String, arg: String, descr: String, default: String) = add(new StringSetting(name, arg, descr, default))
def PathSetting(name: String, descr: String, default: String): PathSetting = {
val prepend = StringSetting(name + "/p", "", "", "").internalOnly()
val append = StringSetting(name + "/a", "", "", "").internalOnly()
add(new PathSetting(name, descr, default, prepend, append))
}
def PrefixSetting(name: String, prefix: String, descr: String): PrefixSetting = add(new PrefixSetting(name, prefix, descr))
trait SettingValue extends AbsSettingValue {
protected var v: T
protected var setByUser: Boolean = false
def postSetHook(): Unit
def isDefault: Boolean = !setByUser
def value: T = v
def value_=(arg: T) = {
setByUser = true
v = arg
postSetHook()
}
}
class OutputDirs {
private var outputDirs: List[(AbstractFile, AbstractFile)] = Nil
private var singleOutDir: Option[AbstractFile] = None
def add(srcDir: String, outDir: String): Unit =
add(checkDir(AbstractFile.getDirectory(srcDir), srcDir),
checkDir(AbstractFile.getDirectory(outDir), outDir))
private def checkDir(dir: AbstractFile, name: String, allowJar: Boolean = false): AbstractFile = (
if (dir != null && dir.isDirectory)
dir
else if (allowJar && dir == null && Path.isJarOrZip(name, false))
new PlainFile(Path(name))
else
throw new FatalError(name + " does not exist or is not a directory")
)
def setSingleOutput(outDir: String) {
val dst = AbstractFile.getDirectory(outDir)
setSingleOutput(checkDir(dst, outDir, true))
}
def getSingleOutput: Option[AbstractFile] = singleOutDir
def setSingleOutput(dir: AbstractFile) {
singleOutDir = Some(dir)
}
def add(src: AbstractFile, dst: AbstractFile) {
singleOutDir = None
outputDirs ::= (src, dst)
}
def outputs: List[(AbstractFile, AbstractFile)] = outputDirs
def outputDirFor(src: AbstractFile): AbstractFile = {
def isBelow(srcDir: AbstractFile, outDir: AbstractFile) =
src.path.startsWith(srcDir.path)
singleOutDir match {
case Some(d) => d
case None =>
(outputs find (isBelow _).tupled) match {
case Some((_, d)) => d
case _ =>
throw new FatalError("Could not find an output directory for "
+ src.path + " in " + outputs)
}
}
}
def srcFilesFor(classFile : AbstractFile, srcPath : String) : List[AbstractFile] = {
def isBelow(srcDir: AbstractFile, outDir: AbstractFile) =
classFile.path.startsWith(outDir.path)
singleOutDir match {
case Some(d) =>
d match {
case _: VirtualDirectory | _: io.ZipArchive => Nil
case _ => List(d.lookupPathUnchecked(srcPath, false))
}
case None =>
(outputs filter (isBelow _).tupled) match {
case Nil => Nil
case matches => matches.map(_._1.lookupPathUnchecked(srcPath, false))
}
}
}
}
abstract class Setting(val name: String, val helpDescription: String) extends AbsSetting with SettingValue with Mutable {
private var _postSetHook: this.type => Unit = (x: this.type) => ()
def postSetHook(): Unit = _postSetHook(this)
def withPostSetHook(f: this.type => Unit): this.type = { _postSetHook = f ; this }
private var _helpSyntax = name
override def helpSyntax: String = _helpSyntax
def withHelpSyntax(s: String): this.type = { _helpSyntax = s ; this }
private var _abbreviations: List[String] = Nil
override def abbreviations = _abbreviations
def withAbbreviation(s: String): this.type = { _abbreviations ++= List(s) ; this }
private var dependency: Option[(Setting, String)] = None
override def dependencies = dependency.toList
def dependsOn(s: Setting, value: String): this.type = { dependency = Some((s, value)); this }
private var _deprecationMessage: Option[String] = None
override def deprecationMessage = _deprecationMessage
def withDeprecationMessage(msg: String): this.type = { _deprecationMessage = Some(msg) ; this }
}
class IntSetting private[nsc](
name: String,
descr: String,
val default: Int,
val range: Option[(Int, Int)],
parser: String => Option[Int])
extends Setting(name, descr) {
type T = Int
protected var v = default
val IntMin = Int.MinValue
val IntMax = Int.MaxValue
def min = range map (_._1) getOrElse IntMin
def max = range map (_._2) getOrElse IntMax
override def value_=(s: Int) =
if (isInputValid(s)) super.value_=(s) else errorMsg
assert(min <= max)
private def isInputValid(k: Int): Boolean = (min <= k) && (k <= max)
private def getValidText: String = (min, max) match {
case (IntMin, IntMax) => "can be any integer"
case (IntMin, x) => "must be less than or equal to "+x
case (x, IntMax) => "must be greater than or equal to "+x
case _ => "must be between %d and %d".format(min, max)
}
assert(isInputValid(default))
def parseArgument(x: String): Option[Int] = {
parser(x) orElse {
try { Some(x.toInt) }
catch { case _: NumberFormatException => None }
}
}
def errorMsg() = errorFn("invalid setting for -"+name+" "+getValidText)
def tryToSet(args: List[String]) =
if (args.isEmpty) errorAndValue("missing argument", None)
else parseArgument(args.head) match {
case Some(i) => value = i ; Some(args.tail)
case None => errorMsg ; None
}
def unparse: List[String] =
if (value == default) Nil
else List(name, value.toString)
withHelpSyntax(name + " <n>")
}
class BooleanSetting private[nsc](
name: String,
descr: String)
extends Setting(name, descr) {
type T = Boolean
protected var v = false
def tryToSet(args: List[String]) = { value = true ; Some(args) }
def unparse: List[String] = if (value) List(name) else Nil
override def tryToSetFromPropertyValue(s : String) {
value = s.equalsIgnoreCase("true")
}
}
class PrefixSetting private[nsc](
name: String,
prefix: String,
descr: String)
extends Setting(name, descr) {
type T = List[String]
protected var v: List[String] = Nil
def tryToSet(args: List[String]) = args match {
case x :: xs if x startsWith prefix =>
v = v :+ x
Some(xs)
case _ =>
None
}
override def respondsTo(token: String) = token startsWith prefix
def unparse: List[String] = value
}
class StringSetting private[nsc](
name: String,
val arg: String,
descr: String,
val default: String)
extends Setting(name, descr) {
type T = String
protected var v = default
def tryToSet(args: List[String]) = args match {
case Nil => errorAndValue("missing argument", None)
case x :: xs => value = x ; Some(xs)
}
def unparse: List[String] = if (value == default) Nil else List(name, value)
withHelpSyntax(name + " <" + arg + ">")
}
class PathSetting private[nsc](
name: String,
descr: String,
default: String,
prependPath: StringSetting,
appendPath: StringSetting)
extends StringSetting(name, "path", descr, default) {
import util.ClassPath.join
def prepend(s: String) = prependPath.value = join(s, prependPath.value)
def append(s: String) = appendPath.value = join(appendPath.value, s)
override def value = join(
prependPath.value,
super.value,
appendPath.value
)
}
class OutputSetting private[nsc](
private[nsc] val outputDirs: OutputDirs,
default: String)
extends StringSetting("-d", "directory|jar", "destination for generated classfiles.", default) {
value = default
override def value_=(str: String) {
super.value_=(str)
try outputDirs.setSingleOutput(str)
catch { case FatalError(msg) => errorFn(msg) }
}
}
class MultiStringSetting private[nsc](
name: String,
val arg: String,
descr: String)
extends Setting(name, descr) {
type T = List[String]
protected var v: List[String] = Nil
def appendToValue(str: String) { value ++= List(str) }
def tryToSet(args: List[String]) = {
val (strings, rest) = args span (x => !x.startsWith("-"))
strings foreach appendToValue
Some(rest)
}
override def tryToSetColon(args: List[String]) = tryToSet(args)
override def tryToSetFromPropertyValue(s: String) = tryToSet(s.trim.split(" +").toList)
def unparse: List[String] = value map { name + ":" + _ }
withHelpSyntax(name + ":<" + arg + ">")
}
class ChoiceSetting private[nsc](
name: String,
helpArg: String,
descr: String,
override val choices: List[String],
val default: String)
extends Setting(name, descr + choices.mkString(" (", ",", ") default:" + default)) {
type T = String
protected var v: String = default
def indexOfChoice: Int = choices indexOf value
private def usageErrorMessage = {
"Usage: %s:<%s>\n where <%s> choices are %s (default: %s)\n".format(
name, helpArg, helpArg, choices mkString ", ", default)
}
def tryToSet(args: List[String]) = errorAndValue(usageErrorMessage, None)
override def tryToSetColon(args: List[String]) = args match {
case Nil => errorAndValue(usageErrorMessage, None)
case List(x) if choices contains x => value = x ; Some(Nil)
case List(x) => errorAndValue("'" + x + "' is not a valid choice for '" + name + "'", None)
case xs => errorAndValue("'" + name + "' does not accept multiple arguments.", None)
}
def unparse: List[String] =
if (value == default) Nil else List(name + ":" + value)
override def tryToSetFromPropertyValue(s: String) = tryToSetColon(s::Nil)
withHelpSyntax(name + ":<" + helpArg + ">")
}
class PhasesSetting private[nsc](
name: String,
descr: String)
extends Setting(name, descr + " <phase>.") {
type T = List[String]
protected var v: List[String] = Nil
override def value = if (v contains "all") List("all") else super.value
private lazy val (numericValues, stringValues) =
value filterNot (_ == "" ) partition (_ forall (ch => ch.isDigit || ch == '-'))
private def stringToPhaseIdTest(s: String): Int => Boolean = (s indexOf '-') match {
case -1 => (_ == s.toInt)
case 0 => (_ <= s.tail.toInt)
case idx =>
if (s.last == '-') (_ >= s.init.toInt)
else (s splitAt idx) match {
case (s1, s2) => (id => id >= s1.toInt && id <= s2.tail.toInt)
}
}
private lazy val phaseIdTest: Int => Boolean =
(numericValues map stringToPhaseIdTest) match {
case Nil => _ => false
case fns => fns.reduceLeft((f1, f2) => id => f1(id) || f2(id))
}
def tryToSet(args: List[String]) = errorAndValue("missing phase", None)
override def tryToSetColon(args: List[String]) = args match {
case Nil => errorAndValue("missing phase", None)
case xs => value = (value ++ xs).distinct.sorted ; Some(Nil)
}
def contains(phName: String) = doAllPhases || containsName(phName)
def containsName(phName: String) = stringValues exists (phName startsWith _)
def containsId(phaseId: Int) = phaseIdTest(phaseId)
def containsPhase(ph: Phase) = contains(ph.name) || containsId(ph.id)
def doAllPhases = stringValues contains "all"
def unparse: List[String] = value map (name + ":" + _)
withHelpSyntax(name + ":<phase>")
}
}