-------------------------------------------------------------------------------
--
--  <STRONG>Copyright &copy; 2001, 2002 by Thomas Wolf.</STRONG>
--  <BLOCKQUOTE>
--    This piece of software is free software; you can redistribute it and/or
--    modify it under the terms of the  GNU General Public License as published
--    by the Free Software  Foundation; either version 2, or (at your option)
--    any later version. This software is distributed in the hope that it will
--    be useful, but <EM>without any warranty</EM>; without even the implied
--    warranty of <EM>merchantability or fitness for a particular purpose.</EM>
--    See the GNU General Public License for  more details. You should have
--    received a copy of the GNU General Public License with this distribution,
--    see file "<A HREF="GPL.txt">GPL.txt</A>". If not, write to the Free
--    Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307,
--    USA.
--  </BLOCKQUOTE>
--  <BLOCKQUOTE>
--    As a special exception from the GPL, if other files instantiate generics
--    from this unit, or you link this unit with other files to produce an
--    executable, this unit does not by itself cause the resulting executable
--    to be covered by the GPL. This exception does not however invalidate any
--    other reasons why the executable file might be covered by the GPL.
--  </BLOCKQUOTE>
--
--  <AUTHOR>
--    Thomas Wolf  (TW) <E_MAIL>
--  </AUTHOR>
--
--  <PURPOSE>
--    This is a thick binding to the @popen@ and @pclose@ routines available
--    on both Unix and Win32. It gives a convenient way to execute an external
--    program and pass it some input, or read its output.
--  </PURPOSE>
--
--  <TASKING>
--    Neither task- not abortion-safe. All operations here should be
--    considered <EM>potentially blocking</EM>.
--  </TASKING>
--
--  <NO_STORAGE>
--
--  <HISTORY>
--    26-APR-2002   TW  Initial version.
--    29-APR-2002   TW  Added auto-close feature.
--  </HISTORY>
-------------------------------------------------------------------------------

pragma License (Modified_GPL);

with Ada.Finalization;
with Ada.IO_Exceptions;
with Ada.Streams;

with Interfaces.C.Strings;

