Introduction

The Ada.Directories package provides tools for manipulation of directories and files. If the environment supports the notion of files and directories, it is possible to manipulate these in a way that is fairly OS agnostic.

A package like Ada.Directories is very implementation dependent 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:

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.

All the examples in this article is done on a Slackware 12.1 system.

Using Ada.Directories

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

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

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

Set_Directory

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

Create_Directory

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

Delete_Directory

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

Create_Path

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 implementations 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, ie. / 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

Delete_Tree

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

Delete_File

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

Rename

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

Copy_File

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

Name Operations

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

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

Simple_Name

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

Containing_Directory

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

Extension

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

Base_Name

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

Compose

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

File and directory queries

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

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

Kind

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.

Ada.Directories.Kind Example Source

Size

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.

Ada.Directories.Size Example Source

Modification_Time

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.

Ada.Directories.Modification_Time Example Source

Directory Searching

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

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

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

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.

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_SearchMore_EntriesGet_Next_EntryEnd_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.

Ada.Directories.Start_Search Example 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

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

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)

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)

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)

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)

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)

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.


Navigation