Task state.

* Allow tasks to provide State transformations that are applied after all tasks complete.
* Provide convenience methods for preserving state across invocations.
* Option of session or persisted state.
This commit is contained in:
Mark Harrah 2011-09-21 22:54:46 -04:00
parent 97028cb7f8
commit 5918c24722
13 changed files with 243 additions and 37 deletions

View File

@ -72,9 +72,8 @@ final object Aggregation
def applyTasks[T](s: State, structure: BuildStructure, ps: Values[Parser[Task[T]]], show: Boolean)(implicit display: Show[ScopedKey[_]]): Parser[() => State] =
Command.applyEffect(seqParser(ps)) { ts =>
runTasks(s, structure, ts, Dummies(KNil, HNil), show)
s
}
def runTasks[HL <: HList, T](s: State, structure: Load.BuildStructure, ts: Values[Task[T]], extra: Dummies[HL], show: Boolean)(implicit display: Show[ScopedKey[_]])
def runTasks[HL <: HList, T](s: State, structure: Load.BuildStructure, ts: Values[Task[T]], extra: Dummies[HL], show: Boolean)(implicit display: Show[ScopedKey[_]]): State =
{
import EvaluateTask._
import std.TaskExtra._
@ -82,13 +81,15 @@ final object Aggregation
val toRun = ts map { case KeyValue(k,t) => t.map(v => KeyValue(k,v)) } join;
val workers = maxWorkers(extracted, structure)
val start = System.currentTimeMillis
val result = withStreams(structure){ str => runTask(toRun, str, structure.index.triggers, maxWorkers = workers)(nodeView(s, str, extra.tasks, extra.values)) }
val (newS, result) = withStreams(structure){ str => runTask(toRun, s,str, structure.index.triggers, maxWorkers = workers)(nodeView(s, str, extra.tasks, extra.values)) }
val stop = System.currentTimeMillis
val log = logger(s)
val log = logger(newS)
val success = result match { case Value(_) => true; case Inc(_) => false }
try { onResult(result, log) { results => if(show) printSettings(results, log) } }
finally { printSuccess(start, stop, extracted, success, log) }
newS
}
def maxWorkers(extracted: Extracted, structure: Load.BuildStructure): Int =
(Keys.parallelExecution in extracted.currentRef get structure.data) match {
@ -138,7 +139,6 @@ final object Aggregation
val dummies = Dummies( InputTask.inputMap :^: KNil, inputMap :+: HNil)
val roots = inputs.map { case KeyValue(k,t) => KeyValue(k,t.task) }
runTasks(s, structure, roots, dummies, show)
s
}
}
def valueParser(s: State, structure: BuildStructure, show: Boolean)(key: ScopedKey[_])(implicit display: Show[ScopedKey[_]]): Parser[() => State] =

View File

@ -1125,4 +1125,18 @@ trait BuildCommon
val newConfigs = cs filter { c => !existingName(c.name) }
overridden ++ newConfigs
}
// these are intended for use in input tasks for creating parsers
def getFromContext[T](task: ScopedTask[T], context: ScopedKey[_], s: State): Option[T] =
SessionVar.get(SessionVar.resolveContext(task.scopedKey, context.scope, s), s)
def loadFromContext[T](task: ScopedTask[T], context: ScopedKey[_], s: State)(implicit f: sbinary.Format[T]): Option[T] =
SessionVar.load(SessionVar.resolveContext(task.scopedKey, context.scope, s), s)
// these are for use in tasks
def loadPrevious[T](task: ScopedTask[T])(implicit f: sbinary.Format[T]): Initialize[Task[Option[T]]] =
(state, resolvedScoped) map { (s, ctx) => loadFromContext(task, ctx, s)(f) }
def getPrevious[T](task: ScopedTask[T]): Initialize[Task[Option[T]]] =
(state, resolvedScoped) map { (s, ctx) => getFromContext(task, ctx, s) }
}

View File

