Source file extract_code.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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# 2 "src/odoc/extract_code.cppo.ml"
open Odoc_utils
open Odoc_parser

let tags_included_in_names names tags =
  List.exists
    (function
      | `Binding ({ Loc.value = "name"; _ }, { Loc.value = n; _ })
        when List.exists (String.equal n) names ->
          true
      | _ -> false)
    tags

let needs_extraction names meta =
  let check_language () =
    match meta with
    | None -> true
    | Some { Ast.language; _ } -> String.equal "ocaml" language.Loc.value
  in
  let check_name () =
    match meta with
    | Some { Ast.tags; _ } ->
        tags_included_in_names names tags
    | _ -> false
  in
  match names with [] -> check_language () | _ :: _ -> check_name ()

let print line_directives oc location value =
  if line_directives then (
    Printf.fprintf oc "#%d \"%s\"\n" location.Loc.start.line location.file;
    Printf.fprintf oc "%s%s\n"
      (String.v ~len:location.start.column (fun _ -> ' '))
      value)
  else Printf.fprintf oc "%s" value

let rec nestable_block_element line_directives oc names v =
  match v.Loc.value with
  | `Verbatim _ | `Modules _ | `Math_block _ | `Media _ | `Paragraph _ -> ()
  | `Code_block { Ast.content = { value; location }; meta; _ }
    when needs_extraction names meta ->
      print line_directives oc location value
  | `Code_block _ -> ()
  | `List (_, _, l) ->
      List.iter (List.iter (nestable_block_element line_directives oc names)) l
  | `Table ((table, _), _) ->
      List.iter
        (List.iter (fun (x, _) ->
             List.iter (nestable_block_element line_directives oc names) x))
        table

let block_element line_directives oc names v =
  match v.Loc.value with
  | `Tag
      ( `Deprecated l
      | `Param (_, l)
      | `Raise (_, l)
      | `Return l
      | `See (_, _, l)
      | `Before (_, l) ) ->
      List.iter (nestable_block_element line_directives oc names) l
  | `Tag
      ( `Author _ | `Since _ | `Version _ | `Canonical _ | `Inline | `Open
      | `Children_order _ | `Toc_status _ | `Order_category _ | `Short_title _
      | `Closed | `Hidden )
  | `Heading _ ->
      ()
  | #Ast.nestable_block_element as value ->
      nestable_block_element line_directives oc names { v with value }

let pad_loc loc =
  { loc.Location.loc_start with pos_cnum = loc.loc_start.pos_cnum + 3 }

let iterator line_directives oc names =
  let default_iterator = Tast_iterator.default_iterator in
  let attribute _ attr =
    match Odoc_loader.parse_attribute attr with
    | None | Some (`Stop _ | `Alert _) -> ()
    | Some (`Text (doc, loc) | `Doc (doc, loc)) ->
        let ast_docs =
          Odoc_parser.parse_comment ~location:(pad_loc loc) ~text:doc
        in
        let ast = Odoc_parser.ast ast_docs in
        List.iter (block_element line_directives oc names) ast
  in
  let attributes sub attrs = List.iter (attribute sub) attrs in
  (* For some reason, Tast_iterator.default_iterator does not recurse on
     Tsig_attribute and on attributes... *)
  let signature_item sub sig_ =
    match sig_.Typedtree.sig_desc with
    | Tsig_attribute attr -> attribute sub attr
    | Tsig_include incl -> attributes sub incl.incl_attributes
    | Tsig_open o -> attributes sub o.open_attributes
    | _ -> default_iterator.signature_item sub sig_
  in
  let row_field sub rf =
    attributes sub rf.Typedtree.rf_attributes;
    default_iterator.row_field sub rf
  in
  let value_description sub vd =
    attributes sub vd.Typedtree.val_attributes;
    default_iterator.value_description sub vd
  in
  let label_declaration sub lbls =
    List.iter (fun ld -> attributes sub ld.Typedtree.ld_attributes) lbls
  in
  let constructor_declaration sub cd =
    (match cd.Typedtree.cd_args with
    | Cstr_record lds -> label_declaration sub lds
    | _ -> ());
    attributes sub cd.cd_attributes
  in
  let type_kind sub tk =
    (match tk with
    | Typedtree.Ttype_record lbls -> label_declaration sub lbls
    | Ttype_variant cstrs -> List.iter (constructor_declaration sub) cstrs
    | _ -> ());
    default_iterator.type_kind sub tk
  in
  let type_declaration sub decl =
    attributes sub decl.Typedtree.typ_attributes;
    default_iterator.type_declaration sub decl
  in
  let extension_constructor sub ext =
    attributes sub ext.Typedtree.ext_attributes;
    default_iterator.extension_constructor sub ext
  in
  let class_type_field sub ctf =
    attributes sub ctf.Typedtree.ctf_attributes;
    (match ctf.ctf_desc with
    | Tctf_attribute attr -> attribute sub attr
    | _ -> ());
    default_iterator.class_type_field sub ctf
  in
  let class_type_declaration sub ctd =
    attributes sub ctd.Typedtree.ci_attributes;
    default_iterator.class_type_declaration sub ctd
  in
  let class_description sub cd =
    attributes sub cd.Typedtree.ci_attributes;
    default_iterator.class_description sub cd
  in
  (* let type_exception sub exc = *)
  (*   attributes sub ext.Typedtree.ext_attributes; *)
  (*   default_iterator.extension_constructor sub ext *)
  (* in *)
  let type_extension sub ext =
    attributes sub ext.Typedtree.tyext_attributes;
    default_iterator.type_extension sub ext
  in
  let module_type_declaration sub mtd =
    attributes sub mtd.Typedtree.mtd_attributes;
    default_iterator.module_type_declaration sub mtd
  in
  let module_declaration sub md =
    attributes sub md.Typedtree.md_attributes;
    default_iterator.module_declaration sub md
  in
  let module_expr sub me =
    attributes sub me.Typedtree.mod_attributes;
    default_iterator.module_expr sub me
  in
  let module_substitution sub ms =
    attributes sub ms.Typedtree.ms_attributes;
    default_iterator.module_substitution sub ms
  in
  (* let module_type_substitution sub mtd = *)
  (*   attributes sub mtd.Typedtree.mtd_attributes; *)
  (*   default_iterator.module_type_substitution sub ms *)
  (* in *)
  {
    default_iterator with
    row_field
    (* ; attribute *)
    (* ; attributes *);
    value_description;
    signature_item;
    type_kind;
    type_declaration;
    extension_constructor;
    type_extension;
    class_type_field;
    class_type_declaration;
    class_description;
    module_type_declaration;
    module_declaration;
    module_substitution;
    module_expr;
  }

let load_cmti line_directives oc names input ~warnings_options =
  try
    let res =
      Odoc_loader.wrap_errors ~filename:input @@ fun () ->
      let cmt_info = Cmt_format.read_cmt input in
      match cmt_info.cmt_annots with
      | Interface intf ->
          let iterator = iterator line_directives oc names in
          iterator.signature iterator intf;
          Ok ()
      | _ ->
          Error
            (`Msg (Format.sprintf "Provided file %s is not an interface" input))
    in
    Odoc_model.Error.handle_errors_and_warnings ~warnings_options res
    |> Result.join
  with exn ->
    Error
      (`Msg
         (Format.sprintf
            "Error while unmarshalling input file %s:\n\
             %s\n\
             Check that the input file is a valid cmti file"
            input (Printexc.to_string exn)))

let load_mld line_directives oc names input =
  let location =
    { Lexing.pos_fname = input; pos_lnum = 1; pos_bol = 0; pos_cnum = 0 }
  in
  let c = Io_utils.read_lines input |> String.concat ~sep:"\n" in
  let parsed = parse_comment ~location ~text:c in
  let ast = ast parsed in
  List.iter (block_element line_directives oc names) ast;
  Ok ()

let extract ~dst ~input ~names ~line_directives ~warnings_options =
  let ( let* ) = Result.bind in
  let* loader =
    match input |> Fpath.v |> Fpath.get_ext with
    | ".mld" -> Ok load_mld
    | ".cmti" -> Ok (load_cmti ~warnings_options)
    | _ -> Error (`Msg "Input must have either mld or cmti as extension")
  in
  match dst with
  | None -> loader line_directives stdout names input
  | Some dst ->
      Io_utils.with_open_out dst @@ fun oc ->
      loader line_directives oc names input