Skip to content

Commit

Permalink
Updated for Scala 3.5.1-RC1
Browse files Browse the repository at this point in the history
  • Loading branch information
propensive committed Jun 3, 2024
1 parent 9b1f7fa commit 4574e20
Showing 1 changed file with 71 additions and 73 deletions.
144 changes: 71 additions & 73 deletions src/core/guillotine.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import fulminate.*
import turbulence.*
import gossamer.*
import spectacular.*
import eucalyptus.*
import anticipation.*

import scala.jdk.StreamConverters.StreamHasToScala
Expand All @@ -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
Expand All @@ -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(_)))

Expand All @@ -62,35 +61,35 @@ 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]
def cpuUsage[DurationType: SpecificDuration]: Optional[DurationType]

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))
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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 =
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)

Expand All @@ -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)

Expand Down

0 comments on commit 4574e20

Please sign in to comment.