0031875: Draw Harness, ViewerTest - command vaspects -mostContinuity lacks g1 and...
[occt.git] / src / Draw / Draw_Interpretor.cxx
1 // Created on: 1995-02-23
2 // Created by: Remi LEQUETTE
3 // Copyright (c) 1995-1999 Matra Datavision
4 // Copyright (c) 1999-2014 OPEN CASCADE SAS
5 //
6 // This file is part of Open CASCADE Technology software library.
7 //
8 // This library is free software; you can redistribute it and/or modify it under
9 // the terms of the GNU Lesser General Public License version 2.1 as published
10 // by the Free Software Foundation, with special exception defined in the file
11 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
12 // distribution for complete text of the license and disclaimer of any warranty.
13 //
14 // Alternatively, this file may be used under the terms of Open CASCADE
15 // commercial license or contractual agreement.
16
17 #include <Draw_Interpretor.hxx>
18
19 #include <Draw_Appli.hxx>
20 #include <Message.hxx>
21 #include <Message_PrinterOStream.hxx>
22 #include <OSD.hxx>
23 #include <OSD_File.hxx>
24 #include <OSD_Path.hxx>
25 #include <OSD_Process.hxx>
26 #include <Standard_SStream.hxx>
27 #include <Standard_RangeError.hxx>
28 #include <Standard_ErrorHandler.hxx>
29 #include <Standard_Macro.hxx>
30 #include <TCollection_AsciiString.hxx>
31 #include <TCollection_ExtendedString.hxx>
32
33 #include <string.h>
34 #include <tcl.h>
35 #include <fcntl.h>
36 #ifndef _WIN32
37 #include <unistd.h>
38 #endif
39
40 // for capturing of cout and cerr (dup(), dup2())
41 #ifdef _WIN32
42 #include <io.h>
43 #include <sys/stat.h>  
44 #endif
45
46 #if ! defined(STDOUT_FILENO)
47 #define STDOUT_FILENO fileno(stdout)
48 #endif
49 #if ! defined(STDERR_FILENO)
50 #define STDERR_FILENO fileno(stderr)
51 #endif
52
53 #if ((TCL_MAJOR_VERSION > 8) || ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 1)))
54 #define TCL_USES_UTF8
55 #endif
56
57 // logging helpers
58 namespace {
59   void dumpArgs (Standard_OStream& os, int argc, const char *argv[])
60   {
61     for (int i=0; i < argc; i++)
62       os << argv[i] << " ";
63     os << std::endl;
64   }
65
66   void flush_standard_streams ()
67   {
68     fflush (stderr);
69     fflush (stdout);
70     std::cerr << std::flush;
71     std::cout << std::flush;
72   }
73
74   int capture_start (int theFDStd, int theFDLog)
75   {
76     Standard_ASSERT_RETURN (theFDLog >= 0, "Invalid descriptor of log file", -1);
77
78     // Duplicate a file descriptor of the standard stream to be able to restore output to it later
79     int aFDSave = dup (theFDStd);
80     if (aFDSave < 0)
81     {
82       perror ("Error capturing standard stream to log: dup() returned");
83       return -1;
84     }
85
86     // Redirect the stream to the log file
87     if (dup2 (theFDLog, theFDStd) < 0)
88     {
89       close (aFDSave);
90       perror ("Error capturing standard stream to log: dup2() returned");
91       return -1;
92     }
93
94     // remember saved file descriptor of standard stream
95     return aFDSave;
96   }
97
98   void capture_end (int theFDStd, int& theFDSave)
99   {
100     if (theFDSave < 0)
101       return;
102
103     // restore normal descriptors of console stream
104     if (dup2(theFDSave, theFDStd) < 0)
105     {
106       perror ("Error returning capturing standard stream to log: dup2() returned");
107       return;
108     }
109
110     // close saved file descriptor
111     close(theFDSave);
112     theFDSave = -1;
113   }
114
115 } // anonymous namespace
116
117 static Standard_Integer CommandCmd (ClientData theClientData, Tcl_Interp* interp, Standard_Integer argc, const char* argv[])
118 {
119   Standard_Integer code = TCL_OK;
120   Draw_Interpretor::CallBackData* aCallback = (Draw_Interpretor::CallBackData* )theClientData;
121   Draw_Interpretor& di = *(aCallback->myDI);
122
123   // log command execution, except commands manipulating log itself and echo
124   Standard_Boolean isLogManipulation = (strcmp (argv[0], "dlog") == 0 || 
125                                         strcmp (argv[0], "decho") == 0);
126   Standard_Boolean doLog  = (di.GetDoLog() && ! isLogManipulation);
127   Standard_Boolean doEcho = (di.GetDoEcho() && ! isLogManipulation);
128
129   // flush cerr and cout
130   flush_standard_streams();
131
132   // capture cout and cerr to log
133   int aFDstdout = STDOUT_FILENO;
134   int aFDstderr = STDERR_FILENO;
135   int aFDerr_save = -1;
136   int aFDout_save = -1;
137   if (doLog)
138   {
139     aFDout_save = capture_start (aFDstdout, di.GetLogFileDescriptor());
140     aFDerr_save = capture_start (aFDstderr, di.GetLogFileDescriptor());
141   }
142
143   if (doEcho || doLog)
144     dumpArgs (std::cout, argc, argv);
145
146   // run command
147   try
148   {
149     OCC_CATCH_SIGNALS
150
151     // get exception if control-break has been pressed 
152     OSD::ControlBreak();
153
154     Standard_Integer fres = aCallback->Invoke ( di, argc, argv /*anArgs.GetArgv()*/ );
155     if (fres != 0)
156     {
157       code = TCL_ERROR;
158     }
159   }
160   catch (Standard_Failure const& anException)
161   {
162     // fail if Draw_ExitOnCatch is set
163     const char* toExitOnCatch = Tcl_GetVar (interp, "Draw_ExitOnCatch", TCL_GLOBAL_ONLY);
164     if (toExitOnCatch != NULL && Draw::Atoi (toExitOnCatch))
165     {
166       Message::SendFail() << "An exception was caught " << anException;
167 #ifdef _WIN32
168       Tcl_Exit(0);
169 #else      
170       Tcl_Eval(interp,"exit");
171 #endif
172     }
173
174     Standard_SStream ss;
175     ss << "An exception was caught " << anException << std::ends;
176     Tcl_SetResult(interp,(char*)(ss.str().c_str()),TCL_VOLATILE);
177     code = TCL_ERROR;
178   }
179   catch (std::exception const& theStdException)
180   {
181     const char* toExitOnCatch = Tcl_GetVar (interp, "Draw_ExitOnCatch", TCL_GLOBAL_ONLY);
182     if (toExitOnCatch != NULL && Draw::Atoi (toExitOnCatch))
183     {
184       Message::SendFail() << "An exception was caught " << theStdException.what() << " [" << typeid(theStdException).name() << "]";
185     #ifdef _WIN32
186       Tcl_Exit (0);
187     #else
188       Tcl_Eval (interp, "exit");
189     #endif
190     }
191
192     Standard_SStream ss;
193     ss << "An exception was caught " << theStdException.what() << " [" << typeid(theStdException).name() << "]" << std::ends;
194     Tcl_SetResult(interp,(char*)(ss.str().c_str()),TCL_VOLATILE);
195     code = TCL_ERROR;
196   }
197   catch (...)
198   {
199     const char* toExitOnCatch = Tcl_GetVar (interp, "Draw_ExitOnCatch", TCL_GLOBAL_ONLY);
200     if (toExitOnCatch != NULL && Draw::Atoi (toExitOnCatch))
201     {
202       Message::SendFail() << "UNKNOWN exception was caught ";
203     #ifdef _WIN32
204       Tcl_Exit (0);
205     #else
206       Tcl_Eval (interp,"exit");
207     #endif
208     }
209
210     Standard_SStream ss;
211     ss << "UNKNOWN exception was caught " << std::ends;
212     Tcl_SetResult(interp,(char*)(ss.str().c_str()),TCL_VOLATILE);
213     code = TCL_ERROR;
214   }
215
216   // log command result
217   if (doLog || doEcho)
218   {
219     const char* aResultStr = Tcl_GetStringResult (interp);
220     if (aResultStr != 0 && aResultStr[0] != '\0' )
221     {
222       std::cout << aResultStr << std::endl;
223     }
224   }
225
226   // flush streams
227   flush_standard_streams();
228
229   // end capturing cout and cerr 
230   if (doLog) 
231   {
232     capture_end (aFDstderr, aFDerr_save);
233     capture_end (aFDstdout, aFDout_save);
234   }
235
236   return code;
237 }
238
239 static void CommandDelete (ClientData theClientData)
240 {
241   Draw_Interpretor::CallBackData* aCallback = (Draw_Interpretor::CallBackData* )theClientData;
242   delete aCallback;
243 }
244
245 //=======================================================================
246 //function : Draw_Interpretor
247 //purpose  :
248 //=======================================================================
249 Draw_Interpretor::Draw_Interpretor()
250 : // the tcl interpreter is not created immediately as it is kept
251   // by a global variable and created and deleted before the main()
252   myInterp (NULL),
253   isAllocated (Standard_False),
254   myDoLog (Standard_False),
255   myDoEcho (Standard_False),
256   myToColorize (Standard_True),
257   myFDLog (-1)
258 {
259   //
260 }
261
262 //=======================================================================
263 //function : Draw_Interpretor
264 //purpose  :
265 //=======================================================================
266 Draw_Interpretor::Draw_Interpretor (const Draw_PInterp& theInterp)
267 : myInterp (theInterp),
268   isAllocated (Standard_False),
269   myDoLog (Standard_False),
270   myDoEcho (Standard_False),
271   myToColorize (Standard_True),
272   myFDLog (-1)
273 {
274   //
275 }
276
277 //=======================================================================
278 //function : Init
279 //purpose  : It is necessary to call this function
280 //=======================================================================
281
282 void Draw_Interpretor::Init()
283 {
284   if (isAllocated) 
285     Tcl_DeleteInterp(myInterp);
286   isAllocated=Standard_True;
287   myInterp=Tcl_CreateInterp();
288 }
289
290 //=======================================================================
291 //function : SetToColorize
292 //purpose  :
293 //=======================================================================
294 void Draw_Interpretor::SetToColorize (Standard_Boolean theToColorize)
295 {
296   myToColorize = theToColorize;
297   for (Message_SequenceOfPrinters::Iterator aPrinterIter (Message::DefaultMessenger()->Printers());
298        aPrinterIter.More(); aPrinterIter.Next())
299   {
300     if (Handle(Message_PrinterOStream) aPrinter = Handle(Message_PrinterOStream)::DownCast (aPrinterIter.Value()))
301     {
302       aPrinter->SetToColorize (theToColorize);
303     }
304   }
305 }
306
307 //=======================================================================
308 //function : add
309 //purpose  :
310 //=======================================================================
311 void Draw_Interpretor::add (const Standard_CString          theCommandName,
312                             const Standard_CString          theHelp,
313                             const Standard_CString          theFileName,
314                             Draw_Interpretor::CallBackData* theCallback,
315                             const Standard_CString          theGroup)
316 {
317   Standard_ASSERT_RAISE (myInterp != NULL, "Attempt to add command to Null interpretor");
318
319   Standard_PCharacter aName  = (Standard_PCharacter )theCommandName;
320   Standard_PCharacter aHelp  = (Standard_PCharacter )theHelp;
321   Standard_PCharacter aGroup = (Standard_PCharacter )theGroup;
322   Tcl_CreateCommand (myInterp, aName, CommandCmd, (ClientData )theCallback, CommandDelete);
323
324   // add the help
325   Tcl_SetVar2 (myInterp, "Draw_Helps",  aName,  aHelp, TCL_GLOBAL_ONLY);
326   Tcl_SetVar2 (myInterp, "Draw_Groups", aGroup, aName,
327                      TCL_GLOBAL_ONLY | TCL_APPEND_VALUE | TCL_LIST_ELEMENT);
328
329   // add path to source file (keep not more than two last subdirectories)
330   if (theFileName  == NULL
331    || *theFileName == '\0')
332   {
333     return;
334   }
335
336   OSD_Path aPath (theFileName);
337   Standard_Integer nbTrek = aPath.TrekLength();
338   for (Standard_Integer i = 2; i < nbTrek; ++i)
339   {
340     aPath.RemoveATrek (1);
341   }
342   aPath.SetDisk ("");
343   aPath.SetNode ("");
344   TCollection_AsciiString aSrcPath;
345   aPath.SystemName (aSrcPath);
346   if (aSrcPath.Value(1) == '/')
347     aSrcPath.Remove(1);
348   Tcl_SetVar2 (myInterp, "Draw_Files", aName, aSrcPath.ToCString(), TCL_GLOBAL_ONLY);
349 }
350
351 //=======================================================================
352 //function : Remove
353 //purpose  : 
354 //=======================================================================
355
356 Standard_Boolean Draw_Interpretor::Remove(Standard_CString const n)
357 {
358   Standard_PCharacter pN;
359   //
360   pN=(Standard_PCharacter)n;
361  
362   Standard_Integer result = Tcl_DeleteCommand(myInterp,pN);
363   return result == 0;
364 }
365
366 //=======================================================================
367 //function : Result
368 //purpose  : 
369 //=======================================================================
370
371 Standard_CString Draw_Interpretor::Result() const
372 {
373 #if ((TCL_MAJOR_VERSION > 8) || ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 5)))
374   return Tcl_GetStringResult(myInterp);
375 #else
376   return myInterp->result;
377 #endif
378 }
379
380 //=======================================================================
381 //function : Reset
382 //purpose  : 
383 //=======================================================================
384
385 void Draw_Interpretor::Reset()
386 {
387   Tcl_ResetResult(myInterp);
388 }
389
390 //=======================================================================
391 //function : Append
392 //purpose  : 
393 //=======================================================================
394
395 Draw_Interpretor& Draw_Interpretor::Append(const Standard_CString s)
396 {
397   Tcl_AppendResult(myInterp,s,(Standard_CString)0);
398   return *this;
399 }
400
401 //=======================================================================
402 //function : Append
403 //purpose  : 
404 //=======================================================================
405
406 Draw_Interpretor& Draw_Interpretor::Append(const TCollection_AsciiString& s)
407 {
408   return Append (s.ToCString());
409 }
410
411 //=======================================================================
412 //function : Append
413 //purpose  : 
414 //=======================================================================
415
416 Draw_Interpretor& Draw_Interpretor::Append(const TCollection_ExtendedString& theString)
417 {
418 #ifdef TCL_USES_UTF8
419   // Convert string to UTF-8 format for Tcl
420   char *str = new char[theString.LengthOfCString()+1];
421   theString.ToUTF8CString (str);
422   Tcl_AppendResult ( myInterp, str, (Standard_CString)0 );
423   delete[] str;
424 #else
425   // put as ascii string, replacing non-ascii characters by '?'
426   TCollection_AsciiString str (theString, '?');
427   Tcl_AppendResult(myInterp,str.ToCString(),(Standard_CString)0);
428 #endif
429   return *this;
430 }
431
432 //=======================================================================
433 //function : Append
434 //purpose  : 
435 //=======================================================================
436
437 Draw_Interpretor& Draw_Interpretor::Append(const Standard_Integer i)
438 {
439   char c[100];
440   Sprintf(c,"%d",i);
441   Tcl_AppendResult(myInterp,c,(Standard_CString)0);
442   return *this;
443 }
444
445 //=======================================================================
446 //function : Append
447 //purpose  : 
448 //=======================================================================
449
450 Draw_Interpretor& Draw_Interpretor::Append(const Standard_Real r)
451 {
452   char s[100];
453   Sprintf(s,"%.17g",r);
454   Tcl_AppendResult(myInterp,s,(Standard_CString)0);
455   return *this;
456 }
457
458 //=======================================================================
459 //function : Append
460 //purpose  : 
461 //=======================================================================
462
463 Draw_Interpretor& Draw_Interpretor::Append(const Standard_SStream& s)
464 {
465   return Append (s.str().c_str());
466 }
467
468 //=======================================================================
469 //function : AppendElement
470 //purpose  : 
471 //=======================================================================
472
473 void Draw_Interpretor::AppendElement(const Standard_CString s)
474 {
475   Tcl_AppendElement(myInterp, s);
476 }
477
478 //=======================================================================
479 //function : Eval
480 //purpose  : 
481 //=======================================================================
482
483 Standard_Integer Draw_Interpretor::Eval(const Standard_CString line)
484 {
485   return Tcl_Eval(myInterp,line);
486 }
487
488
489 //=======================================================================
490 //function : Eval
491 //purpose  : 
492 //=======================================================================
493
494 Standard_Integer Draw_Interpretor::RecordAndEval(const Standard_CString line,
495                                                  const Standard_Integer flags)
496 {
497   return Tcl_RecordAndEval(myInterp,line,flags);
498 }
499
500 //=======================================================================
501 //function : EvalFile
502 //purpose  : 
503 //=======================================================================
504
505 Standard_Integer Draw_Interpretor::EvalFile(const Standard_CString fname)
506 {
507   return Tcl_EvalFile(myInterp,fname);
508 }
509
510 //=======================================================================
511 //function : PrintHelp
512 //purpose  :
513 //=======================================================================
514
515 Standard_Integer Draw_Interpretor::PrintHelp (const Standard_CString theCommandName)
516 {
517   TCollection_AsciiString aCmd     = TCollection_AsciiString ("help ") + theCommandName;
518   Standard_PCharacter     aLinePtr = (Standard_PCharacter )aCmd.ToCString();
519   return Tcl_Eval (myInterp, aLinePtr);
520 }
521
522 //=======================================================================
523 //function :Complete
524 //purpose  : 
525 //=======================================================================
526
527 Standard_Boolean Draw_Interpretor::Complete(const Standard_CString line)
528 {
529   Standard_PCharacter pLine;
530   //
531   pLine=(Standard_PCharacter)line;
532   return Tcl_CommandComplete (pLine) != 0;
533 }
534
535 //=======================================================================
536 //function : Destroy
537 //purpose  : 
538 //=======================================================================
539
540 Draw_Interpretor::~Draw_Interpretor()
541 {
542   SetDoLog (Standard_False);
543   if (myFDLog >=0)
544   {
545     close (myFDLog);
546     myFDLog = 0;
547   }
548
549   // MKV 01.02.05
550 #if ((TCL_MAJOR_VERSION > 8) || ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 4)))
551   try {
552     OCC_CATCH_SIGNALS
553     Tcl_Exit(0);
554   }
555   catch (Standard_Failure const&) {
556 #ifdef OCCT_DEBUG
557     std::cout <<"Tcl_Exit have an exeption" << std::endl;
558 #endif
559   }
560 #else
561 #ifdef _WIN32
562   Tcl_Exit(0);
563 #endif  
564 #endif
565 }
566
567 //=======================================================================
568 //function : Interp
569 //purpose  : 
570 //=======================================================================
571
572 Draw_PInterp Draw_Interpretor::Interp() const
573 {
574   Standard_DomainError_Raise_if (myInterp==NULL , "No call for  Draw_Interpretor::Init()");
575   return myInterp;
576 }
577
578 void Draw_Interpretor::Set(const Draw_PInterp& PIntrp)
579 {
580   if (isAllocated)
581     Tcl_DeleteInterp(myInterp);
582   isAllocated = Standard_False;
583   myInterp = PIntrp;
584 }
585
586 //=======================================================================
587 //function : Logging
588 //purpose  : 
589 //=======================================================================
590
591 void Draw_Interpretor::SetDoLog (Standard_Boolean doLog)
592 {
593   if (myDoLog == doLog)
594     return;
595
596   // create log file if not opened yet
597   if (doLog && myFDLog < 0)
598   {
599 #ifdef _WIN32
600     char tmpfile[L_tmpnam + 1];
601     tmpnam(tmpfile);
602     myFDLog = open (tmpfile, O_RDWR | O_CREAT | O_EXCL | O_TEMPORARY, S_IREAD | S_IWRITE);
603 #else
604     // according to Linux Filesystem Hierarchy Standard, 3.17,
605     // /tmp/ is the right directory for temporary files
606     char tmpfile[256] = "/tmp/occt_draw_XXXXXX";
607     myFDLog = mkstemp (tmpfile);
608     if (myFDLog >= 0)
609     {
610 //      printf ("Tmp file: %s\n", tmpfile);
611       unlink (tmpfile); // make sure the file will be deleted on close
612     }
613 #endif
614     if (myFDLog < 0)
615     {
616       perror ("Error creating temporary file for capturing console output");
617       printf ("path: %s\n", tmpfile);
618       return;
619     }
620   }
621
622   myDoLog = doLog;
623 }
624
625 void Draw_Interpretor::SetDoEcho (Standard_Boolean doEcho)
626 {
627   myDoEcho = doEcho;
628 }
629
630 Standard_Boolean Draw_Interpretor::GetDoLog () const
631 {
632   return myDoLog;
633 }
634
635 Standard_Boolean Draw_Interpretor::GetDoEcho () const
636 {
637   return myDoEcho;
638 }
639
640 void Draw_Interpretor::ResetLog ()
641 {
642   if (myFDLog < 0)
643     return;
644
645   // flush cerr and cout, for the case if they are bound to the log
646   flush_standard_streams();
647
648   lseek (myFDLog, 0, SEEK_SET);
649
650 #ifdef _WIN32
651   if (_chsize_s (myFDLog, 0) != 0)
652 #else
653   if (ftruncate (myFDLog, 0) != 0)
654 #endif
655   {
656     perror ("Error truncating the console log");
657   }
658 }
659
660 void Draw_Interpretor::AddLog (const Standard_CString theStr)
661 {
662   if (myFDLog < 0 || ! theStr || ! theStr[0])
663     return;
664
665   // flush cerr and cout, for the case if they are bound to the log
666   flush_standard_streams();
667
668   // write as plain bytes
669   if (write (myFDLog, theStr, (unsigned int)strlen(theStr)) <0)
670   {
671     perror ("Error writing to console log");
672   }
673 }
674
675 TCollection_AsciiString Draw_Interpretor::GetLog ()
676 {
677   TCollection_AsciiString aLog;
678   if (myFDLog < 0)
679     return aLog;
680
681   // flush cerr and cout
682   flush_standard_streams();
683
684   // rewind the file to its start
685   lseek (myFDLog, 0, SEEK_SET);
686
687   // read the whole log to string; this implementation
688   // is not optimized but should be sufficient
689   const int BUFSIZE = 4096;
690   char buffer[BUFSIZE + 1];
691   for (;;)
692   {
693     int nbRead = read (myFDLog, buffer, BUFSIZE);
694     if (nbRead <= 0)
695     {
696       break;
697     }
698     buffer[nbRead] = '\0';
699     aLog.AssignCat (buffer);
700   }
701
702   return aLog;
703 }