blob: 9435cc02ad02144443f7701d2ed11b515ff0ea66 [file] [log] [blame]
------------------------------------------------------------------------------
-- --
-- GNAT RUN-TIME COMPONENTS --
-- --
-- ADA.DIRECTORIES.HIERARCHICAL_FILE_NAMES --
-- --
-- B o d y --
-- --
-- Copyright (C) 2004-2023, Free Software Foundation, Inc. --
-- --
-- GNAT is free software; you can redistribute it and/or modify it under --
-- terms of the GNU General Public License as published by the Free Soft- --
-- ware Foundation; either version 3, or (at your option) any later ver- --
-- sion. GNAT is distributed in the hope that it will be useful, but WITH- --
-- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY --
-- or FITNESS FOR A PARTICULAR PURPOSE. --
-- --
-- As a special exception under Section 7 of GPL version 3, you are granted --
-- additional permissions described in the GCC Runtime Library Exception, --
-- version 3.1, as published by the Free Software Foundation. --
-- --
-- In particular, you can freely distribute your programs built with the --
-- GNAT Pro compiler, including any required library run-time units, using --
-- any licensing terms of your choosing. See the AdaCore Software License --
-- for full details. --
-- --
-- GNAT was originally developed by the GNAT team at New York University. --
-- Extensive contributions were provided by Ada Core Technologies Inc. --
-- --
------------------------------------------------------------------------------
with Ada.Characters.Handling; use Ada.Characters.Handling;
with Ada.Directories.Validity; use Ada.Directories.Validity;
with Ada.Strings.Fixed; use Ada.Strings.Fixed;
with System; use System;
package body Ada.Directories.Hierarchical_File_Names is
Dir_Separator : constant Character;
pragma Import (C, Dir_Separator, "__gnat_dir_separator");
-- Running system default directory separator
-----------------
-- Subprograms --
-----------------
function Equivalent_File_Names
(Left : String;
Right : String)
return Boolean;
-- Perform an OS-independent comparison between two file paths
function Is_Absolute_Path (Name : String) return Boolean;
-- Returns True if Name is an absolute path name, i.e. it designates a
-- file or directory absolutely rather than relative to another directory.
---------------------------
-- Equivalent_File_Names --
---------------------------
function Equivalent_File_Names
(Left : String;
Right : String)
return Boolean
is
begin
-- Check the validity of the input paths
if not Is_Valid_Path_Name (Left)
or else not Is_Valid_Path_Name (Right)
then
return False;
end if;
-- Normalize the paths by removing any trailing directory separators and
-- perform the comparison.
declare
Normal_Left : constant String :=
(if Index (Left, Dir_Separator & "", Strings.Backward) = Left'Last
and then not Is_Root_Directory_Name (Left)
then
Left (Left'First .. Left'Last - 1)
else
Left);
Normal_Right : constant String :=
(if Index (Right, Dir_Separator & "", Strings.Backward) = Right'Last
and then not Is_Root_Directory_Name (Right)
then
Right (Right'First .. Right'Last - 1)
else
Right);
begin
-- Within Windows we assume case insensitivity
if not Windows then
return Normal_Left = Normal_Right;
end if;
-- Otherwise do a straight comparison
return To_Lower (Normal_Left) = To_Lower (Normal_Right);
end;
end Equivalent_File_Names;
----------------------
-- Is_Absolute_Path --
----------------------
function Is_Absolute_Path (Name : String) return Boolean is
function Is_Absolute_Path
(Name : Address;
Length : Integer) return Integer;
pragma Import (C, Is_Absolute_Path, "__gnat_is_absolute_path");
begin
return Is_Absolute_Path (Name'Address, Name'Length) /= 0;
end Is_Absolute_Path;
--------------------
-- Is_Simple_Name --
--------------------
function Is_Simple_Name (Name : String) return Boolean is
begin
-- Verify the file path name is valid and that it is not a root
if not Is_Valid_Path_Name (Name)
or else Is_Root_Directory_Name (Name)
then
return False;
end if;
-- Check for the special paths "." and "..", which are considered simple
if Is_Parent_Directory_Name (Name)
or else Is_Current_Directory_Name (Name)
then
return True;
end if;
-- Perform a comparison with the calculated simple path name
return Equivalent_File_Names (Simple_Name (Name), Name);
end Is_Simple_Name;
----------------------------
-- Is_Root_Directory_Name --
----------------------------
function Is_Root_Directory_Name (Name : String) return Boolean is
begin
-- Check if the path name is a root directory by looking for a slash in
-- the general case, and a drive letter in the case of Windows.
return Name = "/"
or else
(Windows
and then
(Name = "\"
or else
(Name'Length = 3
and then Name (Name'Last - 1) = ':'
and then Name (Name'Last) in '/' | '\'
and then (Name (Name'First) in 'a' .. 'z'
or else
Name (Name'First) in 'A' .. 'Z'))
or else
(Name'Length = 2
and then Name (Name'Last) = ':'
and then (Name (Name'First) in 'a' .. 'z'
or else
Name (Name'First) in 'A' .. 'Z'))));
end Is_Root_Directory_Name;
------------------------------
-- Is_Parent_Directory_Name --
------------------------------
function Is_Parent_Directory_Name (Name : String) return Boolean is
begin
return Name = "..";
end Is_Parent_Directory_Name;
-------------------------------
-- Is_Current_Directory_Name --
-------------------------------
function Is_Current_Directory_Name (Name : String) return Boolean is
begin
return Name = ".";
end Is_Current_Directory_Name;
------------------
-- Is_Full_Name --
------------------
function Is_Full_Name (Name : String) return Boolean is
begin
return Equivalent_File_Names (Full_Name (Name), Name);
end Is_Full_Name;
----------------------
-- Is_Relative_Name --
----------------------
function Is_Relative_Name (Name : String) return Boolean is
begin
return not Is_Absolute_Path (Name)
and then Is_Valid_Path_Name (Name);
end Is_Relative_Name;
-----------------------
-- Initial_Directory --
-----------------------
function Initial_Directory (Name : String) return String is
Start : constant Integer := Index (Name, Dir_Separator & "");
begin
-- Verify path name
if not Is_Valid_Path_Name (Name) then
raise Name_Error with "invalid path name """ & Name & '"';
end if;
-- When there is no starting directory separator or the path name is a
-- root directory then the path name is already simple - so return it.
if Is_Root_Directory_Name (Name) or else Start = 0 then
return Name;
end if;
-- When the initial directory of the path name is a root directory then
-- the starting directory separator is part of the result so we must
-- return it in the slice.
if Is_Root_Directory_Name (Name (Name'First .. Start)) then
return Name (Name'First .. Start);
end if;
-- Otherwise we grab a slice up to the starting directory separator
return Name (Name'First .. Start - 1);
end Initial_Directory;
-------------------
-- Relative_Name --
-------------------
function Relative_Name (Name : String) return String is
begin
-- We cannot derive a relative name if Name does not exist
if not Is_Relative_Name (Name)
and then not Is_Valid_Path_Name (Name)
then
raise Name_Error with "invalid relative path name """ & Name & '"';
end if;
-- Name only has a single part and thus cannot be made relative
if Is_Simple_Name (Name)
or else Is_Root_Directory_Name (Name)
then
raise Name_Error with
"relative path name """ & Name & """ is composed of a single part";
end if;
-- Trim the input according to the initial directory and maintain proper
-- directory separation due to the fact that root directories may
-- contain separators.
declare
Init_Dir : constant String := Initial_Directory (Name);
begin
if Init_Dir (Init_Dir'Last) = Dir_Separator then
return Name (Name'First + Init_Dir'Length .. Name'Last);
end if;
return Name (Name'First + Init_Dir'Length + 1 .. Name'Last);
end;
end Relative_Name;
-------------
-- Compose --
-------------
function Compose
(Directory : String := "";
Relative_Name : String;
Extension : String := "") return String
is
-- Append a directory separator if none is present
Separated_Dir : constant String :=
(if Directory = "" then ""
elsif Directory (Directory'Last) = Dir_Separator then Directory
else Directory & Dir_Separator);
begin
-- Check that relative name is valid
if not Is_Relative_Name (Relative_Name) then
raise Name_Error with
"invalid relative path name """ & Relative_Name & '"';
end if;
-- Check that directory is valid
if Separated_Dir /= ""
and then not Is_Valid_Path_Name (Separated_Dir & Relative_Name)
then
raise Name_Error with
"invalid path composition """ & Separated_Dir & Relative_Name & '"';
end if;
-- Check that the extension is valid
if Extension /= ""
and then not Is_Valid_Path_Name
(Separated_Dir & Relative_Name & Extension)
then
raise Name_Error with
"invalid path composition """
& Separated_Dir & Relative_Name & Extension & '"';
end if;
-- Concatenate the result
return Separated_Dir & Relative_Name & Extension;
end Compose;
end Ada.Directories.Hierarchical_File_Names;