0023152: Possibility to have echo of DRAW commands in log file
authorabv <abv@opencascade.com>
Thu, 28 Jun 2012 13:46:43 +0000 (17:46 +0400)
committerabv <abv@opencascade.com>
Thu, 28 Jun 2012 13:46:43 +0000 (17:46 +0400)
Two commands are added in DRAW:

decho: allows switch on/off echo of commands and their results. When echo is on, all commands implemented as OCCT DRAW C procedures will be echoed to standard output, along with their result. This can be useful to trace process of execution of script evaluated by 'source' command.

dlog: implements off-screen log for recording DRAW commands and their output for further processing in Tcl script (mainly for use in automatic tests). Run this command without arguments to get help.
Added ios::sync_with_stdio() call to Draw::BasicCommands.
Correction for compilation on Linux

src/Draw/Draw_BasicCommands.cxx
src/Draw/Draw_Interpretor.cdl
src/Draw/Draw_Interpretor.cxx

index ab56205..bd39550 100755 (executable)
@@ -211,6 +211,67 @@ static Standard_Integer spy(Draw_Interpretor& di, Standard_Integer n, const char
   return 0;
 }
 
+static Standard_Integer dlog(Draw_Interpretor& di, Standard_Integer n, const char** a)
+{
+  if (n != 2 && n != 3)
+  {
+    cout << "Enable or disable logging: " << a[0] << " {on|off}" << endl;
+    cout << "Reset log: " << a[0] << " reset" << endl;
+    cout << "Get log content: " << a[0] << " get" << endl;
+    return 1;
+  }
+
+  if (! strcmp (a[1], "on") && n == 2)
+  {
+    di.SetDoLog (Standard_True);
+//    di.Log() << "dlog on" << endl; // for symmetry
+  }
+  else if (! strcmp (a[1], "off") && n == 2)
+  {
+    di.SetDoLog (Standard_False);
+  }
+  else if (! strcmp (a[1], "reset") && n == 2)
+  {
+    di.Log().str("");
+  }
+  else if (! strcmp (a[1], "get") && n == 2)
+  {
+    di << di.Log().str().c_str();
+  }
+  else if (! strcmp (a[1], "add") && n == 3)
+  {
+    di.Log() << a[2] << "\n";
+  }
+  else {
+    cout << "Unrecognized option(s): " << a[1] << endl;
+    return 1;
+  }
+  return 0;
+}
+
+static Standard_Integer decho(Draw_Interpretor& di, Standard_Integer n, const char** a)
+{
+  if (n != 2)
+  {
+    cout << "Enable or disable echoing: " << a[0] << " {on|off}" << endl;
+    return 1;
+  }
+
+  if (! strcmp (a[1], "on"))
+  {
+    di.SetDoEcho (Standard_True);
+  }
+  else if (! strcmp (a[1], "off"))
+  {
+    di.SetDoEcho (Standard_False);
+  }
+  else {
+    cout << "Unrecognized option: " << a[1] << endl;
+    return 1;
+  }
+  return 0;
+}
+
 //=======================================================================
 //function : wait
 //purpose  : 
@@ -479,6 +540,8 @@ void Draw::BasicCommands(Draw_Interpretor& theCommands)
   if (Done) return;
   Done = Standard_True;
 
+  ios::sync_with_stdio();
+
   const char* g = "DRAW General Commands";
   
   theCommands.Add("batch", "returns 1 in batch mode",
@@ -500,4 +563,11 @@ void Draw::BasicCommands(Draw_Interpretor& theCommands)
     "meminfo [virt|v] [wset|w] [wsetpeak] [swap] [swappeak] [private]"
     " : memory counters for this process",
          __FILE__, dmeminfo, g);
+
+  // Logging commands; note that their names are hard-coded in the code 
+  // of Draw_Interpretor, thus should not be changed without update of that code!
+  theCommands.Add("dlog", "manage logging of commands and output; run without args to get help",
+                 __FILE__,dlog,g);
+  theCommands.Add("decho", "switch on / off echo of commands to cout; run without args to get help",
+                 __FILE__,decho,g);
 }
index fc87d47..5c563d1 100755 (executable)
@@ -27,6 +27,7 @@ class Interpretor from Draw
 
 uses
 
+        SStream         from Standard,
        PInterp         from Draw,
        CommandFunction from Draw,
        AsciiString     from TCollection,
@@ -143,10 +144,37 @@ is
     Set(me : in out; anInterp : PInterp from Draw);
     
     Interp (me) returns PInterp from Draw;
+
+    SetDoLog (me: in out; doLog: Boolean);
+    ---Purpose: Enables or disables logging of all commands and their
+    -- results
+    ---Level: Advanced
+
+    SetDoEcho (me: in out; doEcho: Boolean);
+    ---Purpose: Enables or disables eachoing of all commands and their
+    -- results to cout
+    ---Level: Advanced
+
+    GetDoLog (me) returns Boolean;
+    ---Purpose: Returns true if logging of commands is enabled
+    ---Level: Advanced
+
+    GetDoEcho (me) returns Boolean;
+    ---Purpose: Returns true if echoing of commands is enabled
+    ---Level: Advanced
+
+    Log (me: in out) returns SStream;
+    ---Purpose: Returns log stream
+    ---Level: Advanced
+    ---C++: return &
        
  fields
  
     isAllocated : Boolean from Standard;
     myInterp    : PInterp from Draw;
 
+    myDoLog: Boolean;
+    myDoEcho: Boolean;
+    myLog: SStream from Standard;
+
 end Interpretor;
index 3b66cd5..e26e405 100755 (executable)
 #include <TCollection_ExtendedString.hxx>
 
 #include <string.h>
-
 #include <tcl.h>
 
+// for capturing of cout and cerr (dup(), dup2())
+#ifdef _MSC_VER
+#include <io.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if ! defined(STDOUT_FILENO)
+#define STDOUT_FILENO fileno(stdout)
+#endif
+#if ! defined(STDERR_FILENO)
+#define STDERR_FILENO fileno(stderr)
+#endif
+
 #if ((TCL_MAJOR_VERSION > 8) || ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 1)))
 #define TCL_USES_UTF8
 #endif
@@ -87,6 +101,66 @@ struct CData {
   Draw_Interpretor*    i;
 };
 
+// logging helpers
+namespace {
+  void dumpArgs (Standard_OStream& os, int argc, const char *argv[])
+  {
+    for (int i=0; i < argc; i++)
+      os << argv[i] << " ";
+    os << endl;
+  }
+
+  void flush_standard_streams ()
+  {
+    fflush (stderr);
+    fflush (stdout);
+    cerr << flush;
+    cout << flush;
+  }
+
+  FILE* capture_start (int std_fd, int *save_fd)
+  {
+    (*save_fd) = 0;
+
+    // open temporary files
+    FILE * aTmpFile = tmpfile();
+    int fd_tmp = fileno(aTmpFile);
+
+    if (fd_tmp <0) 
+    {
+      cerr << "Error: cannot create temporary file for capturing console output" << endl;
+      fclose (aTmpFile);
+      return NULL;
+    }
+
+    // remember current file descriptors of standard stream, and replace it by temporary
+    (*save_fd) = dup(std_fd);
+    dup2(fd_tmp, std_fd);
+    return aTmpFile;
+  }
+
+  void capture_end (FILE* tmp_file, int std_fd, int save_fd, Standard_OStream &log, Standard_Boolean doEcho)
+  {
+    // restore normal descriptors of console stream
+    dup2 (save_fd, std_fd);
+    close(save_fd);
+
+    // extract all output and copy it to log and optionally to cout
+    const int BUFSIZE = 2048;
+    char buf[BUFSIZE];
+    rewind(tmp_file);
+    while (fgets (buf, BUFSIZE, tmp_file) != NULL)
+    {
+      log << buf;
+      if (doEcho) 
+        cout << buf;
+    }
+
+    // close temporary file
+    fclose (tmp_file);
+  }
+};
+
 // MKV 29.03.05
 #if ((TCL_MAJOR_VERSION > 8) || ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 4))) && !defined(USE_NON_CONST)
 static Standard_Integer CommandCmd 
@@ -101,14 +175,39 @@ static Standard_Integer CommandCmd
   static Standard_Integer code;
   code = TCL_OK;
   CData* C = (CData*) clientData;