@ -6,7 +6,7 @@ package sbt
import java.io.File
import Project.{ScopedKey, Setting}
import Keys.{globalLogging, streams, Streams, TaskStreams}
import Keys.{dummyState, dummyStreamsManager, streamsManager, taskDefinitionKey}
import Keys.{dummyState, dummyStreamsManager, streamsManager, taskDefinitionKey, transformState}
import Scope.{GlobalScope, ThisScope}
import scala.Console.{RED, RESET}
@ -29,14 +29,19 @@ object EvaluateTask
{
val root = ProjectRef(pluginDef.root, Load.getRootProject(pluginDef.units)(pluginDef.root))
val pluginKey = Keys.fullClasspath in Configurations.Runtime
val evaluated = evaluateTask(pluginDef, ScopedKey(pluginKey.scope, pluginKey.key), state, root)
val result = evaluated getOrElse error("Plugin classpath does not exist for plugin definition at " + pluginDef.root)
val evaluated = apply(pluginDef, ScopedKey(pluginKey.scope, pluginKey.key), state, root)
val (newS, result) = evaluated getOrElse error("Plugin classpath does not exist for plugin definition at " + pluginDef.root)
Project.runUnloadHooks(newS) // discard state
processResult(result, log)
}
@deprecated("This method does not apply state changes requested during task execution. Use 'apply' instead, which does.", "0.11.1")
def evaluateTask[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State, ref: ProjectRef, checkCycles: Boolean = false, maxWorkers: Int = SystemProcessors): Option[Result[T]] =
apply(structure, taskKey, state, ref, checkCycles, maxWorkers).map(_._2)
def apply[T](structure: BuildStructure, taskKey: ScopedKey[Task[T]], state: State, ref: ProjectRef, checkCycles: Boolean = false, maxWorkers: Int = SystemProcessors): Option[(State, Result[T])] =
withStreams(structure) { str =>
for( (task, toNode) <- getTask(structure, taskKey, state, str, ref) ) yield
runTask(task, str, structure.index.triggers, checkCycles, maxWorkers)(toNode)
runTask(task, state, str, structure.index.triggers, checkCycles, maxWorkers)(toNode)
}
def logIncResult(result: Result[_], streams: Streams) = result match { case Inc(i) => logIncomplete(i, streams); case _ => () }
def logIncomplete(result: Incomplete, streams: Streams)
@ -73,16 +78,30 @@ object EvaluateTask
def nodeView[HL <: HList](state: State, streams: Streams, extraDummies: KList[Task, HL] = KNil, extraValues: HL = HNil): Execute.NodeView[Task] =
Transform(dummyStreamsManager :^: KCons(dummyState, extraDummies), streams :+: HCons(state, extraValues))
def runTask[Task[_] <: AnyRef, T](root: Task[T], streams: Streams, triggers: Triggers[Task], checkCycles: Boolean = false, maxWorkers: Int = SystemProcessors)(implicit taskToNode: Execute.NodeView[Task]): Result[T] =
def runTask[T](root: Task[T], state: State, streams: Streams, triggers: Triggers[Task], checkCycles: Boolean = false, maxWorkers: Int = SystemProcessors)(implicit taskToNode: Execute.NodeView[Task]): (State, Result[T]) =
{
val (service, shutdown) = CompletionService[Task[_], Completed](maxWorkers)
val x = new Execute[Task](checkCycles, triggers)(taskToNode)
val result = try { x.run(root)(service) } finally { shutdown() }
val (newState, result) =
try applyResults(x.runKeep(root)(service), state, root)
catch { case inc: Incomplete => (state, Inc(inc)) }
finally shutdown()
val replaced = transformInc(result)
logIncResult(replaced, streams)
replaced
(newState, replaced)
}
def applyResults[T](results: RMap[Task, Result], state: State, root: Task[T]): (State, Result[T]) =
(stateTransform(results)(state), results(root))
def stateTransform(results: RMap[Task, Result]): State => State =
Function.chain(
results.toTypedSeq flatMap {
case results.TPair(Task(info, _), Value(v)) => info.post(v) get transformState
case _ => Nil
}
)
def transformInc[T](result: Result[T]): Result[T] =
// taskToKey needs to be before liftAnonymous. liftA only lifts non-keyed (anonymous) Incompletes.
result.toEither.left.map { i => Incomplete.transformBU(i)(convertCyclicInc andThen taskToKey andThen liftAnonymous ) }

View File

