Ada Programming/Libraries/Ada.Directories
This language feature is only available from Ada 2005 on.
Ada.Directories is a unit of the Predefined Language Environment since Ada 2005. It allows various manipulations and queries of the filesystem directories.
Introduction
[edit | edit source]If the environment supports the notion of files and directories, it is possible to manipulate these in a way that is fairly OS agnostic using the Ada.Directories
.
This is a very implementation dependent package and, as such, the information that can be extracted about files and directories is kept very basic. If further information is needed (owner, group, and so on), a child package should be created for this, as can be read in the implementation advice from the Ada Reference Manual [1]:
If other information about a file (such as the owner or creation date) is available in a directory entry, the implementation should provide functions in a child package Directories.Information to retrieve it.
Short Use Tutorial
[edit | edit source]This section gives a quick overview on the use of the Ada.Directories
package. If it is not detailed enough, you can also follow the Detailed Use Tutorial.
Traversing Directories
[edit | edit source]Changing the current working directory is done with Set_Directory
. The remaining functions are fairly self-explanatory with the exception of Containing_Directory
, which returns the name of the directory containing the given directory.
function
Current_Directoryreturn
String;procedure
Set_Directory (Directory :in
String);function
Exists (Name :in
String)return
Boolean;function
Containing_Directory (Name :in
String)return
String;
Enumerating Directory Entries
[edit | edit source]This example enumerates the all the entries in the current working directory, that end in ".gpr". The Start_Search
procedure is used to start enumerating a directory. It will To remove the filter criteria you can pass an empty string. Iterating through the directory involves calling Get_Next_Entry
to return the next directory entry and More_Entries
to check for more entries. Once the search is complete, call End_Search
.
type
Search_Typeis
limited
private
;type
Directory_Entry_Typeis
limited
private
;procedure
Start_Search (Search :in
out
Search_Type; Directory :in
String; Pattern :in
String; Filter :in
Filter_Type := (others
=> True));procedure
Get_Next_Entry (Search :in
out
Search_Type; Directory_Entry :out
Directory_Entry_Type);function
More_Entries (Search :in
Search_Type)return
Boolean;procedure
End_Search (Search :in
out
Search_Type);
The resulting Directory_Entry_Type has a full name, simple name, size, kind, and modification type.
function
Simple_Name (Directory_Entry :in
Directory_Entry_Type)return
String;function
Full_Name (Directory_Entry :in
Directory_Entry_Type)return
String;function
Kind (Directory_Entry :in
Directory_Entry_Type)return
File_Kind; -- Directory, Ordinary_File, Special_Filefunction
Size (Directory_Entry :in
Directory_Entry_Type)return
File_Size;function
Modification_Time (Directory_Entry :in
Directory_Entry_Type)return
Ada.Calendar.Time;
with
Ada.Text_IO;with
Ada.Directories;use
Ada.Text_IO;use
Ada.Directories;procedure
Mainis
Dir : Directory_Entry_Type; Dir_Search : Search_Type; Curr_Dir : string := Current_Directory;begin
Put("Current Directory: "); Put_Line(Curr_Dir); Start_Search(Search => Dir_Search, Directory => Curr_Dir, Pattern => "*.gpr");loop
Get_Next_Entry(Dir_Search, Dir); Put(Full_Name(Dir)); Set_Col(50);if
Kind(Dir) = Ordinary_Filethen
Put(Size(Dir)'Image);end
if
; Set_Col(60); Put_Line(Kind(Dir)'Image);exit
when
not
More_Entries(Dir_Search);end
loop
; End_Search(Dir_Search);end
Main;
Directory Manipulation
[edit | edit source]Creating a directory is done with Create_Directory
. Create_Path
creates all the directories in the path. The directory path on GNAT Ada can contain either "\" or "/" characters. Rename
renames a directory or file within a directory. <Delete_Tree> deletes an entire hierarchy of directories and files. Delete_Directory
deletes an empty directory (non-empty directories throw a Use_Error
exception. Delete_File
deletes an ordinary file in a directory. Note that the Form
parameters is implementation specific. On the version of GNAT Ada I'm using, it can only be used to set the encoding.
procedure
Create_Directory (New_Directory :in
String; Form :in
String := "");procedure
Create_Path (New_Directory :in
String; Form :in
String := "");procedure
Rename (Old_Name, New_Name :in
String);
procedure
Delete_Tree (Directory :in
String);procedure
Delete_Directory (Directory :in
String);procedure
Delete_File (Name :in
String);
Below is a simple example that creates a set of directories and iterates through those directories. Note that it runs on both Windows and Linux with the "/" path separator. The returned path separators are appropriate to the filesystem (e.g. Windows return "\").
procedure
Mainis
Dir : Directory_Entry_Type; Dir_Search : Search_Type;begin
Start_Search(Search => Dir_Search, Directory => Curr_Dir, Pattern => ""); Create_Path("Foo"); Create_Path("Bar/Baz/Qux");loop
Get_Next_Entry(Dir_Search, Dir); Put_Line(Full_Name(Dir));exit
when
not
More_Entries(Dir_Search);end
loop
; End_Search(Dir_Search); Delete_Directory("Foo"); Delete_Tree("Bar");end
Main;
Exceptions
[edit | edit source]Status_Error
is raised if a directory entry is invalid or when enumerating directories past the last directory. Name_Error
is usually thrown if the directory or filename is not able to identify a file or directory (either non-existent or invalid depending on context). Use_Error
is thrown if directories are not supported or directories are not traversable.
Status_Error :exception
renames
Ada.IO_Exceptions.Status_Error; Name_Error :exception
renames
Ada.IO_Exceptions.Name_Error; Use_Error :exception
renames
Ada.IO_Exceptions.Use_Error; Device_Error :exception
renames
Ada.IO_Exceptions.Device_Error;
Detailed Use Tutorial
[edit | edit source]This article will be based on a small program, aDir
, to which we will add functionality as we progress through the various functions, procedures, and types. In its most basic form, all it does is output the current default directory:
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
end aDir;
When running the above program, you should see output somewhat similar to this:
Starting default directory: /home/thomas/wiki_examples/aDir
Rather than the above path (/home/thomas/wiki_examples/aDir
), you should see the path of the directory from which you launched aDir
on your system.
Directory and File Operations
[edit | edit source]When working with files and directories, some basic functionality is required. We must be able to move to a specific directory; we must be able to copy, rename, and delete both files and directories; and we must be able to create new directories. Luckily, this is exactly what the next function and procedures enable us to do.
Current_Directory
[edit | edit source]The specification for Current_Directory
looks like this:
function Current_Directory return String;
We've already seen how Current_Directory
works, but there are a few things yet that are worth mentioning about Current_Directory
Current_Directory
returns the full directory name for the current default directory. This does not equal the directory where the program itself resides.- The directory name returned is suitable for use by the Set_Directory procedure.
- The exception
Use_Error
is propagated if a default directory is not supported by the operating system/environment.
When we executed the basic aDir
program above, the output was the path to where the aDir
program resides on my system. But lets see what happens if we invoke the program from somewhere else:
$ pwd /home/thomas/wiki_examples/aDir $ ./adir Starting default directory: /home/thomas/wiki_examples/aDir $ cd && pwd /home/thomas $ wiki_examples/aDir/adir Starting default directory: /home/thomas
As you can see, Current_Directory
does not equal the path to the actual program. Instead it returns the current default directory, which is entirely implementation dependent. In the above example, it varies depending on where from the program is invoked. So unless you've specifically set the default directory using Set_Directory, you cannot be 100% sure what path Current_Directory
will return.
The second bullet-point is self-explanatory and the third is impossible for me to test, as I currently don't own a system that does not support the notion of a directory.
Ada.Directories.Current_Directory Example Source
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
end aDir;
Set_Directory
[edit | edit source]The specification for Set_Directory
looks like this:
procedure Set_Directory (Directory : String);
With Set_Directory
we're able to change the current default directory. Add this little bit of code to the body of the aDir
program to see how it works:
D.Set_Directory (Directory => "/home/thomas");
IO.Put ("New default directory: ");
IO.Put_Line (Item => D.Current_Directory);
If we execute the program now, we get this:
Starting default directory: /home/thomas/wiki_examples/aDir New default directory: /home/thomas
If the directory you're trying to set doesn't exist, a Name_Error
is raised. Lets see how that works:
D.Set_Directory (Directory => "/home/does/not/exist");
exception
when D.Name_Error =>
IO.Put_Line (Item => "Directory does not exist.");
If you add the above snippet to the core aDir
program, you will get output that looks something like this:
Starting default directory: /home/thomas/wiki_examples/aDir Directory does not exist.
Since the directory set by the Set_Directory
does not exist, the Name_Error
exception is raised and caught. You should not, however, habitually use Set_Directory
to check if a given directory exists. For this simple task we have the Exists function.
Ada.Directories.Set_Directory Example Source
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
D.Set_Directory (Directory => "/home/thomas");
IO.Put ("New default directory: ");
IO.Put_Line (Item => D.Current_Directory);
end aDir;
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
D.Set_Directory (Directory => "/home/does/not/exist");
exception
when D.Name_Error =>
IO.Put_Line (Item => "Directory does not exist.");
end aDir;
Create_Directory
[edit | edit source]The specification for Create_Directory
looks like this:
procedure Create_Directory
(New_Directory : String;
Form : String := "");
With this procedure we can, to nobody's surprise, create new directories. But before we try it out, we will first deal with the Form
parameter. The RM has this to say about it:
The Form parameter can be used to give system-dependent characteristics of the directory; the interpretation of the Form parameter is implementation-defined. A null string for Form specifies the use of the default options of the implementation of the new directory.
For my specific environment (Slackware 12.1 and GNATMAKE GPL 2008 compiler) the Form
parameter does precisely nothing. This might not be the case for other implementations, but I'm fairly certain that for most standard systems (Unix, Linux, BSD, Windows) Form
is not used. Thus, Form
should be a null string, unless you're on a platform where the implementation defines the use of Form
.
But enough about that - lets add some code to aDir
to see how Create_Directory
works:
D.Create_Directory (New_Directory => "some_dir");
With that one line of code added to the program, we can see how the program by doing a little test:
$ ls -l -rw-rw-rw- 1 thomas users 502 2009-09-24 22:09 Proj.gpr -rwxr-xr-x 1 thomas users 578199 2009-09-29 22:03 adir* -rw-rw-rw- 1 thomas users 517 2009-09-29 22:03 adir.adb drwxr-xr-x 2 thomas users 0 2009-09-29 22:03 objects/ $ ./adir Starting default directory: /home/thomas/wiki_examples/aDir $ ls -l -rw-rw-rw- 1 thomas users 502 2009-09-24 22:09 Proj.gpr -rwxr-xr-x 1 thomas users 578199 2009-09-29 22:03 adir* -rw-rw-rw- 1 thomas users 517 2009-09-29 22:03 adir.adb drwxr-xr-x 2 thomas users 0 2009-09-29 22:03 objects/ drwxr-xr-x 2 thomas users 0 2009-09-29 22:23 some_dir/ $ cd some_dir/ $ ../adir Starting default directory: /home/thomas/wiki_examples/aDir/some_dir $ ls -l drwxr-xr-x 2 thomas users 0 2009-09-29 22:25 some_dir/
As you can see from the above, Create_Directory
creates the new directory in the current default directory which, in this case, is the directory from where we invoke adir
. In case we want to create the some_dir
directory in a directory other than the current default, we're going to have to add another line of code prior to the Create_Directory
line:
D.Set_Directory (Directory => "/home/thomas/wiki_examples/aDir");
You should of course substitute the above path with something that fits your system.
Next we delete the already created some_dir
directories, and then we try the same chain of commands as before:
$ ls -l -rw-rw-rw- 1 thomas users 502 2009-09-24 22:09 Proj.gpr -rwxr-xr-x 1 thomas users 578224 2009-09-29 22:32 adir* -rw-rw-rw- 1 thomas users 635 2009-09-29 22:32 adir.adb drwxr-xr-x 2 thomas users 0 2009-09-29 22:32 objects/ $ ./adir Starting default directory: /home/thomas/wiki_examples/aDir $ ls -l -rw-rw-rw- 1 thomas users 502 2009-09-24 22:09 Proj.gpr -rwxr-xr-x 1 thomas users 578224 2009-09-29 22:32 adir* -rw-rw-rw- 1 thomas users 635 2009-09-29 22:32 adir.adb drwxr-xr-x 2 thomas users 0 2009-09-29 22:32 objects/ drwxr-xr-x 2 thomas users 0 2009-09-29 22:40 some_dir/ $ cd some_dir/ $ ../adir Starting default directory: /home/thomas/wiki_examples/aDir/some_dir raised ADA.IO_EXCEPTIONS.USE_ERROR : creation of new directory "some_dir" failed
Our program fails at creating the some_dir
directory the second time, because it already exists, proving that the program no longer creates some_dir
wherever we invoke the program. This is exactly what we wanted, though we should probably add some code to catch the exception, instead of just letting the program crash:
exception
when D.Use_Error =>
IO.Put_Line ("Directory cannot be created.");
Now lets try that last command again:
$ ../adir Starting default directory: /home/thomas/wiki_examples/aDir/some_dir Directory cannot be created.
Much better!
Besides Use_Error
, there's also Name_Error
, which is raised if the name given isn't a valid directory. Let's try and change some_dir
to something that is obviously not going to cut it as a directory name: An empty string. We just need to change one line in the program:
D.Create_Directory (New_Directory => "some_dir");
To
D.Create_Directory (New_Directory => "");
Now, running the program we get:
Starting default directory: /home/thomas/wiki_examples/aDir/some_dir raised ADA.IO_EXCEPTIONS.NAME_ERROR : invalid new directory path name ""
We can catch Name_Error
by adding one more exception handler:
exception
when D.Use_Error =>
IO.Put_Line (Item => "Directory cannot be created.");
when D.Name_Error =>
IO.Put_Line (Item => "Directory is not valid.");
Running the program we now get this:
Starting default directory: /home/thomas/wiki_examples/aDir Directory is not valid.
Perfect.
Ada.Directories.Create_Directory Example Source
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
D.Set_Directory (Directory => "/home/thomas/wiki_examples/aDir");
D.Create_Directory (New_Directory => "some_dir");
-- Running the program multiple times will raise the Use_Error.
-- Changing some_dir to a null string will raise the Name_Error.
exception
when D.Use_Error =>
IO.Put_Line (Item => "Directory cannot be created.");
when D.Name_Error =>
IO.Put_Line (Item => "Directory is not valid.");
end aDir;
Delete_Directory
[edit | edit source]The specification for Delete_Directory
looks like this:
procedure Delete_Directory (Directory : String);
With Delete_Directory
you can delete a directory which is empty and for which the program has sufficient permissions. The Directory
parameter accepts both a single named directory or the full path to a directory. In the case of a single name, the current default directory is prepended to create a full path.
Add these lines to the basic aDir
program to see it in action:
D.Set_Directory (Directory => "/home/thomas/wiki_examples/aDir");
IO.Put ("New default directory: ");
IO.Put_Line (Item => D.Current_Directory);
D.Create_Directory (New_Directory => "some_dir");
if D.Exists (Name => "some_dir") then
IO.Put_Line ("some_dir exists.");
end if;
D.Delete_Directory (Directory => "some_dir");
D.Create_Directory (New_Directory => "some_dir2");
if D.Exists (Name => "some_dir2") then
IO.Put_Line ("some_dir2 exists.");
end if;
D.Delete_Directory (Directory => "/home/thomas/wiki_examples/aDir/some_dir2");
Executing this program, we get the following output:
$ ./adir Starting default directory: /home/thomas/wiki_examples/aDir New default directory: /home/thomas/wiki_examples/aDir some_dir exists. some_dir2 exists. $ ls -l -rw-rw-rw- 1 thomas users 502 2009-09-24 22:09 Proj.gpr -rwxr-xr-x 1 thomas users 578224 2009-09-30 16:13 adir* -rw-rw-rw- 1 thomas users 973 2009-09-30 16:14 adir.adb drwxr-xr-x 2 thomas users 0 2009-09-30 16:13 objects/
The example shows that Delete_Directory
does indeed work with both the full path to the directory and with just the directory name. The two Exists
calls are just there to help visualize the fact that the directories are, in fact, created.
The Name_Error
exception is raised if the Directory
string does not match an existing directory and the Use_Error
exception is raised if the directory cannot be deleted or if it isn't empty. Add this code to the core aDir
program:
Delete_Non_Existing :
declare
begin
D.Delete_Directory (Directory => "does_not_exist");
exception
when D.Name_Error =>
IO.Put_Line (Item => "Directory not found.");
end Delete_Non_Existing;
Delete_With_Contents :
declare
begin
D.Delete_Directory (Directory => "dir_with_contents");
exception
when D.Use_Error =>
IO.Put_Line (Item => "Cannot delete non-empty directory.");
end Delete_With_Contents;
And the output is:
Starting default directory: /home/thomas/wiki_examples/aDir Directory not found. Cannot delete non-empty directory.
As expected. In order to delete non-empty directories, you will have to use the Delete_Tree procedure.
Ada.Directories.Delete_Directory Example Source
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
D.Set_Directory (Directory => "/home/thomas/wiki_examples/aDir");
IO.Put ("New default directory: ");
IO.Put_Line (Item => D.Current_Directory);
D.Create_Directory (New_Directory => "some_dir");
if D.Exists (Name => "some_dir") then
IO.Put_Line ("some_dir exists.");
end if;
D.Delete_Directory (Directory => "some_dir");
D.Create_Directory (New_Directory => "some_dir2");
if D.Exists (Name => "some_dir2") then
IO.Put_Line ("some_dir2 exists.");
end if;
D.Delete_Directory (Directory => "/home/thomas/wiki_examples/aDir/some_dir2");
end Adir;
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
Delete_Non_Existing :
declare
begin
D.Delete_Directory (Directory => "does_not_exist");
exception
when D.Name_Error =>
IO.Put_Line (Item => "Directory not found.");
end Delete_Non_Existing;
Delete_With_Contents :
declare
begin
D.Delete_Directory (Directory => "dir_with_contents");
exception
when D.Use_Error =>
IO.Put_Line (Item => "Cannot delete non-empty directory.");
end Delete_With_Contents;
end aDir;
Create_Path
[edit | edit source]The specification for Create_Path
looks like this:
procedure Create_Path
(New_Directory : String;
Form : String := "");
With Create_Path
it is possible to create nested directories, even if the parent directories do not exist. It is much like issuing a mkdir -p /some/path/to/new/dir
command, where the entire path is created, all the way down to the dir
directory.
Just as with its sibling Create_Directory
, the Form
parameter is used for implementation specific characteristics. A null string specifies the use of the implementation's default options for creating new directories.
To create the directory this/is/a/new/directory
all we have to add to the aDir
program is this:
D.Create_Path (New_Directory => "this/is/a/new/directory");
if D.Exists (Name => "this/is/a/new/directory") then
IO.Put_Line (Item => "this/is/a/new/directory exists!");
end if;
And the output is:
Starting default directory: /home/thomas/wiki_examples/aDir this/is/a/new/directory exists!
If the path already exists when calling Create_Path
, the procedure simply do nothing.
Create_Path
works relative to the current default directory, unless the Directory
given describes a path starting from the root of a filesystem, i.e. / for *nix systems and c:/ for a Windows system. To create the this/is/a/new/directory
in /tmp
, all we have to do is add /tmp
to the Directory
string:
D.Create_Path (New_Directory => "/tmp/this/is/a/new/directory");
if D.Exists (Name => "/tmp/this/is/a/new/directory") then
IO.Put_Line (Item => "/tmp/this/is/a/new/directory exists!");
end if;
So if the path is relative, the current default directory is used as the base, but if the path is absolute, the current default directory is ignored and the path is created entirely from the parameter.
The two exceptions, Name_Error
and Use_Error
, are raised under the same circumstances as Create_Directory
Name_Error
if the New_Directory
string does not identify a directory, and Use_Error
if the program is not able to create any one of the directories, for example due to permission issues.
Ada.Directories.Create_Path Example Source
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
D.Create_Path (New_Directory => "this/is/a/new/directory");
if D.Exists (Name => "this/is/a/new/directory") then
IO.Put_Line (Item => "this/is/a/new/directory exists!");
end if;
end aDir;
Delete_Tree
[edit | edit source]The specification for Delete_Tree
looks like this:
procedure Delete_Tree (Directory : String);
Delete_Tree
enables us to delete directories and their contents in one fell swoop, so if we build on the example from Create_Path, we can delete the this/is/a/new/directory
tree by adding this to the program:
D.Delete_Tree (Directory => "this/");
if not D.Exists (Name => "this/") then
IO.Put_Line (Item => "this/is/a/new/directory does NOT exist!");
end if;
And the output is:
Starting default directory: /home/thomas/wiki_examples/aDir this/is/a/new/directory exists! this/is/a/new/directory does NOT exist!
The two exceptions Name_Error
and Use_Error
are also available to us. They work as expected, with Name_Error
being raised if the Directory
doesn't exist and Use_Error
being raised if Directory
can't be deleted for some reason or another. There's an important note about this behavior in the RM:
The exception Use_Error is propagated if the external environment does not support the deletion of the directory or some portion of its contents with the given name (in the absence of Name_Error). If Use_Error is propagated, it is unspecified whether a portion of the contents of the directory is deleted.
It is important to pay special attention to the last sentence: it is unspecified whether a portion of the contents of the directory is deleted.
What this means, is that a Use_Error
can be raised, but that wont necessarily mean that the directory and its contents are 100% intact. Some of it may very well have been deleted.
Finally, it should be mentioned that Delete_Tree
looks for Directory
in the current default directory, unless an absolute path is given.
Ada.Directories.Delete_Tree Example Source
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
D.Create_Path (New_Directory => "this/is/a/new/directory");
if D.Exists (Name => "this/is/a/new/directory") then
IO.Put_Line (Item => "this/is/a/new/directory exists!");
end if;
D.Delete_Tree (Directory => "this/");
if not D.Exists (Name => "this/") then
IO.Put_Line (Item => "this/is/a/new/directory does NOT exist!");
end if;
end aDir;
Delete_File
[edit | edit source]The specification for Delete_File
looks like this:
procedure Delete_File (Name : String);
Delete_File
works much the same as Delete_Directory, it just deletes files instead of directories. Files, in the context of Ada.Directories
can be either an ordinary file or a special file:
External files may be classified as directories, special files, or ordinary files. A directory is an external file that is a container for files on the target system. A special file is an external file that cannot be created or read by a predefined Ada input-output package. External files that are not special files or directories are called ordinary files.
The usual exceptions also apply to Delete_File
.
Lets create a new file:
$ touch some_file
And now add this to the basic aDir
program:
if D.Exists (Name => "some_file") then
IO.Put_Line (Item => "some_file exists");
end if;
D.Delete_File (Name => "some_file");
if not D.Exists (Name => "some_file") then
IO.Put_Line (Item => "some_file does NOT exist");
end if;
The output is:
Starting default directory: /home/thomas/wiki_examples/aDir some_file exists some_file does NOT exist
And the some_file
file is gone.
Here's an example where we (A) don't have the proper permissions to delete the some_file
file and (B) try to delete a non-existent file:
if D.Exists (Name => "some_file") then
IO.Put_Line (Item => "some_file exists");
end if;
declare
begin
D.Delete_File (Name => "some_file");
exception
when D.Use_Error =>
IO.Put_Line (Item => "Cannot delete some_file");
end;
declare
begin
D.Delete_File (Name => "some_wrong_filename");
exception
when D.Name_Error =>
IO.Put_Line (Item => "File does not exist");
end;
if D.Exists (Name => "some_file") then
IO.Put_Line (Item => "some_file still exists");
end if;
The output should look like this:
Starting default directory: /home/thomas/wiki_examples/aDir some_file exists Cannot delete some_file File does not exist some_file still exists
And with that, we conclude our look at Delete_File
.
Ada.Directories.Delete_File Example Source
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
if D.Exists (Name => "some_file") then
IO.Put_Line (Item => "some_file exists");
end if;
D.Delete_File (Name => "some_file");
if not D.Exists (Name => "some_file") then
IO.Put_Line (Item => "some_file does NOT exist");
end if;
end aDir;
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
if D.Exists (Name => "some_file") then
IO.Put_Line (Item => "some_file exists");
end if;
declare
begin
D.Delete_File (Name => "some_file");
exception
when D.Use_Error =>
IO.Put_Line (Item => "Cannot delete some_file");
end;
declare
begin
D.Delete_File (Name => "some_wrong_filename");
exception
when D.Name_Error =>
IO.Put_Line (Item => "File does not exist");
end;
if D.Exists (Name => "some_file") then
IO.Put_Line (Item => "some_file still exists");
end if;
end aDir;
Rename
[edit | edit source]The specification for Rename
looks like this:
procedure Rename (Old_Name, New_Name : String);
The Rename
procedures renames both directories and files, providing that the New_Name
doesn't already exist. The Name_Error
exception is raised if Old_Name
does not match a directory or file, and Use_Error
is raised if the directory/file cannot be renamed. In the following example, we will assume the existence of the directory some_dir
and the file some_file
.
First we will add a procedure to the declarative part of the aDir
program:
procedure Do_We_Exist (A_Name : String) is
begin
if D.Exists (Name => A_Name) then
IO.Put_Line (Item => "Yes, " & A_Name & " exists");
else
IO.Put_Line (Item => "No, " & A_Name & " does not exist");
end if;
end Do_We_Exist;
This procedure is just a bit of sugar to the program. It's mainly here to avoid having to repeat the if
block over and over.
Next we'll add a few lines to the body of the program:
Do_We_Exist (A_Name => "some_new_dir");
Do_We_Exist (A_Name => "some_new_file");
D.Rename (Old_Name => "some_dir",
New_Name => "some_new_dir");
D.Rename (Old_Name => "some_file",
New_Name => "some_new_file");
Do_We_Exist (A_Name => "some_new_dir");
Do_We_Exist (A_Name => "some_new_file");
D.Rename (Old_Name => "some_new_dir",
New_Name => "some_dir");
D.Rename (Old_Name => "some_new_file",
New_Name => "some_file");
The output from this is:
Starting default directory: /home/thomas/wiki_examples/aDir No, some_new_dir does not exist No, some_new_file does not exist Yes, some_new_dir exists Yes, some_new_file exists
Renaming files and directories just doesn't get any easier than this.
Ada.Directories.Rename Example Source
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
procedure Do_We_Exist (A_Name : String) is
begin
if D.Exists (Name => A_Name) then
IO.Put_Line (Item => "Yes, " & A_Name & " exists");
else
IO.Put_Line (Item => "No, " & A_Name & " does not exist");
end if;
end Do_We_Exist;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
Do_We_Exist (A_Name => "some_new_dir");
Do_We_Exist (A_Name => "some_new_file");
D.Rename (Old_Name => "some_dir",
New_Name => "some_new_dir");
D.Rename (Old_Name => "some_file",
New_Name => "some_new_file");
Do_We_Exist (A_Name => "some_new_dir");
Do_We_Exist (A_Name => "some_new_file");
D.Rename (Old_Name => "some_new_dir",
New_Name => "some_dir");
D.Rename (Old_Name => "some_new_file",
New_Name => "some_file");
end aDir;
Copy_File
[edit | edit source]The specification for Copy_File
looks like this:
procedure Copy_File
(Source_Name : String;
Target_Name : String;
Form : String := "");
Copy_File
copies the contents of an existing file, Source_Name
, to a new file named Target_Name
. The result is a duplicate of the source file. Copy_File
does not copy directories. Only ordinary files are copied. As noted earlier, the Form
parameter is only used if the implementation requires it. Here's what the RM has to say about Form
:
The Form parameter can be used to give system-dependent characteristics of the resulting external file; the interpretation of the Form parameter is implementation-defined.
In order to copy some_file
to some_new_file
, all we have to do is add this line to the body of the aDir
program:
D.Copy_File (Source_Name => "some_file",
Target_Name => "some_new_file");
Please note that if some_new_file
already exists, it will be overwritten. The usual exceptions, Name_Error
and Use_Error
, also apply to Copy_File
declare
begin
D.Copy_File (Source_Name => "some_non_existant_file",
Target_Name => "");
exception
when D.Name_Error =>
IO.Put_Line (Item => "Source and/or Target Name_Error");
end;
declare
begin
D.Copy_File (Source_Name => "some_file",
Target_Name => "/root/illegal_file_location");
exception
when D.Use_Error =>
IO.Put_Line (Item => "Source and/or Target Use_Error");
end;
Name_Error
is raised if a Source_Name
or Target_Name
does not properly identify a file. Use_Error
is raised if the action fails; the files are there, but for some other reason, copying is not possible.
Ada.Directories.Copy_File Example Source
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
D.Copy_File (Source_Name => "some_file",
Target_Name => "some_new_file");
end aDir;
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
declare
begin
D.Copy_File (Source_Name => "some_non_existant_file",
Target_Name => "");
exception
when D.Name_Error =>
IO.Put_Line (Item => "Source and/or Target Name_Error");
end;
declare
begin
D.Copy_File (Source_Name => "some_file",
Target_Name => "/root/illegal_file_location");
exception
when D.Use_Error =>
IO.Put_Line (Item => "Source and/or Target Use_Error");
end;
end aDir;
Name Operations
[edit | edit source]The following six functions do not interact with the file system at all. They just manipulate strings. Armed with these functions, we are able to build file path strings, extract file names from paths, extract file extensions and discover the name of the directory containing a file.
Full_Name
[edit | edit source]The specification for Full_Name
looks like this:
function Full_Name (Name : String) return String;
Full_Name
returns the full path to a given Name
. The Name
parameter can be both a single filename, a relative path to a file or an absolute path. Full_Name
does not care about whether the file actually exists or not. It is only concerned with returning a proper full path. The RM has this to say about it:
Returns the full name corresponding to the file name specified by Name. The exception Name_Error is propagated if the string given as Name does not allow the identification of an external file (including directories and special files).
When I first read that, I thought that the sentence The exception Name_Error is propagated if the string given as Name does not allow the identification of an external file meant that Name_Error
would be raised if the given Name
parameter could not be resolved to an actual existing file. This is, however, not the case. Instead it means that Name_Error
is raised if Name
is malformed.
Lets see how it works:
IO.Put_Line (Item => D.Full_Name (Name => "foo"));
IO.Put_Line (Item => D.Full_Name (Name => "foo/bar"));
IO.Put_Line (Item => D.Full_Name (Name => "/home/thomas/stuff"));
D.Set_Directory (Directory => "/tmp");
IO.Put_Line (Item => D.Full_Name (Name => "foo"));
IO.Put_Line (Item => D.Full_Name (Name => "foo/bar"));
IO.Put_Line (Item => D.Full_Name (Name => "/home/thomas/stuff"));
IO.Put_Line (Item => D.Full_Name (Name => ""));
-- Malformed Name parameter
exception
when D.Name_Error =>
IO.Put_Line (Item => "Name parameter is malformed");
And the resulting output:
Starting default directory: /home/thomas/wiki_examples/aDir /home/thomas/wiki_examples/aDir/foo /home/thomas/wiki_examples/aDir/foo/bar /home/thomas/stuff New default directory: /tmp /tmp/foo /tmp/foo/bar /home/thomas/stuff Name parameter is malformed
As with all the other functions and procedures which depend on the current default directory, the Name
parameter is appended to the current default directory if Name
is relative, whereas the current default directory is ignored if Name
is an absolute path.
Ada.Directories.Full_Name Example Source
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
IO.Put_Line (Item => D.Full_Name (Name => "foo"));
IO.Put_Line (Item => D.Full_Name (Name => "foo/bar"));
IO.Put_Line (Item => D.Full_Name (Name => "/home/thomas/stuff"));
D.Set_Directory (Directory => "/tmp");
IO.Put ("New default directory: ");
IO.Put_Line (Item => D.Current_Directory);
IO.Put_Line (Item => D.Full_Name (Name => "foo"));
IO.Put_Line (Item => D.Full_Name (Name => "foo/bar"));
IO.Put_Line (Item => D.Full_Name (Name => "/home/thomas/stuff"));
IO.Put_Line (Item => D.Full_Name (Name => ""));
-- Malformed Name parameter
exception
when D.Name_Error =>
IO.Put_Line (Item => "Name parameter is malformed");
end aDir;
Simple_Name
[edit | edit source]The specification for Simple_Name
looks like this:
function Simple_Name (Name : String) return String;
Where Full_Name
returns the entire path to a file, Simple_Name
returns the simple name component of a path. So given the Name
parameter /home/thomas/foo
, it will return foo
. The Name_Error
exception is raised only if Name
is malformed. Lets see an example:
IO.Put_Line (Item => D.Simple_Name (Name => "foo"));
IO.Put_Line (Item => D.Simple_Name (Name => "foo/bar"));
IO.Put_Line (Item => D.Simple_Name (Name => "/home/thomas/stuff"));
IO.Put_Line (Item => D.Simple_Name (Name => ""));
-- Malformed Name parameter
exception
when D.Name_Error =>
IO.Put_Line (Item => "Name parameter is malformed");
end aDir;
And the output is:
Starting default directory: /home/thomas/wiki_examples/aDir foo bar stuff Name parameter is malformed
Quite handy.
Ada.Directories.Simple_Name Example Source
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
IO.Put_Line (Item => D.Simple_Name (Name => "foo"));
IO.Put_Line (Item => D.Simple_Name (Name => "foo/bar"));
IO.Put_Line (Item => D.Simple_Name (Name => "/home/thomas/stuff"));
IO.Put_Line (Item => D.Simple_Name (Name => ""));
-- Malformed Name parameter
exception
when D.Name_Error =>
IO.Put_Line (Item => "Name parameter is malformed");
end aDir;
Containing_Directory
[edit | edit source]The specification for Containing_Directory
looks like this:
function Containing_Directory (Name : String) return String;
Containing_Directory
removes the simple name of the Name
path given. Lets see it in action:
IO.Put_Line (Item => D.Containing_Directory (Name => "foo"));
IO.Put_Line (Item => D.Containing_Directory (Name => "foo/bar"));
IO.Put_Line (Item => D.Containing_Directory (Name => "/home/thomas/stuff"));
declare
begin
IO.Put_Line (Item => D.Containing_Directory (Name => ""));
-- Malformed Name parameter
exception
when D.Name_Error =>
IO.Put_Line (Item => "Name_Error raised. Malformed Name.");
end;
declare
begin
IO.Put_Line (Item => D.Containing_Directory (Name => "/"));
-- No parent directory
exception
when D.Use_Error =>
IO.Put_Line (Item => "Use_Error raised. No containing directory.");
end;
And the output is:
Starting default directory: /home/thomas/wiki_examples/aDir /home/thomas/wiki_examples/aDir foo /home/thomas Name_Error raised. Malformed Name. Use_Error raised. No containing directory.
As expected. Notice that the Use_Error
is raised only when the given Name
parameter does not have a containing directory.
Ada.Directories.Containing_Directory Example Source
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
IO.Put_Line (Item => D.Containing_Directory (Name => "foo"));
IO.Put_Line (Item => D.Containing_Directory (Name => "foo/bar"));
IO.Put_Line (Item => D.Containing_Directory (Name => "/home/thomas/stuff"));
declare
begin
IO.Put_Line (Item => D.Containing_Directory (Name => ""));
-- Malformed Name parameter
exception
when D.Name_Error =>
IO.Put_Line (Item => "Name_Error raised. Malformed Name.");
end;
declare
begin
IO.Put_Line (Item => D.Containing_Directory (Name => "/"));
-- No parent directory
exception
when D.Use_Error =>
IO.Put_Line (Item => "Use_Error raised. No containing directory.");
end;
end aDir;
Extension
[edit | edit source]The specification for Extension
looks like this:
function Extension (Name : String) return String;
If you want to extract the extension of a file, this function is what you need. Here's an example on usage:
IO.Put_Line (Item => D.Extension (Name => "foo.txt"));
IO.Put_Line (Item => D.Extension (Name => "foo/bar"));
IO.Put_Line (Item => D.Extension (Name => "/home/thomas/stuff.conf"));
IO.Put_Line (Item => D.Extension (Name => ""));
-- Malformed Name parameter
exception
when D.Name_Error =>
IO.Put_Line (Item => "Name_Error raised. Malformed Name.");
And the output:
Starting default directory: /home/thomas/wiki_examples/aDir txt conf Name_Error raised. Malformed Name.
As you can see, if there are no extension (the foo/bar line), a null string is returned. The Name_Error
exception is raised when the Name
parameter is malformed.
Ada.Directories.Extension Example Source
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
IO.Put_Line (Item => D.Extension (Name => "foo.txt"));
IO.Put_Line (Item => D.Extension (Name => "foo/bar"));
IO.Put_Line (Item => D.Extension (Name => "/home/thomas/stuff.conf"));
IO.Put_Line (Item => D.Extension (Name => ""));
-- Malformed Name parameter
exception
when D.Name_Error =>
IO.Put_Line (Item => "Name_Error raised. Malformed Name.");
end aDir;
Base_Name
[edit | edit source]The specification for Base_Name
looks like this:
function Base_Name (Name : String) return String;
I'm quite sure you've already guessed what Base_Name
does, but indulge me in some example-code:
IO.Put_Line (Item => D.Base_Name (Name => "foo.txt"));
IO.Put_Line (Item => D.Base_Name (Name => ".secret"));
IO.Put_Line (Item => D.Base_Name (Name => ".secret.conf"));
IO.Put_Line (Item => D.Base_Name (Name => "foo/bar"));
IO.Put_Line (Item => D.Base_Name (Name => "/home/thomas/stuff.conf"));
IO.Put_Line (Item => D.Base_Name (Name => ""));
-- Malformed Name parameter
exception
when D.Name_Error =>
IO.Put_Line (Item => "Name_Error raised. Malformed Name.");
The output:
Starting default directory: /home/thomas/wiki_examples/aDir foo .secret bar stuff Name_Error raised. Malformed Name.
Notice the "odd" behavior in regards to the .secret
file. Instead of returning the full .secret
string, which is in fact the base name of the file, all we get is a null string. This might be considered somewhat weird, but it is in line with what the programmers of Base_Name
intended. Here's a comment from the actual Base_Name
function:
-- Look for the last dot in the file name and return the part of the
-- file name preceding this last dot. If the first dot is the first
-- character of the file name, the base name is the empty string.
So there we have it, straight from the horse's mouth: If Base_Name
returns a null string, you are probably dealing with a dot file, and you will have to parse it manually. So no, you cannot expect Base_Name
to return the same results as for example the standard GNU tool basename
. Base_Name
must work reliably across many platforms, thus the authors had to decide on a common, predictable, method. Returning a null string for dot files is just that, predictable.
Ada.Directories.Base_Name Example Source
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
IO.Put_Line (Item => D.Base_Name (Name => "foo.txt"));
IO.Put_Line (Item => D.Base_Name (Name => ".secret"));
IO.Put_Line (Item => D.Base_Name (Name => ".secret.conf"));
IO.Put_Line (Item => D.Base_Name (Name => "foo/bar"));
IO.Put_Line (Item => D.Base_Name (Name => "/home/thomas/stuff.conf"));
IO.Put_Line (Item => D.Base_Name (Name => ""));
-- Malformed Name parameter
exception
when D.Name_Error =>
IO.Put_Line (Item => "Name_Error raised. Malformed Name.");
end aDir;
Compose
[edit | edit source]The specification for Compose
looks like this:
function Compose
(Containing_Directory : String := "";
Name : String;
Extension : String := "") return String;
Compose
allows us to construct strings from directory paths, simple names and extensions. Some basic checks are made to ensure that the resulting string is syntactically sound. Compose
does not bother with the actual existence of the resulting path, it only cares about it's validity as a full name to a file.
Lets see how it works:
IO.Put_Line (Item => D.Compose (Containing_Directory => "foo/",
Name => "bar",
Extension => "conf"));
IO.Put_Line (Item => D.Compose (Containing_Directory => "",
Name => "bar",
Extension => "conf"));
IO.Put_Line (Item => D.Compose (Containing_Directory => "/foo",
Name => "bar",
Extension => ""));
IO.Put_Line (Item => D.Compose (Containing_Directory => "/foo",
Name => "bar.conf",
Extension => ""));
IO.Put_Line (Item => D.Compose (Containing_Directory => "/foo",
Name => "",
Extension => "conf"));
IO.Put_Line (Item => D.Compose (Containing_Directory => "",
Name => "",
Extension => "conf"));
IO.Put_Line (Item => D.Compose (Containing_Directory => "foo/",
Name => "",
Extension => ""));
-- Force Name_Error by omitting Name and Extension
exception
when D.Name_Error =>
IO.Put_Line (Item => "Name_Error raised.");
The output from the above is:
Starting default directory: /home/thomas/wiki_examples/aDir foo/bar.conf bar.conf /foo/bar /foo/bar.conf /foo/.conf .conf Name_Error raised. Malformed Name.
There's no surprise here.
The Name_Error
exception is raised when:
The exception Name_Error is propagated if the string given as Containing_Directory is not null and does not allow the identification of a directory, or if the string given as Extension is not null and is not a possible extension, or if the string given as Name is not a possible simple name (if Extension is null) or base name (if Extension is non-null).
Compose
is quite handy when building string paths to a file. It's a far stretch better than just concatenating the strings yourself.
Ada.Directories.Compose Example Source
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
IO.Put_Line (Item => D.Compose (Containing_Directory => "foo/",
Name => "bar",
Extension => "conf"));
IO.Put_Line (Item => D.Compose (Containing_Directory => "",
Name => "bar",
Extension => "conf"));
IO.Put_Line (Item => D.Compose (Containing_Directory => "/foo",
Name => "bar",
Extension => ""));
IO.Put_Line (Item => D.Compose (Containing_Directory => "/foo",
Name => "bar.conf",
Extension => ""));
IO.Put_Line (Item => D.Compose (Containing_Directory => "/foo",
Name => "",
Extension => "conf"));
IO.Put_Line (Item => D.Compose (Containing_Directory => "",
Name => "",
Extension => "conf"));
IO.Put_Line (Item => D.Compose (Containing_Directory => "foo/",
Name => "",
Extension => ""));
-- Force Name_Error by omitting Name and Extension
exception
when D.Name_Error =>
IO.Put_Line (Item => "Name_Error raised.");
end aDir;
File and directory queries
[edit | edit source]The purpose of the following functions is obvious. With names such as Exists
, Kind
, Size
and Modification_Time
it shouldn't be too hard to guess what these functions do, so without further ado, lets dive in.
Exists
[edit | edit source]The specification for Exists
looks like this:
function Exists (Name : String) return Boolean;
If you want to know whether a file or a directory exists, you should use Exists
. We've already seen Exists
in action in some of the previous examples, but let's see a snippet of code where Exists
is the star of the show:
if D.Exists (Name => "some_file") then
IO.Put_Line (Item => "some_file exists");
end if;
if D.Exists (Name => "/home/thomas/wiki_examples/aDir/some_dir") then
IO.Put_Line (Item => "/home/thomas/wiki_examples/aDir/some_dir exists");
end if;
if not D.Exists (Name => "nonexistant_file") then
IO.Put_Line (Item => "nonexistant_file does not exist");
end if;
if D.Exists (Name => "") then
IO.Put_Line (Item => "This is impossible!");
end if;
exception
when D.Name_Error =>
IO.Put_Line (Item => "Name_Error raised");
And the output:
Starting default directory: /home/thomas/wiki_examples/aDir some_file exists /home/thomas/wiki_examples/aDir/some_dir exists nonexistant_file does not exist Name_Error raised
As can be seen, the current default directory is taken into account if a relative path is given and Name_Error
is raised when the given Name
is invalid as either a file or a directory, as is the case with the empty string in the above example. Other than that, there's not much to say about this function.
Ada.Directories.Exists Example Source
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
if D.Exists (Name => "some_file") then
IO.Put_Line (Item => "some_file exists");
end if;
if D.Exists (Name => "/home/thomas/wiki_examples/aDir/some_dir") then
IO.Put_Line (Item => "/home/thomas/wiki_examples/aDir/some_dir exists");
end if;
if not D.Exists (Name => "nonexistant_file") then
IO.Put_Line (Item => "nonexistant_file does not exist");
end if;
if D.Exists (Name => "") then
IO.Put_Line (Item => "This is impossible!");
end if;
exception
when D.Name_Error =>
IO.Put_Line (Item => "Name_Error raised");
end aDir;
Kind
[edit | edit source]The specification for Kind
looks like this:
function Kind (Name : String) return File_Kind;
In Ada.Directories
files/directories are classified as one of three possible "file kinds": DIRECTORY
, SPECIAL_FILE
or ORDINARY_FILE
. A DIRECTORY
is a file that is a container for other files. A SPECIAL_FILE
is a file that cannot be read or created by a predefined Ada input-output package. A File that is not either a DIRECTORY
or a SPECIAL_FILE
is an ORDINARY_FILE
.
Lets see how it works. First we must add a line of code to the declarative part of the aDir
program:
package IOE is new Ada.Text_IO.Enumeration_IO (D.File_Kind);
And then the body:
IOE.Put (Item => D.Kind (Name => "some_file"));
IO.New_Line;
IOE.Put (Item => D.Kind (Name => "some_dir"));
IO.New_Line;
IOE.Put (Item => D.Kind (Name => "/dev/sda"));
IO.New_Line;
IOE.Put (Item => D.Kind (Name => ""));
exception
when D.Name_Error =>
IO.Put_Line (Item => "Name_Error raised");
And the output:
Starting default directory: /home/thomas/wiki_examples/aDir ORDINARY_FILE DIRECTORY SPECIAL_FILE Name_Error raised
Kind
honors the current default directory, just as all the other functions and procedures of Ada.Directories
. Name_Error
is raised if the given Name
parameter is invalid.
{{collapsible box|title=Ada.Directories.Kind Example Source|collapsed=yes|content=
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
package IOE is new Ada.Text_IO.Enumeration_IO (D.File_Kind);
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
IOE.Put (Item => D.Kind (Name => "some_file"));
IO.New_Line;
IOE.Put (Item => D.Kind (Name => "some_dir"));
IO.New_Line;
IOE.Put (Item => D.Kind (Name => "/dev/sda"));
IO.New_Line;
IOE.Put (Item => D.Kind (Name => ""));
exception
when D.Name_Error =>
IO.Put_Line (Item => "Name_Error raised");
end aDir;
Size
[edit | edit source]The specification for Size
looks like this:
function Size (Name : String) return File_Size;
The File_Size
type returned by Size
is an integer with the range 0 .. Long_Long_Integer'Last
. That is, on most systems, a very large number. With that noted, Size
does exactly what is expected of it:
IO.Put_Line (Item => D.Size (Name => "some_file")'Img);
IO.Put_Line (Item => D.Size (Name => "adir")'Img);
IO.Put_Line
(Item => "Max File_Size on this system: " & Long_Long_Integer'Last'Img);
And the result is:
Starting default directory: /home/thomas/wiki_examples/aDir 7 578580 Max File_Size on this system: 9223372036854775807
The size returned is the number of stream elements contained in the file, in this case 7 and 578580 bytes. The 9223372036854775807 number is there to show you how very large a number the File_Size
type can handle. I think we'd be hard pressed to find any kind of OS or file system that can handle files that large. Note that Long_Long_Integer
is implementation dependent: It might be totally different on your system.
The usual Name_Error
exception applies to Size
and you will also find a Constraint_Error
if the file size is not of type File_Size
, ie. it is either lower than 0 or higher than Long_Long_Integer'Last
.
{{collapsible box|title=Ada.Directories.Size Example Source|collapsed=yes|content=
with Ada.Text_IO;
with Ada.Directories;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
IO.Put_Line (Item => D.Size (Name => "some_file")'Img);
IO.Put_Line (Item => D.Size (Name => "adir")'Img);
IO.Put_Line
(Item => "Max File_Size on this system: " & Long_Long_Integer'Last'Img);
end aDir;
Modification_Time
[edit | edit source]The specification for Modification_Time
looks like this:
function Modification_Time (Name : String) return Ada.Calendar.Time;
If you need to know what time a file was last modified, this is the function for it. As you can see, it returns an Ada.Calendar.Time
object, so to actually see the output, we need to add a with
clause and an extra line to the declarative part of aDir
. First the with
clause:
with Ada.Calendar.Formatting;
And then the declaration:
package ACF renames Ada.Calendar.Formatting;
And finally the code that goes into the body:
IO.Put_Line (Item => ACF.Image (D.Modification_Time (Name => "some_file")));
The output from this little snippet is:
Starting default directory: /home/thomas/wiki_examples/aDir 2009-10-06 14:10:04
The usual Name_Error
is raised if the file is invalid and a Use_Error
is raised if the environment doesn't support the notion of modification time on a file.
{{collapsible box|title=Ada.Directories.Modification_Time Example Source|collapsed=yes|content=
with Ada.Text_IO;
with Ada.Directories;
with Ada.Calendar.Formatting;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
package ACF renames Ada.Calendar.Formatting;
begin
IO.Put ("Starting default directory: ");
IO.Put_Line (Item => D.Current_Directory);
IO.Put_Line (Item => ACF.Image (D.Modification_Time (Name => "some_file")));
end aDir;
Directory Searching
[edit | edit source]Searching over a directory structure is done using the following types and subprograms. In the previous sections we haven't really bothered with the types, as they didn't really play an active role in using the functions and procedures, but with searching things are a bit different: Here we actively use the types to keep track of the state of a search and the result of a search.
Searching can be done either using an active or a passive iterator. The active iterator approach is merely a simple loop and the passive iterator is done using access to a subprogram that defines what to do with each result of the search. We will show how both methods work.
Directory_Entry_Type
[edit | edit source]The specification for Directory_Entry_Type
looks like this:
type Directory_Entry_Type is limited private;
private
type Directory_Entry_Type is record
Is_Valid : Boolean := False;
Simple : Ada.Strings.Unbounded.Unbounded_String;
Full : Ada.Strings.Unbounded.Unbounded_String;
Kind : File_Kind := Ordinary_File;
end record;
The Directory_Entry_Type
represents a single item in a directory. The only way to create a Directory_Entry_Type
is by using the Get_Next_Entry procedure. One such Directory_Entry_Type
object can then be used by appropriate subprograms, such as Simple_Name, Kind and so on. It should be obvious from the Directory_Entry_Type
record what kind of information we can extract from it.
Filter_Type
[edit | edit source]The specification for Filter_Type
looks like this:
type Filter_Type is array (File_Kind) of Boolean;
There are three different kinds of files in Ada.Directories
Directory
, Ordinary_File
and Special_File
. With Filter_Type
you define which of these you wish to search for:
Filter : Filter_Type := (Ordinary_File => True,
Special_File => False,
Directory => True);
Here we setup a filter that ignores Special_File
and allows Ordinary_File
and Directory
.
Search_Type
[edit | edit source]The specification for Search_Type
looks like this:
type Search_Type is limited private;'
type Search_Data;
type Search_Ptr is access Search_Data;
private
type Search_Type is new Ada.Finalization.Controlled with record
Value : Search_Ptr;
end record;
Search_Type
contains the state of the search. You initialize the Search_Type
object with Start_Search, you check it with More_Entries, you read it with Get_Next_Entry and you clean it up with End_Search.
Start_Search
[edit | edit source]The specification for Start_Search
looks like this:
procedure Start_Search
(Search : in out Search_Type;
Directory : String;
Pattern : String;
Filter : Filter_Type := (others => True));
All the searching related types and subprograms depend heavily on each other. Because of that, I believe it will make the most sense to present a complete program that encompass them all. This means that we will deviate from the usual aDir
program, and instead go with this:
with Ada.Text_IO;
with Ada.Directories; use Ada.Directories;
with Ada.Calendar.Formatting;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
package IOI is new IO.Integer_IO (D.File_Size);
package ACF renames Ada.Calendar.Formatting;
A_Search : D.Search_Type;
Search_Item : D.Directory_Entry_Type;
Filter : constant D.Filter_Type := (D.Ordinary_File => True,
D.Special_File => False,
D.Directory => True);
begin
D.Start_Search (Search => A_Search,
Directory => D.Current_Directory,
Pattern => "",
Filter => Filter);
while D.More_Entries (Search => A_Search) loop
D.Get_Next_Entry (Search => A_Search,
Directory_Entry => Search_Item);
IO.Put (Item => D.Simple_Name (Directory_Entry => Search_Item));
IO.Set_Col (To => 25);
if D.Kind (Directory_Entry => Search_Item) = D.Ordinary_File then
IOI.Put (Item => D.Size (Directory_Entry => Search_Item),
Width => 1);
IO.Put (Item => " bytes");
else
IO.Put (Item => "dir");
end if;
IO.Set_Col (To => 45);
IO.Put (Item => ACF.Image
(D.Modification_Time (Directory_Entry => Search_Item)));
IO.Set_Col (To => 70);
IO.Put (Item => D.Full_Name (Directory_Entry => Search_Item));
IO.New_Line;
end loop;
D.End_Search (Search => A_Search);
end aDir;
The output from this program, when run on my system, is:
/home/thomas/wiki_examples/aDir/adir . dir 2009-10-23 20:17:22 /home/thomas/wiki_examples/aDir/. .. dir 2009-09-24 20:07:57 /home/thomas/wiki_examples/aDir/.. objects dir 2009-10-27 20:56:32 /home/thomas/wiki_examples/aDir/objects Proj.gpr 502 bytes 2009-09-24 20:09:20 /home/thomas/wiki_examples/aDir/Proj.gpr adir 594193 bytes 2009-10-23 20:17:22 /home/thomas/wiki_examples/aDir/adir some_dir dir 2009-10-06 14:10:25 /home/thomas/wiki_examples/aDir/some_dir adir.adb 1781 bytes 2009-10-23 20:17:21 /home/thomas/wiki_examples/aDir/adir.adb some_file 7 bytes 2009-10-06 14:10:04 /home/thomas/wiki_examples/aDir/some_file some_new_file 7 bytes 2009-10-06 14:37:15 /home/thomas/wiki_examples/aDir/some_new_file
Start_Search
sets the stage for a search. It takes four parameters: Search
which contains the state of the search, Directory
which is the directory to search, Pattern
which is a, well, pattern for matching filenames and Filter
which defines the kinds of files that are returned by the search.
The interpretation of the Pattern
value is implementation defined, except that an empty string equals "match everything". It is highly probable that simple and well-known patterns like *.txt
will yield the expected result: All files with the .txt
extension are matched.
In this case we search for everything (empty Pattern
string) that isn't considered a Special_File
(Filter is assigned D.Special_File => False
) in the current directory.
The above program uses an active iterator: The while
loop. As you can see, this loop goes on as long as D.More_Entries (Search => A_Search)
is True
. When this is no longer the case, the loop ends and we call End_Search
to clean up the A_Search
object. This basically resets it, so it no longer contains any entries.
We can avoid having to manage such basic house-keeping if we instead use a passive iterator. Here's what the program looks like when using a passive iterator:
with Ada.Text_IO;
with Ada.Directories; use Ada.Directories;
with Ada.Calendar.Formatting;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
package IOI is new IO.Integer_IO (D.File_Size);
package ACF renames Ada.Calendar.Formatting;
procedure Write_Search_Item (Search_Item : in D.Directory_Entry_Type) is
begin
IO.Put (Item => D.Simple_Name (Directory_Entry => Search_Item));
IO.Set_Col (To => 25);
if D.Kind (Directory_Entry => Search_Item) = D.Ordinary_File then
IOI.Put (Item => D.Size (Directory_Entry => Search_Item),
Width => 1);
IO.Put (Item => " bytes");
else
IO.Put (Item => "dir");
end if;
IO.Set_Col (To => 45);
IO.Put (Item => ACF.Image
(D.Modification_Time (Directory_Entry => Search_Item)));
IO.Set_Col (To => 70);
IO.Put (Item => D.Full_Name (Directory_Entry => Search_Item));
IO.New_Line;
end Write_Search_Item;
Filter : constant D.Filter_Type := (D.Ordinary_File => True,
D.Special_File => False,
D.Directory => True);
begin
D.Search (Directory => D.Current_Directory,
Pattern => "",
Filter => Filter,
Process => Write_Search_Item'Access);
end aDir;
The output is:
/home/thomas/wiki_examples/aDir/adir . dir 2009-10-23 20:17:22 /home/thomas/wiki_examples/aDir/. .. dir 2009-09-24 20:07:57 /home/thomas/wiki_examples/aDir/.. objects dir 2009-10-27 20:56:32 /home/thomas/wiki_examples/aDir/objects Proj.gpr 502 bytes 2009-09-24 20:09:20 /home/thomas/wiki_examples/aDir/Proj.gpr adir 594193 bytes 2009-10-23 20:17:22 /home/thomas/wiki_examples/aDir/adir some_dir dir 2009-10-06 14:10:25 /home/thomas/wiki_examples/aDir/some_dir adir.adb 1781 bytes 2009-10-23 20:17:21 /home/thomas/wiki_examples/aDir/adir.adb some_file 7 bytes 2009-10-06 14:10:04 /home/thomas/wiki_examples/aDir/some_file some_new_file 7 bytes 2009-10-06 14:37:15 /home/thomas/wiki_examples/aDir/some_new_file
With the passive iterator, you get rid of the Search_Type
and the Start_Search
-> More_Entries
-> Get_Next_Entry
-> End_Search
chain. It is much cleaner and less error-prone, because you don't have to worry about forgetting the final End_Search
call. Both methods have their advantages though, so use whatever is the best fit for the situation.
The Search
procedure accepts almost the same parameters as Start_Search
, except that we have Process
instead of Search
. Process
is of course access to the subprogram that is to handle each item returned by the search. This subprogram must accept one parameter: Item : in Directory_Entry_Type
.
{{collapsible box|title=Ada.Directories.Start_Search Example Source|collapsed=yes|content=
with Ada.Text_IO;
with Ada.Directories; use Ada.Directories;
with Ada.Calendar.Formatting;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
package IOI is new IO.Integer_IO (D.File_Size);
package ACF renames Ada.Calendar.Formatting;
A_Search : D.Search_Type;
Search_Item : D.Directory_Entry_Type;
Filter : constant D.Filter_Type := (D.Ordinary_File => True,
D.Special_File => False,
D.Directory => True);
begin
D.Start_Search (Search => A_Search,
Directory => D.Current_Directory,
Pattern => "",
Filter => Filter);
while D.More_Entries (Search => A_Search) loop
D.Get_Next_Entry (Search => A_Search,
Directory_Entry => Search_Item);
IO.Put (Item => D.Simple_Name (Directory_Entry => Search_Item));
IO.Set_Col (To => 25);
if D.Kind (Directory_Entry => Search_Item) = D.Ordinary_File then
IOI.Put (Item => D.Size (Directory_Entry => Search_Item),
Width => 1);
IO.Put (Item => " bytes");
else
IO.Put (Item => "dir");
end if;
IO.Set_Col (To => 45);
IO.Put (Item => ACF.Image
(D.Modification_Time (Directory_Entry => Search_Item)));
IO.Set_Col (To => 70);
IO.Put (Item => D.Full_Name (Directory_Entry => Search_Item));
IO.New_Line;
end loop;
D.End_Search (Search => A_Search);
end Adir;
with Ada.Text_IO;
with Ada.Directories; use Ada.Directories;
with Ada.Calendar.Formatting;
procedure aDir is
package IO renames Ada.Text_IO;
package D renames Ada.Directories;
package IOI is new IO.Integer_IO (D.File_Size);
package ACF renames Ada.Calendar.Formatting;
procedure Write_Search_Item (Search_Item : in D.Directory_Entry_Type) is
begin
IO.Put (Item => D.Simple_Name (Directory_Entry => Search_Item));
IO.Set_Col (To => 25);
if D.Kind (Directory_Entry => Search_Item) = D.Ordinary_File then
IOI.Put (Item => D.Size (Directory_Entry => Search_Item),
Width => 1);
IO.Put (Item => " bytes");
else
IO.Put (Item => "dir");
end if;
IO.Set_Col (To => 45);
IO.Put (Item => ACF.Image
(D.Modification_Time (Directory_Entry => Search_Item)));
IO.Set_Col (To => 70);
IO.Put (Item => D.Full_Name (Directory_Entry => Search_Item));
IO.New_Line;
end Write_Search_Item;
Filter : constant D.Filter_Type := (D.Ordinary_File => True,
D.Special_File => False,
D.Directory => True);
begin
D.Search (Directory => D.Current_Directory,
Pattern => "",
Filter => Filter,
Process => Write_Search_Item'Access);
end aDir;
End_Search
[edit | edit source]The specification for End_Search
looks like this:
procedure End_Search (Search : in out Search_Type);
End_Search
is a clean-up procedure. It is only necessary to use End_Search
if you've called Start_Search
earlier. It is used to reset the Search_Type
, effectively clearing out all search entries.
Typical usage would look something like this:
Start_Search(Search => A_Search, ...);
while More_Entries (Search => A_Search) loop
Get_Next_Entry (Search => A_Search, ...);
-- Do stuff
end loop;
End_Search (Search => A_Search);
An actual usage example can be found here.
More_Entries
[edit | edit source]The specification for More_Entries
looks like this:
function More_Entries (Search : Search_Type) return Boolean;
More_Entries
is only relevant if a prior call to Start_Search
has been made. A call to More_Entries
return boolean True
if more entries are available on a call to Get_Next_Entry
. Otherwise boolean False
is returned. See Start_Search for an actual usage example.
Get_Next_Entry
[edit | edit source]The specification for Get_Next_Entry
looks like this:
procedure Get_Next_Entry
(Search : in out Search_Type;
Directory_Entry : out Directory_Entry_Type);
I'm just going to quote the Ada Reference Manual on Get_Next_Entry
Returns the next Directory_Entry for the search described by Search that matches the pattern and filter. If no further matches are available, Status_Error is raised. It is implementation-defined as to whether the results returned by this routine are altered if the contents of the directory are altered while the Search object is valid (for example, by another program). The exception Use_Error is propagated if the external environment does not support continued searching of the directory represented by Search.
How this works, can be seen in the Start_Search section.
Simple_Name (Directory Entries)
[edit | edit source]The specification for Simple_Name
looks like this:
function Simple_Name (Directory_Entry : Directory_Entry_Type) return String;
The functionality of this Simple_Name
function is exactly the same as the "regular" Simple_Name function, except it accepts a Directory_Entry_Type
instead of a String
for the Directory_Entry
parameter. See Start_Search for an example.
Full_Name (Directory Entries)
[edit | edit source]The specification for Full_Name
looks like this:
function Full_Name (Directory_Entry : Directory_Entry_Type) return String;
The functionality of this Full_Name
function is exactly the same as the "regular" Full_Name function, except it accepts a Directory_Entry_Type
instead of a String
for the Directory_Entry
parameter. See Start_Search for an example.
Kind (Directory Entries)
[edit | edit source]The specification for Kind
looks like this:
function Kind (Directory_Entry : Directory_Entry_Type) return File_Kind;
The functionality of this Kind
function is exactly the same as the "regular" Kind function, except it accepts a Directory_Entry_Type
instead of a String
for the Directory_Entry
parameter. See Start_Search for an example.
Size (Directory Entries)
[edit | edit source]The specification for Size
looks like this:
function Size (Directory_Entry : Directory_Entry_Type) return File_Size;
The functionality of this Size
function is exactly the same as the "regular" Size function, except it accepts a Directory_Entry_Type
instead of a String
for the Directory_Entry
parameter. See Start_Search for an example.
Modification_Time (Directory Entries)
[edit | edit source]The specification for Modification_Time
looks like this:
function Modification_Time
(Directory_Entry : Directory_Entry_Type) return Ada.Calendar.Time;
The functionality of this Modification_Time
function is exactly the same as the "regular" Modification_Time function, except it accepts a Directory_Entry_Type
instead of a String
for the Directory_Entry
parameter. See Start_Search for an example.
Specification
[edit | edit source]-- Standard Ada library specification -- Copyright (c) 2003-2018 Maxim Reznik <reznikmm@gmail.com> -- Copyright (c) 2004-2016 AXE Consultants -- Copyright (c) 2004, 2005, 2006 Ada-Europe -- Copyright (c) 2000 The MITRE Corporation, Inc. -- Copyright (c) 1992, 1993, 1994, 1995 Intermetrics, Inc. -- SPDX-License-Identifier: BSD-3-Clause and LicenseRef-AdaReferenceManual -- -------------------------------------------------------------------------with
Ada.Calendar;with
Ada.IO_Exceptions;package
Ada.Directoriesis
-- Directory and file operations:function
Current_Directoryreturn
String;procedure
Set_Directory (Directory :in
String);procedure
Create_Directory (New_Directory :in
String; Form :in
String := "");procedure
Delete_Directory (Directory :in
String);procedure
Create_Path (New_Directory :in
String; Form :in
String := "");procedure
Delete_Tree (Directory :in
String);procedure
Delete_File (Name :in
String);procedure
Rename (Old_Name :in
String; New_Name :in
String);procedure
Copy_File (Source_Name :in
String; Target_Name :in
String; Form :in
String := ""); -- File and directory name operations:function
Full_Name (Name :in
String)return
String;function
Simple_Name (Name :in
String)return
String;function
Containing_Directory (Name :in
String)return
String;function
Extension (Name :in
String)return
String;function
Base_Name (Name :in
String)return
String;function
Compose (Containing_Directory :in
String := ""; Name :in
String; Extension :in
String := "")return
String; -- File and directory queries:type
File_Kindis
(Directory, Ordinary_File, Special_File);type
File_Sizeis
range
0 .. implementation_defined;function
Exists (Name :in
String)return
Boolean;function
Kind (Name :in
String)return
File_Kind;function
Size (Name :in
String)return
File_Size;function
Modification_Time (Name :in
String)return
Ada.Calendar.Time; -- Directory searching:type
Directory_Entry_Typeis
limited
private
;type
Filter_Typeis
array
(File_Kind)of
Boolean;type
Search_Typeis
limited
private
;procedure
Start_Search (Search :in
out
Search_Type; Directory :in
String; Pattern :in
String; Filter :in
Filter_Type := (others
=> True));procedure
End_Search (Search :in
out
Search_Type);function
More_Entries (Search :in
Search_Type)return
Boolean;procedure
Get_Next_Entry (Search :in
out
Search_Type; Directory_Entry :out
Directory_Entry_Type);procedure
Search (Directory :in
String; Pattern :in
String; Filter :in
Filter_Type := (others
=> True); Process :not
null
access
procedure
(Directory_Entry :in
Directory_Entry_Type)); -- Operations on Directory Entries:function
Simple_Name (Directory_Entry :in
Directory_Entry_Type)return
String;function
Full_Name (Directory_Entry :in
Directory_Entry_Type)return
String;function
Kind (Directory_Entry :in
Directory_Entry_Type)return
File_Kind;function
Size (Directory_Entry :in
Directory_Entry_Type)return
File_Size;function
Modification_Time (Directory_Entry :in
Directory_Entry_Type)return
Ada.Calendar.Time; Status_Error :exception
renames
Ada.IO_Exceptions.Status_Error; Name_Error :exception
renames
Ada.IO_Exceptions.Name_Error; Use_Error :exception
renames
Ada.IO_Exceptions.Use_Error; Device_Error :exception
renames
Ada.IO_Exceptions.Device_Error;private
pragma
Import (Ada, Directory_Entry_Type);pragma
Import (Ada, Search_Type);end
Ada.Directories;
See also
[edit | edit source]Wikibook
[edit | edit source]External examples
[edit source]- Search for examples of
Ada.Directories
in: Rosetta Code, GitHub (gists), any Alire crate or this Wikibook. - Search for posts related to
Ada.Directories
in: Stack Overflow, comp.lang.ada or any Ada related page.
Ada Reference Manual
[edit | edit source]Ada 2005
[edit | edit source]Ada 2012
[edit | edit source]Open-Source Implementations
[edit | edit source]FSF GNAT
- Specification: a-direct.ads
- Body: a-direct.adb
drake
- Specification: directories/a-direct.ads
- Body: directories/a-direct.adb