+  Draw_Interpretor& di = *(C->i);
+
+  // log command execution, except commands manipulating log itself and echo
+  Standard_Boolean isLogManipulation = (strcmp (argv[0], "dlog") == 0 || 
+                                        strcmp (argv[0], "decho") == 0);
+  Standard_Boolean doLog  = (di.GetDoLog() && ! isLogManipulation);
+  Standard_Boolean doEcho = (di.GetDoEcho() && ! isLogManipulation);
+  if (doLog)
+    dumpArgs (di.Log(), argc, argv);
+  if (doEcho)
+    dumpArgs (cout, argc, argv);
+
+  // flush cerr and cout
+  flush_standard_streams();
+
+  // capture cout and cerr to log
+  FILE * aFile_err = NULL;
+  FILE * aFile_out = NULL;
+  int fd_err_save = 0;
+  int fd_out_save = 0;
+  if (doLog)
+  {
+    aFile_out = capture_start (STDOUT_FILENO, &fd_out_save);
+    aFile_err = capture_start (STDERR_FILENO, &fd_err_save);
+  }
 
+  // run command
   try {
     OCC_CATCH_SIGNALS
 
     // OCC63: Convert strings from UTF-8 to local encoding, normally expected by OCC commands
     TclUTFToLocalStringSentry anArgs ( argc, (const char**)argv );
       
-    Draw_Interpretor& di = *(C->i);
     Standard_Integer fres = C->f ( di, argc, anArgs.GetArgv() );
     if (fres != 0) 
       code = TCL_ERROR;
@@ -147,7 +246,33 @@ static Standard_Integer CommandCmd
 #endif    
     code = TCL_ERROR;
   }
-  
+
+  // flush streams
+  flush_standard_streams();
+
+  // end capturing cout and cerr 
+  if (doLog) 
+  {
+    capture_end (aFile_err, STDERR_FILENO, fd_err_save, di.Log(), doEcho);
+    capture_end (aFile_out, STDOUT_FILENO, fd_out_save, di.Log(), doEcho);
+  }
+
+  // log command result
+  const char* aResultStr = NULL;
+  if (doLog)
+  {
+    aResultStr = Tcl_GetStringResult (interp);
+    if (aResultStr != 0 && aResultStr[0] != '\0' )
+      di.Log() << Tcl_GetStringResult (interp) << endl;
+  }
+  if (doEcho)
+  {
+    if (aResultStr == NULL)
+      aResultStr = Tcl_GetStringResult (interp);
+    if (aResultStr != 0 && aResultStr[0] != '\0' )
+      cout << Tcl_GetStringResult (interp) << endl;
+  }
+
   return code;
 }
 
@@ -164,7 +289,7 @@ static void CommandDelete (ClientData clientData)
 //=======================================================================
 
 Draw_Interpretor::Draw_Interpretor() :
-  isAllocated(Standard_False)
+  isAllocated(Standard_False), myDoLog(Standard_False), myDoEcho(Standard_False)
 {
 // The tcl interpreter is not created immediately as it is kept 
 // by a global variable and created and deleted before the main().
@@ -191,7 +316,9 @@ void Draw_Interpretor::Init()
 
 Draw_Interpretor::Draw_Interpretor(const Draw_PInterp& p) :
   isAllocated(Standard_False),
-  myInterp(p)
+  myInterp(p),
+  myDoLog(Standard_False),
+  myDoEcho(Standard_False)
 {
 }
 
@@ -535,3 +662,33 @@ void Draw_Interpretor::Set(const Draw_PInterp& PIntrp)
   isAllocated = Standard_False;
   myInterp = PIntrp;
 }
+
+//=======================================================================
+//function : Logging
+//purpose  : 
+//=======================================================================
+
+void Draw_Interpretor::SetDoLog (Standard_Boolean doLog)
+{
+  myDoLog = doLog;
+}
+
+void Draw_Interpretor::SetDoEcho (Standard_Boolean doEcho)
+{
+  myDoEcho = doEcho;
+}
+
+Standard_Boolean Draw_Interpretor::GetDoLog () const
+{
+  return myDoLog;
+}
+
+Standard_Boolean Draw_Interpretor::GetDoEcho () const
+{
+  return myDoEcho;
+}
+
+Standard_SStream& Draw_Interpretor::Log ()
+{
+  return myLog;
+}
\ No newline at end of file