ada_util_0d266031/src/base/files/util-files-walk.ads

  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
-- --------------------------------------------------------------------
--  util-files-walk -- Walk directory trees
--  Written by Stephane Carrez (Stephane.Carrez@gmail.com)
--  SPDX-License-Identifier: Apache-2.0
-----------------------------------------------------------------------

with Ada.Finalization;
with Ada.Directories;
with Util.Files.Filters;

--  == Directory tree walk ==
--  It is sometimes necessary to walk a directory tree while taking into
--  account some inclusion or exclusion patterns or more complex ignore lists.
--  The `Util.Files.Walk` package provides a support to walk such directory
--  tree while taking into account some possible ignore lists such as the
--  `.gitignore` file.  The package defines the `Filter_Type` tagged type
--  to represent and control the exclusion or inclusion filters and a second
--  tagged type `Walker_Type` to walk the directory tree.
--
--  The `Filter_Type` provides two operations to add patterns in the filter
--  and one operation to check against a path whether it matches a pattern.
--  A pattern can contain fixed paths, wildcards or regular expressions.
--  Similar to `.gitignore` rules, a pattern which starts with a `/` will
--  define a pattern that must match the complete path.  Otherwise, the pattern
--  is a recursive pattern.  Example of pattern setup:
--
--     Filter : Util.Files.Walk.Filter_Type;
--     ...
--     Filter.Exclude ("*.o");
--     Filter.Exclude ("/alire/");
--     Filter.Include ("/docs/*");
--
--  The `Match` function looks in the filter for a match.  The path could be
--  included, excluded or not found.  For example, the following paths will
--  match:
--
--  | Operation                    | Result         |
--  | ---------------------------- | -------------- |
--  | Filter.Match ("test.o")      | Walk.Excluded  |
--  | Filter.Match ("test.a")      | Walk.Not_Found |
--  | Filter.Match ("docs/test.o") | Walk.Included  |
--  | Filter.Match ("alire/")      | Walk.Included  |
--  | Filter.Match ("test/alire")  | Walk.Not_Found |
--
--  To scan a directory tree, the `Walker_Type` must have some of its operations
--  overriden:
--
--  * The `Scan_File` should be overriden to be notified when a file is found
--    and handle it.
--  * The `Scan_Directory` should be overriden to be notified when a directory
--    is entered.
--  * The `Get_Ignore_Path` is called when entering a new directory.  It can
--    be overriden to indicate a path of a file which contains some patterns
--    to be ignored (ex: the `.gitignore` file).
--
--  See the `tree.adb` example.
package Util.Files.Walk is

   package AF renames Ada.Finalization;

   type Filter_Mode is (Not_Found, Included, Excluded);

   package Path_Filter is
      new Util.Files.Filters (Filter_Mode);

   use Path_Filter;

   subtype Filter_Result is Path_Filter.Filter_Result;
   subtype Filter_Context_Type is Path_Filter.Filter_Context_Type;

   type Filter_Type is limited new Path_Filter.Filter_Type with null record;

   --  Add a new pattern to include files or directories in the walk.
   procedure Include (Filter  : in out Filter_Type;
                      Pattern : in String) with
     Pre => Pattern'Length > 0;

   --  Add a new pattern to exclude (ignore) files or directories in the walk.
   procedure Exclude (Filter  : in out Filter_Type;
                      Pattern : in String) with
     Pre => Pattern'Length > 0;

   --  Check if a path matches the included or excluded patterns.
   function Match (Filter : in Filter_Type;
                   Path   : in String) return Filter_Mode;

   --  Load a series of lines that contains a list of files to ignore.
   --  The `Reader` procedure is called with a `Process` procedure that is
   --  expected to be called for each line which comes from the ignore
   --  file (such as the .gitignore file).  The `Process` procedure handles
   --  the interpretation of ignore patterns as defined by `.gitignore`
   --  and it updates the `Filter` accordingly.
   procedure Load_Ignore (Filter : in out Filter_Type'Class;
                          Label  : in String;
                          Reader : not null access
                              procedure (Process : not null access
                                   procedure (Line : in String)));
   procedure Load_Ignore (Filter : in out Filter_Type'Class;
                          Path   : String) with
      Pre => Path'Length > 0 and then Ada.Directories.Exists (Path);

   type Walker_Type is limited new AF.Limited_Controlled with null record;

   --  Scan the directory tree given by the path for files and sub-directories
   --  matching the filters:
   --  * it calls `Get_Ignore_Path` to get an optional path of files to read
   --    for patterns to ignore,
   --  * if that file exist, it calls `Load_Ignore` in a local filter and
   --    loads the patterns in the filter.
   --  * it scans the directory for files and sub-directories matching the
   --    filters (either defined by the loaded ignored files) or by the main
   --    filter.
   --  * when files are found and are not excluded, it calls `Scan_File`,
   --  * when sub-directories are found and are not excluded, it calls
   --    `Scan_Directory`.
   procedure Scan (Walker : in out Walker_Type;
                   Path   : in String;
                   Filter : in Filter_Type'Class) with
     Pre => Path'Length > 0 and then Ada.Directories.Exists (Path);

   --  Called from `Scan` to give the opportunity to load the ignore files
   --  from the root directory (see `Find_Root`), down to the inner directory
   --  that must be scanned (identified by `Scan_Path`).  The `Rel_Path` indicates
   --  the relative paths from `Path` that must still be sanned before reaching
   --  the `Scan_Path`.  Once the `Rel_Path` becomes empty, calls `Scan_Directory`
   --  with the filter and the ignore files collected up to the root directory.
   --  The `Level` is incremented each time we enter a sub-directory to load its
   --  .gitignore file.
   procedure Scan_Subdir_For_Ignore (Walker    : in out Walker_Type;
                                     Path      : in String;
                                     Scan_Path : in String;
                                     Rel_Path  : in String;
                                     Level     : in Natural;
                                     Filter    : in Filter_Context_Type) with
     Pre => Path'Length > 0 and then Scan_Path'Length > 0;

   --  Get the path of a file that can be read to get a list of files to ignore
   --  in the given directory (ie, .gitignore).
   function Get_Ignore_Path (Walker : Walker_Type;
                             Path   : String) return String;

   --  Load the file that contains a list of files to ignore.  The default
   --  implementation reads patterns as defined in `.gitignore` files.
   procedure Load_Ignore (Walker : in out Walker_Type;
                          Path   : String;
                          Filter : in out Filter_Type'Class) with
     Pre => Path'Length > 0 and then Ada.Directories.Exists (Path);

   --  Returns true if the path corresponds to a root path for a project:
   --  The default returns True.
   function Is_Root (Walker : in Walker_Type;
                     Path   : in String) return Boolean with
     Pre => Path'Length > 0;

   --  Find the root directory of a project knowing a path of a file or
   --  directory of that project.  Move up to parent directories until
   --  a path returns true when `Is_Root` is called.
   function Find_Root (Walker : in Walker_Type;
                       Path   : in String) return String with
     Pre'Class => Path'Length > 0 and then Ada.Directories.Exists (Path);

   --  Called when a file is found during the directory tree walk.
   procedure Scan_File (Walker : in out Walker_Type;
                        Path   : String) is null with
     Pre'Class => Path'Length > 0 and then Ada.Directories.Exists (Path);

   --  Called when a directory is found during a directory tree walk.
   --  The default implementation checks for a configuration file to ignore
   --  files (a .gitignore) and builds a new filter to scan the sub-tree.
   --  Once the filter is created, it calls Scan_Directory to proceed with
   --  the scan.
   procedure Scan_Subdir (Walker : in out Walker_Type;
                          Path   : in String;
                          Filter : in Filter_Context_Type;
                          Match  : in Filter_Result) with
     Pre => Path'Length > 0 and then Ada.Directories.Exists (Path);

   --  Called by Scan_Subdir when a directory was found.
   --  The default implementation scans the directory for files and directories
   --  matching the filter.  It can be overriden to implement specific
   --  behaviors.
   procedure Scan_Directory (Walker : in out Walker_Type;
                             Path   : String;
                             Filter : Filter_Context_Type) with
     Pre => Path'Length > 0 and then Ada.Directories.Exists (Path);

end Util.Files.Walk;