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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
(* src/package_manager/toml_parser.ml *)
(* TOML parsing for DESCRIPTION.toml and tproject.toml using otoml *)

open Package_types

(** Helper: get a string from a TOML table, with a default *)
let get_string_opt toml path ~default =
  try Otoml.find toml Otoml.get_string path
  with _ -> default

(** Helper: get a string list from a TOML table, with a default *)
let get_string_list_opt toml path ~default =
  try Otoml.find toml (Otoml.get_array Otoml.get_string) path
  with _ -> default

(** Parse dependencies from [dependencies] table *)
let parse_dependencies toml =
  try
    match Otoml.find toml Otoml.get_table ["dependencies"] with
    | pairs ->
      List.filter_map (fun (name, value) ->
        try
          let git_url = Otoml.find value Otoml.get_string ["git"] in
          let tag = Otoml.find value Otoml.get_string ["tag"] in
          Some { dep_name = name; git_url; tag }
        with _ -> None
      ) pairs
  with _ -> []

(** Parse a DESCRIPTION.toml string into package_config *)
let parse_description_toml (content : string) : (package_config, string) result =
  try
    let toml = Otoml.Parser.from_string content in
    let name = get_string_opt toml ["package"; "name"] ~default:"" in
    if name = "" then Error "Missing required field: package.name"
    else
      Ok {
        name;
        version = get_string_opt toml ["package"; "version"] ~default:"0.1.0";
        description = get_string_opt toml ["package"; "description"] ~default:"";
        authors = get_string_list_opt toml ["package"; "authors"] ~default:[];
        license = get_string_opt toml ["package"; "license"] ~default:"EUPL-1.2";
        homepage = get_string_opt toml ["package"; "homepage"] ~default:"";
        repository = get_string_opt toml ["package"; "repository"] ~default:"";
        dependencies = parse_dependencies toml;
        min_t_version = get_string_opt toml ["t"; "min_version"] ~default:Version.version;
        additional_tools = get_string_list_opt toml ["additional-tools"; "packages"] ~default:[];
        latex_packages = get_string_list_opt toml ["latex"; "packages"] ~default:[];
      }
  with
  | Otoml.Parse_error (_, msg) -> Error (Printf.sprintf "TOML parse error: %s" msg)
  | exn -> Error (Printf.sprintf "Failed to parse DESCRIPTION.toml: %s" (Printexc.to_string exn))

(** Parse a tproject.toml string into project_config *)
let parse_tproject_toml (content : string) : (project_config, string) result =
  try
    let toml = Otoml.Parser.from_string content in
    let name = get_string_opt toml ["project"; "name"] ~default:"" in
    if name = "" then Error "Missing required field: project.name"
    else
      Ok {
        proj_name = name;
        proj_description = get_string_opt toml ["project"; "description"] ~default:"";
        proj_dependencies = parse_dependencies toml;
        proj_r_dependencies = get_string_list_opt toml ["r-dependencies"; "packages"] ~default:[];
        proj_py_dependencies = get_string_list_opt toml ["py-dependencies"; "packages"] ~default:[];
        proj_py_version = get_string_opt toml ["py-dependencies"; "version"] ~default:"python314";
        proj_visualization_tool = get_string_opt toml ["visualization-tool"; "command"] ~default:"";
        proj_min_t_version = get_string_opt toml ["t"; "min_version"] ~default:Version.version;
        proj_nixpkgs_date = get_string_opt toml ["nixpkgs"; "date"] ~default:"";
        proj_additional_tools = get_string_list_opt toml ["additional-tools"; "packages"] ~default:[];
        proj_latex_packages = get_string_list_opt toml ["latex"; "packages"] ~default:[];
      }
  with
  | Otoml.Parse_error (_, msg) -> Error (Printf.sprintf "TOML parse error: %s" msg)
  | exn -> Error (Printf.sprintf "Failed to parse tproject.toml: %s" (Printexc.to_string exn))

