The following examples show gradually complex uses of the EDSL.

Exec

Simple call to the exec construct. [Try-Online]

Genspio.EDSL.(
  exec ["ls"; "-la"]
)

Pretty-printed:

(exec (byte-array-to-c-string (string "ls"))
  (byte-array-to-c-string (string "-la")))

Running it succeeds.

Exec with Comment

Adding comments with the %%% operator, we can see them in the compiled output. [Try-Online]

Genspio.EDSL.(
  "This is a very simple command" %%%
  exec ["ls"; "-la"]
)

Pretty-printed:

(comment "This is a very simple command"
 (exec (byte-array-to-c-string (string "ls"))
   (byte-array-to-c-string (string "-la"))))

Compiled to POSIX (122 bytes):

export genspio_trap_3_31641=$$
trap 'exit 78' USR2
 {  { ':' 'This is a very simple command' ; }  ;  { 'ls' '-la' ; }  ; }

Running it succeeds.

Failure with Comment

When an expression is wrapped with “comments” they also appear in some error messages (compilation and run-time when using the default compiler) as “the comment stack.” [Try-Online]

Genspio.EDSL.(
  "This is a very simple comment" %%% seq [
    exec ["ls"; "-la"];
    "This comment provides a more precise pseudo-location" %%% seq [
       (* Here we use the `fail` EDSL facility: *)
       fail "asserting False ☺";
    ];
  ]
)

Running it fails; returns 78.

Standard error:

Error: asserting False ☺; Comment-stack:
[`This comment provides a more precise pseudo-location`,
`This is a very simple comment`]

Call a command with Shell-Strings

The call construct is a more general version of exec that can take any EDSL string. As with exec the string will be checked for C-String compatibilty, hence the calls to byte-array-to-c-string in the pretty-printed output. [Try-Online]

Genspio.EDSL.(
  call [
    str "echo";
    Str.concat_list [str "foo"; str "bar"]; (* A concatenation at run-time. *)
  ]
)

Pretty-printed:

(exec (byte-array-to-c-string (string "echo"))
  (byte-array-to-c-string
    (byte-array-concat (list (string "foo") (string "bar")))))

Running it succeeds.

Standard output:

foobar

C-String Compilation Failure

When a string literal cannot be converted to a “C-String” the default compiler tries to catch the error at compile-time. [Try-Online]

Genspio.EDSL.(
  "A sequence that will fail" %%% seq [
    call [str "ls"; str "foo\x00bar"]; (* A string containing `NUL` *)
  ]
)

Compilation fails with:

