open! Import

module Lang = struct
  type version = int * int

  let pp_version fmt (major, minor) = Format.fprintf fmt "%d.%d" major minor
  let version_to_string version = Format.asprintf "%a" pp_version version

  let stanza_regexp =
    let open Re in
    compile
      (seq
         [
           bos;
           char '(';
           rep blank;
           str "lang";
           rep1 blank;
           str "dune";
           rep1 blank;
           group (rep1 digit);
           char '.';
           group (rep1 digit);
           rep blank;
           char ')';
           rep blank;
           opt (char '\r');
           eol;
         ])

  let from_match group =
    let major_str = Re.Group.get group 1 in
    let minor_str = Re.Group.get group 2 in
    match (int_of_string_opt major_str, int_of_string_opt minor_str) with
    | Some major, Some minor -> Ok (major, minor)
    | _ ->
        Rresult.R.error_msgf
          "Invalid dune-project file: invalid lang version %s.%s" major_str
          minor_str

  let from_content content =
    match Re.all stanza_regexp content with
    | [ group ] -> from_match group
    | _ ->
        (* We match on [bos], meaning the only possible case here is the empty
           list *)
        Rresult.R.error_msg
          "Invalid dune-project file: It does not start with a valid lang \
           stanza"

  let update_stanza ~version group =
    let new_ver = version_to_string version in
    let old_ver =
      Printf.sprintf "%s.%s" (Re.Group.get group 1) (Re.Group.get group 2)
    in
    let stanza = Re.Group.get group 0 in
    Base.String.substr_replace_first ~pattern:old_ver ~with_:new_ver stanza

  let update ~version content =
    Re.replace ~all:false stanza_regexp ~f:(update_stanza ~version) content

  let compare_version (major, minor) (major', minor') =
    match Int.compare major major' with
    | 0 -> Int.compare minor minor'
    | _ as ord -> ord

  let duniverse_minimum_version = (1, 11)
end

module Raw = struct
  let as_sexps path =
    try Ok (Sexplib.Sexp.load_sexps (Fpath.to_string path)) with
    | Sexplib.Sexp.Parse_error pe ->
        Error
          (`Msg
            (Format.asprintf "Failed to parse dune file %a: %s" Fpath.pp path
               pe.err_msg))
    | Failure _ ->
        Error
          (`Msg
            (Format.asprintf "Failed to parse dune file %a: Invalid sexp"
               Fpath.pp path))

  let comment s = Printf.sprintf "; %s" s
  let vendored_dirs glob = Printf.sprintf "(vendored_dirs %s)" glob

  let duniverse_dune_content =
    [
      comment "This file is generated by opam-monorepo.";
      comment
        "Be aware that it is likely to be overwritten by your next opam \
         monorepo pull invocation.";
      "";
      vendored_dirs "*";
    ]
end

module Project = struct
  module OV = Ocaml_version

  let rec name sexps =
    match (sexps : Sexplib0.Sexp.t list) with
    | [] -> Error (`Msg "Missing a name field in the dune-project file")
    | List [ Atom "name"; Atom name ] :: _ -> Ok name
    | _ :: tl -> name tl
end

 We match on [bos], meaning the only possible case here is the empty
           list 
open! Import

module Lang = struct
  type version = int * int

  let pp_version fmt (major, minor) = Format.fprintf fmt "%d.%d" major minor
  let version_to_string version = Format.asprintf "%a" pp_version version

  let stanza_regexp =
    let open Re in
    compile
      (seq
         [
           bos;
           char '(';
           rep blank;
           str "lang";
           rep1 blank;
           str "dune";
           rep1 blank;
           group (rep1 digit);
           char '.';
           group (rep1 digit);
           rep blank;
           char ')';
           rep blank;
           opt (char '\r');
           eol;
         ])

  let from_match group =
    let major_str = Re.Group.get group 1 in
    let minor_str = Re.Group.get group 2 in
    match (int_of_string_opt major_str, int_of_string_opt minor_str) with
    | Some major, Some minor -> Ok (major, minor)
    | _ ->
        Rresult.R.error_msgf
          "Invalid dune-project file: invalid lang version %s.%s" major_str
          minor_str

  let from_content content =
    match Re.all stanza_regexp content with
    | [ group ] -> from_match group
    | _ ->
        Rresult.R.error_msg
          "Invalid dune-project file: It does not start with a valid lang \
           stanza"

  let update_stanza ~version group =
    let new_ver = version_to_string version in
    let old_ver =
      Printf.sprintf "%s.%s" (Re.Group.get group 1) (Re.Group.get group 2)
    in
    let stanza = Re.Group.get group 0 in
    Base.String.substr_replace_first ~pattern:old_ver ~with_:new_ver stanza

  let update ~version content =
    Re.replace ~all:false stanza_regexp ~f:(update_stanza ~version) content

  let compare_version (major, minor) (major', minor') =
    match Int.compare major major' with
    | 0 -> Int.compare minor minor'
    | _ as ord -> ord

  let duniverse_minimum_version = (1, 11)
end

module Raw = struct
  let as_sexps path =
    try Ok (Sexplib.Sexp.load_sexps (Fpath.to_string path)) with
    | Sexplib.Sexp.Parse_error pe ->
        Error
          (`Msg
            (Format.asprintf "Failed to parse dune file %a: %s" Fpath.pp path
               pe.err_msg))
    | Failure _ ->
        Error
          (`Msg
            (Format.asprintf "Failed to parse dune file %a: Invalid sexp"
               Fpath.pp path))

  let comment s = Printf.sprintf "; %s" s
  let vendored_dirs glob = Printf.sprintf "(vendored_dirs %s)" glob

  let duniverse_dune_content =
    [
      comment "This file is generated by opam-monorepo.";
      comment
        "Be aware that it is likely to be overwritten by your next opam \
         monorepo pull invocation.";
      "";
      vendored_dirs "*";
    ]
end

module Project = struct
  module OV = Ocaml_version

  let rec name sexps =
    match (sexps : Sexplib0.Sexp.t list) with
    | [] -> Error (`Msg "Missing a name field in the dune-project file")
    | List [ Atom "name"; Atom name ] :: _ -> Ok name
    | _ :: tl -> name tl
end
