From 4574e209eb2404afb640ccef919b2c86748cbb6f Mon Sep 17 00:00:00 2001 From: Jon Pretty Date: Mon, 3 Jun 2024 09:45:48 +0200 Subject: [PATCH] Updated for Scala 3.5.1-RC1 --- src/core/guillotine.scala | 144 +++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 73 deletions(-) diff --git a/src/core/guillotine.scala b/src/core/guillotine.scala index 4eb70eb..08c716c 100644 --- a/src/core/guillotine.scala +++ b/src/core/guillotine.scala @@ -24,7 +24,6 @@ import fulminate.* import turbulence.* import gossamer.* import spectacular.* -import eucalyptus.* import anticipation.* import scala.jdk.StreamConverters.StreamHasToScala @@ -34,7 +33,7 @@ import scala.compiletime.* import annotation.targetName import java.io as ji -import language.experimental.captureChecking +import language.experimental.pureFunctions enum Context: case Awaiting, Unquoted, Quotes2, Quotes1 @@ -49,11 +48,11 @@ object Executor: given stream: Executor[LazyList[Text]] = proc => val reader = ji.BufferedReader(ji.InputStreamReader(proc.getInputStream)) reader.lines().nn.toScala(LazyList).map(_.tt) - + given list: Executor[List[Text]] = proc => val reader = ji.BufferedReader(ji.InputStreamReader(proc.getInputStream)) reader.lines().nn.toScala(List).map(_.tt) - + given text: Executor[Text] = proc => Text.construct(stream.interpret(proc).map(_.s).each(append(_))) @@ -62,27 +61,27 @@ object Executor: given dataStream(using streamCut: Errant[StreamError]): Executor[LazyList[Bytes]] = proc => Readable.inputStream.read(proc.getInputStream.nn) - + given exitStatus: Executor[ExitStatus] = _.waitFor() match case 0 => ExitStatus.Ok case other => ExitStatus.Fail(other) - + given unit: Executor[Unit] = exitStatus.map(_ => ()) - + given path[PathType](using SpecificPath { type Self = PathType }): Executor[PathType] = proc => SpecificPath(text.interpret(proc)) -@capability +@capability trait Executor[ResultType]: def interpret(process: java.lang.Process): ResultType - + def map[ResultType2](lambda: ResultType => ResultType2): Executor[ResultType2] = process => lambda(interpret(process)) trait ProcessRef: def pid: Pid - def kill()(using Log[Text]): Unit - def abort()(using Log[Text]): Unit + def kill()(using GenericLogger): Unit + def abort()(using GenericLogger): Unit def alive: Boolean def attend(): Unit def startTime[InstantType: SpecificInstant]: Optional[InstantType] @@ -90,7 +89,7 @@ trait ProcessRef: object OsProcess: private def allHandles = ProcessHandle.allProcesses.nn.iterator.nn.asScala.to(List) - + def apply(pid: Pid)(using pidError: Errant[PidError]): OsProcess = val handle = ProcessHandle.of(pid.value).nn if handle.isPresent then new OsProcess(handle.get.nn) else abort(PidError(pid)) @@ -101,32 +100,32 @@ object OsProcess: class OsProcess private (java: ProcessHandle) extends ProcessRef: def pid: Pid = Pid(java.pid) - def kill()(using Log[Text]): Unit = java.destroy() - def abort()(using Log[Text]): Unit = java.destroyForcibly() + def kill()(using GenericLogger): Unit = java.destroy() + def abort()(using GenericLogger): Unit = java.destroyForcibly() def alive: Boolean = java.isAlive def attend(): Unit = java.onExit.nn.get() - - def parent: Optional[OsProcess] = + + def parent: Optional[OsProcess] = val parent = java.parent.nn if parent.isPresent then new OsProcess(parent.get.nn) else Unset - + def children: List[OsProcess] = java.children.nn.iterator.nn.asScala.map(new OsProcess(_)).to(List) - + def startTime[InstantType: SpecificInstant]: Optional[InstantType] = val instant = java.info.nn.startInstant.nn if instant.isPresent then SpecificInstant(instant.get.nn.toEpochMilli) else Unset - + def cpuUsage[DurationType: SpecificDuration]: Optional[DurationType] = val duration = java.info.nn.totalCpuDuration.nn if duration.isPresent then SpecificDuration(duration.get.nn.toMillis) else Unset object Process: given appendable[ChunkType](using writable: Writable[ji.OutputStream, ChunkType]) - : (Appendable[Process[?, ?], ChunkType]^{writable}) = + : Appendable[Process[?, ?], ChunkType] = (process, stream) => process.stdin(stream) - - given appendableText(using streamCut: Errant[StreamError]): (Appendable[Process[?, ?], Text]^{streamCut}) = + + given appendableText(using streamCut: Errant[StreamError]): Appendable[Process[?, ?], Text] = (process, stream) => process.stdin(stream.map(_.sysBytes)) class Process[+ExecType <: Label, ResultType](process: java.lang.Process) extends ProcessRef: @@ -135,34 +134,34 @@ class Process[+ExecType <: Label, ResultType](process: java.lang.Process) extend def attend(): Unit = process.waitFor() def stdout(): LazyList[Bytes] raises StreamError = Readable.inputStream.read(process.getInputStream.nn) def stderr(): LazyList[Bytes] raises StreamError = Readable.inputStream.read(process.getErrorStream.nn) - - def stdin[ChunkType](stream: LazyList[ChunkType]^)(using writable: Writable[ji.OutputStream, ChunkType]) - : Unit^{stream, writable} = + + def stdin[ChunkType](stream: LazyList[ChunkType])(using writable: Writable[ji.OutputStream, ChunkType]) + : Unit = writable.write(process.getOutputStream.nn, stream) - def await()(using executor: Executor[ResultType]): ResultType^{executor} = executor.interpret(process) - + def await()(using executor: Executor[ResultType]): ResultType = executor.interpret(process) + def exitStatus(): ExitStatus = process.waitFor() match case 0 => ExitStatus.Ok case other => ExitStatus.Fail(other) - - def abort()(using Log[Text]): Unit = - //Log.info(t"The process with PID ${pid.value} was aborted") + + def abort()(using logger: GenericLogger): Unit = + logger.info(t"The process with PID ${pid.value} was aborted") process.destroy() - - def kill()(using Log[Text]): Unit = - //Log.warn(t"The process with PID ${pid.value} was killed") + + def kill()(using logger: GenericLogger): Unit = + logger.warn(t"The process with PID ${pid.value} was killed") process.destroyForcibly() def osProcess(using Errant[PidError]) = OsProcess(pid) - + def startTime[InstantType: SpecificInstant]: Optional[InstantType] = try import errorHandlers.throwUnsafely osProcess.startTime[InstantType] catch case _: PidError => Unset - + def cpuUsage[InstantType: SpecificDuration]: Optional[InstantType] = try import errorHandlers.throwUnsafely @@ -172,35 +171,35 @@ class Process[+ExecType <: Label, ResultType](process: java.lang.Process) extend sealed trait Executable: type Exec <: Label - def fork[ResultType]()(using working: WorkingDirectory, log: Log[Text], exec: Errant[ExecError]) - : Process[Exec, ResultType]^{working} - + def fork[ResultType]()(using working: WorkingDirectory, logger: GenericLogger, exec: Errant[ExecError]) + : Process[Exec, ResultType] + def exec[ResultType]() - (using working: WorkingDirectory, log: Log[Text], executor: Executor[ResultType], exec: Errant[ExecError]) - : ResultType^{executor, working} = - + (using working: WorkingDirectory, logger: GenericLogger, executor: Executor[ResultType], exec: Errant[ExecError]) + : ResultType = + fork[ResultType]().await() def apply[ResultType]()(using erased commandOutput: CommandOutput[Exec, ResultType]) - (using working: WorkingDirectory, log: Log[Text], executor: Executor[ResultType], exec: Errant[ExecError]) - : ResultType^{executor, working} = - + (using working: WorkingDirectory, logger: GenericLogger, executor: Executor[ResultType], exec: Errant[ExecError]) + : ResultType = + fork[ResultType]().await() def apply(command: Executable): Pipeline = command match case Pipeline(commands*) => this match case Pipeline(commands2*) => Pipeline((commands ++ commands2)*) case command: Command => Pipeline((commands :+ command)*) - + case command: Command => this match case Pipeline(commands2*) => Pipeline((command +: commands2)*) case command2: Command => Pipeline(command, command2) - + @targetName("pipeTo") infix def | (command: Executable): Pipeline = command(this) object Command: - given communicable[CommandType <: Command]: (Communicable { type Self = CommandType }) = + given Command is Communicable = command => Message(formattedArguments(command.arguments)) private def formattedArguments(arguments: Seq[Text]): Text = @@ -216,16 +215,16 @@ object Command: given Debug[Command] = command => val commandText: Text = formattedArguments(command.arguments) if commandText.contains(t"\"") then t"sh\"\"\"$commandText\"\"\"" else t"sh\"$commandText\"" - + given Show[Command] = command => formattedArguments(command.arguments) - + case class Command(arguments: Text*) extends Executable: - def fork[ResultType]()(using working: WorkingDirectory, log: Log[Text], exec: Errant[ExecError]) + def fork[ResultType]()(using working: WorkingDirectory, logger: GenericLogger, exec: Errant[ExecError]) : Process[Exec, ResultType] = val processBuilder = ProcessBuilder(arguments.ss*) processBuilder.directory(ji.File(working.directory().s)) - + //Log.info(msg"Starting process ${this}") try new Process(processBuilder.start().nn) @@ -234,21 +233,21 @@ case class Command(arguments: Text*) extends Executable: object Pipeline: given [PipelineType <: Pipeline]: (Communicable { type Self = PipelineType }) = pipeline => msg"${pipeline.commands.map(_.show).join(t" | ")}" - + given Debug[Pipeline] = _.commands.map(_.debug).join(t" | ") given Show[Pipeline] = _.commands.map(_.show).join(t" | ") - + case class Pipeline(commands: Command*) extends Executable: - def fork[ResultType]()(using working: WorkingDirectory, log: Log[Text], exec: Errant[ExecError]) + def fork[ResultType]()(using working: WorkingDirectory, logger: GenericLogger, exec: Errant[ExecError]) : Process[Exec, ResultType] = val processBuilders = commands.map: command => val processBuilder = ProcessBuilder(command.arguments.ss*) - + processBuilder.directory(ji.File(working.directory().s)) - + processBuilder.nn - + //Log.info(msg"Starting pipelined processes ${this}") new Process[Exec, ResultType](ProcessBuilder.startPipeline(processBuilders.asJava).nn.asScala.to(List).last) @@ -263,14 +262,14 @@ object Sh: object Prefix extends Interpolator[Parameters, State, Command]: import Context.* - + def complete(state: State): Command = val arguments = state.current match case Quotes2 => throw InterpolationError(msg"the double quotes have not been closed") case Quotes1 => throw InterpolationError(msg"the single quotes have not been closed") case _ if state.escape => throw InterpolationError(msg"cannot terminate with an escape character") case _ => state.arguments - + Command(arguments*) def initial: State = State(Awaiting, false, Nil) @@ -282,23 +281,23 @@ object Sh: if state.escape then throw InterpolationError(msg""" escaping with '\\' is not allowed immediately before a substitution """) - + (state: @unchecked) match case State(Awaiting, false, arguments) => State(Unquoted, false, arguments ++ (h :: t)) case State(Unquoted, false, arguments :+ last) => State(Unquoted, false, arguments ++ (t"$last$h" :: t)) - + case State(Quotes1, false, arguments :+ last) => State(Quotes1, false, arguments :+ (t"$last$h" :: t).join(t" ")) - + case State(Quotes2, false, arguments :+ last) => State(Quotes2, false, arguments :+ (t"$last$h" :: t).join(t" ")) - + case _ => state - + def parse(state: State, next: Text): State = next.chars.to(List).foldLeft(state): (state, next) => ((state, next): @unchecked) match case (State(Awaiting, _, arguments), ' ') => State(Awaiting, false, arguments) @@ -318,17 +317,16 @@ object Sh: given Insertion[Parameters, Text] = value => Parameters(value) given Insertion[Parameters, List[Text]] = xs => Parameters(xs*) given Insertion[Parameters, Command] = command => Parameters(command.arguments*) - + given [ValueType: Parameterizable]: Insertion[Parameters, ValueType] = value => Parameters(summon[Parameterizable[ValueType]].show(value)) object Parameterizable: - given [PathType](using genericPath: GenericPath { type Self = PathType }): (Parameterizable[PathType]^{genericPath}) = - _.pathText - - given Parameterizable[Int] = _.show - - given [ValueType](using encoder: Encoder[ValueType]): (Parameterizable[ValueType]^{encoder}) = + given path[PathType: GenericPath]: Parameterizable[PathType] = _.pathText + + given int: Parameterizable[Int] = _.show + + given [ValueType](using encoder: Encoder[ValueType]): Parameterizable[ValueType] = new Parameterizable[ValueType]: def show(value: ValueType): Text = encoder.encode(value) @@ -337,10 +335,10 @@ trait Parameterizable[-ValueType]: object Guillotine: given Realm = realm"guillotine" - + def sh(context: Expr[StringContext], parts: Expr[Seq[Any]])(using Quotes): Expr[Command] = import quotes.reflect.* - + val execType = ConstantType(StringConstant(context.value.get.parts.head.split(" ").nn.head.nn)) val bounds = TypeBounds(execType, execType)