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
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
open EzCompat
open Types
open EzFile.OP
let default_flags =
{ flag_file = "";
flag_create = false;
flag_record = true;
flag_skips = [];
flag_skip = false ;
flag_skipper = ref [];
flag_subst = true ;
flag_perm = 0;
}
let bracket flags eval_cond =
let bracket flags ( (), p ) s =
match EzString.split s ':' with
| [ "file"; v ] ->
flags.flag_file <- v;
""
| [ "create" ] ->
flags.flag_create <- true;
""
| [ "skip"; v ] ->
flags.flag_skips <- v :: flags.flag_skips;
""
| [ "skip" ] ->
flags.flag_skip <- true;
""
| [ "no-record" ] ->
flags.flag_record <- false;
""
| [ "perm" ; v ] ->
flags.flag_perm <- int_of_string ( "0o" ^ v );
""
| "if" :: cond ->
flags.flag_skipper := ( not ( eval_cond p cond ) ) :: (!)
flags.flag_skipper;
""
| [ "else" ] ->
flags.flag_skipper := ( match (!) flags.flag_skipper with
| cond :: tail -> ( not cond ) :: tail
| [] -> failwith "else without if");
""
| "elif" :: cond ->
flags.flag_skipper := ( match (!) flags.flag_skipper with
| _cond :: tail -> ( not (eval_cond p cond) ) :: tail
| [] -> failwith "elif without if");
""
| [ "fi" | "endif" ] ->
flags.flag_skipper := ( match (!) flags.flag_skipper with
| _ :: tail -> tail
| [] -> failwith "fi without if");
""
| _ ->
Printf.eprintf "Warning: unknown flag %S\n%!" s;
""
in
bracket flags
let flags_encoding =
EzToml.encoding
~to_toml:(fun _ -> assert false)
~of_toml:(fun ~key v ->
let table = EzToml.expect_table ~key ~name:"flags" v in
let flags = { default_flags with flag_file = "" } in
EzToml.iter
(fun k v ->
let key = key @ [ k ] in
match k with
| "file" ->
flags.flag_file <- EzToml.expect_string ~key v
| "create" ->
flags.flag_create <- EzToml.expect_bool ~key v
| "record" ->
flags.flag_record <- EzToml.expect_bool ~key v
| "skips" ->
flags.flag_skips <- EzToml.expect_string_list ~key v
| "skip" ->
flags.flag_skip <- EzToml.expect_bool ~key v
| "subst" ->
flags.flag_subst <- EzToml.expect_bool ~key v
| "perm" ->
flags.flag_perm <-
int_of_string ( "0o" ^ EzToml.expect_string ~key v)
| _ ->
Printf.eprintf "Warning: discarding flags field %S\n%!"
(EzToml.key2str key)
)
table;
flags
)
let load_skeleton ~drom ~dir ~toml ~kind =
let table =
match EzToml.from_file toml with
| `Ok table -> table
| `Error _ -> Error.raise "Could not parse skeleton file %S" toml
in
let name = try
EzToml.get_string table [ "skeleton"; "name" ]
with Not_found ->
failwith "load_skeleton: wrong or missing key skeleton.name"
in
let skeleton_inherits =
EzToml.get_string_option table [ "skeleton"; "inherits" ]
in
let skeleton_toml =
let basename =
if kind = "project" then "drom.toml" else
if kind = "package" then "package.toml" else
assert false
in
let file = dir // basename in
if Sys.file_exists file then
[ EzFile.read_file file ]
else begin
Printf.eprintf "Warning: file %s does not exist\n%!" file;
[]
end
in
let skeleton_files =
let files = ref [] in
EzFile.make_select EzFile.iter_dir ~deep:true dir ~kinds:[ S_REG; S_LNK ]
~f:(fun path ->
match String.lowercase ( Filename.basename path ) with
| "drom.toml"
| "package.toml"
| "skeleton.toml" -> ()
| _ ->
if not ( Filename.check_suffix path "~" ) then
let filename = dir // path in
let content = EzFile.read_file filename in
let st = Unix.lstat filename in
let perm = st.Unix.st_perm in
files := (path, content, perm) :: !files);
!files
in
let skeleton_flags = EzToml.get_encoding_default
(EzToml.ENCODING.stringMap flags_encoding) table [ "file" ]
StringMap.empty in
begin
match EzToml.get table [ "files" ] with
| exception Not_found -> ()
| _ ->
Printf.eprintf
"Warning: %s skeleton %S has an entry [files], probably instead of [file]\n%!"
kind name
end;
(name, { skeleton_toml;
skeleton_inherits;
skeleton_files ;
skeleton_flags ;
skeleton_drom = drom;
skeleton_name = name;
})
let load_dir_skeletons ~drom map kind dir =
if Sys.file_exists dir then begin
let map = ref map in
EzFile.iter_dir dir ~f:(fun file ->
let dir = dir // file in
let toml = dir // "skeleton.toml" in
if Sys.file_exists toml then
try
let name, skeleton = load_skeleton ~drom ~dir ~toml ~kind in
if !Globals.verbosity > 0 && StringMap.mem name !map then
Printf.eprintf "Warning: %s skeleton %S overwritten in %s\n%!"
kind name dir;
map := StringMap.add name skeleton !map
with exn ->
Printf.eprintf "Warning: could not load %s skeleton from %S, exception:\n%S\n%!" kind dir (Printexc.to_string exn)
);
!map
end else
map
let kind_dir ~kind = "skeletons" // kind ^ "s"
let load_system_skeletons map kind =
match Config.share_dir () with
| Some dir ->
let global_skeletons_dir = dir // kind_dir ~kind in
load_dir_skeletons ~drom:true map kind global_skeletons_dir
| None ->
Printf.eprintf "Warning: could not load skeletons from share/%s/skeletons/%s\n%!" Globals.command kind;
map
let load_user_skeletons map kind =
let user_skeletons_dir = Globals.config_dir // kind_dir ~kind in
load_dir_skeletons ~drom:false map kind user_skeletons_dir
let load_skeletons kind =
let map = load_system_skeletons StringMap.empty kind in
load_user_skeletons map kind
let project_skeletons = lazy (load_skeletons "project")
let package_skeletons = lazy (load_skeletons "package")
let rec inherit_files self_files super_files =
match (self_files, super_files) with
| _, [] -> self_files
| [], _ -> super_files
| ( (self_file, self_content, self_mode) :: self_files_tail,
(super_file, super_content, super_mode) :: super_files_tail ) ->
if self_file = super_file then
(self_file, self_content, self_mode)
:: inherit_files self_files_tail super_files_tail
else if self_file < super_file then
(self_file, self_content, self_mode) ::
inherit_files self_files_tail super_files
else
(super_file, super_content, super_mode)
:: inherit_files self_files super_files_tail
let lookup_skeleton ?(project=false) skeletons name =
let skeletons = Lazy.force skeletons in
let rec iter name =
match StringMap.find name skeletons with
| exception Not_found -> download_skeleton name
| self ->
match self.skeleton_inherits with
| None ->
self
| Some super ->
let super = iter super in
let skeleton_toml = self.skeleton_toml @ super.skeleton_toml in
let skeleton_files =
inherit_files self.skeleton_files super.skeleton_files
in
let skeleton_flags =
StringMap.union (fun _ x _ -> Some x)
self.skeleton_flags super.skeleton_flags
in
{ skeleton_name = name;
skeleton_inherits = None;
skeleton_toml; skeleton_files ;
skeleton_drom = false;
skeleton_flags ;
}
and download_skeleton name =
if project then
match EzString.chop_prefix name ~prefix:"gh:" with
| None ->
Error.raise "Missing skeleton %S" name
| Some github_project ->
let url = Printf.sprintf "https://github.com/%s/tarball/master"
github_project in
let output = Filename.temp_file "archive" ".tgz" in
Misc.wget ~url ~output;
let basedir = Printf.sprintf "gh-%s"
( Digest.string name |> Digest.to_hex ) in
let dir = Globals.config_dir // "skeletons"
// "projects" // basedir in
EzFile.make_dir ~p:true dir;
Misc.call [| "tar" ; "zxf"; output ;
"--strip-components=1"; "-C"; dir |];
let toml = dir // "skeleton.toml" in
if not ( Sys.file_exists toml ) then
EzFile.write_file toml
( Printf.sprintf {|[skeleton]
name = "%s"
|} name );
let (skel_name, skeleton) =
load_skeleton ~drom:false ~dir ~toml ~kind:"project"
in
if skel_name = name then
skeleton
else
Error.raise "Wrong remote skeleton %S instead of %S in %s\n"
skel_name name dir
else
Error.raise "Missing skeleton %S" name
in
iter name
let backup_skeleton file content ~perm =
let skeleton_dir = Globals.drom_dir // "skeleton" in
let drom_file = skeleton_dir // file in
EzFile.make_dir ~p:true (Filename.dirname drom_file);
EzFile.write_file drom_file content;
Unix.chmod drom_file perm
let lookup_project skeleton =
lookup_skeleton ~project:true
project_skeletons (Misc.project_skeleton skeleton)
let lookup_package skeleton = lookup_skeleton package_skeletons skeleton
let rec eval_project_cond p cond =
match cond with
| [ "skeleton" ; "is" ; skeleton ] ->
Misc.project_skeleton p.skeleton = skeleton
| [ "skip" ; skip ] -> List.mem skip p.skip
| [ "gen" ; skip ] -> not ( List.mem skip p.skip )
| "not" :: cond -> not ( eval_project_cond p cond )
| [ "true" ] -> true
| [ "false" ] -> false
| [ "ci" ; system ] -> List.mem system p.ci_systems
| [ "github-organization"] -> p.github_organization <> None
| [ "homepage"] -> Misc.homepage p <> None
| [ "copyright"] -> p.copyright <> None
| [ "bug-reports"] -> Misc.bug_reports p <> None
| [ "dev-repo"] -> Misc.dev_repo p <> None
| [ "doc-gen"] -> Misc.doc_gen p <> None
| [ "doc-api"] -> Misc.doc_api p <> None
| [ "sphinx-target"] -> p.sphinx_target <> None
| [ "profile"] -> p.profile <> None
| [ "min-edition" ] -> p.min_edition <> p.edition
| [ "field" ; name ] -> StringMap.mem name p.fields
| "field" :: name :: v ->
let v = String.concat ":" v in
begin
match StringMap.find name p.fields with
| exception Not_found -> false
| x -> x = v
end
| _ ->
Printf.kprintf failwith "eval_project_cond: unknown condition %S\n%!"
( String.concat ":" cond )
let rec eval_package_cond p cond =
match cond with
| [ "skeleton" ; "is" ; skeleton ] ->
Misc.package_skeleton p = skeleton
| [ "kind" ; "is" ; kind ] -> kind = Misc.string_of_kind p.kind
| [ "pack" ] -> Misc.p_pack_modules p
| [ "skip" ; skip ] -> List.mem skip p.project.skip
| [ "gen" ; skip ] -> not ( List.mem skip p.project.skip )
| "not" :: cond -> not ( eval_package_cond p cond )
| [ "true" ] -> true
| [ "false" ] -> false
| "project" :: cond -> eval_project_cond p.project cond
| [ "field" ; name ] -> StringMap.mem name p.p_fields
| "field" :: name :: v ->
let v = String.concat ":" v in
begin
match StringMap.find name p.p_fields with
| exception Not_found -> false
| x -> x = v
end
| _ ->
Printf.kprintf failwith "eval_package_cond: unknown condition %S\n%!"
( String.concat ":" cond )
let default_flags flag_file =
{ default_flags with flag_file ; flag_skipper = ref [] }
let skeleton_flags skeleton file =
try
let flags =
try
StringMap.find file skeleton.skeleton_flags
with
Not_found ->
try
StringMap.find ( Printf.sprintf "\"%s\"" file )
skeleton.skeleton_flags
with
Not_found ->
if Misc.verbose 2 then
Printf.eprintf "skeleton %S has no flags for file %S\n%!"
skeleton.skeleton_name file;
raise Not_found
in
if flags.flag_file = "" then
{ flags with
flag_file = file ;
flag_skipper = ref [] ;
}
else
{ flags with flag_skipper = ref [] }
with Not_found ->
default_flags file
let skeleton_flags skeleton file perm =
let flags = skeleton_flags skeleton file in
if flags.flag_perm = 0 then flags.flag_perm <- perm;
if Filename.check_suffix file ".sh" then
flags.flag_perm <- flags.flag_perm lor 0o111;
flags
let write_project_files write_file p =
let skeleton = lookup_project p.skeleton in
List.iter
(fun (file, content, perm) ->
backup_skeleton file content ~perm;
let flags = skeleton_flags skeleton file perm in
let bracket = bracket flags eval_project_cond in
let content =
if flags.flag_subst then
try Subst.project () ~bracket ~skipper:flags.flag_skipper p content
with Not_found ->
Printf.kprintf failwith "Exception Not_found in %S\n%!" file;
else content
in
let { flag_file;
flag_create = create;
flag_skips = skips;
flag_record = record;
flag_skip = skip;
flag_perm = perm;
flag_skipper= _ ;
flag_subst = _ ; } = flags in
let flag_file = Subst.project () p flag_file in
write_file flag_file ~create ~skips ~content ~record ~skip ~perm;
)
skeleton.skeleton_files;
()
let subst_package_file flags content package =
let bracket = bracket flags eval_package_cond in
let content =
if flags.flag_subst then
try
Subst.package () ~bracket
~skipper:flags.flag_skipper package content
with Not_found ->
Printf.kprintf failwith "Exception Not_found in %S\n%!"
flags.flag_file
else
content
in
content
let write_package_files write_file package =
let skeleton = lookup_package (Misc.package_skeleton package) in
List.iter
(fun (file, content, perm) ->
backup_skeleton file content ~perm;
let flags = skeleton_flags skeleton file perm in
let content = subst_package_file flags content package in
let { flag_file;
flag_create = create;
flag_skips = skips;
flag_record = record;
flag_skip = skip;
flag_perm = perm;
flag_skipper= _ ;
flag_subst = _ ; } = flags in
let flag_file = Subst.package () package flag_file in
let file = package.dir // flag_file in
write_file file ~create ~skips ~content ~record ~skip ~perm)
skeleton.skeleton_files
let write_files write_file p =
write_project_files write_file p;
List.iter (fun package -> write_package_files write_file package) p.packages
let project_skeletons () =
Lazy.force project_skeletons |> StringMap.to_list |> List.map snd
let package_skeletons () =
Lazy.force package_skeletons |> StringMap.to_list |> List.map snd
let known_skeletons () =
Printf.sprintf "project skeletons: %s\npackage skeletons: %s\n"
(project_skeletons ()
|> List.map (fun s -> s.skeleton_name)
|> String.concat " " )
(package_skeletons ()
|> List.map (fun s -> s.skeleton_name)
|> String.concat " " )