summaryrefslogtreecommitdiff
path: root/src/SomhairlesDream.Cli/Arguments.fs
blob: 2de96bee61153698e72fa7a417683f91b424a1ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
namespace SomhairlesDream.Cli

open System
open SomhairlesDream.Shared

[<CLIMutable>]
type RunOptions =
    { Ids: RunIds
      OutputRoot: string
      StageRoot: string
      Python: string
      Render: bool }

type Command =
    | Run of RunOptions
    | Verify of string
    | Help

module Arguments =
    let usage =
        "usage: dotnet run --project src/SomhairlesDream.Cli -- run [options]\n"
        + "       dotnet run --project src/SomhairlesDream.Cli -- verify <manifest.json>"

    let private defaultPython () =
        let configured = Environment.GetEnvironmentVariable("SOMHAIRLES_BPYTHON")
        if String.IsNullOrWhiteSpace(configured) then "python3" else configured

    let private defaults () =
        { Ids =
              { ProjectId = "heritage-001"
                RunId = "run-001"
                BaseId = "base-000"
                TargetId = "target-003" }
          OutputRoot = ".artifacts"
          StageRoot = "stage"
          Python = defaultPython ()
          Render = false }

    let private requiredValue (args: string array) (index: int) (optionName: string) =
        if index + 1 >= args.Length || String.IsNullOrWhiteSpace(args[index + 1]) then
            Error $"{optionName} requires a value"
        else
            Ok args[index + 1]

    let private setValue optionName value (options: RunOptions) =
        match optionName with
        | "--project-id" -> Ok { options with Ids = { options.Ids with ProjectId = value } }
        | "--run-id" -> Ok { options with Ids = { options.Ids with RunId = value } }
        | "--base-id" -> Ok { options with Ids = { options.Ids with BaseId = value } }
        | "--target-id" -> Ok { options with Ids = { options.Ids with TargetId = value } }
        | "--output-root" -> Ok { options with OutputRoot = value }
        | "--stage-root" -> Ok { options with StageRoot = value }
        | "--python" -> Ok { options with Python = value }
        | _ -> Error $"unknown option: {optionName}"

    let private validateRunOptions (options: RunOptions) =
        [| options.Ids.ProjectId; options.Ids.RunId; options.Ids.BaseId; options.Ids.TargetId |]
        |> Array.tryFind (Ids.validateResourceId >> Result.isError)
        |> function
            | Some value -> Error $"invalid resource id: {value}"
            | None -> Ok(Run options)

    let private parseRun (args: string array) =
        let rec loop (index: int) (options: RunOptions) =
            if index >= args.Length then
                validateRunOptions options
            else
                match args[index] with
                | "--render" -> loop (index + 1) { options with Render = true }
                | "--help" -> Ok Help
                | optionName ->
                    match requiredValue args index optionName with
                    | Error message -> Error message
                    | Ok value ->
                        match setValue optionName value options with
                        | Error message -> Error message
                        | Ok updated -> loop (index + 2) updated

        loop 0 (defaults ())

    let parse (args: string array) =
        match args with
        | [||]
        | [| "--help" |]
        | [| "help" |] -> Ok Help
        | [| "verify" |] -> Error "verify requires a manifest path"
        | [| "verify"; path |] when not (String.IsNullOrWhiteSpace(path)) -> Ok(Verify path)
        | [| "verify"; _ |] -> Error "verify requires a manifest path"
        | values when values.Length > 2 && values[0] = "verify" -> Error "verify accepts exactly one manifest path"
        | values when values.Length > 0 && values[0] = "run" -> parseRun (values |> Array.skip 1)
        | _ -> Error usage