(*
 * Copyright (c) 2013-2020 Thomas Gazagnaire <thomas@gazagnaire.org>
 * Copyright (c) 2013-2020 Anil Madhavapeddy <anil@recoil.org>
 * Copyright (c) 2015-2020 Gabriel Radanne <drupyog@zoho.com>
 * Copyright (c) 2019-2020 Etienne Millon <etienne@tarides.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *)

(** Wrapper around [Bos] which provides a "dry run" feature. *)

(** {1 The action type} *)

type 'a t
(** An action that when executed may return a value of type ['a]. *)

val ok : 'a -> 'a t
(** An action that returns a value. *)

val error : string -> 'a t
(** [error e] is the failed action with error message [e]. *)

val errorf : ('a, Format.formatter, unit, 'b t) format4 -> 'a
(** [errorf fmt] is the failed action with error message [fmt]. *)

val map : f:('a -> 'b) -> 'a t -> 'b t
(** Functor instance. *)

val bind : f:('a -> 'b t) -> 'a t -> 'b t
(** Monad instance. *)

val seq : unit t list -> unit t
(** [seq t] runs the elements of [t] in sequence. *)

module List : sig
  val iter : f:('a -> unit t) -> 'a list -> unit t
  val map : f:('a -> 'b t) -> 'a list -> 'b list t
end

module Infix : sig
  val ( >>= ) : 'a t -> ('a -> 'b t) -> 'b t
  val ( >|= ) : 'a t -> ('a -> 'b) -> 'b t
end

module Syntax : sig
  val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t
  val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t
end

(** {1 Actions} *)

val rm : Fpath.t -> unit t
(** Delete a file. (see [Bos.OS.File.delete]) *)

val mkdir : Fpath.t -> bool t
(** Create a directory. (See [Bos.OS.Dir.create] *)

val rmdir : Fpath.t -> unit t
(** Remove a directory. (see [Bos.OS.Dir.delete]) *)

val with_dir : Fpath.t -> (unit -> 'a t) -> 'a t
(** [with_dir d f] runs [f] with [d] as current working directory. (See
    [Bos.OS.Dir.with_current]). *)

val pwd : unit -> Fpath.t t
(** [pwd ()] is the current working directory. (See [Bos.OS.Dir.current]) *)

val is_file : Fpath.t -> bool t
(** Does a file exist? (see [Bos.OS.File.exists]) *)

val is_dir : Fpath.t -> bool t
(** Does a directory exist? (see [Bos.OS.Dir.exists]) *)

val size_of : Fpath.t -> int option t
(** [size_of f] is [Some i] if [f] exists and is of size [i], and [None] if [f]
    doesn't exist. *)

val set_var : string -> string option -> unit t
(** [set_var v c] sets env variable [c] to [c]. (see [Bos.OS.Env.set_var]) *)

val get_var : string -> string option t
(** [get_var v] gets the value of the variable [c] in the environment. (see
    [Bos.OS.Env.get]) *)

type channel = [ `Null | `Fmt of Format.formatter ]
(** The type for channels. *)

val run_cmd : ?err:channel -> ?out:channel -> Bos.Cmd.t -> unit t
(** Run a command. By default, [err] is [Fmt.stderr] and [out] is [Fmt.stdout].
    (see [Bos.OS.Cmd.run]) *)

val run_cmd_out : ?err:channel -> Bos.Cmd.t -> string t
(** Run a command and return its trimmed stdout. By default [err] is
    [Fmt.stderr]. (See [Bos.OS.Cmd.run_out]) *)

val run_cmd_cli : Bos.Cmd.t -> unit t
(** Run a command as a command line interface, meaning stdout and stderr remain
    untouched. *)

val write_file : Fpath.t -> string -> unit t
(** Write some data to a file. (see [Bos.OS.File.write]) *)

val read_file : Fpath.t -> string t
(** [read_file f] is [f]'s contents. (see [Bos.OS.File.read]) *)

val tmp_file : ?mode:int -> Bos.OS.File.tmp_name_pat -> Fpath.t t
(** [tmp_file pat] is a tempory file built using the pattern [pat]. (See
    [Bos.OS.File.tmp]) *)

val ls : Fpath.t -> (Fpath.t -> bool) -> Fpath.t list t
(** [ls dir] is the list of files in [dir]. *)

val with_output :
  ?mode:int ->
  ?append:bool ->
  path:Fpath.t ->
  purpose:string ->
  (Format.formatter -> 'a) ->
  'a t
(** Open a file with a given mode, and write some data to it through a function.
    (see [Bos.OS.File.with_oc]). [purpose] is used in error messages. If
    [append] is set (by default it is not), the data is appended to [path]. *)

(** {1 Interpreters} *)

val run : 'a t -> ('a, Rresult.R.msg) result
(** Run the command through [Bos]. *)

type env
(** The type for virtual environments. *)

type files = [ `Passtrough of Fpath.t | `Files of (Fpath.t * string) list ]

val default_exec : Bos.Cmd.t -> (string * string) option
(** [default_exec cmd] is [Some ("$(<cmd>)", "")]. *)

val env :
  ?exec:(Bos.Cmd.t -> (string * string) option) ->
  ?env:(string * string) list ->
  ?pwd:Fpath.t ->
  ?files:files ->
  unit ->
  env

type 'a domain = {
  result : ('a, Rresult.R.msg) result;
  env : env;
  logs : string list;
}
(** The type for interpreted values. *)

val eq_domain : ('a -> 'a -> bool) -> 'a domain -> 'a domain -> bool
(** [eq_domain] is the equality function between {!domain}s. *)

val pp_domain : 'a Fmt.t -> 'a domain Fmt.t
(** [pp_domain] is the pretty-printer for {!domain}s. *)

val dry_run : ?env:env -> 'a t -> 'a domain
(** Emulate the action. This will not do IO on the actual files. Some
    approximation is done to determine the result of actions. [files] is a list
    of paths that are supposed to exist at the beginning. Returns:

    - the result of the action (which can be an [Bos] error)
    - the list of files after execution
    - a trace (list of log messages) *)

val dry_run_trace : ?env:env -> 'a t -> unit
(** Only output the trace part of [dry_run]. *)

val generated_files : ?env:env -> 'a t -> Fpath.Set.t
(** [generated_files t] is the set of files created by [t]. Note: this might be
    incomplete/incorrect in case of calls to external commands. *)
