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
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
}
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
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
;;