~ubuntu-branches/ubuntu/trusty/ocamlnet/trusty

« back to all changes in this revision

Viewing changes to src/netstring/netglob.ml

  • Committer: Bazaar Package Importer
  • Author(s): Stéphane Glondu
  • Date: 2011-09-02 14:12:33 UTC
  • mfrom: (18.2.3 sid)
  • Revision ID: james.westby@ubuntu.com-20110902141233-zbj0ygxb92u6gy4z
Tags: 3.4-1
* New upstream release
  - add a new NetcgiRequire directive to ease dependency management
    (Closes: #637147)
  - remove patches that were applied upstream:
    + Added-missing-shebang-lines-in-example-shell-scripts
    + Try-also-ocamlc-for-POSIX-threads

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
(* $Id: netglob.ml 1659 2011-08-23 17:28:03Z gerd $ *)
 
2
 
 
3
open Netglob_lex
 
4
open Printf
 
5
 
 
6
type glob_expr = glob_expr_atom list
 
7
 
 
8
and glob_expr_atom =
 
9
    [ `Literal of string
 
10
    | `Star
 
11
    | `Qmark
 
12
    | `Bracket of (bool * glob_set)
 
13
    | `Brace of glob_expr list
 
14
    | `Tilde of string 
 
15
    ]
 
16
 
 
17
and glob_set = < set : (int * int) list >
 
18
 
 
19
 
 
20
type valid_glob_expr =
 
21
    { pat : glob_expr;
 
22
      encoding : Netconversion.encoding;
 
23
    }
 
24
 
 
25
exception Bad_glob_expr of string
 
26
 
 
27
exception Unsupported_expr of string
 
28
 
 
29
class type user_info =
 
30
object
 
31
  method path_encoding : Netconversion.encoding option
 
32
  method home_directory : string -> string
 
33
end
 
34
 
 
35
class type glob_fsys =
 
36
object
 
37
  method path_encoding : Netconversion.encoding option
 
38
  method read_dir : string -> string list
 
39
  method file_is_dir : string -> bool
 
40
  method file_exists : string -> bool
 
41
end
 
42
 
 
43
type glob_mode = [ `Existing_paths
 
44
                 | `All_paths
 
45
                 | `All_words
 
46
                 ]
 
47
 
 
48
type pattern = [ `String of string | `Expr of valid_glob_expr ]
 
49
 
 
50
 
 
51
let literal_glob_expr enc s =
 
52
  { pat = [ `Literal s ];
 
53
    encoding = enc
 
54
  }
 
55
 
 
56
 
 
57
let reparse_bracket_expr enc l =
 
58
  (* In order to support multi-byte encodings, reparse the expression
 
59
     now. For simplifying this, we require that ranges (like c-d) are
 
60
     purely ASCII. So only the chars outside ranges need to be reparsed
 
61
   *)
 
62
  let rec collect buf toks =
 
63
    match toks with
 
64
      | Bracket_char c :: toks' ->
 
65
          Buffer.add_char buf c;
 
66
          collect buf toks'
 
67
      | Bracket_range(c1,c2) as tok :: toks' ->
 
68
          let new_toks = reparse buf in
 
69
          new_toks @ [tok] @ collect (Buffer.create 80) toks'
 
70
      | Bracket_code _ :: _ ->
 
71
          assert false
 
72
      | Bracket_end :: _
 
73
      | [] ->
 
74
          reparse buf
 
75
 
 
76
  and reparse buf =
 
77
    let s = Buffer.contents buf in
 
78
    let codes = ref [] in
 
79
    ( try
 
80
        Netconversion.ustring_iter enc (fun i -> codes := i :: !codes) s
 
81
      with _ -> raise Lexing_Error
 
82
    );
 
83
    List.rev_map
 
84
      (fun i -> Bracket_code i)
 
85
      !codes
 
86
  in
 
87
 
 
88
  collect (Buffer.create 80) l
 
89
 
 
90
    
 
91
let parse_glob_expr
 