Error: String literal is not a valid/escapable C-string: "foo\000bar".; Code:
  (exec (byte-array-to-c-string (string "ls"))
  (byte-array-to-c-string …;
  Comment-backtrace: ["A sequence that will fail"]

Playing with the output of a command

Here we use the constructs:

val get_stdout : unit t -> str t
val (||>) : unit t -> unit t -> unit t

We use let (s : …) = … to show the types.

We then “pipe” the output to another exec call with ||> (which is a 2-argument shortcut for EDSL.pipe).

[Try-Online]

Genspio.EDSL.(
  let (s : str t) = get_stdout (exec ["cat"; "README.md"]) in
  call [str "printf"; str "%s"; s] ||> exec ["wc"; "-l"];
)

Pretty-printed:

(pipe:
  (exec (byte-array-to-c-string (string "printf"))
    (byte-array-to-c-string (string "%s"))
    (byte-array-to-c-string
      (as-string
        (exec (byte-array-to-c-string (string "cat"))
          (byte-array-to-c-string (string "README.md"))))))
  |
  (exec (byte-array-to-c-string (string "wc"))
    (byte-array-to-c-string (string "-l"))))

Running it succeeds.

Standard output:

188

Feeding a string to a command's stdin

The operator >> puts any byte-array into the stdin of any unit t expression. [Try-Online]

Genspio.EDSL.(
  (* Let's see wether `wc -l` is fine with a NUL in the middle of a “line:” *)
  str "one\ntwo\nth\000ree\n" >> exec ["wc"; "-l"];
)

Pretty-printed:

((string "one\ntwo\nth\000ree\n") >>
  (exec (byte-array-to-c-string (string "wc"))
    (byte-array-to-c-string (string "-l"))))

Running it succeeds.

Standard output:

3

Comparing byte-arrays, using conditionals

We show that str .. >> cat is not changing anything and we try if_seq; a version of EDSL.if_then_else more practical for sequences/imperative code. [Try-Online]

Genspio.EDSL.(
    (* With a 🐱: *)
  let original = str "one\ntwo\nth\000ree\n" in
  let full_cycle = original >> exec ["cat"] |> get_stdout in
  if_seq
    Str.(full_cycle =$= original)
    ~t:[
      exec ["echo"; "They are the same"];
    ]
    ~e:[
      exec ["echo"; "They are NOT the same"];
    ]
)

Pretty-printed:

(if
 (string-eq
   (as-string
     ((string "one\ntwo\nth\000ree\n") >>
       (exec (byte-array-to-c-string (string "cat")))))
   (string "one\ntwo\nth\000ree\n"))
 then: (seq
         (exec (byte-array-to-c-string (string "echo"))
           (byte-array-to-c-string (string "They are the same"))))
 else: (seq
         (exec (byte-array-to-c-string (string "echo"))
           (byte-array-to-c-string (string "They are NOT the same")))))

Running it succeeds.

Standard output:

They are the same

“While” loops

The default and simplest loop construct is loop_while, the EDSL has also a simple API to manage temporary files and use them as pseudo-global-variables. [Try-Online]

Genspio.EDSL.(
  let tmp = tmp_file "genspio-example" in
  let body =
    seq [
      if_then_else Str.(tmp#get =$= str "")
         (tmp#set (str "magic-"))
         (if_then_else Str.(tmp#get =$= str "magic-")
            (tmp#append (str "string"))
            nop);
      call [str "printf"; str "Currently '%s'\\n"; tmp#get];
    ] in
  seq [
    tmp#set (str "");
    loop_while Str.(tmp#get <$> str "magic-string") ~body
  ]
)

Running it succeeds.

Standard output:

Currently 'magic-'
Currently 'magic-string'

Arbitrary Redirections

The function EDSL.with_redirections follows POSIX's exec semantics.

The printf call will output to the file /tmp/genspio-two because redirections are set in that order:

[Try-Online]

Genspio.EDSL.(
  seq [
    with_redirections (exec ["printf"; "%s"; "hello"]) [
      to_file (int 3) (str "/tmp/genspio-one");
      to_file (int 3) (str "/tmp/genspio-two");
      to_fd (int 2) (int 3);
      to_fd (int 1) (int 2);
    ];
    call [str "printf"; str "One: '%s'\\nTwo: '%s'\\n";
          exec ["cat"; "/tmp/genspio-one"] |> get_stdout;
          exec ["cat"; "/tmp/genspio-two"] |> get_stdout];
  ]
)

Pretty-printed:

(seq
  (redirect
    (exec (byte-array-to-c-string (string "printf"))
      (byte-array-to-c-string (string "%s"))
      (byte-array-to-c-string (string "hello")))
    ((int 3) > (byte-array-to-c-string (string "/tmp/genspio-one")))
    ((int 3) > (byte-array-to-c-string (string "/tmp/genspio-two")))
    ((int 2) > (int 3)) ((int 1) > (int 2)))
  (exec (byte-array-to-c-string (string "printf"))
    (byte-array-to-c-string (string "One: '%s'\\nTwo: '%s'\\n"))
    (byte-array-to-c-string
      (as-string
        (exec (byte-array-to-c-string (string "cat"))
          (byte-array-to-c-string (string "/tmp/genspio-one")))))
    (byte-array-to-c-string
      (as-string
        (exec (byte-array-to-c-string (string "cat"))
          (byte-array-to-c-string (string "/tmp/genspio-two")))))))

Running it succeeds.

Standard output:

One: ''
Two: 'hello'

Lists

The module EList provides lists within the EDSL.

[Try-Online]

Genspio.EDSL.(
  let l = Elist.make [
    str "One";
    str "Two";
  ] in
  Elist.iter l ~f:begin fun current ->
    printf (str "Current: %s\\n") [current ()];
  end
)

Pretty-printed:

(list-iter list: (list (string "One") (string "Two"))
 f: (fun VARIABLE ->
        (exec (byte-array-to-c-string (string "printf"))
          (byte-array-to-c-string (string "--"))
          (byte-array-to-c-string (string "Current: %s\\n"))
          (byte-array-to-c-string (raw-command "VARIABLE")))))

Running it succeeds.

Standard output:

Current: One
Current: Two

Loop until something is true

The EDSL also provides high-level utilities implemented with the API (like a standard library).

Here is an example with loop_until_true that fails after 4 attempts (i.e. (4 - 1) × 1 = 3 seconds), unless there is line containing godot in /etc/passwd.

We customize the output with an ~on_failed_attempt function that (on most terminals) erases the previous display (with \r).

[Try-Online]

Genspio.EDSL.(
  let the_condition who =
    exec ["cat"; "/etc/passwd"] ||> exec ["grep"; "^" ^ who]
    |> returns ~value:0
  in
  let the_wait who =
    loop_until_true
      ~attempts:4
      ~sleep:1
      ~on_failed_attempt:(fun nth ->
        printf (str "\rWaiting for '%s: %s-th attempt.")
         [str who; Integer.to_str nth])
      (the_condition who)
  in
  if_seq (the_wait "godot") ~t:[
      printf (str "It was worth waiting\\n") [];
    ]
     ~e:[
      printf (str "It was NOT worth waiting\\n") [];
    ]
  )

Running it succeeds.

Standard output:


Waiting for 'godot: 1-th attempt.
Waiting for 'godot: 2-th attempt.
Waiting for 'godot: 3-th attempt.
Waiting for 'godot: 4-th attempt.
It was NOT worth waiting

Check Sequence

Another function from the “extra constructs:” check_sequence.

We customize its output with the ~verbosity (by adding a nice prompt) and ~on_success arguments.

[Try-Online]

Genspio.EDSL.(
   check_sequence
     ~verbosity:(`Announce "♦ Check-seq-example → ") (* Try also `Output_all or `Silent *)
     ~on_success:begin fun ~step:(name, expr) ~stdout ~stderr ->
       let code = Genspio.Compile.to_one_line_hum expr in
       printf (str "  ↳ Extra “On Success” for command `%s`\\n\
                   \    code: `%s`\\n\
                   \    stdout: `%s`\\n\
                   \    stderr: `%s`\\n")
          [str name; str code; stdout; stderr]
     end
     [
        "This will succeed", exec ["ls"; "/tmp"];
        "This too", exec ["ls"; "/"];
        "BUT NOT THIS", exec ["ls"; "/somecrazy path"];
        "This won't happen", exec ["ls"; "/etc"];
     ]
)

Running it fails; returns 1.

Standard output:

♦ Check-seq-example →  1. This will succeed
  ↳ Extra “On Success” for command `1. This will succeed`
    code: `(exec (byte-array-to-c-string (string "ls")) (byte-array-to-c-string (string "/tmp")))`
    stdout: `/tmp/genspio-check-sequence-g-44-9344--cmd-stdout-1__This_will_succeed`
    stderr: `/tmp/genspio-check-sequence-g-44-9344--cmd-stderr-1__This_will_succeed`
♦ Check-seq-example →  2. This too
  ↳ Extra “On Success” for command `2. This too`
    code: `(exec (byte-array-to-c-string (string "ls")) (byte-array-to-c-string (string "/")))`
    stdout: `/tmp/genspio-check-sequence-g-44-9344--cmd-stdout-2__This_too`
    stderr: `/tmp/genspio-check-sequence-g-44-9344--cmd-stderr-2__This_too`
♦ Check-seq-example →  3. BUT NOT THIS
Step '3. BUT NOT THIS' FAILED:
``````````stdout

``````````
``````````stderr
ls: cannot access '/somecrazy path': No such file or directory

``````````

Read stdin Line by Line

Let's try now the on_stdin_lines function, to read a stream of lines.

Note that for the word “lines” to really make sense, the input should be proper “text,” in the example below the '\000' character is just silently forgotten, not counted.

[Try-Online]

Genspio.EDSL.(
  printf (str "123\\n12345\\n1234\\00056\\n12\\n") []
  ||> on_stdin_lines begin fun line ->
    printf (str "→ %s bytes\\n")
      [line
       >> exec ["wc"; "-c"] ||> exec ["tr"; "-d"; "\\n"]
       |> get_stdout]
  end
)

Running it succeeds.

Standard output:

→ 3 bytes
→ 5 bytes
→ 6 bytes
→ 2 bytes