package Util.Pipes is

   type Stream_Mode is (In_Stream, Out_Stream);
   --  Used in @Open@ to specify the type of the stream.

   type Pipe_Stream is new Ada.Streams.Root_Stream_Type with private;
   --  I have found no indication whatsoever about the type of the underlying
   --  C stream (binary or text mode?). Although you <EM>can</EM> write and
   --  read binary data to and from a @Pipe_Stream@, it's probably prudent to
   --  consider these streams as <EM>text-only</EM>, because they set up pipes
   --  to or from an external processes stdin or stdout handles, which are
   --  defined by the ISO C standard as text streams.

   Name_Error : exception renames Ada.IO_Exceptions.Name_Error;
   --  Raised by @Open@ if the command cannot be executed.

   Use_Error  : exception renames Ada.IO_Exceptions.Use_Error;
   --  Raised by any stream operation if the stream is not open (and by @Open@,
   --  if it already is open).

   Mode_Error : exception renames Ada.IO_Exceptions.Mode_Error;
   --  Stream operations on the returned stream may raise @Mode_Error@ if
   --  writing on an @In_Stream@ or reading from an @Out_Stream@ is attempted.

   End_Error  : exception renames Ada.IO_Exceptions.End_Error;
   --  Stream operations on an @In_Stream@ will raise @End_Error@ if EOF is
   --  encountered.

   Device_Error : exception renames Ada.IO_Exceptions.Device_Error;
   --  Raised by the @Put@ or @Get@ operations (and also by @Write@ and
   --  @Read@) if the underlying system operations indicate any reading or
   --  writing error.

   procedure Open
     (Stream  : in out Pipe_Stream;
      Command : in     String;
      Mode    : in     Stream_Mode;
      Close   : in     Boolean := True;
      Std_In  : in     String  := "");
   --  Executes @Command@, which may contain arguments, setting it up such that
   --  the executed command's stdin comes from the returned stream if @Mode@
   --  is @Out_Stream@, or the command's stdout can be read from that stream
   --  if @Mode@ is @In_Stream@. Note that "in" and "out" are from the caller's
   --  perspective.
   --
   --  If <CODE>Close = True</CODE>, the stream is closed automatically when
   --  the stream object disappears. Note that this may involve waiting until
   --  the external command terminates. @Out_Stream@s always are closed
   --  automatically.
   --
   --  Raises @Name_Error@ if the @Command@ cannot be executed, or @Use_Error@
   --  if the stream is already open.
   --
   --  Note that an <EM>output filter</EM> is created using a stream of mode
   --  @Out_Stream@. The external process will share its stdout with the
   --  calling application, so you should carefully flush your stdout before
   --  opening such a pipe, otherwise, output may become garbled and mixed up.
   --  The same applies to an <EM>input filter</EM>: it shares its stdin with
   --  the calling application! Unfortunately, one cannot flush an input stream
   --  (that would be a meaningless operation anyway), so be careful!
   --
   --  One way to avoid such problems is using the @Std_In@ parameter. If it
   --  is not the empty string, the command's stdin is redirected to come
   --  from the given filename (which had better be valid). In other words,
   --  the command actually executed is <CODE>Command & " <" & Std_In</CODE>.
   --  If this feature is used, the caller is responsible for removing the
   --  file, if that is desired.

   procedure Close
     (Stream    : in out Pipe_Stream;
      Exit_Code :    out Integer);
   --  Closes a stream obtained through @Open@. Raises @Use_Error@ if the
   --  stream has not been opened.
   --
   --  An @Out_Stream@ <EM>must</EM> be closed, otherwise your program might
   --  not terminate. therefore, @Pipe_Stream@s are controlled, and any
   --  @Out_Stream@ is closed automatically when the stream object disappears.
   --
   --  An @In_Stream@ is only closed automatically if it has been opened with
   --  <CODE>Close = True</CODE>, because closing such a stream may involve
   --  waiting until the external command has terminated.
   --
   --  Note: this is a blocking call: it waits until the executed command
   --  has terminated, and then returns that commands exit code.

   function Is_Open
     (Stream : in Pipe_Stream)
     return Boolean;
   --  Returns @True@ if the @Stream@ is open, and @False@ otherwise.

   function End_Of_Stream
     (Stream : in Pipe_Stream)
     return Boolean;
   --  Raises @Mode_Error@ if the @Stream@ is an @Out_Stream@. Otherwise,
   --  returns @True@ if the @In_Stream@s end has been reached and @False@
   --  otherwise.

   procedure Put
     (Stream : in Pipe_Stream;
      Text   : in String);
   --  Same semantics as @Ada.Text_IO.Put@.

   procedure Put
     (Stream : in Pipe_Stream;
      Ch     : in Character);
   --  Same semantics as @Put@ for a string.

   procedure Put_Line
     (Stream : in Pipe_Stream;
      Text   : in String);
   --  Same semantics as @Ada.Text_IO.Put_Line@.

   procedure Get
     (Stream : in     Pipe_Stream;
      Ch     :    out Character);
   --  Same semantics as @Ada.Text_IO.Get@.

   procedure Get
     (Stream : in     Pipe_Stream;
      Buffer :    out String);
   --  Same semantics as @Ada.Text_IO.Get@.

   procedure Get_Line
     (Stream : in     Pipe_Stream;
      Buffer :    out String;
      Last   :    out Natural);
   --  Same semantics as @Ada.Text_IO.Get_Line@.

   function Get_Line
     (Stream : in Pipe_Stream)
     return String;
   --  As @Ada.Text_IO.Get_Line@, but always returns a full line.

private

   type C_File_Ptr is new Interfaces.C.Strings.chars_ptr;

   Null_Ptr : constant C_File_Ptr :=
     C_File_Ptr (Interfaces.C.Strings.Null_Ptr);

   type Closer (Stream : access Pipe_Stream) is
     new Ada.Finalization.Limited_Controlled with null record;

   procedure Finalize (C : in out Closer);

   type Pipe_Stream is new Ada.Streams.Root_Stream_Type with
      record
         F        : C_File_Ptr := Null_Ptr;
         Mode     : Stream_Mode;
         Close_It : Boolean    := True;
         Clean_Up : Closer (Pipe_Stream'Access);
      end record;

   procedure Read
     (Stream : in out Pipe_Stream;
      Item   :    out Ada.Streams.Stream_Element_Array;
      Last   :    out Ada.Streams.Stream_Element_Offset);

   procedure Write
     (Stream : in out Pipe_Stream;
      Item   : in     Ada.Streams.Stream_Element_Array);

end Util.Pipes;
