Rationale for Ada 2005
7.4 Operational environment
Two new packages are added to Ada 2005 in order to
aid communication with the operational environment. They are Ada.Environment_Variables
and Ada.Directories.
The package
Ada.Environment_Variables
has 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 Set
actually clears the variable concerned and then defines a new one with
the given name and value. The procedure Clear
without 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_One
prints the name and value of the variable passed as parameters. We then
pass an access to this procedure as a parameter to the procedure Iterate
and Iterate then calls Print_One
for each variable in turn.
Note that the slave procedure has both Name
and 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_Error
or 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_Directory
will return the string
"c:\adastuff".
The procedure
Set_Directory sets the current
default directory. The procedures
Create_Directory
and
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_Path
creates 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_File
behave as expected. Note in particular that Copy_File
can 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_File
have 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_Name
and 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_Name
depends upon the current directory.
The function
Full_Name
returns 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_Error
is 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_Name
removes 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 "\".
The functions
Extension
and
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 Compose
can 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_Entry
and 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_Type
is 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_Entry
whose 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_Type
provides 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 Search
are the same as those of Start_Search except
that the first parameter of type Search_Type
is omitted and a final parameter which identifies the procedure Do_It
is added. The variable Item which we declared
in the active iterator is now the parameter Item
of 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_Name
and Full_Name convert a value of Directory_Entry_Type
to 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 Image
is from the package Ada.Calendar.Formatting
discussed 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.Directories
is 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_Error
or Device_Error from the package IO_Exceptions.
© 2005, 2006 John Barnes Informatics.
Sponsored in part by: