aboutsummaryrefslogtreecommitdiff
path: root/src/lustre/cli/step.gleam
blob: d3f8c8aac699a3b26cda486db9683bfe964a4bd5 (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
92
93
94
95
96
97
98
99
100
import gleam/io
import gleam_community/ansi
import spinner.{type Spinner}

type SpinnerStatus {
  Running(message: String)
  Stopped
}

type Env {
  Env(spinner: Spinner, spinner_status: SpinnerStatus)
}

pub opaque type Step(a, e) {
  Step(run: fn(Env) -> #(Env, Result(a, e)))
}

/// Replace the current spinner label with a new one.
///
pub fn new(message: String, then continue: fn() -> Step(a, e)) -> Step(a, e) {
  use Env(spinner, spinner_status) <- Step
  case spinner_status {
    Running(_) -> {
      spinner.set_text(spinner, message)
      continue().run(Env(spinner, Running(message)))
    }
    Stopped -> {
      let new_spinner =
        spinner.new(message)
        |> spinner.with_frames(spinner.snake_frames)
        |> spinner.start
      continue().run(Env(new_spinner, Running(message)))
    }
  }
}

/// Stops the current spinner and prints out the given message.
///
pub fn done(message: String, then continue: fn() -> Step(b, e)) -> Step(b, e) {
  use Env(spinner, spinner_status) <- Step
  case spinner_status {
    Running(_) -> spinner.stop(spinner)
    Stopped -> Nil
  }
  io.println(ansi.green(message))
  continue().run(Env(spinner, Stopped))
}

/// Runs another step as part of this one. The step will use the same spinner
/// as the previous one overriding its content.
///
pub fn run(
  step: Step(a, e),
  on_error map_error: fn(e) -> e1,
  then continue: fn(a) -> Step(b, e1),
) -> Step(b, e1) {
  use env <- Step
  case step.run(env) {
    #(new_env, Ok(res)) -> continue(res).run(new_env)
    #(new_env, Error(e)) -> #(new_env, Error(map_error(e)))
  }
}

/// If the result is `Ok` will continue by passing its wrapped value to the
/// `continue` function; otherwise will result in an error stopping the stepwise
/// execution.
///
pub fn try(
  result: Result(a, e),
  on_error map_error: fn(e) -> e1,
  then continue: fn(a) -> Step(b, e1),
) -> Step(b, e1) {
  Step(fn(env) { #(env, result) })
  |> run(map_error, continue)
}

/// Returns a value without changing the state of any spinner.
/// Any running spinner will still be running.
///
pub fn return(value: a) -> Step(a, e) {
  use env <- Step
  #(env, Ok(value))
}

pub fn execute(step: Step(a, e)) -> Result(a, e) {
  let initial_spinner =
    spinner.new("")
    |> spinner.with_frames(spinner.snake_frames)
    |> spinner.start

  let #(Env(spinner, status), res) = step.run(Env(initial_spinner, Running("")))
  case status {
    Running(message) -> {
      spinner.stop(spinner)
      io.println("❌ " <> ansi.red(message))
    }
    Stopped -> Nil
  }
  res
}