namespace SomhairlesDream.Cli open System open SomhairlesDream.Shared [] 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 " 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