Salome HOME
Copyright update 2022
[modules/gui.git] / tools / PyConsole / src / PyConsole_Interp.cxx
1 // Copyright (C) 2007-2022  CEA/DEN, EDF R&D, OPEN CASCADE
2 //
3 // Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
10 //
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // Lesser General Public License for more details.
15 //
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 //
20 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 //
22 // File   : PyConsole_Interp.cxx
23 // Author : Nicolas REJNERI (OPEN CASCADE), Adrien BRUNETON (CEA/DEN), Vadim SANDLER (OPEN CASCADE)
24
25 #include "PyConsole_Interp.h"
26
27 /*!
28   \class PyConsole_Interp
29   \brief Python interpreter to be embedded to the SALOME study's GUI.
30
31   There is only one Python interpreter for the whole SALOME environment.
32
33   Call the initialize() method defined in the base class PyInterp_Interp,
34   to initialize the interpreter after instance creation.
35
36   The method initialize() calls virtuals methods
37   - initPython()  to initialize global Python interpreter
38   - initContext() to initialize interpreter internal context
39   - initRun()     to prepare interpreter for running commands
40
41   See PyInterp_Interp class for more details.
42 */
43
44 /*!
45   \brief Constructor.
46
47   Creates new python interpreter.
48 */
49 PyConsole_Interp::PyConsole_Interp()
50   : PyInterp_Interp()
51 {
52 }
53
54 /*!
55   \brief Destructor.
56 */
57 PyConsole_Interp::~PyConsole_Interp()
58 {
59 }
60
61 /*!
62   \brief Performs specific actions before each Python command
63   
64   Sets the variable "__IN_SALOME_GUI_CONSOLE" to True.
65   This is not attached to a module (like salome_iapp.IN_SALOME_GUI_CONSOLE)
66   since modules are shared across all interpreters in SALOME.
67
68   \note GIL is already acquired here.
69 */
70 int PyConsole_Interp::beforeRun()
71 {
72   return PyRun_SimpleString("__builtins__.__IN_SALOME_GUI_CONSOLE=True");
73 }
74  
75 /*!
76   \brief Performs specific actions after each Python command
77
78   Sets the variable "__IN_SALOME_GUI_CONSOLE" to False.
79   \sa beforeRun()
80
81   \note GIL is already acquired here.
82 */
83 int PyConsole_Interp::afterRun()
84 {
85   return PyRun_SimpleString("__builtins__.__IN_SALOME_GUI_CONSOLE=False");
86 }
87
88 /*!
89   \brief Run Python dir() command to get matches.
90   \internal
91   \param dirArgument Python expression to pass to the dir command. The parsing of what the
92   user actually started typing is dedicated to the caller
93   \param startMatch string representing the begining of the patter to be completed. For example, when
94   the user types "a_string_variable.rsp <TAB>", this is "rsp".
95   \param[out] matches resulting list of matches
96   \param[out] docString resulting docstring of single match
97   \return \true if completion succeeded, \c false otherwise
98 */
99 bool PyConsole_Interp::runDirCommand( const QString& dirArgument, const QString& startMatch, 
100                                       QStringList& matches, QString& docString )
101 {
102   static QStringList keywords;
103   if ( keywords.isEmpty() ) {
104     keywords << "and" << "as" << "assert" << "break" << "class" << "continue"
105              << "def" << "del" << "elif" << "else" << "except" << "exec"
106              << "finally" << "for" << "from" << "global" << "if" << "import"
107              << "in" << "is" << "lambda" << "not" << "or" << "pass" << "print" << "raise"
108              << "return" << "try" << "while" << "with" << "yield";
109   }
110   
111   // run dir() command and extract completions
112   if ( !runDirAndExtract( dirArgument, startMatch, matches ) )
113     return false;
114   
115   // If dirArgument is empty, we append the __builtins__
116   if ( dirArgument.isEmpty() ) {
117     if ( !runDirAndExtract( QString( "__builtins__" ), startMatch, matches, false ) )
118       return false;
119     
120     // ... and we match on Python's keywords as well
121     foreach( QString keyword, keywords ) {
122       if ( keyword.startsWith( startMatch ) )
123         matches.append( keyword );
124     }
125   }
126   
127   // Try to get doc string of the first match
128   if ( matches.size() > 0 ) {
129     QString cmd = QString( "%1.__doc__" ).arg( matches[0] );
130     if ( !dirArgument.trimmed().isEmpty() )
131       cmd.prepend( QString( "%1." ).arg( dirArgument ) );
132     
133     PyObject* str = PyRun_String( cmd.toStdString().c_str(), Py_eval_input, _global_context, _local_context );
134     if ( !str || str == Py_None || !PyUnicode_Check( str ) )
135       {
136         if ( !str )
137           PyErr_Clear();
138       }
139     else {
140       docString = QString( PyUnicode_AsUTF8( str ) );
141     }
142     Py_XDECREF( str );
143   }
144   return true;
145 }
146
147 /*!
148   \internal
149   \sa runDirCommand()
150   \param dirArgument see runDirCommand()
151   \param startMatch  see runDirCommand()
152   \param[out] result resulting list of matches
153   \param discardSwig if \c true, a regular expression is used to discard all static method generated
154   by SWIG. Typically: MEDCouplingUMesh_Blabla
155   \return \c true if the call to dir() and parsing of the result succeeded, \false otherwise.
156 */
157 bool PyConsole_Interp::runDirAndExtract( const QString& dirArgument,
158                                          const QString& startMatch,
159                                          QStringList& result,
160                                          bool discardSwig ) const
161 {
162   QRegExp re( "^[A-Z].+_[A-Z]+[a-z]+.+$" ); // REX to discard SWIG static method, e.g. MEDCouplingUMesh_Blabla
163
164   // Execute dir() command
165   QString command( "dir(" + dirArgument + ")" );
166   PyObject* plst = PyRun_String( command.toStdString().c_str(), Py_eval_input, _global_context, _local_context );
167   if ( !plst || plst == Py_None ) {
168     if ( !plst )
169       PyErr_Clear();
170     Py_XDECREF( plst );
171     return false;
172   }
173
174   // Check result
175   if ( !PySequence_Check( plst ) ) {
176     // Should never happen ...
177     Py_XDECREF( plst );
178     return false;
179   }
180
181   // Extract the returned list
182   int n = PySequence_Length( plst );
183   for ( int i = 0; i < n; i++ ) {
184     PyObject* it;
185     it = PySequence_GetItem( plst, i );
186     QString s( PyUnicode_AsUTF8( it ) );
187     // if the method is not from swig, not static (guessed from the reg exp) and matches
188     // what is already there
189     if ( s.startsWith( startMatch ) ) {
190       if ( !discardSwig || ( !re.exactMatch( s ) && !s.contains( "swig" ) ) )
191         result.append( s );
192     }
193     Py_DECREF( it );
194   }
195   Py_DECREF( plst );
196   
197   return true;
198 }