Operational environment (original) (raw)

Rationale for Ada 2005

Contents Index References Search Previous Next


Two new packages are added to Ada 2005 in order to aid communication with the operational environment. They are Ada.Environment_Variablesand Ada.Directories.

The package Ada.Environment_Variableshas the following specification

package Ada.Environment_Variables is
pragma Preelaborate(Environment_Variables);

function Value(Name: String) return String;
function Exists(Name: String) return Boolean;
procedure Set(Name: in String; Value: in String);

procedure Clear(Name: in String);
procedure Clear;

procedure Iterate(Process: not null access procedure (Name, Value: in String));

end Ada.Environment_Variables;

This package provides access to environment variables by name. What this means and whether it is supported depends upon the implementation. But most operating systems have environment variables of some sort. And if not, the implementation is encouraged to simulate them.

The values of the variable are also implementation defined and so simply represented by strings.

The behaviour is straightforward. We might check to see if there is a variable with the name "Ada"and then read and print her value and set it to 2005 if it is not, thus

if not Exists("Ada") then
raise Horror; -- quel dommage!
end if;

Put("Current value of Ada is "); Put_Line(Value("Ada"));

if Value("Ada") /= "2005" then
Put_Line("Revitalizing Ada now");
Set("Ada", "2005");
end if;

The procedure Clear with a parameter deletes the variable concerned. Thus Clear("Ada")eliminates her completely so that a subsequent call Exists("Ada")will return False. Note that Setactually clears the variable concerned and then defines a new one with the given name and value. The procedure Clearwithout a parameter clears all variables.

We can iterate over the variables using the procedure Iterate. For example we can print out the current state by

procedure Print_One(Name, Value: in String) is
begin
Put_Line(Name & "=" & Value);
end Print_One;
...
Iterate(Print_One'Access);

The procedure Print_Oneprints the name and value of the variable passed as parameters. We then pass an access to this procedure as a parameter to the procedure Iterateand Iterate then calls Print_Onefor each variable in turn.

Note that the slave procedure has both Nameand Value as parameters. It might be thought that this was unnecessary since the user can always call the function Value. However, real operating systems can sometimes have several variables with the same name; providing two parameters ensures that the name/value pairs are correctly matched.

Attempting to misuse the environment package such as reading a variable that doesn't exist raises Constraint_Erroror Program_Error.

There are big dangers of race conditions because the environment variables are really globally shared. Moreover, they might be shared with the operating system itself as well as programs written in other languages.

A particular point is that we must not call the procedures Set or Clear within a procedure passed as a parameter to Iterate.

The other environment package is Ada.Directories. Its specification is

with Ada.IO_Exceptions;
with Ada.Calendar;
package Ada.Directories is

-- Directory and file operations:
function Current_Directory return 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: String) return String;
function Simple_Name(Name: String) return String;
function Containing_Directory(Name: String) return String;
function Extension(Name: String) return String;
function Base_Name(Name: String) return String;
function Compose(Containing_Directory: String := ""; Name: String; Extension: String := "")
return String;

-- File and directory queries:
type File_Kind is (Directory, Ordinary_File, Special_File);
type File_Size is range 0 .. implementation_defined;
function Exists(Name: String) return Boolean;
function Kind(Name: String) return File_Kind;
function Size(Name: String) return File_Size;
function Modification_Time(Name: String) return Ada.Calendar.Time;