@ -32,10 +32,11 @@ object GlobalPlugin
def load(base: File, s: State, config: LoadBuildConfiguration): GlobalPlugin =
{
val (structure, state) = build(base, s, config)
val data = extract(state, structure)
val (newS, data) = extract(state, structure)
Project.runUnloadHooks(newS) // discard state
GlobalPlugin(data, structure, inject(data), base)
}
def extract(state: State, structure: BuildStructure): GlobalPluginData =
def extract(state: State, structure: BuildStructure): (State, GlobalPluginData) =
{
import structure.{data, root, rootProject}
val p: Scope = Scope.GlobalScope in ProjectRef(root, rootProject(root))
@ -47,12 +48,13 @@ object GlobalPlugin
val task = taskInit mapReferenced Project.mapScope(Scope replaceThis p) evaluate data
evaluate(state, structure, task)
}
def evaluate[T](state: State, structure: BuildStructure, t: Task[T]): T =
def evaluate[T](state: State, structure: BuildStructure, t: Task[T]): (State, T) =
{
import EvaluateTask._
withStreams(structure) { str =>
val nv = nodeView(state, str)
processResult(runTask(t, str, structure.index.triggers)(nv), log(state))
val (newS, result) = runTask(t, state, str, structure.index.triggers)(nv)
(newS, processResult(result, log(newS)))
}
}
private[this] def log(s: State) = CommandSupport.logger(s)

View File

