session manipulation commands

save, clear, list, and remove session settings
This commit is contained in:
Mark Harrah 2011-02-02 22:56:11 -05:00
parent 14990b5127
commit c9a6d2f6b0
4 changed files with 143 additions and 5 deletions

View File

@ -62,8 +62,11 @@ SetCommand + """ <setting-expression>
This command does not rebuild the build definitions, plugins, or configurations.
It does not automatically persist the setting.
This is done by running 'save-settings'.
This is done by running 'settings save' or 'settings save-all'.
"""
def SessionCommand = "session"
def sessionBrief = (SessionCommand + " ...", "Manipulates session settings. For details, run 'help " + SessionCommand + "'..")
/** The command name to terminate the program.*/
val TerminateAction: String = Exit

View File

@ -60,7 +60,7 @@ class xMain extends xsbti.AppMain
object Commands
{
def DefaultCommands: Seq[Command] = Seq(ignore, help, reload, read, history, continuous, exit, loadCommands, loadProject, compile, discover,
projects, project, setOnFailure, ifLast, multi, shell, set, get, eval, alias, append, nop, act)
projects, project, setOnFailure, ifLast, multi, shell, set, get, eval, alias, append, nop, sessionCommand, act)
def nop = Command.custom(s => success(() => s), Nil)
def ignore = Command.command(FailureWall)(identity)
@ -221,15 +221,20 @@ object Commands
log.info("ans: " + result.tpe + " = " + result.value.toString)
s
}
def sessionCommand = Command(SessionCommand, sessionBrief, SessionSettings.Help)(SessionSettings.command)
def reapply(newSession: SessionSettings, structure: Load.BuildStructure, s: State): State =
{
logger(s).info("Reapplying settings...")
val newStructure = Load.reapply(newSession.mergeSettings, structure)
setProject(newSession, newStructure, s)
}
def set = Command.single(SetCommand, setBrief, setDetailed) { (s, arg) =>
val extracted = Project extract s
import extracted._
val setting = EvaluateConfigurations.evaluateSetting(session.currentEval(), "<set>", imports(extracted), arg, 0)
val append = Load.transformSettings(Load.projectScope(curi, cid), curi, rootProject, setting :: Nil)
val newSession = session.appendSettings( append map (a => (a, arg)))
logger(s).info("Reapplying settings...")
val newStructure = Load.reapply(newSession.mergeSettings, structure)
setProject(newSession, newStructure, s)
reapply(newSession, structure, s)
}
def get = Command.single(GetCommand, getBrief, getDetailed) { (s, arg) =>
val extracted = Project extract s

View File

@ -117,6 +117,7 @@ final case class SessionSettings(currentBuild: URI, currentProject: Map[URI, Str
def appendSettings(s: Seq[SessionSetting]): SessionSettings = copy(append = modify(append, _ ++ s))
def prependSettings(s: Seq[SessionSetting]): SessionSettings = copy(prepend = modify(prepend, s ++ _))
def mergeSettings: Seq[Setting[_]] = merge(prepend) ++ original ++ merge(append)
def clearExtraSettings: SessionSettings = copy(prepend = Map.empty, append = Map.empty)
private[this] def merge(map: SessionMap): Seq[Setting[_]] = map.values.toSeq.flatten[SessionSetting].map(_._1)
private[this] def modify(map: SessionMap, onSeq: Endo[Seq[SessionSetting]]): SessionMap =
@ -129,6 +130,131 @@ object SessionSettings
{
type SessionSetting = (Setting[_], String)
type SessionMap = Map[(URI, String), Seq[SessionSetting]]
def reapply(session: SessionSettings, s: State): State =
Commands.reapply(session, Project.structure(s), s)
def clearSettings(s: State): State =
withSettings(s)(session => reapply(session.copy(append = session.append - session.current), s))
def clearAllSettings(s: State): State =
withSettings(s)(session => reapply(session.clearExtraSettings, s))
def withSettings(s: State)(f: SessionSettings => State): State =
{
val extracted = Project extract s
import extracted._
if(session.prepend.isEmpty && session.append.isEmpty)
{
logger(s).info("No session settings defined.")
s
}
else
f(session)
}
def removeRanges[T](in: Seq[T], ranges: Seq[(Int,Int)]): Seq[T] =
{
val asSet = (Set.empty[Int] /: ranges) { case (s, (hi,lo)) => s ++ (hi to lo) }
in.zipWithIndex.flatMap { case (t, index) => if(asSet(index+1)) Nil else t :: Nil }
}
def removeSettings(s: State, ranges: Seq[(Int,Int)]): State =
withSettings(s) { session =>
val current = session.current
val newAppend = session.append.updated(current, removeRanges(session.append.getOrElse(current, Nil), ranges))
reapply(session.copy( append = newAppend ), s)
}
def saveAllSettings(s: State): State = saveSomeSettings(s)((_,_) => true)
def saveSettings(s: State): State =
{
val (curi,cid) = Project.session(s).current
saveSomeSettings(s)( (uri,id) => uri == curi && id == cid)
}
def saveSomeSettings(s: State)(include: (URI,String) => Boolean): State =
withSettings(s){session =>
for( ((uri,id), settings) <- session.append if !settings.isEmpty && include(uri,id) )
writeSettings(ProjectRef(uri, id), settings, Project.structure(s))
reapply(session.copy(original = session.mergeSettings, append = Map.empty, prepend = Map.empty), s)
}
def writeSettings(pref: ProjectRef, settings: Seq[SessionSetting], structure: Load.BuildStructure)
{
val project = Project.getProject(pref, structure).getOrElse(error("Invalid project reference " + pref))
val appendTo: File = BuildPaths.configurationSources(project.base).headOption.getOrElse(new File(project.base, "build.sbt"))
val sbtAppend = settingStrings(settings).flatMap("" :: _ :: Nil)
IO.writeLines(appendTo, sbtAppend, append = true)
}
def printAllSettings(s: State): State =
withSettings(s){ session =>
for( ((uri,id), settings) <- session.append if !settings.isEmpty) {
println("In " + Project.display(ProjectRef(uri,id)))
printSettings(settings)
}
s
}
def printSettings(s: State): State =
withSettings(s){ session =>
printSettings(session.append.getOrElse(session.current, Nil))
s
}
def printSettings(settings: Seq[SessionSetting]): Unit =
for((stringRep, index) <- settingStrings(settings).zipWithIndex)
println(" " + (index+1) + ". " + stringRep)
def settingStrings(s: Seq[SessionSetting]): Seq[String] = s.map(_._2)
def Help = """session <command>
Manipulates session settings, which are temporary settings that do not persist past the current sbt execution (that is, the current session).
Valid commands are:
clear, clear-all
Removes temporary settings added using 'set' and re-evaluates all settings.
For 'clear', only the settings defined for the current project are cleared.
For 'clear-all', all settings in all projects are cleared.
list, list-all
Prints a numbered list of session settings defined.
The numbers may be used to remove individual settings or ranges of settings using 'remove'.
For 'list', only the settings for the current project are printed.
For 'list-all', all settings in all projets are printed.
remove <range-spec>
<range-spec> is a comma-separated list of individual numbers or ranges of numbers.
For example, 'remove 1,3,5-7'.
The temporary settings at the given indices for the current project are removed and all settings are re-evaluated.
Use the 'list' command to see a numbered list of settings for the current project.
save, save-all
Makes the session settings permanent by writing them to a '.sbt' configuration file.
For 'save', only the current project's settings are saved (the settings for other projects are left alone).
For 'save-all', the session settings are saved for all projects.
The session settings defined for a project are appended to the first '.sbt' configuration file in that project.
If no '.sbt' configuration file exists, the settings are written to 'build.sbt' in the project's base directory."""
sealed trait SessionCommand
final class Print(val all: Boolean) extends SessionCommand
final class Clear(val all: Boolean) extends SessionCommand
final class Save(val all: Boolean) extends SessionCommand
final class Remove(val ranges: Seq[(Int,Int)]) extends SessionCommand
import complete._
import DefaultParsers._
lazy val parser =
token(Space) ~>
(token("list-all" ^^^ new Print(true)) | token("list" ^^^ new Print(false)) | token("clear" ^^^ new Clear(false)) |
token("save-all" ^^^ new Save(true)) | token("save" ^^^ new Save(false)) | token("clear-all" ^^^ new Clear(true)) |
remove)
lazy val remove = token("remove") ~> token(Space) ~> natSelect.map(ranges => new Remove(ranges))
def natSelect = rep1sep(token(range, "<range>"), ',')
def range: Parser[(Int,Int)] = (NatBasic ~ ('-' ~> NatBasic).?).map { case lo ~ hi => (lo, hi getOrElse lo)}
def command(s: State) = Command.applyEffect(parser){
case p: Print => if(p.all) printAllSettings(s) else printSettings(s)
case v: Save => if(v.all) saveAllSettings(s) else saveSettings(s)
case c: Clear => if(c.all) clearAllSettings(s) else clearSettings(s)
case r: Remove => removeSettings(s,r.ranges)
}
}
trait ProjectConstructors

View File

@ -39,9 +39,13 @@ trait Parsers
lazy val Port = token(IntBasic, "<port>")
lazy val IntBasic = mapOrFail( '-'.? ~ Digit.+ )( Function.tupled(toInt) )
lazy val NatBasic = mapOrFail( Digit.+ )( _.mkString.toInt )
private[this] def toInt(neg: Option[Char], digits: Seq[Char]): Int =
(neg.toSeq ++ digits).mkString.toInt
def rep1sep[T](rep: Parser[T], sep: Parser[_]): Parser[Seq[T]] =
(rep ~ (sep ~> rep).*).map { case (x ~ xs) => x +: xs }
def mapOrFail[S,T](p: Parser[S])(f: S => T): Parser[T] =
p flatMap { s => try { success(f(s)) } catch { case e: Exception => failure(e.toString) } }