# 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

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 :

## Short Use Tutorial

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

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.

### Enumerating Directory Entries

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.
The resulting Directory_Entry_Type has a full name, simple name, size, kind, and modification type.

### Directory Manipulation

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.
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 "\").

### Exceptions

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.

## Detailed Use Tutorial

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:
When running the above program, you should see output somewhat similar to this:
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:
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:
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:
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:
If we execute the program now, we get this:
If the directory you're trying to set doesn't exist, a Name_Error is raised. Lets see how that works:
If you add the above snippet to the core aDir program, you will get output that looks something like this:
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:
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:
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:
With that one line of code added to the program, we can see how the program by doing a little test:
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:
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:
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:
Now lets try that last command again:
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:
To
Now, running the program we get:
We can catch Name_Error by adding one more exception handler:
Running the program we now get this:
Perfect.
Ada.Directories.Create_Directory Example Source

#### Delete_Directory

The specification for Delete_Directory looks like this:
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:
Executing this program, we get the following output:
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:
And the output is:
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:
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:
And the output is:
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:
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:
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:
And the output is:
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:
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:
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:
The usual exceptions also apply to Delete_File.
Lets create a new file:
And now add this to the basic aDir program:
The output is:
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:
The output should look like this:
And with that, we conclude our look at Delete_File.
Ada.Directories.Delete_File Example Source

#### Rename

The specification for Rename looks like this:
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:
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:
The output from this is:
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:
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:
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:
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
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:
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:
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:
And the resulting output:
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:
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:
And the output is:
Quite handy.
Ada.Directories.Simple_Name Example Source

#### Containing_Directory

The specification for Containing_Directory looks like this:
Containing_Directory removes the simple name of the Name path given. Lets see it in action:
And the output is:
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:
If you want to extract the extension of a file, this function is what you need. Here's an example on usage:
And the output:
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:
I'm quite sure you've already guessed what Base_Name does, but indulge me in some example-code:
The output:
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:
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:
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:
The output from the above is:
There's no surprise here.
The Name_Error exception is raised when:
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:
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:
And the output:
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:
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:
And then the body:
And the output:
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=

#### Size

The specification for Size looks like this:
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:
And the result is:
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=

#### Modification_Time

The specification for Modification_Time looks like this:
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:
And then the declaration:
And finally the code that goes into the body:
The output from this little snippet is:
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=

### 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:
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:
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:
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:
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

The specification for Start_Search looks like this:
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:
The output from this program, when run on my system, is:
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:
The output is:
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=

#### End_Search

The specification for End_Search looks like this:
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:
An actual usage example can be found here.

#### More_Entries

The specification for More_Entries looks like this:
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:
I'm just going to quote the Ada Reference Manual on Get_Next_Entry
How this works, can be seen in the Start_Search section.

#### Simple_Name (Directory Entries)

The specification for Simple_Name looks like this:
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:
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:
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:
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:
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.

## See also
### Wikibook

* Ada Programming/Libraries/Ada

### External examples

* 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
#### Ada 2005

* A.16: The Package Directories

#### Ada 2012

* A.16: The Package Directories

### Open-Source Implementations

FSF GNAT
* Specification: a-direct.ads
* Body: a-direct.adb
drake
* Specification: directories/a-direct.ads
* Body: directories/a-direct.adb