92
      ?(encoding = `Enc_iso88591)
 
93
      ?(enable_star = true)
 
94
      ?(enable_qmark = true)
 
95
      ?(enable_brackets = true)
 
96
      ?(enable_braces = true)
 
97
      ?(enable_tilde = true)
 
98
      ?(enable_escape = true)
 
99
      s =
 
100
 
 
101
  if not (Netconversion.is_ascii_compatible encoding) then
 
102
    failwith
 
103
      "Netglob.parse_glob_expr: the encoding is not ASCII-compatible";
 
104
  
 
105
  let feat =
 
106
    { enable_star = enable_star;
 
107
      enable_qmark = enable_qmark;
 
108
      enable_brackets = enable_brackets;
 
109
      enable_braces = enable_braces;
 
110
      enable_tilde = enable_tilde;
 
111
      enable_escape = enable_escape;
 
112
      escaped = false;
 
113
    } in
 
114
 
 
115
  let rec collect_until lexbuf =
 
116
    let tok = glob_expr feat lexbuf in
 
117
    if tok = Glob_end then
 
118
      []
 
119
    else
 
120
      tok :: (collect_until lexbuf)
 
121
  in
 
122
 
 
123
  let rec process_brace_list current list =
 
124
    match list with
 
125
      | Brace_literal s :: list' ->
 
126
          let gl = collect_until (Lexing.from_string s) in
 
127
          process_brace_list (current @ gl) list'
 
128
      | Brace_braces l :: list' ->
 
129
          process_brace_list (current @ [Glob_braces l]) list'
 
130
      | Brace_comma :: list' ->
 
131
          let ge = process_glob_list [] current in
 
132
          ge :: process_brace_list [] list'
 
133
      | Brace_end :: _ ->
 
134
          assert false
 
135
      | [] ->
 
136
          let ge = process_glob_list [] current in
 
137
          [ ge ]
 
138
 
 
139
  and process_glob_list acc list =
 
140
    match list with
 
141
      | Glob_star :: list' ->
 
142
          ( match acc with
 
143
              | `Star :: acc' ->
 
144
                  (* Ignore the second star! *)
 
145
                  process_glob_list acc list'
 
146
              | _ ->
 
147
                  process_glob_list (`Star :: acc) list'
 
148
          )
 
149
      | Glob_qmark :: list' ->
 
150
          process_glob_list (`Qmark :: acc) list'
 
151
      | Glob_brackets (neg,btoks) :: list' ->
 
152
          let set =
 
153
            List.map
 
154
              (function
 
155
                 | Bracket_char c ->
 
156
                     assert false
 
157
                 | Bracket_range (c1,c2) -> (* c1, c2 are ASCII *)
 
158
                     (Char.code c1, Char.code c2)
 
159
                 | Bracket_code i ->
 
160
                     (i, i)
 
161
                 | Bracket_end ->
 
162
                     assert false
 
163
              )
 
164
              (reparse_bracket_expr encoding btoks) in
 
165
          let set_obj = ( object method set = set end ) in
 
166
          process_glob_list (`Bracket(neg,set_obj) :: acc) list'
 
167
      | Glob_braces btoks :: list' ->
 
168
          let alts = process_brace_list [] btoks in
 
169
          process_glob_list (`Brace alts :: acc) list'
 
170
      | Glob_literal s :: list' ->
 
171
          if s <> "" then
 
172
            ( match acc with
 
173
                | `Literal s' :: acc' ->
 
174
                    process_glob_list (`Literal(s' ^ s) :: acc') list'
 
175
                | _ ->
 
176
                    process_glob_list (`Literal s :: acc) list'
 
177
            )
 
178
          else
 
179
            process_glob_list acc list'
 
180
      | Glob_tilde(s,slash) :: list' ->
 
181
          let atoms =
 
182
            if slash then [ `Literal "/"; `Tilde s ] else [ `Tilde s ] in
 
183
          process_glob_list ( atoms @ acc ) list'
 
184
      | Glob_end :: _ ->
 
185
          assert false
 
186
      | [] ->
 
187
          List.rev acc
 
188
  in
 
189
 
 
190
  try
 
191
    let glob_list = 
 
192
      collect_until (Lexing.from_string s) in
 
193
    
 
194
    let glob_expr =
 
195
      process_glob_list [] glob_list in
 
196
 
 
197
    { pat = glob_expr;
 
198
      encoding = encoding
 
199
    }
 
200
 
 
201
  with
 
202
    | Bracket_Unsupported ->
 
203
        raise (Unsupported_expr s)
 
204
    | Lexing_Error ->
 
205
        raise (Bad_glob_expr s)
 
206
 
 
207
 
 
208
let validate_glob_expr enc expr =
 
209
  let checkenc s =
 
210
    try Netconversion.verify enc s
 
211
    with _ ->
 
212
      failwith "Netglob.validate_glob_expr: literal does not conform \
 
213
                to selected pattern encoding" in
 
214
 
 
215
  let rec validate ge =
 
216
    match ge with
 
217
      | `Literal s :: ge' ->
 
218
          if s = "" then
 
219
            failwith "Netglob.validate_glob_expr: empty literal";
 
220
          checkenc s;
 
221
          validate ge'
 
222
      | `Bracket(_,set) :: ge' ->
 
223
          List.iter
 
224
            (fun (j,k) ->
 
225
               if j < 0 || k < 0 || j > k then
 
226
                 failwith "Netglob.validate_glob_expr: bad bracket set";
 
227
            )
 
228
            set#set
 
229
      | `Brace l :: ge' ->
 
230
          List.iter validate l;
 
231
          validate ge'
 
232
      | `Tilde s :: ge' ->
 
233
          checkenc s;
 
234
          validate ge'
 
235
      | _ :: ge' ->
 
236
          validate ge'
 
237
      | [] ->
 
238
          () in
 
239
  if not (Netconversion.is_ascii_compatible enc) then
 
240
    failwith
 
241
      "Netglob.validate_glob_expr: the encoding is not ASCII-compatible";
 
242
  validate expr;
 
243
  { pat = expr;
 
244
    encoding = enc
 
245
  }
 
246
 
 
247
let recover_glob_expr expr =
 
248
  expr.pat
 
249
 
 
250
let encoding_of_glob_expr expr =
 
251
  expr.encoding
 
252
 
 
253
 
 
254
(* A more efficient representation for sets: *)
 
255
 
 
256
type eff_set =
 
257
    { ascii : bool array;
 
258
      non_ascii : (int, unit) Hashtbl.t
 
259
    }
 
260
 
 
261
let to_eset set =
 
262
  let ascii = Array.create 128 false in
 
263
  let non_ascii = Hashtbl.create 13 in
 
264
  List.iter
 
265
    (fun (k0,k1) ->
 
266
       assert(k0 <= k1);
 
267
       for p = k0 to k1 do
 
268
         if p < 128 then
 
269
           ascii.(p) <- true
 
270
         else
 
271
           Hashtbl.replace non_ascii p ()
 
272
       done
 
273
    )
 
274
    set;
 
275
  { ascii = ascii; non_ascii = non_ascii }
 
276
 
 
277
 
 
278
let rec mem_eset code eset =
 
279
  if code >= 0 && code < 128 then
 
280
    eset.ascii.(code)
 
281
  else
 
282
    Hashtbl.mem eset.non_ascii code
 
283
 
 
284
 
 
285
let size_eset eset =
 
286
  let n = ref 0 in
 
287
  for k = 0 to 127 do
 
288
    if eset.ascii.(k) then incr n
 
289
  done;
 
290
  !n + Hashtbl.length eset.non_ascii
 
291
 
 
292
 
 
293
let ascii_ranges eset =
 
294
  let ranges = ref [] in
 
295
  let inrange = ref None in
 
296
  for k = 0 to 127 do
 
297
    let p = eset.ascii.(k) in
 
298
    match !inrange with
 
299
      | None ->
 
300
          if p then inrange := Some k
 
301
      | Some q ->
 
302
          if not p then (
 
303
            ranges := (q, k-1) :: !ranges;
 
304
            inrange := None;
 
305
          )
 
306
  done;
 
307
  ( match !inrange with
 
308
      | None -> ()
 
309
      | Some q -> ranges := (q, 127) :: !ranges
 
310
  );
 
311
  List.rev !ranges
 
312
 
 
313
 
 
314
let rec exclude_set codes set =
 
315
  match set with
 
316
      [] -> []
 
317
    | (x,y) :: set' ->
 
318
        let x' = if List.mem x codes then x+1 else x in
 
319
        let y' = if List.mem y codes then y-1 else y in
 
320
        if x = x' && y = y' && x <= y then
 
321
          (x,y) :: exclude_set codes set'
 
322
        else if x' <= y' then 
 
323
          exclude_set codes ( (x',y') :: set')
 
324
        else
 
325
          exclude_set codes set'
 
326
 
 
327
 
 
328
let print_set buf encoding neg_char negated set =
 
329
  (* Always produce a portable expression: *)
 
330
  let eset = to_eset set in
 
331
 
 
332
  (* Check for special characters: *)
 
333
  let want_minus    = mem_eset (Char.code '-') eset in
 
334
  let want_rbracket = mem_eset (Char.code ']') eset in
 
335
  let want_circum   = mem_eset (Char.code '^') eset in
 
336
  let want_exclam   = mem_eset (Char.code '!') eset in
 
337
  let size = size_eset eset in
 
338
  
 
339
  (* Check for very special sets: *)
 
340
  if not negated && want_circum && size = 1 then
 
341
    Buffer.add_string buf "^"  (* "[^]" would not be portable enough *)
 
342
  else if not negated && want_exclam && size = 1 then
 
343
    Buffer.add_string buf "!"  (* "[!]" would not be portable enough *)
 
344
  else if not negated && want_circum && want_exclam && size = 2 then
 
345
    failwith "print_glob_expr"
 
346
      (* There is no portable representation *)
 
347
  else (
 
348
    (* First create a set expression where the special characters
 
349
     * '-', ']', '^', and '!' do not occur literally.
 
350
     *)
 
351
    let empty = ref true in
 
352
    let buf' = Buffer.create 200 in
 
353
    let ascii_part = ascii_ranges eset in
 
354
    let ascii_part' = 
 
355
      exclude_set (List.map Char.code ['-'; ']'; '^'; '!']) ascii_part in
 
356
    let ascii_part'_eset = to_eset ascii_part' in
 
357
    List.iter
 
358
      (fun (x0,x1) ->
 
359
         if x0 = x1 then (
 
360
           Buffer.add_char buf' (Char.chr x0);
 
361
           empty := false;
 
362
         ) 
 
363
          else if x0 <= x1 then (
 
364
           Buffer.add_char buf' (Char.chr x0);
 
365
           Buffer.add_char buf' '-';
 
366
           Buffer.add_char buf' (Char.chr x1);
 
367
           empty := false;
 
368
         )
 
369
      )
 
370
      ascii_part';
 
371
    (* The non-ascii part is easy: *)
 
372
    Hashtbl.iter
 
373
      (fun code _ ->
 
374
         let encoded =
 
375
           Netconversion.ustring_of_uarray encoding [| code |] in
 
376
         Buffer.add_string buf' encoded
 
377
      )
 
378
      eset.non_ascii;
 
379
    (* Check which of the special characters are already covered
 
380
     * by ranges:
 
381
     *)
 
382
    let done_minus    = mem_eset (Char.code '-') ascii_part'_eset in
 
383
    let done_rbracket = mem_eset (Char.code ']') ascii_part'_eset in
 
384
    let done_circum   = mem_eset (Char.code '^') ascii_part'_eset in
 
385
    let done_exclam   = mem_eset (Char.code '!') ascii_part'_eset in
 
386
    (* Begin with printing *)
 
387
    Buffer.add_string
 
388
      buf 
 
389
      (if negated then "[" ^ String.make 1 neg_char else "[");
 
390
    (* ']' must always be the first character of the set: *)
 
391
    if want_rbracket && not done_rbracket then (
 
392
      Buffer.add_string buf "]";
 
393
      empty := false;
 
394
    );
 
395
    Buffer.add_buffer buf buf';
 
396
    (* '-' must be the first or the last character; '^' and '!' must
 
397
     * not be the first character. So we usually print these
 
398
     * characters in the order "^!-". One case is special: We have
 
399
     * not yet printed any character. Then, "-" must be printed
 
400
     * first (if member of the set), or we have one of the very
 
401
     * special cases already tested above.
 
402
     *)
 
403
    if !empty then (
 
404
      if want_minus && not done_minus then Buffer.add_char buf '-';
 
405
      if want_circum && not done_circum then Buffer.add_char buf '^';
 
406
      if want_exclam && not done_exclam then Buffer.add_char buf '!';
 
407
    ) else (
 
408
      if want_circum && not done_circum then Buffer.add_char buf '^';
 
409
      if want_exclam && not done_exclam then Buffer.add_char buf '!';
 
410
      if want_minus && not done_minus then Buffer.add_char buf '-';
 
411
    );
 
412
    Buffer.add_char buf ']';
 
413
  )
 
414
 
 
415
 
 
416
let esc_re = Netstring_str.regexp "[][*?{},\\~]";;
 
417
 
 
418
let esc_subst m s =
 
419
  "\\" ^ Netstring_str.matched_group m 0 s
 
420
 
 
421
let print_glob_expr ?(escape_in_literals=true) expr =
 
422
  let buf = Buffer.create 200 in
 
423
  let rec print gl =
 
424
    match gl with
 
425
      | `Literal s :: gl' ->
 
426
          Buffer.add_string buf
 
427
            (if escape_in_literals then
 
428
               Netstring_str.global_substitute esc_re esc_subst s
 
429
             else
 
430
               s
 
431
            );
 
432
          print gl'
 
433
      | `Star :: gl' ->
 
434
          Buffer.add_string buf "*";
 
435
          print gl'
 
436
      | `Qmark :: gl' ->
 
437
          Buffer.add_string buf "?";
 
438
          print gl'
 
439
      | `Bracket (negated,set) :: gl' ->
 
440
          print_set buf expr.encoding '!' negated set#set;
 
441
          print gl'
 
442
      | `Brace ge_list :: gl' ->
 
443
          Buffer.add_string buf "{";
 
444
          let first = ref true in
 
445
          List.iter 
 
446
            (fun ge ->
 
447
               if not !first then Buffer.add_string buf ",";
 
448
               print ge;
 
449
            )
 
450
            ge_list;
 
451
          Buffer.add_string buf "}";
 
452
          print gl'
 
453
      | `Tilde s :: gl' ->
 
454
          Buffer.add_char buf '~';
 
455
          Buffer.add_string buf s;
 
456
          print gl'
 
457
      | [] ->
 
458
          ()
 
459
  in
 
460
  print expr.pat;
 
461
  Buffer.contents buf
 
462
 
 
463
 
 
464
class local_user_info() =
 
465
  let pe =
 
466
    match Sys.os_type with
 
467
      | "Win32" ->
 
468
          Netconversion.user_encoding()
 
469
      | _ -> None in
 
470
object
 
471
  method path_encoding = pe
 
472
 
 
473
  method home_directory name =
 
474
    (* Win32: only the HOME method works *)
 
475
    try
 
476
      if name = "" then (
 
477
        try Sys.getenv "HOME"
 
478
        with Not_found ->
 
479
          let pw = Unix.getpwuid(Unix.getuid()) in
 
480
          pw.Unix.pw_dir
 
481
      ) else
 
482
        (Unix.getpwnam name).Unix.pw_dir
 
483
    with
 
484
      | _ -> raise Not_found
 
485
end
 
486
 
 
487
 
 
488
let local_user_info = new local_user_info
 
489
 
 
490
 
 
491
let rec product f l1 l2 =
 
492
  match l1 with
 
493
      [] ->
 
494
        []
 
495
    | x1 :: l1' ->
 
496
        List.map (fun x2 -> f x1 x2) l2 @ product f l1' l2
 
497
 
 
498
 
 
499
let rec expand_braces ge =
 
500
  match ge with
 
501
    | [] ->
 
502
        [ [] ]
 
503
    | `Brace gelist :: ge' ->
 
504
        let gelist' = 
 
505
          List.flatten (List.map expand_braces gelist) in
 
506
        let ge_alts' = expand_braces ge' in
 
507
        product ( @ ) gelist' ge_alts'
 
508
          
 
509
    | any :: ge' ->
 
510
        let ge_alts' = expand_braces ge' in
 
511
        List.map (fun ge_alt' -> any :: ge_alt') ge_alts'
 
512
 
 
513
 
 
514
let rec expand_tildes encoding user_info ge =
 
515
  match ge with
 
516
    | [] ->
 
517
        []
 
518
    | `Tilde name :: ge' ->
 
519
        let atom =
 
520
          try
 
521
            let dir = user_info#home_directory name in
 
522
            if dir="" then raise Not_found; (* empty literals not allowed *)
 
523
            ( match user_info#path_encoding with
 
524
                | None -> `Literal dir
 
525
                | Some ui_enc ->
 
526
                    if ui_enc = encoding then
 
527
                      `Literal dir
 
528
                    else
 
529
                      `Literal
 
530
                        (Netconversion.convert
 
531
                           ~in_enc:ui_enc ~out_enc:encoding dir)
 
532
            )
 
533
          with Not_found ->
 
534
            `Literal ("~" ^ name) in
 
535
        atom :: expand_tildes encoding user_info ge'
 
536
    | any :: ge' ->
 
537
        any :: expand_tildes encoding user_info ge'
 
538
 
 
539
 
 
540
let expand_glob_expr ?(user_info=local_user_info())
 
541
                     ?(expand_brace=true) ?(expand_tilde=true) expr =
 
542
  let pat' =
 
543
    if expand_tilde then
 
544
      expand_tildes expr.encoding user_info expr.pat
 
545
    else
 
546
      expr.pat in
 
547
  let pat_l =
 
548
    if expand_brace then
 
549
      expand_braces pat'
 
550
    else
 
551
      [pat'] in
 
552
  List.map (fun p -> { expr with pat = p }) pat_l
 
553
 
 
554
 
 
555
let period = Char.code '.'
 
556
let slash = Char.code '/'
 
557
 
 
558
let match_glob_expr ?(protect_period=true) ?(protect_slash=true)
 
559
                    ?encoding
 
560
                    expr s =
 
561
  let esets = Hashtbl.create 5 in
 
562
  let get_eset set =
 
563
    try Hashtbl.find esets set
 
564
    with Not_found ->
 
565
      let eset = to_eset set#set in
 
566
      Hashtbl.add esets set eset;
 
567
      eset in
 
568
 
 
569
  let u = 
 
570
    Netconversion.uarray_of_ustring 
 
571
      ( match encoding with
 
572
          | None -> expr.encoding
 
573
          | Some e -> e
 
574
      )
 
575
      s in
 
576
  let n = Array.length u in
 
577
 
 
578
  let leading_period p =
 
579
    u.(p) = period && 
 
580
      (p = 0 || (protect_slash && u.(p - 1) = slash)) in
 
581
 
 
582
  let rec match_at c ge =
 
583
    match ge with
 
584
      | `Literal lit :: ge' ->
 
585
          let lit_u = Netconversion.uarray_of_ustring expr.encoding lit in
 
586
          let lit_n = Array.length lit_u in
 
587
          let ok =
 
588
            try
 
589
              for k = 0 to lit_n - 1 do
 
590
                if c+k >= n then raise Not_found;
 
591
                let code = u.(c+k) in
 
592
                if code <> lit_u.(k) then raise Not_found;
 
593
              done;
 
594
              true
 
595
            with 
 
596
              | Not_found -> false in
 
597
          ok && match_at (c+lit_n) ge'    
 
598
      | `Star :: ge' ->
 
599
          let k = ref 0 in
 
600
          let cont = ref true in
 
601
          let found = ref false in
 
602
          while c + !k <= n && not !found && !cont do
 
603
            found := match_at (c + !k) ge';
 
604
            if c + !k < n then
 
605
              cont :=
 
606
                (not protect_period || not (leading_period (c + !k))) &&
 
607
                   (not protect_slash || u.(c + !k) <> slash);
 
608
            incr k;
 
609
          done;
 
610
          !found
 
611
      | `Qmark :: ge' ->
 
612
          let ok =
 
613
            c < n && 
 
614
              (not protect_period || not (leading_period c)) &&
 
615
              (not protect_slash || u.(c) <> slash) in
 
616
          ok && match_at (c+1) ge'            
 
617
      | `Bracket(neg,set) :: ge' ->
 
618
          let ok =
 
619
            c < n && (
 
620
              let code = u.(c) in
 
621
              (not protect_slash || code <> slash) && 
 
622
                (not protect_period || not (leading_period c)) && (
 
623
                  let eset = get_eset set in
 
624
                  let is_mem = mem_eset code eset in
 
625
                  (neg <> is_mem)
 
626
                )
 
627
            ) in
 
628
          ok &&
 
629
            match_at (c+1) ge'
 
630
      | `Brace _ :: _ ->
 
631
          failwith "Netglob.match_glob_expr: found `Brace subpattern"
 
632
      | `Tilde _ :: _ ->
 
633
          failwith "Netglob.match_glob_expr: found `Tilde subpattern"
 
634
      | [] ->
 
635
          c = n in
 
636
 
 
637
  match_at 0 expr.pat
 
638
 
 
639
 
 
640
let skip_slashes s k =
 
641
  let l = String.length s in
 
642
  let j = ref k in
 
643
  while !j < l && s.[!j] = '/' do incr j done;
 
644
  !j
 
645
 
 
646
let rev_skip_slashes s k =
 
647
  let j = ref k in
 
648
  while !j >= 0 && s.[!j] = '/' do decr j done;
 
649
  !j
 
650
 
 
651
let search_slash s =
 
652
  let k = String.index s '/' in
 
653
  let j = skip_slashes s (k+1) in
 
654
  (k, j)
 
655
 
 
656
 
 
657
let split_glob_expr expr =
 
658
 
 
659
  let rec split_loop is_first acc ge =
 
660
    (* acc: accumulates the current component *)
 
661
    match ge with
 
662
      | [] ->
 
663
          [ List.rev acc ]
 
664
      | (`Literal s as atom) :: ge' ->
 
665
          assert(s <> "");
 
666
          ( try 
 
667
              let (k,j) = search_slash s in    (* or Not_found *)
 
668
              let l = String.length s in
 
669
              let s1 = String.sub s 0 k in         (* part before '/' *)
 
670
              let s2 = String.sub s j (l - j) in   (* part after '/' *)
 
671
              if is_first && k = 0 then (
 
672
                (* Case: rooted expression *)
 
673
                let ge'' =
 
674
                  if s2 <> "" then (`Literal s2) :: ge' else ge' in
 
675
                let comps = split_loop false [] ge'' in
 
676
                (* N.B. comps is a list of lists... *)
 
677
                match comps with
 
678
                  | ( (`Literal s3) :: r ) :: l ->
 
679
                      ( `Literal("/" ^ s3) :: r) :: l
 
680
                  | r :: l ->
 
681
                      (`Literal "/" :: r) :: l
 
682
                  | [] ->
 
683
                      [ [ `Literal "/" ] ]
 
684
              )
 
685
              else
 
686
                if ge' = [] && s2 = "" then (
 
687
                  (* Case: component matches only directory *)
 
688
                  [ List.rev (`Literal (s1 ^ "/") :: acc) ]
 
689
                )
 
690
                else (
 
691
                  let acc' = 
 
692
                    if s1 <> "" then (`Literal s1)::acc else acc in
 
693
                  let ge'' =
 
694
                    if s2 <> "" then (`Literal s2) :: ge' else ge' in
 
695
                  (List.rev acc') :: split_loop false [] ge''
 
696
                )
 
697
            with
 
698
              | Not_found ->
 
699
                  split_loop false (atom::acc) ge'
 
700
          )
 
701
      | (`Star | `Qmark | `Bracket(_,_) as atom) :: ge' ->
 
702
          split_loop false (atom::acc) ge'
 
703
 
 
704
      | `Brace _ :: _ ->
 
705
          failwith "Netglob.split_glob_expr: brace expression found"
 
706
 
 
707
      | `Tilde _ :: _ ->
 
708
          failwith "Netglob.split_glob_expr: tilde expression found"
 
709
  in
 
710
 
 
711
  List.map
 
712
    (fun p -> { expr with pat = p })
 
713
    (split_loop true [] expr.pat)
 
714
 
 
715
 
 
716
let check_rooted_glob_expr expr =
 
717
  match expr.pat with
 
718
    | (`Literal s) :: r ->
 
719
        assert(s <> "");
 
720
        if s.[0] = '/' then (
 
721
          let j = skip_slashes s 1 in
 
722
          let l = String.length s in
 
723
          let s' = String.sub s j (l - j) in   (* part after '/' *)
 
724
          if s' = "" then 
 
725
            Some { expr with pat = r }
 
726
          else
 
727
            Some { expr with pat = `Literal s' :: r }
 
728
        )
 
729
        else
 
730
          None
 
731
    | _ ->
 
732
        None
 
733
 
 
734
 
 
735
let check_directory_glob_expr expr =
 
736
  match List.rev expr.pat with
 
737
    | (`Literal s) :: r ->
 
738
        assert(s <> "");
 
739
        ( try
 
740
            let l = String.length s in
 
741
            if s.[l-1] <> '/' then raise Not_found;
 
742
            let k = rev_skip_slashes s (l-1) + 1 in
 
743
            let s' = String.sub s 0 k in   (* the part before '/' *)
 
744
            if s' = "" then
 
745
              Some { expr with pat = List.rev r }
 
746
            else
 
747
              Some { expr with pat = List.rev (`Literal s' :: r) }
 
748
          with
 
749
              Not_found -> None
 
750
        )
 
751
    | _ ->
 
752
        None
 
753
 
 
754
class of_dual_stream_fs (abs_fs:Netfs.stream_fs) rel_fs =
 
755
  let is_abs name = name <> "" && name.[0] = '/' in
 
756
  let fix name =
 
757
    if is_abs name then
 
758
      (abs_fs, name)
 
759
    else
 
760
      (rel_fs, "/" ^ name) in
 
761
object
 
762
  method path_encoding = abs_fs#path_encoding
 
763
  method read_dir name =
 
764
    let (fs,name) = fix name in
 
765
    try fs#readdir [] name with _ -> []
 
766
  method file_is_dir name =
 
767
    let (fs,name) = fix name in
 
768
    try fs#test [] name `D with _ -> false
 
769
  method file_exists name =
 
770
    let (fs,name) = fix name in
 
771
    try fs#test [] name `E with _ -> false
 
772
end
 
773
 
 
774
 
 
775
class of_stream_fs fs0 =
 
776
  let fs = (fs0 : #Netfs.stream_fs :> Netfs.stream_fs) in
 
777
  of_dual_stream_fs fs fs
 
778
 
 
779
let of_stream_fs = new of_stream_fs
 
780
 
 
781
 
 
782
class local_fsys ?encoding () =
 
783
  let abs_fs = Netfs.local_fs ?encoding () in
 
784
  let rel_fs = Netfs.local_fs ?encoding ~root:"." () in
 
785
  of_dual_stream_fs abs_fs rel_fs
 
786
 
 
787
let local_fsys = new local_fsys
 
788
 
 
789
 
 
790
 
 
791
 
 
792
let fn_concat d f =
 
793
  let l = String.length d in
 
794
  if l = 0 || d.[l-1] = '/' then
 
795
    d ^ f
 
796
  else
 
797
    d ^ "/" ^ f
 
798
 
 
799
 
 
800
let glob1 ?base_dir 
 
801
          ?(protect_period=true) 
 
802
          ?(fsys = local_fsys())
 
803
          ?user_info
 
804
          ?(mode = `Existing_paths)
 
805
          expr =
 
806
 
 
807
  (* File names and paths are encoded as [fsys] demands it.
 
808
     The encoding of the pattern can be different!
 
809
   *)
 
810
 
 
811
  let rec collect_and_match base_dir generated_prefix components =
 
812
    match components with
 
813
      | [] ->
 
814
          if generated_prefix <> "" then [ generated_prefix ] else []
 
815
      | comp :: components' ->
 
816
          let full_path file =
 
817
            match base_dir with
 
818
              | Some d -> fn_concat d file
 
819
              | None   -> file in
 
820
 
 
821
          let dir_ge = check_directory_glob_expr comp in
 
822
          let comp' = 
 
823
            match dir_ge with 
 
824
              | Some ge' -> ge'
 
825
              | None -> comp in
 
826
 
 
827
          let check_for_match only_dirs e file =
 
828
            (* file is encoded in fsys#path_encoding. For matching, we
 
829
               need to convert it to the encoding of the pattern.
 
830
             *)
 
831
            try
 
832
              let pe =
 
833
                match fsys#path_encoding with
 
834
                  | None -> `Enc_iso88591 (* so no conv errors possible *)
 
835
                  | Some pe -> pe in
 
836
              match_glob_expr ~protect_period ~encoding:pe e file &&
 
837
                (not only_dirs || fsys#file_is_dir (full_path file))
 
838
            with
 
839
              | Netconversion.Cannot_represent _ -> false
 
840
          in
 
841
          
 
842
          let files =
 
843
            match comp'.pat with
 
844
              | [ `Literal s ] ->
 
845
                  (* s is encoded in expr.encoding. We need it here
 
846
                     in the fsys#encoding
 
847
                   *)
 
848
                  ( try
 
849
                      let s' =
 
850
                        match fsys#path_encoding with
 
851
                          | None -> s
 
852
                          | Some pe -> 
 
853
                              Netconversion.convert 
 
854
                                ~in_enc:expr.encoding ~out_enc:pe s in
 
855
                      match mode with
 
856
                        | `Existing_paths ->
 
857
                            let path = full_path s' in
 
858
                            if fsys # file_exists path then
 
859
                              [ s' ]
 
860
                            else
 
861
                              []
 
862
                        | _ ->
 
863
                            [ s' ]
 
864
                    with Netconversion.Cannot_represent _ 
 
865
                         when mode = `Existing_paths -> []
 
866
                  )
 
867
              | _ ->
 
868
                  let only_dirs = components' <> [] || dir_ge <> None in
 
869
                  let file_list = fsys#read_dir (full_path ".") in
 
870
(*eprintf "Files in %s: %s\n%!" (full_path ".") (String.concat "," file_list);*)
 
871
                  List.filter (check_for_match only_dirs comp') file_list 
 
872
          in
 
873
          List.flatten
 
874
            (List.map
 
875
               (fun file -> 
 
876
                  let prefixed_file = 
 
877
                    fn_concat generated_prefix file 
 
878
                    ^ (if dir_ge <> None then "/" else "") in
 
879
                  
 
880
                  collect_and_match 
 
881
                    (Some(full_path file))
 
882
                    prefixed_file
 
883
                    components'
 
884
               )
 
885
               files
 
886
            )
 
887
 
 
888
  in
 
889
 
 
890
  let collect_and_match_0 components =
 
891
    match components with
 
892
      | comp :: components' ->
 
893
          ( match check_rooted_glob_expr comp with
 
894
              | None ->
 
895
                  collect_and_match base_dir "" components
 
896
              | Some comp' ->
 
897
                  if comp'.pat = [] then
 
898
                    (* Special case "/" *)
 
899
                    [ "/" ]
 
900
                  else
 
901
                    collect_and_match (Some "/") "/" (comp' :: components')
 
902
          )
 
903
      | [] ->
 
904
          []
 
905
  in
 
906
 
 
907
  let e_list = expand_glob_expr ?user_info expr in
 
908
  List.flatten
 
909
    (List.map
 
910
       (fun e' ->
 
911
          let l = collect_and_match_0 (split_glob_expr e') in
 
912
          if mode = `All_words && l = [] && e'.pat <> [] then
 
913
            [print_glob_expr e']
 
914
          else
 
915
            l
 
916
       )
 
917
       e_list
 
918
    )
 
919
 
 
920
 
 
921
let glob ?encoding ?base_dir ?protect_period ?fsys ?user_info ?mode pat =
 
922
  match pat with
 
923
    | `Expr e ->
 
924
        glob1 ?base_dir ?protect_period ?fsys ?user_info ?mode e
 
925
    | `String s ->
 
926
        let e =
 
927
          parse_glob_expr ?encoding s in
 
928
        glob1 ?base_dir ?protect_period ?fsys ?user_info ?mode e