Compare commits

3 Commits

Author SHA1 Message Date
Smaug123
64127ef139 How about going without weird typars 2023-12-21 22:32:58 +00:00
Smaug123
04552cb995 wip 2023-12-21 21:06:27 +00:00
Smaug123
46d9ac111a Type safe 2023-12-21 18:57:46 +00:00
4 changed files with 134 additions and 70 deletions

View File

@@ -6,113 +6,170 @@ type Stdout = Stdout of string Comp
type PipelineModifier =
private
| WithEnv of key : string * value : string Comp
| WorkingDir of dir : DirectoryInfo Comp
| Remote of image : string Comp
{
EnvVars : (string * string Comp) list
// TODO: does this stack the right way? Does last win or first win?
WorkingDir : DirectoryInfo Comp option
AgentModifier : string Comp option
}
override this.ToString () =
let agent =
match this.AgentModifier with
| None -> "(same agent)"
| Some c ->
"New agent with image: " + Comp.force c
let envVars =
if this.EnvVars.IsEmpty then
""
else
this.EnvVars
|> Seq.map (fun (k, v) -> k + ":" + Comp.force v)
|> String.concat ", "
|> fun s -> "; Env vars: " + s
let workingDir =
match this.WorkingDir with
| None -> ""
| Some dir -> "; Working dir: " + (Comp.force dir).FullName
agent + envVars + workingDir
type Pipeline =
| Empty
| ShellScript of script : string * andThen : Pipeline
| ShellScript of script : string * andThen : (Pipeline)
| ShellScriptBind of script : string * consumeStdout : (Stdout -> Pipeline)
| WithModifier of Pipeline * PipelineModifier list
| Sequence of first : Pipeline * second : Pipeline
| Sequence of first : (Pipeline * PipelineModifier option) * second : (Pipeline)
[<RequireQualifiedAccess>]
module Pipeline =
let rec private toStringInner (bindCount : int) (indentCount : int) (this : Pipeline) : string =
let indent = String.replicate indentCount " "
match this with
| Pipeline.Empty -> $"%s{indent}<nothing>"
| Pipeline.ShellScript(script, (andThen)) ->
$"%s{indent}Run: %s{script}\n%s{indent}Then\n%s{toStringInner bindCount (indentCount + 2) andThen}"
| Pipeline.ShellScriptBind(script, consumeStdout) ->
let outputDummy = $"<stdout-%i{bindCount}>"
let dummyPipeline = consumeStdout (Stdout (Comp.make outputDummy))
$"%s{indent}Run: %s{script}\n%s{indent}With its output labelled %s{outputDummy}:\n%s{toStringInner (bindCount + 1) (indentCount + 2) dummyPipeline}"
| Pipeline.Sequence((first, modifier), second) ->
let firstMod =
match modifier with
| None -> ""
| Some modifier ->
$"%O{modifier}"
let first = $"%s{toStringInner bindCount (indentCount + 2) first}"
let second = $"%s{toStringInner bindCount (indentCount + 2) second}"
$"%s{indent} {firstMod}\n%s{first}\n%s{indent}----\n%s{second}"
let toString (p : Pipeline) = toStringInner 0 0 p
[<RequireQualifiedAccess>]
module private PipelineModifier =
let empty (agent : string Comp option) : PipelineModifier =
{
EnvVars = []
WorkingDir = None
AgentModifier = agent
}
[<Sealed ; Class>]
type Pipeline<'plat> () =
type PipelineBuilder (agent : string Comp option) =
let mutable modifier : PipelineModifier =
PipelineModifier.empty agent
[<CustomOperation "withEnv">]
member _.WithEnv
(mods : PipelineModifier list, (key : string, value : string Comp))
: PipelineModifier list
(mods : PipelineModifier, (key : string, value : string Comp))
: PipelineModifier
=
PipelineModifier.WithEnv (key, value) :: mods
{ mods with
EnvVars = (key, value) :: mods.EnvVars
}
[<CustomOperation "withEnv">]
member _.WithEnv
(() : unit, (key : string, value : string Comp))
: PipelineModifier list
: PipelineModifier
=
[PipelineModifier.WithEnv (key, value)]
{ PipelineModifier.empty agent with
EnvVars = [key, value]
}
[<CustomOperation "workingDir">]
member _.WorkingDir
(mods : PipelineModifier list, (dir : DirectoryInfo Comp))
: PipelineModifier list
(mods : PipelineModifier, dir : DirectoryInfo Comp)
: PipelineModifier
=
PipelineModifier.WorkingDir dir :: mods
{ mods with
WorkingDir = Some dir
}
[<CustomOperation "workingDir">]
member _.WorkingDir
(() : unit, dir : DirectoryInfo Comp)
: PipelineModifier list
: PipelineModifier
=
[PipelineModifier.WorkingDir dir]
{ PipelineModifier.empty agent with
WorkingDir = Some dir
}
[<CustomOperation "remote">]
member _.Remote
(() : unit, image : string Comp)
: PipelineModifier list
=
[PipelineModifier.Remote image]
[<CustomOperation "remote">]
member _.Remote
(mods : PipelineModifier list, image : string Comp)
: PipelineModifier list
=
PipelineModifier.Remote image :: mods
member _.Return (() : unit) = Pipeline.Empty
member _.Return (() : unit) : Pipeline =
Pipeline.Empty
/// For running a script, capturing stdout
member _.Bind<'b> (toRun : string, cont : Stdout -> Pipeline) : Pipeline =
Pipeline.ShellScriptBind (toRun, cont)
member _.Bind (toRun : string, cont : Stdout -> Pipeline) : Pipeline =
let go stdout =
let subsequent = cont stdout
subsequent
Pipeline.ShellScriptBind (toRun, go)
/// For running a script, without capturing stdout
member _.Bind (toRun : string, cont : unit -> Pipeline) : Pipeline =
Pipeline.ShellScript (toRun, cont ())
member _.Bind (p : Pipeline, cont : unit -> Pipeline) : Pipeline =
// Things later in the pipeline can't alter the modifiers that were specified earlier in the pipeline,
// so throw those modifications away.
let subsequent = cont ()
Pipeline.ShellScript (toRun, subsequent)
/// Bind in another pipeline, perhaps on a different agent
member _.Bind (p : Pipeline * PipelineModifier option, cont : unit -> Pipeline) : Pipeline =
Pipeline.Sequence (p, cont ())
member _.Yield (() : unit) : unit = ()
member _.For (expr : PipelineModifier list, cont : unit -> Pipeline) : Pipeline =
Pipeline.WithModifier (cont (), expr)
member _.Yield (x : unit) : unit = ()
member _.For
(expr : PipelineModifier, cont : unit -> Pipeline)
: Pipeline
=
modifier <- expr
let pipeline = cont ()
pipeline
member _.Run x = x, Some modifier
[<AutoOpen>]
module Pipeline =
let pipeline<'plat> = Pipeline<'plat> ()
let toStepDag (p : Pipeline) : SealedStepDag<unit, unit> = failwith "TODO"
module PipelineBuilder =
let pipeline (agent : string Comp) = PipelineBuilder (Some agent)
let pipelineSameAgent = PipelineBuilder None
let toStepDag (p : Pipeline, modifier : PipelineModifier option) : SealedStepDag<unit, unit> =
match modifier with
| None -> failwith "Pipeline must run on an agent, but no agent was specified"
| Some modifier ->
failwith $"TODO\n%O{modifier}\n%s{Pipeline.toString p}"
let foo : SealedStepDag<unit, unit> =
pipeline {
withEnv ("hi", Comp.make "bye")
let foo () : SealedStepDag<unit, unit> =
pipeline (Comp.make "some-image") {
workingDir (Comp.make (DirectoryInfo "code root here"))
withEnv ("foo", Comp.make "bar")
remote (Comp.make "some-image")
withEnv ("hi", Comp.make "bye")
let! (Stdout stdout) = "sh script here"
do! pipeline {
do! pipelineSameAgent {
withEnv ("foo", stdout)
do! "git config --foo"
return ()
}
do! pipeline (Comp.make "another-image") {
let! (Stdout stdout) = "maybe run a formatter"
return ()
}
do! "a shell script"
let! (Stdout stdout) = "another shell script"
return ()
}
|> toStepDag
(*
declarative.Bind<object, a, SealedStepDag<a, Unit>>(
declarative.RunShell<object, string, string>(
declarative.Bind<StepDag<object, Unit>, object, string>(
"sh script here",
(FSharpFunc<StepDag<object, Unit>, SealedStepDag<object, Unit>>) declarative.Return
),
(FSharpFunc<object, string>) (fun _ -> "a shell script")
),
(FSharpFunc<object, SealedStepDag<a, Unit>>) (fun _ ->
string message = "hi";
if (true)
throw Operators.Failure(message);
StepDag<a, Unit> sd = (StepDag<a, Unit>) null;
return declarative.Return<a>(sd);
)
);
*)

View File

@@ -3,6 +3,6 @@
module Program =
[<EntryPoint>]
let main argv =
Pipeline.foo
PipelineBuilder.foo ()
|> ignore
0

View File

@@ -5,11 +5,16 @@ type SealedStepDag<'a, 'plat> = private | SealedStepDag
type Step<'a> = private | Step
type Comp<'a> = private | Comp of (unit -> 'a)
type Comp<'a> =
private
| Thunk of (unit -> 'a)
[<RequireQualifiedAccess>]
module Comp =
let make (x : 'a) : Comp<'a> = Comp (fun () -> x)
let make (x : 'a) : Comp<'a> = Thunk (fun () -> x)
let force (x : 'a Comp) =
match x with
| Thunk x -> x ()
[<RequireQualifiedAccess>]
module StepDag =

View File

@@ -3,6 +3,8 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarnOn>FS3559</WarnOn>
</PropertyGroup>
<ItemGroup>