@ -15,6 +15,7 @@ package sbt
import descriptor.ModuleDescriptor, id.ModuleRevisionId
import org.scalatools.testing.Framework
import Configurations.CompilerPlugin
import Types.Id
object Keys
{
@ -47,6 +48,7 @@ object Keys
val onLoad = SettingKey[State => State]("on-load", "Transformation to apply to the build state when the build is loaded.")
val onUnload = SettingKey[State => State]("on-unload", "Transformation to apply to the build state when the build is unloaded.")
val onLoadMessage = SettingKey[String]("on-load-message", "Message to display when the project is loaded.")
val transformState = AttributeKey[State => State]("transform-state", "State transformation to apply after tasks run.")
val onComplete = SettingKey[() => Unit]("on-complete", "Hook to run when task evaluation completes. The type of this setting is subject to change, pending the resolution of SI-2915.")
// https://issues.scala-lang.org/browse/SI-2915
@ -291,6 +293,7 @@ object Keys
val skip = TaskKey[Boolean]("skip", "For tasks that support it (currently only 'compile'), setting skip to true will force the task to not to do its work. This exact semantics may vary by task.")
// special
val sessionVars = AttributeKey[SessionVar.Map]("session-vars", "Bindings that exist for the duration of the session.")
val parallelExecution = SettingKey[Boolean]("parallel-execution", "Enables (true) or disables (false) parallel execution of tasks.")
val settings = TaskKey[Settings[Scope]]("settings", "Provides access to the project data for the build.")
val streams = TaskKey[TaskStreams]("streams", "Provides streams for logging and persisting data.")

View File

@ -6,11 +6,11 @@ package sbt
import java.io.File
import java.net.URI
import Project._
import Keys.{appConfiguration, stateBuildStructure, commands, configuration, historyPath, projectCommand, sessionSettings, shellPrompt, streams, thisProject, thisProjectRef, watch}
import Keys.{appConfiguration, stateBuildStructure, commands, configuration, historyPath, projectCommand, sessionSettings, sessionVars, shellPrompt, thisProject, thisProjectRef, watch}
import Scope.{GlobalScope,ThisScope}
import Load.BuildStructure
import CommandSupport.logger
import Types.idFun
import Types.{idFun, Id}
sealed trait ProjectDefinition[PR <: ProjectReference]
{
@ -71,13 +71,15 @@ final case class Extracted(structure: BuildStructure, session: SessionSettings,
def get[T](key: ScopedSetting[T]) = getOrError(inCurrent(key), key.key)
def getOpt[T](key: ScopedSetting[T]): Option[T] = structure.data.get(inCurrent(key), key.key)
private[this] def inCurrent[T](key: ScopedSetting[T]): Scope = if(key.scope.project == This) key.scope.copy(project = Select(currentRef)) else key.scope
def evalTask[T](key: ScopedTask[T], state: State): T =
@deprecated("This method does not apply state changes requested during task execution. Use 'runTask' instead, which does.", "0.11.1")
def evalTask[T](key: ScopedTask[T], state: State): T = runTask(key, state)._2
def runTask[T](key: ScopedTask[T], state: State): (State, T) =
{
import EvaluateTask._
val rkey = Project.mapScope(Scope.resolveScope(GlobalScope, currentRef.build, rootProject) )( key.scopedKey )
val value: Option[Result[T]] = evaluateTask(structure, key.task.scopedKey, state, currentRef)
val result = getOrError(rkey.scope, rkey.key, value)
processResult(result, ConsoleLogger())
val value: Option[(State, Result[T])] = apply(structure, key.task.scopedKey, state, currentRef)
val (newS, result) = getOrError(rkey.scope, rkey.key, value)
(newS, processResult(result, logger(newS)))
}
private def getOrError[T](scope: Scope, key: AttributeKey[_], value: Option[T])(implicit display: Show[ScopedKey[_]]): T =
value getOrElse error(display(ScopedKey(scope, key)) + " is undefined.")
@ -153,12 +155,16 @@ object Project extends Init[Scope] with ProjectExtra
def getProject(ref: ProjectRef, units: Map[URI, Load.LoadedBuildUnit]): Option[ResolvedProject] =
(units get ref.build).flatMap(_.defined get ref.project)
def setProject(session: SessionSettings, structure: BuildStructure, s: State): State =
def runUnloadHooks(s: State): State =
{
val previousOnUnload = orIdentity(s get Keys.onUnload.key)
val unloaded = previousOnUnload(s.runExitHooks())
previousOnUnload(s.runExitHooks())
}
def setProject(session: SessionSettings, structure: BuildStructure, s: State): State =
{
val unloaded = runUnloadHooks(s)
val (onLoad, onUnload) = getHooks(structure.data)
val newAttrs = unloaded.attributes.put(stateBuildStructure, structure).put(sessionSettings, session).put(Keys.onUnload.key, onUnload)
val newAttrs = unloaded.attributes.put(stateBuildStructure, structure).put(sessionSettings, session).put(Keys.onUnload.key, onUnload).put(sessionVars, SessionVar.emptyMap)
val newState = unloaded.copy(attributes = newAttrs)
onLoad(updateCurrent( newState ))
}
@ -323,11 +329,13 @@ object Project extends Init[Scope] with ProjectExtra
val newS = setProjectReturn(s, newBase :: projectReturn(s))
(newS, newBase)
}
@deprecated("This method does not apply state changes requested during task execution. Use 'runTask' instead, which does.", "0.11.1")
def evaluateTask[T](taskKey: ScopedKey[Task[T]], state: State, checkCycles: Boolean = false, maxWorkers: Int = EvaluateTask.SystemProcessors): Option[Result[T]] =
runTask(taskKey, state, checkCycles, maxWorkers).map(_._2)
def runTask[T](taskKey: ScopedKey[Task[T]], state: State, checkCycles: Boolean = false, maxWorkers: Int = EvaluateTask.SystemProcessors): Option[(State, Result[T])] =
{
val extracted = Project.extract(state)
EvaluateTask.evaluateTask(extracted.structure, taskKey, state, extracted.currentRef, checkCycles, maxWorkers)
EvaluateTask(extracted.structure, taskKey, state, extracted.currentRef, checkCycles, maxWorkers)
}
// this is here instead of Scoped so that it is considered without need for import (because of Project.Initialize)
implicit def richInitializeTask[T](init: Initialize[Task[T]]): Scoped.RichInitializeTask[T] = new Scoped.RichInitializeTask(init)
@ -345,4 +353,66 @@ trait ProjectExtra
inScope(ThisScope.copy(task = Select(t.key)) )( ss )
def inScope(scope: Scope)(ss: Seq[Setting[_]]): Seq[Setting[_]] =
Project.transform(Scope.replaceThis(scope), ss)
}
import sbinary.{Format, Operations}
object SessionVar
{
// these are required because of inference+manifest limitations
final case class Key[T](key: ScopedKey[Task[T]])
final case class Map(map: IMap[Key, Id]) {
def get[T](k: ScopedKey[Task[T]]): Option[T] = map get Key(k)
def put[T](k: ScopedKey[Task[T]], v: T): Map = Map(map put (Key(k), v))
}
def emptyMap = Map(IMap.empty)
def persistAndSet[T](key: ScopedKey[Task[T]], state: State, value: T)(implicit f: sbinary.Format[T]): State =
{
persist(key, state, value)(f)
set(key, state, value)
}
def persist[T](key: ScopedKey[Task[T]], state: State, value: T)(implicit f: sbinary.Format[T]): Unit =
Project.structure(state).streams.use(key)( s =>
Operations.write(s.binary(TaskData.DefaultDataID), value)(f)
)
def get[T](key: ScopedKey[Task[T]], state: State): Option[T] = orEmpty(state get sessionVars) get key
def set[T](key: ScopedKey[Task[T]], state: State, value: T): State = state.update(sessionVars)(om => orEmpty(om) put (key, value))
def orEmpty(opt: Option[Map]) = opt getOrElse emptyMap
def transform[S](task: Task[S], f: (State, S) => State): Task[S] =
{
val g = (s: S, map: AttributeMap) => map.put(Keys.transformState, (state: State) => f(state, s))
task.copy(info = task.info.postTransform(g))
}
def resolveContext[T](key: ScopedKey[Task[T]], context: Scope, state: State): ScopedKey[Task[T]] =
{
val subScope = Scope.replaceThis(context)(key.scope)
val scope = Project.structure(state).data.definingScope(subScope, key.key) getOrElse subScope
ScopedKey(scope, key.key)
}
def read[T](key: ScopedKey[Task[T]], state: State)(implicit f: Format[T]): Option[T] =
Project.structure(state).streams.use(key) { s =>
try { Some(Operations.read(s.readBinary(key, TaskData.DefaultDataID))) }
catch { case e: Exception => None }
}
def load[T](key: ScopedKey[Task[T]], state: State)(implicit f: Format[T]): Option[T] =
get(key, state) orElse read(key, state)(f)
def loadAndSet[T](key: ScopedKey[Task[T]], state: State, setIfUnset: Boolean = true)(implicit f: Format[T]): (State, Option[T]) =
get(key, state) match {
case s: Some[T] => (state, s)
case None => read(key, state)(f) match {
case s @ Some(t) =>
val newState = if(setIfUnset && get(key, state).isDefined) state else set(key, state, t)
(newState, s)
case None => (state, None)
}
}
}

View File

@ -166,4 +166,4 @@ save, save-all
case c: Clear => if(c.all) clearAllSettings(s) else clearSettings(s)
case r: Remove => removeSettings(s,r.ranges)
}
}
}

