~ubuntu-branches/debian/stretch/adabrowse/stretch

« back to all changes in this revision

Viewing changes to util-pathes.adb

  • Committer: Bazaar Package Importer
  • Author(s): Ludovic Brenta
  • Date: 2004-02-14 13:22:40 UTC
  • Revision ID: james.westby@ubuntu.com-20040214132240-cqumhiq1677pkvzo
Tags: upstream-4.0.2
ImportĀ upstreamĀ versionĀ 4.0.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
-------------------------------------------------------------------------------
 
2
--
 
3
--  <STRONG>Copyright &copy; 2001, 2002 by Thomas Wolf.</STRONG>
 
4
--  <BLOCKQUOTE>
 
5
--    This piece of software is free software; you can redistribute it and/or
 
6
--    modify it under the terms of the  GNU General Public License as published
 
7
--    by the Free Software  Foundation; either version 2, or (at your option)
 
8
--    any later version. This software is distributed in the hope that it will
 
9
--    be useful, but <EM>without any warranty</EM>; without even the implied
 
10
--    warranty of <EM>merchantability or fitness for a particular purpose.</EM>
 
11
--    See the GNU General Public License for  more details. You should have
 
12
--    received a copy of the GNU General Public License with this distribution,
 
13
--    see file "<A HREF="GPL.txt">GPL.txt</A>". If not, write to the Free
 
14
--    Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307,
 
15
--    USA.
 
16
--  </BLOCKQUOTE>
 
17
--  <BLOCKQUOTE>
 
18
--    As a special exception from the GPL, if other files instantiate generics
 
19
--    from this unit, or you link this unit with other files to produce an
 
20
--    executable, this unit does not by itself cause the resulting executable
 
21
--    to be covered by the GPL. This exception does not however invalidate any
 
22
--    other reasons why the executable file might be covered by the GPL.
 
23
--  </BLOCKQUOTE>
 
24
--
 
25
--  <AUTHOR>
 
26
--    Thomas Wolf  (TW) <E_MAIL>
 
27
--  </AUTHOR>
 
28
--
 
29
--  <PURPOSE>
 
30
--    Operations for manipulating file names. The package is intended for
 
31
--    use on Windows or Unix systems. Upon elaboration, it tries to figure
 
32
--    out the host operating system by examining the @PATH@ environment
 
33
--    variable: if that contains Windows-looking pathes (i.e., a '\' is found
 
34
--    before a any '/'), it assumes it's being used on Windows. If, on the
 
35
--    other hand, it finds a '/' first, it assumes Unix. If it finds neither,
 
36
--    it uses @GNAT.Os_Lib.Directory_Separator@ as its directory separator.
 
37
--    (If you intend to use this package on a non-GNAT system, you'll have
 
38
--    to change the body of this package as appropriate.)
 
39
--
 
40
--    All operations in this package are pur string manipulation operations.
 
41
--    There are no file system operations involved.
 
42
--  </PURPOSE>
 
43
--
 
44
--  <NOT_TASK_SAFE>
 
45
--
 
46
--  <NO_STORAGE>
 
47
--
 
48
--  <HISTORY>
 
49
--    19-MAR-2002   TW  Initial version.
 
50
--    03-MAY-2002   TW  Added 'Drive' and 'Node'; various minor corrections.
 
51
--  </HISTORY>
 
52
-------------------------------------------------------------------------------
 
53
 
 
54
pragma License (Modified_GPL);
 
55
 
 
56
with Util.Environment;
 
57
with Util.Strings;
 
58
 
 
59
with GNAT.OS_Lib;
 
60
 
 
61
pragma Elaborate_All (Util.Environment);
 
62
pragma Elaborate_All (Util.Strings);
 
63
 
 
64
package body Util.Pathes is
 
65
 
 
66
   --  Syntax:
 
67
   --
 
68
   --  Windows:
 
69
   --     [(\\Node\{\}|Drive_Letter:{\})]{Name\{\}}[Base_Name][.Extension]
 
70
   --     where Name . and .. have special meanings.
 
71
   --
 
72
   --  Unix:
 
73
   --     [/{/}]{Name/{/}][Base_Name][.Extension]
 
74
   --     where Name . and .. have special meanings.
 
75
   --
 
76
   --  VMS:
 
77
   --     [Disk:]["["[Name]{.Dir_Name}"]"][Base_Name][.Extension][;Version]
 
78
   --     where Dir_Name . and "" have special meaning
 
79
   --
 
80
   --  Mac:
 
81
   --     Not sure. I think it went like this:
 
