-- Topal: GPG/GnuPG and Alpine/Pine integration
-- Copyright (C) 2001--2022  Phillip J. Brooke
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License version 3 as
-- published by the Free Software Foundation.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

with Ada.Characters.Latin_1;
with Ada.IO_Exceptions;
with Ada.Text_IO;
with Misc;

package body Externals.Simple is

   procedure Cat (D : in String) is
      use Ada.Text_IO;
      use Misc;
      F1 : File_Type;
      TL : UBS;
   begin
      Open(File => F1,
           Mode => In_File,
           Name => D);
  File_Loop:
      loop
         begin
            TL := Unbounded_Get_Line(F1);
            Put_Line(ToStr(TL));
         exception
            when Ada.IO_Exceptions.End_Error =>
               exit File_Loop;
         end;
      end loop File_Loop;
      Close(F1);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Cat");
         raise;
   end Cat;

   procedure Cat_Out (D1 : in String;
                      D2 : in String) is
      use Ada.Text_IO;
      use Misc;
      F1, F2 : File_Type;
      TL : UBS;
   begin
      Open(File => F1,
           Mode => In_File,
           Name => D1);
      Create(File => F2,
             Mode => Out_File,
             Name => D2);
  File_Loop:
      loop
         begin
            TL := Unbounded_Get_Line(F1);
            Put_Line(F2, ToStr(TL));
         exception
            when Ada.IO_Exceptions.End_Error =>
               exit File_Loop;
         end;
      end loop File_Loop;
      Close(F1);
      Close(F2);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Cat_Out");
         raise;
   end Cat_Out;

   -- Equivalent of `cat > D'.
   procedure Cat_Stdin_Out (D : in String) is
      use Ada.Text_IO;
      use Misc;
      F2 : File_Type;
      TL : UBS;
   begin
      Create(File => F2,
             Mode => Out_File,
             Name => D);
  File_Loop:
      loop
         begin
            TL := Unbounded_Get_Line;
            Put_Line(F2, ToStr(TL));
         exception
            when Ada.IO_Exceptions.End_Error =>
               exit File_Loop;
         end;
      end loop File_Loop;
      Close(F2);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Cat_Stdin_Out");
         raise;
   end Cat_Stdin_Out;

   procedure Cat_Append (D1 : in String;
                         D2 : in String) is
      use Ada.Text_IO;
      use Misc;
      F1, F2 : File_Type;
      TL : UBS;
   begin
      Open(File => F1,
           Mode => In_File,
           Name => D1);
      begin
         Open(File => F2,
              Mode => Append_File,
              Name => D2);
      exception
         when Ada.IO_Exceptions.Name_Error =>
            -- Try again with a create.
            Create(File => F2,
                   Mode => Append_File,
                   Name => D2);
      end;
  File_Loop:
      loop
         begin
            TL := Unbounded_Get_Line(F1);
            Put_Line(F2, ToStr(TL));
         exception
            when Ada.IO_Exceptions.End_Error =>
               exit File_Loop;
         end;
      end loop File_Loop;
      Close(F1);
      Close(F2);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Cat_Append("&D1&","&D2&")");
         raise;
   end Cat_Append;

   procedure Chmod (P : in String;
                    D : in String) is
   begin
      if ForkExec(Misc.Value_Nonempty(Config.Binary(Chmod)),
                  UBS_Array'(0 => ToUBS("chmod"),
                             1 => ToUBS(P),
                             2 => ToUBS(D))) /= 0 then
         Misc.Error("`chmod " & P & " " & D & "' failed.");
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Chmod");
         raise;
   end Chmod;

   procedure Clear is
   begin
      if ForkExec(Misc.Value_Nonempty(Config.Binary(Clear)),
                  UBS_Array'(0 => ToUBS("clear"))) /= 0 then
         Misc.Error("`clear' failed.");
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Clear");
         raise;
   end Clear;

   procedure Date_Append (D : in String) is
   begin
      if ForkExec_Append(Misc.Value_Nonempty(Config.Binary(Date)),
                         UBS_Array'(0 => ToUBS("date")),
                         D) /= 0 then
         Misc.Error("`date >> " & D & "' failed.");
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Date_Append");
         raise;
   end Date_Append;

   procedure Date_1_Append (A, D : in String) is
   begin
      if ForkExec_Append(Misc.Value_Nonempty(Config.Binary(Date)),
                         UBS_Array'(0 => ToUBS("date"),
                                    1 => ToUBS(A)),
                         D) /= 0 then
         Misc.Error("`date " & A & " >> " & D & "' failed.");
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Date_1_Append");
         raise;
   end Date_1_Append;

   function Date_String return String is
      use Ada.Text_IO;
      use Misc;
      Target : constant String := Temp_File_Name("thedate");
      TL     : UBS;
      F1     : File_Type;
   begin
      if ForkExec_Out(Misc.Value_Nonempty(Config.Binary(Date)),
                      UBS_Array'(0 => ToUBS("date"),
                                 1 => ToUBS("+%Y%m%d%H%M%S")),
                      Target) /= 0 then
         Misc.Error("`date '+%Y%m%d%H%M%S' >" & Target & "' failed.");
      end if;
      -- Now, extract that string.
      Open(File => F1,
           Mode => In_File,
           Name => Target);
      -- There should be exactly one line that has the date in it.
      TL := Unbounded_Get_Line(F1);
      Close(F1);
      -- Return that string.
      return ToStr(TL);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Date_String");
         raise;
   end Date_String;

   function Diff_Brief (F1 : in String;
                        F2 : in String) return Boolean is
      E : Integer;
   begin
      E := ForkExec_Out(Misc.Value_Nonempty(Config.Binary(Diff)),
                        UBS_Array'(0 => ToUBS("diff"),
                                   1 => ToUBS("--brief"),
                                   2 => ToUBS(F1),
                                   3 => ToUBS(F2)),
                        Target => "/dev/null");
      case E is
         when 0 =>  -- No changes.
            return False;
         when 1 =>  -- There are changes.
            return True;
         when others => -- There's a problem.
            Misc.Error("`diff --brief " & F1 & "  " & F2 & " >/dev/null' failed.");
            return False; -- Won't be executed.
      end case;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Diff_Brief");
         raise;
   end Diff_Brief;

   procedure Dos2Unix_U (D : in String) is
      use Misc;
      F1, F2 : Character_IO.File_Type;
      D2     : constant String := Temp_File_Name("d2u");
      C, CP  : Character;
   begin
      Character_IO.Open(File => F1,
                        Mode => Character_IO.In_File,
                        Name => D);
      Character_IO.Create(File => F2,
                          Mode => Character_IO.Out_File,
                          Name => D2);
      begin
         -- This one's Unix to DOS.  So all \n go to \r\n unless it's
         --  prefixed already by \r.
         CP := ' ';
     Char_Loop:
         loop
            Character_IO.Read(F1, C);
            if C = Ada.Characters.Latin_1.LF then
               if CP = Ada.Characters.Latin_1.CR then
                  Character_IO.Write(F2, Ada.Characters.Latin_1.LF);
               else
                  Character_IO.Write(F2, Ada.Characters.Latin_1.CR);
                  Character_IO.Write(F2, Ada.Characters.Latin_1.LF);
               end if;
            else
               Character_IO.Write(F2, C);
            end if;
            CP := C;
         end loop Char_Loop;
      exception
         when Ada.IO_Exceptions.End_Error => null;
      end;
      Character_IO.Close(File => F1);
      Character_IO.Close(File => F2);
      Cat_Out(D2, D);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Dos2Unix_U");
         raise;
   end Dos2Unix_U;

   procedure Dos2Unix (D : in String) is
      use Misc;
      F1, F2 : Character_IO.File_Type;
      D2     : constant String := Temp_File_Name("d2u");
      C      : Character;
      CR     : Boolean; -- Flag indicating a pending CR.
   begin
      Character_IO.Open(File => F1,
                        Mode => Character_IO.In_File,
                        Name => D);
      Character_IO.Create(File => F2,
                          Mode => Character_IO.Out_File,
                          Name => D2);
      begin
         -- This one's DOS to Unix.  So all \r\n pairs go to \n.
         CR := False;
     Char_Loop:
         loop
            Character_IO.Read(F1, C);
            if CR then
               if C /= Ada.Characters.Latin_1.LF then
                  Character_IO.Write(F2, Ada.Characters.Latin_1.CR);
               end if;
               CR := False;
            end if;
            if C = Ada.Characters.Latin_1.CR then
               CR := True;
            else
               Character_IO.Write(F2, C);
            end if;
         end loop Char_Loop;
      exception
         when Ada.IO_Exceptions.End_Error => null;
      end;
      if CR then
         -- We've reached the end, but with a pending CR.
         Character_IO.Write(F2, Ada.Characters.Latin_1.CR);
      end if;
      Character_IO.Close(File => F1);
      Character_IO.Close(File => F2);
      Cat_Out(D2, D);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Dos2Unix");
         raise;
   end Dos2Unix;

   procedure Echo_Append (E : in String;
                          D : in String) is
      use Ada.Text_IO;
      use Misc;
      F : File_Type;
   begin
      begin
         Open(File => F,
              Mode => Append_File,
              Name => D);
      exception
         when Ada.IO_Exceptions.Name_Error =>
            -- Try again with a create.
            Create(File => F,
                   Mode => Append_File,
                   Name => D);
      end;
      Put_Line(F, E);
      Close(F);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Echo_Append");
         raise;
   end Echo_Append;

   procedure Echo_Append_N (E : in String;
                            D : in String) is
      use Misc;
      use Character_IO;
      F : File_Type;
   begin
      begin
         Open(File => F,
              Mode => Append_File,
              Name => D);
      exception
         when Ada.IO_Exceptions.Name_Error =>
            -- Try again with a create.
            Create(File => F,
                   Mode => Append_File,
                   Name => D);
      end;
      Character_IO_Put(F, E);
      Close(F);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Echo_Append_N");
         raise;
   end Echo_Append_N;

   procedure Echo_Out (E : in String;
                       D : in String) is
      use Ada.Text_IO;
      use Misc;
      F : File_Type;
   begin
      Create(File => F,
             Mode => Out_File,
             Name => D);
      Put_Line(F, E);
      Close(F);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Echo_Out");
         raise;
   end Echo_Out;

   procedure Echo_Out_N (E : in String;
                            D : in String) is
      use Misc;
      use Character_IO;
      F : File_Type;
   begin
      Create(File => F,
             Mode => Out_File,
             Name => D);
      Character_IO_Put(F, E);
      Close(F);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Echo_Out_N");
         raise;
   end Echo_Out_N;

   function Grep_I_InOut (T : in String;
                          D1 : in String;
                          D2 : in String;
			  E  : in Boolean := False;
			  C  : in Boolean := False;
			  V  : in Boolean := False) return Integer is
      F : UBS;
   begin
      if E then
	 F := ToUBS("-iE");
      else 
	 F := ToUBS("-i");
      end if;
      if C then
	 declare
	    use type UBS;
	 begin
	    F := F & 'c';
	 end;
      end if;
      if V then
	 declare
	    use type UBS;
	 begin
	    F := F & 'v';
	 end;
      end if;
      return ForkExec_InOut(Misc.Value_Nonempty(Config.Binary(Grep)),
                            UBS_Array'(0 => ToUBS("grep"),
				       1 => F,
                                       2 => ToUBS(T)),
                            Source => D1,
                            Target => D2);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Grep_I_InOut");
         raise;
   end Grep_I_InOut;

   procedure Mkdir_P (D : in String) is
   begin
      if ForkExec(Misc.Value_Nonempty(Config.Binary(Mkdir)),
                  UBS_Array'(0 => ToUBS("mkdir"),
                             1 => ToUBS("-p"),
                             2 => ToUBS(D))) /= 0 then
         Misc.Error("`mkdir -p " & D & "' failed.");
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Mkdir_P");
         raise;
   end Mkdir_P;

   procedure Mv_F (D1 : in String;
                   D2 : in String) is
   begin
      if not Test_F(D1) then
         Misc.Error("`" & D1 & "' does not exist to be moved!");
      end if;
      if ForkExec(Misc.Value_Nonempty(Config.Binary(Mv)),
                  UBS_Array'(0 => ToUBS("mv"),
                             1 => ToUBS("-f"),
                             2 => ToUBS(D1),
                             3 => ToUBS(D2))) /= 0 then
         Misc.Error("`mv -f " & D1 & " " & D2 & "' failed.");
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Mv_F");
         raise;
   end Mv_F;

   -- View a file with a pager.
   procedure Pager (F : in String) is
   begin
      if ForkExec(Misc.Value_Nonempty(Config.Binary(Less)),
                  UBS_Array'(0 => ToUBS("less"),
                             1 => ToUBS(F))) /= 0 then
         Misc.Error("less failed! (ff4)");
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Pager");
         raise;
   end Pager;

   procedure Rm_File (F : in String) is
      Dummy : Integer;
      pragma Unreferenced(Dummy);
   begin
      -- We ignore Rm's return code.
      Dummy := ForkExec(Misc.Value_Nonempty(Config.Binary(Rm)),
                        UBS_Array'(0 => ToUBS("rm"),
                                   1 => ToUBS("-f"),
                                   2 => ToUBS(F)));
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Rm_File");
         raise;
   end Rm_File;

   procedure Rm_Glob (Pattern : in String) is
      Files : constant UBS_Array := Glob(Pattern);
      Dummy : Integer;
      pragma Unreferenced(Dummy);
   begin
      -- Now, rather than deleting all of these at once, we'll give the list
      -- to a single instantiation of rm.
      Misc.Debug("Rm_Glob: start: count of files is "
                 & Integer'Image(Files'Length));
      -- Do nothing if there are no files....
      if Files'Length /= 0 then
      -- Construct a suitable array.
         declare
            FEA : UBS_Array(0 .. Files'Length + 2);
         begin
            FEA(0) := ToUBS("rm");
            FEA(1) := ToUBS("-f");
            for I in 1 .. Files'Length loop
               -- When I = 1, we refer to FEA(2 = I + 1)
               -- and Files(Files'First = Files''First + I - 1).
               FEA(I + 1) := Files(Files'First + I -1);
               -- We ignore Rm's return code.
               Dummy := ForkExec(Misc.Value_Nonempty(Config.Binary(Rm)), FEA);
            end loop;
         end;
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Rm_Glob");
         raise;
   end Rm_Glob;

   procedure Rm_Tempfiles is
   begin
      -- We ignore Rm's return code.
      Rm_Glob(ToStr(Topal_Directory) & "/temp*");
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Rm_Tempfiles");
         raise;
   end Rm_Tempfiles;

   procedure Rm_Tempfiles_PID is
   begin
      -- We ignore Rm's return code.
      Rm_Glob(ToStr(Topal_Directory) & "/temp-"
              & Misc.Trim_Leading_Spaces(Integer'Image(Our_PID))
              & "*");
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Rm_Tempfiles_PID");
         raise;
   end Rm_Tempfiles_PID;

   procedure Rm_Cachefiles is
   begin
      -- We ignore Rm's return code.
      Rm_Glob(ToStr(Topal_Directory) & "/cache/*");
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Rm_Cachefiles");
         raise;
   end Rm_Cachefiles;

   -- General sed invocation.  Only accepts a SINGLE argument.
   procedure Sed_InOut (A      : in String;
                        Source : in String;
                        Target : in String) is
   begin
      if ForkExec_InOut(Misc.Value_Nonempty(Config.Binary(Sed)),
                        UBS_Array'(0 => ToUBS("sed"),
                                   1 => ToUBS(A)),
                        Source => Source,
                        Target => Target) /=  0 then
         Misc.Error("Sed failed! (ff6)");
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Sed_InOut");
         raise;
   end Sed_InOut;

   procedure Stty_Sane is
   begin
      if ForkExec(Misc.Value_Nonempty(Config.Binary(Stty)),
               UBS_Array'(0 => ToUBS("stty"),
                          1 => ToUBS("sane"))) /= 0 then
         Misc.Error("`stty sane' failed.");
      end if;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Stty_Sane");
         raise;
   end;

   function Test_D (D : in String)  return Boolean is
   begin
      return ForkExec(Misc.Value_Nonempty(Config.Binary(Test)),
                      UBS_Array'(0 => ToUBS("test"),
                                 1 => ToUBS("-d"),
                                 2 => ToUBS(D))) = 0;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Test_D");
         raise;
   end Test_D;

   function Test_F (D : in String)  return Boolean is
   begin
      return ForkExec(Misc.Value_Nonempty(Config.Binary(Test)),
                      UBS_Array'(0 => ToUBS("test"),
                                 1 => ToUBS("-f"),
                                 2 => ToUBS(D))) = 0;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Test_F");
         raise;
   end Test_F;

   function Test_R (D : in String) return Boolean is
   begin
      return ForkExec(Misc.Value_Nonempty(Config.Binary(Test)),
                      UBS_Array'(0 => ToUBS("test"),
                                 1 => ToUBS("-r"),
                                 2 => ToUBS(D))) = 0;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Test_R");
         raise;
   end Test_R;

   function Test_P (D : in String) return Boolean is
   begin
      return ForkExec(Misc.Value_Nonempty(Config.Binary(Test)),
                      UBS_Array'(0 => ToUBS("test"),
                                 1 => ToUBS("-p"),
                                 2 => ToUBS(D))) = 0;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Test_P");
         raise;
   end Test_P;

   function Test_S (D : in String) return Boolean is
   begin
      return ForkExec(Misc.Value_Nonempty(Config.Binary(Test)),
                      UBS_Array'(0 => ToUBS("test"),
                                 1 => ToUBS("-S"),
                                 2 => ToUBS(D))) = 0;
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Test_S");
         raise;
   end Test_S;

   -- Guess content type of F using `file'.
   function Guess_Content_Type (F : in String) return String is
      use Misc;
      E1, E2     : Integer;
      GCT        : constant String        := Temp_File_Name("gct");
      H          : Ada.Text_IO.File_Type;
      First_Line : UBS;
   begin
      ForkExec2_Out(File1 => Value_Nonempty(Config.Binary(File)),
                    Argv1 => UBS_Array'(0 => ToUBS("file"),
                                        1 => ToUBS("-bi"),
                                        2 => ToUBS(F)),
                    Exit1 => E1,
                    File2 => Value_Nonempty(Config.Binary(Sed)),
                    Argv2 => UBS_Array'(0 => ToUBS("sed"),
                                        1 => ToUBS("s/,.*//")),
                    Exit2 => E2,
                    Target => GCT);
      if E1 /= 0 then
         Error("Problem generating keylist, GPG barfed (ff7a)");
      end if;
      if E2 /= 0 then
         Error("Problem generating keylist, grep barfed (ff7b)");
      end if;
      Ada.Text_IO.Open(File => H,
                       Mode => Ada.Text_IO.In_File,
                       Name => GCT);
      First_Line := Unbounded_Get_Line(H);
      Ada.Text_IO.Close(H);
      return ToStr(First_Line);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Guess_Content_Type");
         raise;
   end Guess_Content_Type;



   -- Use locale charmap to get the current keymap.
   function Get_Charmap return String is
      use Misc;
      E          : Integer;
      GCM        : constant String        := Temp_File_Name("gcm");
      H          : Ada.Text_IO.File_Type;
      First_Line : UBS;
   begin
      E := ForkExec_Out(File => Value_Nonempty(Config.Binary(Locale)),
                        Argv => ToUBS("locale charmap"),
                        Target => GCM);
      if E /= 0 then
         Error("Problem extracting charmap from locale (ff10)");
      end if;
      Ada.Text_IO.Open(File => H,
                       Mode => Ada.Text_IO.In_File,
                       Name => GCM);
      First_Line := Unbounded_Get_Line(H);
      Ada.Text_IO.Close(H);
      return ToStr(First_Line);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Get_Charmap");
         raise;
   end Get_Charmap;

   -- Convert charmaps within a single file (F) from charset CF to
   --  charset CT.
   procedure Convert_Charmap (F, CF, CT : in String) is
      use Misc;
      E          : Integer;
      CCM        : constant String        := Temp_File_Name("ccm");
   begin
      Misc.Debug("Convert_Charmap: attempt conversion of file `"
                 & F
                 & "' from charset `"
                 & CF
                 & "' to `"
                 & CT
                 & "'");
      E := ForkExec_InOut(File   => Value_Nonempty(Config.Binary(Iconv)),
                          Argv   => ToUBS("iconv -f " & CF & " -t " & CT),
                          Source => F,
                          Target => CCM);
      if E /= 0 then
         Error("Problem converting file charmap (ff15)");
      end if;
      -- Overwrite F.
      Mv_F(CCM, F);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.Convert_Charmap");
         raise;
   end Convert_Charmap;

   -- Equivalent of `system'.
   function System (Argv : UBS) return Integer is
      A : constant UBS_Array := Misc.Split_Arguments(Argv);
   begin
      return ForkExec(ToStr(A(A'First)), Argv);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.System (Argv=`" & ToStr(Argv) & "'");
         raise;
   end System;

   function System (Argv : String) return Integer is
   begin
      return System(ToUBS(Argv));
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.System (B)");
         raise;
   end System;

   function System_In (Argv : String; Source : String) return Integer is
      A : constant UBS_Array := Misc.Split_Arguments(ToUBS(Argv));
   begin
      return ForkExec_In(ToStr(A(A'First)), A, Source);
   exception
      when others =>
         Ada.Text_IO.Put_Line(Ada.Text_IO.Standard_Error,
                              "Exception raised in Externals.Simple.System_In (Argv=‘" & Argv & "’, Source=‘" & Source & "’");
	 raise;
   end System_In;

end Externals.Simple;