(** Generate a DESCRIPTION.toml string from package_config *)
let serialize_description_toml (cfg : package_config) : string =
  let buf = Buffer.create 512 in
  Buffer.add_string buf "[package]\n";
  Printf.bprintf buf "name = %S\n" cfg.name;
  Printf.bprintf buf "version = %S\n" cfg.version;
  Printf.bprintf buf "description = %S\n" cfg.description;
  Printf.bprintf buf "authors = [%s]\n"
    (String.concat ", " (List.map (fun a -> Printf.sprintf "%S" a) cfg.authors));
  Printf.bprintf buf "license = %S\n" cfg.license;
  if cfg.homepage <> "" then Printf.bprintf buf "homepage = %S\n" cfg.homepage;
  if cfg.repository <> "" then Printf.bprintf buf "repository = %S\n" cfg.repository;
  Buffer.add_char buf '\n';
  Buffer.add_string buf "[dependencies]\n";
  List.iter (fun dep ->
    Printf.bprintf buf "%s = { git = %S, tag = %S }\n"
      dep.dep_name dep.git_url dep.tag
  ) cfg.dependencies;
  Buffer.add_char buf '\n';
  Buffer.add_string buf "[t]\n";
  Printf.bprintf buf "min_version = %S\n" cfg.min_t_version;
  Printf.bprintf buf "\n[additional-tools]\n";
  Printf.bprintf buf "packages = [%s]\n"
    (String.concat ", " (List.map (fun a -> Printf.sprintf "%S" a) cfg.additional_tools));
  Printf.bprintf buf "\n[latex]\n";
  Printf.bprintf buf "packages = [%s]\n"
    (String.concat ", " (List.map (fun a -> Printf.sprintf "%S" a) cfg.latex_packages));
  Buffer.contents buf

(** Generate a tproject.toml string from project_config *)
let serialize_tproject_toml (cfg : project_config) : string =
  let buf = Buffer.create 512 in
  Buffer.add_string buf "[project]\n";
  Printf.bprintf buf "name = %S\n" cfg.proj_name;
  Printf.bprintf buf "description = %S\n" cfg.proj_description;
  Buffer.add_char buf '\n';
  Buffer.add_string buf "[dependencies]\n";
  List.iter (fun dep ->
    Printf.bprintf buf "%s = { git = %S, tag = %S }\n"
      dep.dep_name dep.git_url dep.tag
  ) cfg.proj_dependencies;
  Buffer.add_char buf '\n';
  Buffer.add_string buf "[r-dependencies]\n";
  Printf.bprintf buf "packages = [%s]\n\n"
    (String.concat ", " (List.map (fun a -> Printf.sprintf "%S" a) cfg.proj_r_dependencies));
  Buffer.add_string buf "[py-dependencies]\n";
  Printf.bprintf buf "version = %S\n" cfg.proj_py_version;
  Printf.bprintf buf "packages = [%s]\n\n"
    (String.concat ", " (List.map (fun a -> Printf.sprintf "%S" a) cfg.proj_py_dependencies));
  if cfg.proj_visualization_tool <> "" then begin
    Buffer.add_string buf "[visualization-tool]\n";
    Printf.bprintf buf "command = %S\n\n" cfg.proj_visualization_tool
  end;
  Buffer.add_string buf "[additional-tools]\n";
  Printf.bprintf buf "packages = [%s]\n\n"
    (String.concat ", " (List.map (fun a -> Printf.sprintf "%S" a) cfg.proj_additional_tools));
  Buffer.add_string buf "[latex]\n";
  Printf.bprintf buf "packages = [%s]\n\n"
    (String.concat ", " (List.map (fun a -> Printf.sprintf "%S" a) cfg.proj_latex_packages));
  Buffer.add_string buf "[t]\n";
  Printf.bprintf buf "min_version = %S\n\n" cfg.proj_min_t_version;
  Printf.bprintf buf "[nixpkgs]\ndate = %S\n" cfg.proj_nixpkgs_date;
  Buffer.contents buf