View File

@ -40,6 +40,7 @@ trait StateOps {
def get[T](key: AttributeKey[T]): Option[T]
def put[T](key: AttributeKey[T], value: T): State
def remove(key: AttributeKey[_]): State
def update[T](key: AttributeKey[T])(f: Option[T] => T): State
def has(key: AttributeKey[_]): Boolean
def baseDir: File
def locked[T](file: File)(t: => T): T
@ -73,6 +74,7 @@ object State
def exit(ok: Boolean) = setResult(Some(Exit(if(ok) 0 else 1)))
def get[T](key: AttributeKey[T]) = s.attributes get key
def put[T](key: AttributeKey[T], value: T) = s.copy(attributes = s.attributes.put(key, value))
def update[T](key: AttributeKey[T])(f: Option[T] => T): State = put(key, f(get(key)))
def has(key: AttributeKey[_]) = s.attributes contains key
def remove(key: AttributeKey[_]) = s.copy(attributes = s.attributes remove key)
def fail =

View File

@ -190,6 +190,15 @@ object Scoped
def dependsOn(tasks: AnyInitTask*): Initialize[Task[S]] = (i, Initialize.joinAny(tasks)) { (thisTask, deps) => thisTask.dependsOn(deps : _*) }
import SessionVar.{persistAndSet, resolveContext, set, transform}
def updateState(f: (State, S) => State): Initialize[Task[S]] = i(t => transform(t, f))
def storeAs(key: ScopedTask[S])(implicit f: sbinary.Format[S]): Initialize[Task[S]] = (Keys.resolvedScoped, i) { (scoped, task) =>
transform(task, (state, value) => persistAndSet( resolveContext(key, scoped.scope, state), state, value)(f))
}
def keepAs(key: ScopedTask[S]): Initialize[Task[S]] =
(i, Keys.resolvedScoped)( (t,scoped) => transform(t, (state,value) => set(resolveContext(key, scoped.scope, state), state, value) ) )
def triggeredBy(tasks: AnyInitTask*): Initialize[Task[S]] = nonLocal(tasks, Keys.triggeredBy)
def runBefore(tasks: AnyInitTask*): Initialize[Task[S]] = nonLocal(tasks, Keys.runBefore)
private[this] def nonLocal(tasks: Seq[AnyInitTask], key: AttributeKey[Seq[Task[_]]]): Initialize[Task[S]] =

View File

@ -0,0 +1,50 @@
import sbt._
import Keys._
import complete.Parser
import complete.DefaultParsers._
import sbinary.DefaultProtocol._
import Project.Initialize
object MyBuild extends Build
{
val keep = TaskKey[Int]("keep")
val persisted = TaskKey[Int]("persist")
val checkKeep = InputKey[Unit]("check-keep")
val checkPersisted = InputKey[Unit]("check-persist")
val updateDemo = TaskKey[Int]("demo")
val check = InputKey[Unit]("check")
val sample = AttributeKey[Int]("demo-key")
def updateDemoInit = state map { s => (s get sample getOrElse 9) + 1 }
lazy val root = Project("root", file(".")) settings(
updateDemo <<= updateDemoInit updateState demoState,
check <<= checkInit,
inMemorySetting,
persistedSetting,
inMemoryCheck,
persistedCheck
)
def demoState(s: State, i: Int): State = s put (sample, i + 1)
def checkInit: Initialize[InputTask[Unit]] = InputTask( (_: State) => token(Space ~> IntBasic) ~ token(Space ~> IntBasic).?) { key =>
(key, updateDemo, state) map { case ((curExpected, prevExpected), value, s) =>
val prev = s get sample
assert(value == curExpected, "Expected current value to be " + curExpected + ", got " + value)
assert(prev == prevExpected, "Expected previous value to be " + prevExpected + ", got " + prev)
}
}
def inMemorySetting = keep <<= getPrevious(keep) map { case None => 3; case Some(x) => x + 1} keepAs(keep)
def persistedSetting = persisted <<= loadPrevious(persisted) map { case None => 17; case Some(x) => x + 1} storeAs(keep)
def inMemoryCheck = checkKeep <<= inputCheck( (ctx, s) => Space ~> str(getFromContext(keep, ctx, s)) )
def persistedCheck = checkPersisted <<= inputCheck( (ctx, s) => Space ~> str(loadFromContext(persisted, ctx, s)) )
def inputCheck[T](f: (ScopedKey[_], State) => Parser[T]): Initialize[InputTask[Unit]] =
InputTask( resolvedScoped(ctx => (s: State) => f(ctx, s)) )( dummyTask )
def dummyTask = (key: Any) => maxErrors map { _ => () }
def str(o: Option[Int]) = o match { case None => "blue"; case Some(i) => i.toString }
}

View File

@ -0,0 +1,39 @@
> check 10
> check 12 11
> check 14 13
> reload
> check 16 15
-> check-keep 3
> check-keep blue
> keep
> check-keep 3
-> check-keep 4
> check-keep 3
> keep
-> check-keep 3
> check-keep 4
> check-keep 4
> reload
> check-keep blue
> keep
> check-keep 3
-> check-persist 17
> check-persist blue
> persist
> check-persist 17
-> check-persist blue
> persist
> check-persist 18
> reload
> check-persist 18
> persist
> check-persist 19

View File

@ -22,13 +22,12 @@ object Task
type Results[HL <: HList] = KList[Result, HL]
}
final case class Task[T](info: Info, work: Action[T])
final case class Task[T](info: Info[T], work: Action[T])
{
override def toString = info.name getOrElse ("Task(" + info + ")")
override def hashCode = info.hashCode
}
/** `original` is used during transformation only.*/
final case class Info(attributes: AttributeMap = AttributeMap.empty)
final case class Info[T](attributes: AttributeMap = AttributeMap.empty, post: T => AttributeMap = const(AttributeMap.empty))
{
import Info._
def name = attributes.get(Name)
@ -36,12 +35,9 @@ final case class Info(attributes: AttributeMap = AttributeMap.empty)
def setName(n: String) = set(Name, n)
def setDescription(d: String) = set(Description, d)
def set[T](key: AttributeKey[T], value: T) = copy(attributes = this.attributes.put(key, value))
def postTransform[A](f: (T, AttributeMap) => AttributeMap) = copy(post = (t: T) => f(t, post(t)) )
override def toString =
if(attributes.isEmpty)
"_"
else
attributes.toString
override def toString = if(attributes.isEmpty) "_" else attributes.toString
}
object Info
{

View File

@ -23,6 +23,7 @@ sealed trait TaskStreams[Key]
final def readText(a: Key, sid: Option[String]): BufferedReader = readText(a, getID(sid))
final def readBinary(a: Key, sid: Option[String]): BufferedInputStream = readBinary(a, getID(sid))
def key: Key
def text(sid: String = default): PrintWriter
def binary(sid: String = default): BufferedOutputStream
@ -91,7 +92,8 @@ object Streams
opened ::= t
t
}
def key: Key = a
def open() {}
def close(): Unit = synchronized {