-- Directory searching:
type Directory_Entry_Type is limited private;
type Filter_Type is array (File_Kind) of Boolean;
type Search_Type is 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: 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: Directory_Entry_Type) return String;
function Full_Name(Directory_Entry: Directory_Entry_Type) return String;
function Kind(Directory_Entry: Directory_Entry_Type) return File_Kind;
function Size(Directory_Entry: Directory_Entry_Type) return File_Size;
function Modification_Time(Directory_Entry: 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
-- Not specified by the language
end Ada.Directories;

Most operating systems have some sort of tree-structured filing system. The general idea of this package is that it allows the manipulation of file and directory names as far as is possible in a unified manner which is not too dependent on the implementation and operating system.

Files are classified as directories, special files and ordinary files. Special files are things like devices on Windows and soft links on Unix; these cannot be created or read by the predefined Ada input–output packages.

Files and directories are identified by strings in the usual way. The interpretation is implementation defined.

The full name of a file is a string such as

"c:\adastuff\rat\library.doc"

and the simple name is

"library.doc"

At least that is in good old DOS. In Windows XP it is something like

"C:\Documents and Settings\john.JBI3\My Documents\adastuff\rat\library.doc"

For the sake of illustration we will continue with the simple DOS example. The current directory is that set by the "cd" command. So assuming we have done

c:\>cd adastuff
c:\adastuff>

then the function Current_Directorywill return the string "c:\adastuff". The procedure Set_Directory sets the current default directory. The procedures Create_Directoryand Delete_Directory create and delete a single directory. We can either give the full name or just the part starting from the current default. Thus

Create_Directory("c:\adastuff\history");
Delete_Directory("history");

will cancel out.

The procedure Create_Pathcreates several nested directories as necessary. Thus starting from the situation above, if we write

Create_Path("c:\adastuff\books\old");

then it will first create a directory "books"in "c:\adastuff" and then a directory "old" in "books". On the other hand if we wrote Create_Path("c:\adastuff\rat");then it would do nothing since the path already exists. The procedure Delete_Tree deletes a whole tree including subdirectories and files.

The procedures Delete_File, Rename and Copy_Filebehave as expected. Note in particular that Copy_Filecan be used to copy any file that could be copied using a normal input–output package such as Text_IO. For example, it is really tedious to use Text_IO to copy a file intact including all line and page terminators. It is a trivial matter using Copy_File.

Note also that the procedures Create_Directory, Create_Path and Copy_Filehave an optional Form parameter. Like similar parameters in the predefined input–output packages the meaning is implementation defined.

The next group of six functions, Full_Name, Simple_Name, Containing_Directory, Extension, Base_Nameand Compose just manipulate strings representing file names and do not in any way interact with the actual external file system. Moreover, of these, only the behaviour of Full_Namedepends upon the current directory.

The function Full_Namereturns the full name of a file. Thus assuming the current directory is still "c:\adastuff"

Full_Name("rat\library.doc")

returns "c:\adastuff\rat\library.doc"and

Full_Name("library.doc")

returns "c:\adastuff\library.doc". The fact that such a file does not exist is irrelevant. We might be making up the name so that we can then create the file. If the string were malformed in some way (such as "66##77") so that the corresponding full name if returned would be nonsense then Name_Erroris raised. But Name_Error is never raised just because the file does not exist.

On the other hand

Simple_Name("c:\adastuff\rat\library.doc")

returns "library.doc"and not "rat\library.doc". We can also apply Simple_Name to a string that does not go back to the root. Thus

Simple_Name("rat\library.doc");

is allowed and also returns "library.doc".

The function Containing_Directory_Nameremoves the simple name part of the parameter. We can even write

Containing_Directory_Name("..\rat\library.doc")

and this returns "..\rat"; note that it also removes the separator "\". "\". We can apply it again

Containing_Directory_Name("..\rat")

and this returns ".." on its own.

The functions Extensionand Base_Name return the corresponding parts of a file name thus

Base_Name("rat\library.doc") -- "library"
Extension("rat\library.doc") -- "doc"

Note that they can be applied to a simple name or to a full name or, as here, to something midway between.

The function Composecan be used to put the various bits together, thus

Compose("rat", "library", "doc")

returns "rat\library.doc". The default parameters enable bits to be omitted. In fact if the third parameter is omitted then the second parameter is treated as a simple name rather than a base name. So we could equally write

Compose("rat","library.doc")

The next group of functions, Exists, Kind, Size and Modification_Time act on a file name (that is the name of a real external file) and return the obvious result. (The size is measured in stream elements – usually bytes.)

Various types and subprograms are provided to support searching over a directory structure for entities with appropriate properties. This can be done in two ways, either as a loop under the direct control of the programmer (sometimes called an active iterator) or via an access to subprogram parameter (often called a passive iterator). We will look at the active iterator approach first.

The procedures Start_Search, End_Search and Get_Next_Entryand the function More_Entries control the search loop. The general pattern is

Start_Search( ... );
while More_Entries( ... ) loop
Get_Next_Entry( ... );
... -- do something with the entry found
end loop;
End_Search( ... );

Three types are involved. The type Directory_Entry_Typeis limited private and acts as a sort of handle to the entries found. Valid values of this type can only be created by a call of Get_Next_Entrywhose second parameter is an out parameter of the type Directory_Entry_Type. The type Search_Type is also limited private and contains the state of the search. The type Filter_Typeprovides a simple means of identifying the kinds of file to be found. It has three components corresponding to the three values of the enumeration type File_Kind and is used by the procedure Start_Search.

Suppose we want to look for all ordinary files with extension "doc"in the directory "c:\adastuff\rat". We could write

Rat_Search: Search_Type;
Item: Directory_Entry_Type;
Filter: Filter_Type := (Ordinary_File => True, others => False);
...
Start_Search(Rat_Search, "c:\adastuff\rat", "*.doc", Filter);
while More_Entries(Rat_Search) loop
Get_Next_Entry(Rat_Search, Item);
... -- do something with Item
end loop;
End_Search(Rat_Search);

The third parameter of Start_Search(which is "*.doc" in the above example) represents a pattern for matching names and thus provides further filtering of the search. The interpretation is implementation defined except that a null string means match everything. However, we would expect that writing "*.doc" would mean search only for files with the extension "doc".

The alternative mechanism using a passive iterator is as follows. We first declare a subprogram such as

procedure Do_It(Item: in Directory_Entry_Type) is
begin
... -- do something with item
end Do_It;

and then declare a filter and call the procedure Search thus

Filter: Filter_Type := (Ordinary_File => True, others => False);
...
Search("c:\adastuff\rat", "*.doc", Filter, Do_It'Access);

The parameters of Searchare the same as those of Start_Search except that the first parameter of type Search_Typeis omitted and a final parameter which identifies the procedure Do_Itis added. The variable Item which we declared in the active iterator is now the parameter Itemof the procedure Do_It.

Each approach has its advantages. The passive iterator has the merit that we cannot make mistakes such as forget to call End_Search. But some find the active iterator easier to understand and it can be easier to use for parallel searches.

The final group of functions enables us to do useful things with the results of our search. Thus Simple_Nameand Full_Name convert a value of Directory_Entry_Typeto the corresponding simple or full file name. Having obtained the file name we can do everything we want but for convenience the functions Kind, Size and Modification_Time are provided which also directly take a parameter of Directory_Entry_Type.

So to complete this example we might print out a table of the files found giving their simple name, size and modification time. Using the active approach the loop might then become

while More_Entries(Rat_Search) loop
Get_Next_Entry(Rat_Search, Item);
Put(Simple_Name(Item)); Set_Col(15);
Put(Size(Item/1000)); Put(" KB"); Set_Col(25);
Put_Line(Image(Modification_Time(Item)));
end loop;

This might produce a table such as

access.doc 152 KB 2005-04-05 09:03:10
containers.doc 372 KB 2005-06-14 21:39:05
general.doc 181 KB 2005-03-03 08:43:15
intro.doc 173 KB 2004-11-25 15:52:20
library.doc 149 KB 2005-04-08 13:50:05
oop.doc 179 KB 2005-02-25 18:34:55
structure.doc 151 KB 2005-04-05 09:09:25
tasking.doc 174 KB 2005-03-31 11:16:40

Note that the function Imageis from the package Ada.Calendar.Formattingdiscussed in the previous section.

Observe that the search is carried out on the directory given and does not look at subdirectories. If we want to do that then we can use the function Kind to identify subdirectories and then search recursively.

It has to be emphasized that the package Ada.Directoriesis very implementation dependent and indeed might not be supported by some implementations at all. Implementations are advised to provide any additional useful functions concerning retrieving other information about files (such as name of the owner or the original creation date) in a child package Ada.Directories.Information.

Finally, note that misuse of the various operations will raise one of the exceptions Status_Error, Name_Error, Use_Erroror Device_Error from the package IO_Exceptions.


Contents Index References Search Previous Next

© 2005, 2006, 2007 John Barnes Informatics.

Sponsored in part by:

The Ada Resource Association and its member companies: ARA Members and Ada-Europe: Ada-Europe