82
   --     {Name:}[Base_Name][.Extension]
 
83
   --     where Name : has a special meaning.
 
84
   --
 
85
   --  VMS and Mac are not done yet!
 
86
 
 
87
   use Util.Strings;
 
88
 
 
89
   ----------------------------------------------------------------------------
 
90
   --  Internal operations:
 
91
 
 
92
   function Determine_Host_Separator
 
93
     return Character
 
94
   is
 
95
      Path : constant String  := Util.Environment.Safe_Get ("PATH");
 
96
      I    : Natural := First_Index (Path, '\');
 
97
      J    : Natural := First_Index (Path, '/');
 
98
   begin
 
99
      if I = 0 then I := Natural'Last; end if;
 
100
      if J = 0 then J := Natural'Last; end if;
 
101
      if I < J then
 
102
         return '\';
 
103
      elsif J < I then
 
104
         return '/';
 
105
      else
 
106
         return GNAT.OS_Lib.Directory_Separator;
 
107
      end if;
 
108
   end Determine_Host_Separator;
 
109
 
 
110
   Dir_Sep : constant Character := Determine_Host_Separator;
 
111
 
 
112
   function Node_End
 
113
     (Full_Name : in String;
 
114
      Separator : in Character)
 
115
     return Natural
 
116
   is
 
117
   begin
 
118
      --  Needs revision for VMS!
 
119
      if (Separator = '\' or else Separator = '/') and then
 
120
         Full_Name'Last > Full_Name'First + 1 and then
 
121
         Full_Name (Full_Name'First) = Separator and then
 
122
         Full_Name (Full_Name'First + 1) = Separator
 
123
      then
 
124
         declare
 
125
            I : constant Natural :=
 
126
              First_Index (Full_Name
 
127
                             (Full_Name'First + 2 .. Full_Name'Last),
 
128
                           Separator);
 
129
         begin
 
130
            if I = 0 then
 
131
               return Full_Name'Last;
 
132
            else
 
133
               return I;
 
134
            end if;
 
135
         end;
 
136
      end if;
 
137
      return 0;
 
138
   end Node_End;
 
139
 
 
140
   function Drive_End
 
141
     (Full_Name : in String;
 
142
      Separator : in Character)
 
143
     return Natural
 
144
   is
 
145
      I : Natural := Node_End (Full_Name, Separator);
 
146
   begin
 
147
      if I = 0 then
 
148
         I := Full_Name'First;
 
149
      else
 
150
         if Full_Name (I) /= Separator then
 
151
            --  Only a node name!
 
152
            return 0;
 
153
         end if;
 
154
         I := I + 1;
 
155
      end if;
 
156
      if Separator = '\' and then
 
157
         I + 1 <= Full_Name'Last and then
 
158
         Is_In (Letters, Full_Name (I)) and then
 
159
         Full_Name (I + 1) = ':'
 
160
      then
 
161
         return I + 1;
 
162
      end if;
 
163
      --  Needs revision for VMS!
 
164
      return 0;
 
165
   end Drive_End;
 
166
 
 
167
   function Path_End
 
168
     (Full_Name : in String;
 
169
      Separator : in Character)
 
170
     return Natural
 
171
   is
 
172
      I : constant Natural := Node_End (Full_Name, Separator);
 
173
   begin
 
174
      if I > 0 and then Full_Name (I) /= Separator then
 
175
         return I;
 
176
      end if;
 
177
      return Natural'Max (Last_Index (Full_Name, Separator),
 
178
                          Drive_End (Full_Name, Separator));
 
179
   end Path_End;
 
180
 
 
181
   ----------------------------------------------------------------------------
 
182
 
 
183
   function Directory_Separator
 
184
     return Character
 
185
   is
 
186
   begin
 
187
      return Dir_Sep;
 
188
   end Directory_Separator;
 
189
 
 
190
   function Extension
 
191
     (Full_Name : in String;
 
192
      Separator : in Character := Util.Pathes.Directory_Separator)
 
193
     return String
 
194
   is
 
195
      I : Natural := Path_End (Full_Name, Separator);
 
196
      J : Natural;
 
197
   begin
 
198
      if I = 0 then I := Full_Name'First; else I := I + 1; end if;
 
199
      J := Last_Index (Full_Name (I .. Full_Name'Last), '.');
 
200
      if J <= I then
 
201
         return "";
 
202
      else
 
203
         return Full_Name (J + 1 .. Full_Name'Last);
 
204
      end if;
 
205
   end Extension;
 
206
 
 
207
   function Name
 
208
     (Full_Name : in String;
 
209
      Separator : in Character := Directory_Separator)
 
210
     return String
 
211
   is
 
212
      I : constant Natural := Path_End (Full_Name, Separator);
 
213
   begin
 
214
      if I = 0 then return Full_Name; end if;
 
215
      return Full_Name (I + 1 .. Full_Name'Last);
 
216
   end Name;
 
217
 
 
218
   function Base_Name
 
219
     (Full_Name : in String;
 
220
      Separator : in Character := Directory_Separator)
 
221
     return String
 
222
   is
 
223
      I : Natural := Path_End (Full_Name, Separator);
 
224
      J : Natural;
 
225
   begin
 
226
      if I = 0 then I := Full_Name'First; else I := I + 1; end if;
 
227
      J := Last_Index (Full_Name (I .. Full_Name'Last), '.');
 
228
      if J <= I then
 
229
         --  Also handles cases like ".cshrc".
 
230
         return Full_Name (I .. Full_Name'Last);
 
231
      else
 
232
         return Full_Name (I .. J - 1);
 
233
      end if;
 
234
   end Base_Name;
 
235
 
 
236
   function Path
 
237
     (Full_Name : in String;
 
238
      Separator : in Character := Directory_Separator)
 
239
     return String
 
240
   is
 
241
      I : constant Natural := Path_End (Full_Name, Separator);
 
242
   begin
 
243
      if I = 0 then return ""; end if;
 
244
      return Full_Name (Full_Name'First .. I);
 
245
   end Path;
 
246
 
 
247
   function Drive
 
248
     (Full_Name : in String;
 
249
      Separator : in Character := Directory_Separator)
 
250
     return String
 
251
   is
 
252
      I : Natural := Node_End (Full_Name, Separator);
 
253
   begin
 
254
      if I = 0 then I := Full_Name'First; else I := I + 1; end if;
 
255
      return
 
256
        Full_Name (I .. Drive_End (Full_Name (I .. Full_Name'Last),
 
257
                                   Separator));
 
258
   end Drive;
 
259
 
 
260
   function Has_Drive
 
261
     (Full_Name : in String;
 
262
      Separator : in Character := Directory_Separator)
 
263
     return Boolean
 
264
   is
 
265
      I : Natural := Node_End (Full_Name, Separator);
 
266
   begin
 
267
      if I = 0 then I := Full_Name'First; else I := I + 1; end if;
 
268
      return Drive_End (Full_Name (I .. Full_Name'Last), Separator) > 0;
 
269
   end Has_Drive;
 
270
 
 
271
   function Node
 
272
     (Full_Name : in String;
 
273
      Separator : in Character := Directory_Separator)
 
274
     return String
 
275
   is
 
276
   begin
 
277
      return Full_Name (Full_Name'First .. Node_End (Full_Name, Separator));
 
278
   end Node;
 
279
 
 
280
   function Has_Node
 
281
     (Full_Name : in String;
 
282
      Separator : in Character := Directory_Separator)
 
283
     return Boolean
 
284
   is
 
285
   begin
 
286
      return Node_End (Full_Name, Separator) > 0;
 
287
   end Has_Node;
 
288
 
 
289
   function Normalize
 
290
     (Path      : in String;
 
291
      Separator : in Character := Directory_Separator)
 
292
     return String
 
293
   is
 
294
   begin
 
295
      if Path'Last < Path'First then return '.' & Separator; end if;
 
296
      if Drive_End (Path, Separator) = Path'Last then
 
297
         return Path;
 
298
      elsif Path (Path'Last) = Separator then
 
299
         return Path;
 
300
      else
 
301
         return Path & Separator;
 
302
      end if;
 
303
   end Normalize;
 
304
 
 
305
   function Parent
 
306
     (Path      : in String;
 
307
      Separator : in Character := Directory_Separator)
 
308
     return String
 
309
   is
 
310
 
 
311
      function Up
 
312
        (Path      : in String;
 
313
         Separator : in Character)
 
314
        return String
 
315
      is
 
316
         --  'Path' is a cleaned-up path!
 
317
 
 
318
         I : Natural;
 
319
 
 
320
      begin
 
321
         if Path'Last = Path'First and then Path (Path'First) = Separator then
 
322
            raise Path_Error; --  Root in an absolute path
 
323
         elsif Path = '.' & Separator then
 
324
            return ".." & Separator;
 
325
         end if;
 
326
         I := Last_Index (Path (Path'First .. Path'Last - 1), Separator);
 
327
         if I = 0 then
 
328
            --  "something/", return "./"
 
329
            if Path (Path'First .. Path'Last - 1) = ".." then
 
330
               --  Oops, we already had "../": return "../../".
 
331
               return Path & Path;
 
332
            elsif Path (Path'First .. Path'Last - 1) = "." then
 
333
               --  We had "./", return "./../".
 
334
               return Path & ".." & Separator;
 
335
            else
 
336
               return '.' & Separator;
 
337
            end if;
 
338
         else
 
339
            if Path (I + 1 .. Path'Last - 1) = ".." then
 
340
               --  We have only a sequence of "../": add one more.
 
341
               return Path & ".." & Separator;
 
342
            else
 
343
               return Path (Path'First .. I);
 
344
            end if;
 
345
         end if;
 
346
      end Up;
 
347
 
 
348
      P    : constant String := Clean (Path, Separator);
 
349
      I, J : Natural;
 
350
 
 
351
   begin
 
352
      I := Node_End (P, Separator);
 
353
      if I = 0 then I := P'First; else I := I + 1; end if;
 
354
      J := Drive_End (P (I .. P'Last), Separator);
 
355
      if J > 0 then
 
356
         if J = P'Last then
 
357
            return P & ".." & Separator;
 
358
         else
 
359
            return P (P'First .. J) &
 
360
                   Up (P (J + 1 .. P'Last), Separator);
 
361
         end if;
 
362
      elsif I > 0 then
 
363
         if P (I) = Separator then
 
364
            return P (P'First .. I - 1) &
 
365
                   Up (P (I .. P'Last), Separator);
 
366
         else
 
367
            --  Only a node name: cannot get the parent, for relative pathes
 
368
            --  are not allowed.
 
369
            raise Path_Error;
 
370
         end if;
 
371
      end if;
 
372
      --  Neither node name nor drive:
 
373
      return Up (P,  Separator);
 
374
   end Parent;
 
375
 
 
376
   function Clean
 
377
     (Full_Name : in String;
 
378
      Separator : in Character := Directory_Separator)
 
379
     return String
 
380
   is
 
381
 
 
382
      function Clean_It
 
383
        (Path      : in String;
 
384
         Separator : in Character)
 
385
        return String
 
386
      is
 
387
         Result : String (1 .. Path'Length + 1);
 
388
         K      : Natural;
 
389
      begin
 
390
         K := Result'First;
 
391
         for I in Path'Range loop
 
392
            Result (K) := Path (I);
 
393
            if Path (I) = Separator then
 
394
               if K > Result'First + 2 and then
 
395
                  Result (K - 1) = '.' and then
 
396
                  Result (K - 2) = '.' and then
 
397
                  Result (K - 3) = Separator
 
398
               then
 
399
                  if K = Result'First + 3 then
 
400
                     --  A path cannot start with "/../"!
 
401
                     raise Path_Error;
 
402
                  end if;
 
403
                  declare
 
404
                     J : constant Natural :=
 
405
                       Last_Index (Result (Result'First .. K - 4),
 
406
                                   Separator);
 
407
                  begin
 
408
                     if J > 0 then
 
409
                        if Result (J + 1 .. K - 4) = ".." then
 
410
                           --  We have "../../../", which is legal and can
 
411
                           --  occur only at the beginning!
 
412
                           null;
 
413
                        else
 
414
                           K := J;
 
415
                        end if;
 
416
                     else
 
417
                        if K - 4 = Result'First and then
 
418
                           Result (Result'First) = '.'
 
419
                        then
 
420
                           --  We have "./../", which should become "../".
 
421
                           Result (Result'First + 1) := '.';
 
422
                           Result (Result'First + 2) := Separator;
 
423
                           K := Result'First + 2;
 
424
                        elsif Result (Result'First .. K - 4) = ".." then
 
425
                           --  We have "../../" at the beginning!
 
426
                           null;
 
427
                        else
 
428
                           --  We have "something/../", which should
 
429
                           --  become "./".
 
430
                           Result (Result'First) := '.';
 
431
                           Result (Result'First + 1) := Separator;
 
432
                           K := Result'First + 1;
 
433
                        end if;
 
434
                     end if;
 
435
                  end;
 
436
               elsif K > Result'First + 1 and then
 
437
                     Result (K - 1) = '.' and then
 
438
                     Result (K - 2) = Separator
 
439
               then
 
440
                  K := K - 2;
 
441
               elsif K > Result'First and then
 
442
                     Result (K - 1) = Separator
 
443
               then
 
444
                  --  Eliminate extraneous separators.
 
445
                  K := K - 1;
 
446
               end if;
 
447
            end if;
 
448
            K := K + 1;
 
449
         end loop;
 
450
         K := K - 1;
 
451
         if K >= Result'First and then Result (K) /= Separator then
 
452
            K := K + 1;
 
453
            Result (K) := Separator;
 
454
         end if;
 
455
         return Result (Result'First .. K);
 
456
      end Clean_It;
 
457
 
 
458
      I, J : Natural;
 
459
 
 
460
   begin
 
461
      I := Node_End (Full_Name, Separator);
 
462
      if I > 0 then
 
463
         if Full_Name (I) /= Separator then
 
464
            --  Only a node name, without terminating separator:
 
465
            return Full_Name & Separator;
 
466
         end if;
 
467
         --  Skip multiple separators:
 
468
         J := I + 1;
 
469
         while J <= Full_Name'Last and then Full_Name (J) = Separator loop
 
470
            J := J + 1;
 
471
         end loop;
 
472
         if J > I + 1 then
 
473
            return Clean (Full_Name (Full_Name'First .. I) &
 
474
                          Full_Name (J .. Full_Name'Last));
 
475
         end if;
 
476
      end if;
 
477
      J := Drive_End (Full_Name, Separator);
 
478
      if J > 0 then
 
479
         return Full_Name (Full_Name'First .. J) &
 
480
                Clean_It (Full_Name (J + 1 .. Full_Name'Last), Separator);
 
481
      end if;
 
482
      if I > 0 then
 
483
         return Full_Name (Full_Name'First .. I - 1) &
 
484
                Clean_It (Full_Name (I .. Full_Name'Last), Separator);
 
485
      end if;
 
486
      return Clean_It (Full_Name, Separator);
 
487
   end Clean;
 
488
 
 
489
   function Is_Absolute_Path
 
490
     (Name      : in String;
 
491
      Separator : in Character := Directory_Separator)
 
492
     return Boolean
 
493
   is
 
494
   begin
 
495
      if Name'Last < Name'First then return False; end if;
 
496
      if Separator = ':' then
 
497
         --  Mac? It's been a while, but if I remember correctly, it went
 
498
         --  like this:
 
499
         return Name (Name'First) /= ':';
 
500
      else
 
501
         --  Not Mac, i.e. Windows or Unix.
 
502
         if Name (Name'First) = Separator then
 
503
            return True;
 
504
         end if;
 
505
         if Drive_End (Name, Separator) > 0 then
 
506
            return True;
 
507
         end if;
 
508
      end if;
 
509
      return False;
 
510
   end Is_Absolute_Path;
 
511
 
 
512
   function Concat
 
513
     (Path      : in String;
 
514
      File_Name : in String;
 
515
      Separator : in Character := Directory_Separator)
 
516
     return String
 
517
   is
 
518
   begin
 
519
      if Path'Last      < Path'First      then return File_Name; end if;
 
520
      if File_Name'Last < File_Name'First then return Path;      end if;
 
521
      if Is_Absolute_Path (File_Name, Separator) then
 
522
         raise Path_Error;
 
523
      end if;
 
524
      return Normalize (Path, Separator) & File_Name;
 
525
   end Concat;
 
526
 
 
527
   function Replace_File_Name
 
528
     (Full_Name : in String;
 
529
      File_Name : in String;
 
530
      Separator : in Character := Directory_Separator)
 
531
     return String
 
532
   is
 
533
   begin
 
534
      return Concat (Path (Full_Name, Separator), File_Name, Separator);
 
535
   end Replace_File_Name;
 
536
 
 
537
   function Replace_Extension
 
538
     (Full_Name : in String;
 
539
      Extension : in String;
 
540
      Separator : in Character := Directory_Separator)
 
541
     return String
 
542
   is
 
543
      J : Natural := Path_End (Full_Name, Separator);
 
544
   begin
 
545
      if Full_Name'Last < Full_Name'First or else J = Full_Name'Last then
 
546
         raise Path_Error;
 
547
      end if;
 
548
      declare
 
549
         I : constant Natural := Last_Index (Full_Name, '.');
 
550
      begin
 
551
         if J = 0 then J := Full_Name'First; else J := J + 1; end if;
 
552
         if I <= J then
 
553
            return Full_Name & '.' & Extension;
 
554
         else
 
555
            return Full_Name (Full_Name'First .. I) & Extension;
 
556
         end if;
 
557
      end;
 
558
   end Replace_Extension;
 
559
 
 
560
end Util.Pathes;