jon.recoil.org

Source file odoc_file.ml

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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# 1 "src/odoc/odoc_file.cppo.ml"
(*
 * Copyright (c) 2014 Leo White <leo@lpw25.net>
 *
 * 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.
 *)

open Odoc_utils
open ResultMonad
open Odoc_model

type unit_content = Lang.Compilation_unit.t

type content =
  | Page_content of Lang.Page.t
  | Impl_content of Lang.Implementation.t
  | Unit_content of unit_content
  | Asset_content of Lang.Asset.t

type t = { content : content; warnings : Odoc_model.Error.t list }

(** Written at the top of the files. Checked when loading. *)
(* Compress the marshalled payloads with the compiler's own [Compression]
   module (from compiler-libs.common) - the same mechanism it uses for
   .cmt/.cmti files. Payloads are zstd-compressed when the running compiler was
   built with zstd support, and fall back to plain Marshal otherwise.
   [Compression] only exists from OCaml 5.1 and is absent from OxCaml; there
   we use Marshal directly and the files stay as they were. *)
# 43 "src/odoc/odoc_file.cppo.ml"
let compression_supported = false
let output_value oc v = Marshal.to_channel oc v []
let input_value ic = Marshal.from_channel ic

# 48 "src/odoc/odoc_file.cppo.ml"
let magic = "ODOC"

(* A compressed payload can only be read back by a compiler with zstd support,
   so the version records which format was written: an odoc without it reports
   the mismatch here rather than failing in the unmarshaller with "compressed
   object, cannot decompress". *)
let magic_version_uncompressed = "3.2.1-177-g63708bb"

let magic_version =
  magic_version_uncompressed ^ if compression_supported then "-zstd" else ""

(* An uncompressed payload can be read whether or not this odoc would write
   one, so accept it too rather than rejecting a file we can perfectly well
   read. The converse isn't true, which is what the version distinguishes. *)
let readable_version version =
  version = magic_version || version = magic_version_uncompressed

(** Exceptions while saving are allowed to leak. *)
let save_ file f =
  let len = String.length magic_version in
  (* Sanity check, see similar check in load_ *)
  if len > 255 then
    failwith
      (Printf.sprintf
         "Magic version string %S is too long, must be <= 255 characters"
         magic_version);

  Fs.Directory.mkdir_p (Fs.File.dirname file);
  Io_utils.with_open_out_bin (Fs.File.to_string file) (fun oc ->
      output_string oc magic;
      output_binary_int oc len;
      output_string oc magic_version;
      f oc)

let save_unit file (root : Root.t) (t : t) =
  save_ file (fun oc ->
      output_value oc root;
      output_value oc t)

let save_page file ~warnings page =
  let dir = Fs.File.dirname file in
  let base = Fs.File.(to_string @@ basename file) in
  let file =
    if Astring.String.is_prefix ~affix:"page-" base then file
    else Fs.File.create ~directory:dir ~name:("page-" ^ base)
  in
  save_unit file page.Lang.Page.root { content = Page_content page; warnings }

let save_impl file ~warnings impl =
  let dir = Fs.File.dirname file in
  let base = Fs.File.(to_string @@ basename file) in
  let file =
    if Astring.String.is_prefix ~affix:"impl-" base then file
    else Fs.File.create ~directory:dir ~name:("impl-" ^ base)
  in
  save_unit file impl.Lang.Implementation.root
    { content = Impl_content impl; warnings }

let save_asset file ~warnings asset =
  let dir = Fs.File.dirname file in
  let base = Fs.File.(to_string @@ basename file) in
  let file =
    if Astring.String.is_prefix ~affix:"asset-" base then file
    else Fs.File.create ~directory:dir ~name:("asset-" ^ base)
  in
  let t = { content = Asset_content asset; warnings } in
  save_unit file asset.root t

let save_unit file ~warnings m =
  save_unit file m.Lang.Compilation_unit.root
    { content = Unit_content m; warnings }

let load_ file f =
  let file = Fs.File.to_string file in

  let check_exists () =
    if Sys.file_exists file then Ok ()
    else Error (`Msg (Printf.sprintf "File %s does not exist" file))
  in

  let check_magic ic =
    let actual_magic = really_input_string ic (String.length magic) in
    if actual_magic = magic then Ok ()
    else
      Error
        (`Msg
           (Printf.sprintf "%s has invalid magic %S, expected %S\n%!" file
              actual_magic magic))
  in
  let version_length ic () =
    let len = input_binary_int ic in
    if len > 0 && len <= 255 then Ok len
    else Error (`Msg (Printf.sprintf "%s has invalid version length" file))
  in
  let check_version ic len =
    let actual_magic = really_input_string ic len in
    if readable_version actual_magic then Ok ()
    else
      let msg =
        Printf.sprintf "%s has invalid version %S, expected %S\n%!" file
          actual_magic magic_version
      in
      Error (`Msg msg)
  in

  check_exists () >>= fun () ->
  Io_utils.with_open_in_bin file @@ fun ic ->
  try
    check_magic ic >>= version_length ic >>= check_version ic >>= fun () -> f ic
  with exn ->
    let msg =
      Printf.sprintf "Error while unmarshalling %S: %s\n%!" file
        (match exn with Failure s -> s | _ -> Printexc.to_string exn)
    in
    Error (`Msg msg)

let load file =
  load_ file (fun ic ->
      let _root = input_value ic in
      Ok (input_value ic))

(** The root is saved separately in the files to support this function. *)
let load_root file =
  load_ file (fun ic ->
      let root = input_value ic in
      Ok root)

let save_index dst idx = save_ dst (fun oc -> output_value oc idx)

let load_index file = load_ file (fun ic -> Ok (input_value ic))

let save_sidebar dst idx = save_ dst (fun oc -> output_value oc idx)

let load_sidebar file = load_ file (fun ic -> Ok (input_value ic))