(** String with variables of the form %\{...\} or %(...)

    Variables cannot contain "%\{", "%(", ")" or "\}". For instance in "%(cat
    %\{x\})", only "%\{x\}" will be considered a variable, the rest is text. *)
open! Stdune

open Import

(** A sequence of text and variables. *)
type t

val compare_no_loc : t -> t -> Ordering.t

val equal_no_loc : t -> t -> bool

(** [loc t] returns the location of [t] — typically, in the [dune] file. *)
val loc : t -> Loc.t

val syntax_version : t -> Dune_lang.Syntax.Version.t

val to_dyn : t Dyn.Encoder.t

include Dune_lang.Conv.S with type t := t

(** [t] generated by the OCaml code. The first argument should be [__POS__].
    [quoted] says whether the string is quoted ([false] by default). *)
val virt_var : ?quoted:bool -> string * int * int * int -> string -> t

val virt_text : string * int * int * int -> string -> t

val make_var : ?quoted:bool -> Loc.t -> ?payload:string -> string -> t

val make_text : ?quoted:bool -> Loc.t -> string -> t

val make : Dune_lang.Template.t -> t

val is_var : t -> name:string -> bool

val has_vars : t -> bool

(** If [t] contains no variable, returns the contents of [t]. *)
val text_only : t -> string option

module Mode : sig
  (** Expansion may produce either a [Single] value or [Many] values

      The caller always knows which of the contexts above it requires, therefore
      it can specify this to the expansion functions. This allows us to return a
      precise result type from the expansion, and do some validation to make
      sure we aren't expanding into multiple values in cases where it's not
      allowed. *)
  type _ t =
    | Single : Value.t t
    | Many : Value.t list t
end

module Var : sig
  (** Variables are of the form %\{foo\} or %\{foo:bar\}. The latter form is
      also referred to as macros. *)
  type t

  val to_dyn : t -> Dyn.t

  val name : t -> string

  val loc : t -> Loc.t

  val full_name : t -> string

  (** Variables do not have a payload. While macros always do. *)
  val payload : t -> string option

  val with_name : t -> name:string -> t

  (** Describe what this variable is *)
  val describe : t -> string
end

type yes_no_unknown =
  | Yes
  | No
  | Unknown of Var.t

module Partial : sig
  type string_with_vars

  (** Result of a best effort expansion. If we managed to expand everything we
      return some ['a Mode.t t], otherwise we return a new template where all
      the variables we know about are expanded. *)
  type nonrec 'a t =
    | Expanded of 'a
    | Unexpanded of t

  val to_dyn : ('a -> Dyn.t) -> 'a t -> Dyn.t

  val map : 'a t -> f:('a -> 'b) -> 'b t

  val is_suffix : string t -> suffix:string -> yes_no_unknown

  val is_prefix : string t -> prefix:string -> yes_no_unknown

  val elim : 'a t -> exp:('a -> 'b) -> unexp:(string_with_vars -> 'b) -> 'b

  val expanded : 'a -> 'a t
end
with type string_with_vars := t

type known_suffix =
  | Full of string
  | Partial of (Var.t * string)

type known_prefix =
  | Full of string
  | Partial of (string * Var.t)

val known_suffix : t -> known_suffix

val known_prefix : t -> known_prefix

val is_suffix : t -> suffix:string -> yes_no_unknown

val is_prefix : t -> prefix:string -> yes_no_unknown

val fold_vars : t -> init:'a -> f:(Var.t -> 'a -> 'a) -> 'a

type 'a expander = Var.t -> Dune_lang.Syntax.Version.t -> 'a

module type S = sig
  type 'a app

  (** [expand ~f] attempts to expand all percent forms in a template. If [f]
      returns [None] for any variable (no substitution was found), then this
      function will raise. *)
  val expand :
       t
    -> mode:'a Mode.t
    -> dir:Path.t
    -> f:Value.t list option app expander
    -> 'a app

  (** [partial_expand] does a best effort expansion of the template. If it fails
      to expand any variables, it will return [Unexpanded t] where [t] is the
      maximally expanded template. If it manages to expand everything [Expanded]
      will be returned. *)
  val partial_expand :
       t
    -> mode:'a Mode.t
    -> dir:Path.t
    -> f:Value.t list option app expander
    -> 'a Partial.t app
end

include S with type 'a app := 'a

val remove_locs : t -> t
