package file-rewriter

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file file_rewriter.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
(******************************************************************************)
(*  Copyright 2024 Mathieu Barbin <mathieu.barbin@gmail.com>                  *)
(*                                                                            *)
(*  Licensed under the Apache License, Version 2.0 (the "License");           *)
(*  you may not use this file except in compliance with the License.          *)
(*  You may obtain a copy of the License at                                   *)
(*                                                                            *)
(*  http://www.apache.org/licenses/LICENSE-2.0                                *)
(*                                                                            *)
(*  Unless required by applicable law or agreed to in writing, software       *)
(*  distributed under the License is distributed on an "AS IS" BASIS,         *)
(*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  *)
(*  See the License for the specific language governing permissions and       *)
(*  limitations under the License.                                            *)
(******************************************************************************)

type offset = Loc.Offset.t

type range = Loc.Range.t =
  { start : offset
  ; stop : offset
  }

module Rewrite = struct
  type t =
    { start : int
    ; stop : int
    ; replace_by : string
    }

  let sexp_of_t { start; stop; replace_by } =
    Sexplib0.Sexp.List
      [ List [ Atom "start"; Sexplib0.Sexp_conv.sexp_of_int start ]
      ; List [ Atom "stop"; Sexplib0.Sexp_conv.sexp_of_int stop ]
      ; List [ Atom "replace_by"; Atom replace_by ]
      ]
  ;;

  let compare (t1 : t) (t2 : t) =
    let r = Int.compare t1.start t2.start in
    if r <> 0 then r else Int.compare t1.stop t2.stop
  ;;
end

type t =
  { path : Fpath.t
  ; original_contents : string
  ; mutable rewrites : Rewrite.t list (* New elements added to the front. *)
  }

let path t = t.path
let original_contents t = t.original_contents
let create ~path ~original_contents = { path; original_contents; rewrites = [] }
let reset ({ path = _; original_contents = _; rewrites = _ } as t) = t.rewrites <- []

let insert t ~offset ~text =
  let length = String.length t.original_contents in
  if offset < 0 || offset > length then raise (Invalid_argument "File_rewriter.insert");
  t.rewrites <- { Rewrite.start = offset; stop = offset; replace_by = text } :: t.rewrites
;;

let replace t ~range:{ start; stop } ~text =
  let length = String.length t.original_contents in
  if start < 0 || start > length || stop < 0 || stop > length || start > stop
  then raise (Invalid_argument "File_rewriter.replace");
  t.rewrites <- { Rewrite.start; stop; replace_by = text } :: t.rewrites
;;

let remove t ~range = replace t ~range ~text:""

module Invalid_rewrites = struct
  type t =
    { path : Fpath.t
    ; rewrites_with_overlap : Rewrite.t list
    }

  let to_sexps { path; rewrites_with_overlap } =
    Sexplib0.Sexp.Atom (path |> Fpath.to_string)
    :: List.map Rewrite.sexp_of_t rewrites_with_overlap
  ;;

  let sexp_of_t t = Sexplib0.Sexp.List (to_sexps t)
end

exception Invalid_rewrites of Invalid_rewrites.t

let () =
  Sexplib0.Sexp_conv.Exn_converter.add [%extension_constructor Invalid_rewrites] (function
    | Invalid_rewrites t ->
      List (Atom "File_rewriter.Invalid_rewrites" :: Invalid_rewrites.to_sexps t)
    | _ -> assert false)
;;

let[@tail_mod_cons] rec rewrites_with_overlap current_offset = function
  | [] -> []
  | [ ({ Rewrite.start; stop = _; replace_by = _ } as rewrite) ] ->
    if current_offset > start then [ rewrite ] else []
  | a :: (b :: _ as tl) ->
    if current_offset > a.start || a.stop > b.start
    then a :: rewrites_with_overlap a.stop tl
    else rewrites_with_overlap a.stop tl
;;

let sorted_rewrites t =
  let rewrites = t.rewrites |> List.rev |> List.stable_sort Rewrite.compare in
  match rewrites_with_overlap 0 rewrites with
  | [] -> rewrites
  | _ :: _ as rewrites_with_overlap ->
    raise (Invalid_rewrites { path = t.path; rewrites_with_overlap })
;;

let contents t =
  let rewrites = sorted_rewrites t in
  let buffer = Buffer.create 13 in
  let length = String.length t.original_contents in
  let insert_original_contents ~from ~up_to =
    Buffer.add_substring buffer t.original_contents from (up_to - from)
  in
  let final_offset =
    List.fold_left
      (fun current_offset { Rewrite.start; stop; replace_by } ->
        if current_offset < start
        then insert_original_contents ~from:current_offset ~up_to:start;
        Buffer.add_string buffer replace_by;
        stop)
      0
      rewrites
  in
  (* Add the last portion of the original buffer. *)
  if final_offset < length then insert_original_contents ~from:final_offset ~up_to:length;
  Buffer.contents buffer
;;

let contents_result t =
  match contents t with
  | ok -> Ok ok
  | exception Invalid_rewrites error -> Error error
;;
OCaml

Innovation. Community. Security.