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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
type version_control = [ `git | `darcs | `hg ]
type backend = [ `http | `rsync | version_control ]
type t = {
transport: string;
path: string;
hash: string option;
backend: backend;
}
let empty = {
backend = `http;
transport = "https";
path = "";
hash = None;
}
let compare {transport; path; hash; backend} u =
let transport = String.compare transport u.transport in
if transport <> 0 then transport else
let path = String.compare path u.path in
if path <> 0 then path else
let hash = Option.compare String.compare hash u.hash in
if hash <> 0 then hash else
compare backend u.backend
let equal u v = compare u v = 0
exception Parse_error of string
let parse_error s = raise (Parse_error s)
let split_url =
let re =
Re.(compile @@ whole_string @@ seq [
opt @@ seq [
opt @@ seq [ group @@ rep @@ diff any (set "+:");
alt [ char '+'; str "://"] ];
group @@ rep @@ diff any (char ':');
str "://"
];
group @@ seq [
non_greedy @@ rep @@ diff any (char '#');
opt @@ seq [ char '.'; group @@ rep1 @@ diff any (set "\\/.#")]
];
opt @@ seq [ char '#'; group @@ rep any ];
])
in
fun u ->
match Re.Group.all (Re.exec re u) with
| [| _; vc; transport; path; suffix; hash |] ->
let opt = function "" -> None | s -> Some s in
opt vc, opt transport, path, opt suffix, opt hash
| _ -> assert false
let vc_of_string = function
| "git" -> `git
| "hg" -> `hg
| "darcs" -> `darcs
| vc ->
parse_error (Printf.sprintf "unsupported version control system %s"
(OpamConsole.colorise `underline vc))
let string_of_vc = function
| `git -> "git"
| `darcs -> "darcs"
| `hg -> "hg"
let string_of_backend = function
| `http -> "http"
| `rsync -> "rsync"
| #version_control as vc -> string_of_vc vc
let backend_of_string = function
| "http" | "https" | "ftp" | "wget" | "curl" -> `http
| "file" -> `rsync
| "git" -> `git
| "darcs" -> `darcs
| "hg" -> `hg
| "path" | "local" | "rsync" | "ssh" | "scp" | "sftp" -> `rsync
| p ->
parse_error (Printf.sprintf "unsupported protocol %s"
(OpamConsole.colorise `underline p))
let looks_like_ssh_path =
let re =
Re.(compile @@ seq [
group @@ repn (diff any (set "/:")) 2 None;
char ':';
opt @@ char '/';
opt @@ group @@ seq [
alt [
diff any digit;
seq [rep digit; compl [digit; char '/']]
];
rep any;
];
eos;
])
in
fun path ->
try
let sub = Re.exec re path in
Some (Re.Group.get sub 1 ^
try "/" ^ Re.Group.get sub 2 with Not_found -> "")
with Not_found -> None
let parse ?backend ?(handle_suffix=true) ?(from_file=true) s =
let vc, transport, path, suffix, hash = split_url s in
let backend =
match backend with
| Some b -> b
| None ->
match vc with
| Some vc -> vc_of_string vc
| None ->
let of_suffix ~default =
if not handle_suffix then default else
match suffix with
| Some sf -> (try vc_of_string sf with Parse_error _ -> default)
| None ->
match OpamStd.String.cut_at path '@' with
| Some (user, _) ->
(try vc_of_string user with Parse_error _ -> default)
| None -> default
in
match transport with
| None -> of_suffix ~default:`rsync
| Some tr ->
try vc_of_string tr with Parse_error _ ->
of_suffix ~default:(backend_of_string tr)
in
let transport, path =
match backend, transport, looks_like_ssh_path path with
| `http, None, _ ->
"http", path
| _, (None | Some ("git"|"hg"|"darcs")), Some path ->
"ssh", path
| _, (None | Some ("hg"|"darcs")), None ->
"file", OpamSystem.real_path path |> OpamSystem.back_to_forward
| `rsync, Some "file", _ when not from_file ->
"file", OpamSystem.real_path path |> OpamSystem.back_to_forward
| _, Some tr, _ ->
tr, path
in
{
transport;
path;
hash;
backend;
}
let parse_opt ?(quiet=false) ?backend ?handle_suffix ?from_file s =
try
Some (parse ?backend ?handle_suffix ?from_file s)
with Parse_error pe ->
if not quiet then
OpamConsole.warning "URL parsing error on %s: %s"
(OpamConsole.colorise `underline s) pe;
None
let of_string url = parse ~handle_suffix:false url
let to_string_t ?subpath url =
let hash = match url.hash with Some h -> "#" ^ h | None -> "" in
let subpath =
match subpath with
| Some sb ->
Printf.sprintf "directory /%s in "
(OpamFilename.SubPath.normalised_string sb)
| None -> ""
in
match url.backend with
| #version_control as vc ->
let vc = string_of_backend vc in
if url.transport = vc then
Printf.sprintf "%s%s://%s%s" subpath vc url.path hash
else
Printf.sprintf "%s%s+%s://%s%s" subpath vc url.transport url.path hash
| `rsync | `http ->
Printf.sprintf "%s%s://%s%s" subpath url.transport url.path hash
let to_string url = to_string_t url
let to_string_w_subpath subpath = to_string_t ?subpath
let base_url url =
match url.transport with
| "" -> url.path
| tr -> Printf.sprintf "%s://%s" tr url.path
let local_path = function
| { transport = ("file"|"path"|"local"|"rsync"); path;
hash = _; backend = (#version_control | `rsync); }
when looks_like_ssh_path path = None ->
Some path
| _ -> None
let local_dir url =
let open OpamStd.Option.Op in
local_path url >>|
OpamFilename.Dir.of_string >>= fun d ->
if OpamFilename.exists_dir d then Some d
else None
let local_file url =
let open OpamStd.Option.Op in
local_path url >>|
OpamFilename.of_string >>= fun f ->
if OpamFilename.exists f then Some f
else None
let guess_version_control s =
let vc,transport,path,_,_ = split_url s in
if vc = None && transport = None && looks_like_ssh_path path = None then
let open OpamFilename in
let open Op in
let dir = Dir.of_string path in
if exists_dir (dir / ".git") || exists (dir // ".git")
then Some`git else
if exists_dir (dir / ".hg") then Some `hg else
if exists_dir (dir / "_darcs") then Some `darcs else
None
else
None
let basename =
let re =
Re.(compile @@ seq [
opt @@ seq [rep any; char '/'];
group @@ rep1 (diff any (char '/'));
rep @@ char '/';
])
in
fun t ->
try
Re.Group.get (Re.exec re t.path) 1
with Not_found -> ""
let root =
let re = Re.(compile @@ seq [char '/'; rep any]) in
fun t ->
let path =
if t.transport = "file" then
""
else
Re.replace_string re ~by:"" t.path
in
{ t with path}
let has_trailing_slash url =
OpamCompat.String.ends_with ~suffix:"/" url.path
let to_json url = `String (to_string url)
let of_json = function
| `String s -> (try Some (of_string s) with _ -> None)
| _ -> None
type url = t
let map_file_url f url =
if url.transport = "file" then
{url with path = f url.path}
else
url
module O = struct
type t = url
let to_string = to_string
let to_json = to_json
let of_json = of_json
let compare = compare
end
module Set = OpamStd.Set.Make(O)
module Map = OpamStd.Map.Make(O)
module Op = struct
(** appending to an url path *)
let ( / ) url dir =
let url =
if Filename.is_relative dir then
url
else
root url
in
let dir = OpamSystem.back_to_forward dir in
let path =
if has_trailing_slash url || url.path = "" then url.path ^ dir
else url.path ^ "/" ^ dir
in
{url with path }
end