Salome HOME
updated copyright message
[modules/gui.git] / tools / PyConsole / src / PyConsole_Interp.cxx
1 // Copyright (C) 2007-2023  CEA, EDF, 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 #include <QRegularExpression>
27
28 /*!
29   \class PyConsole_Interp
30   \brief Python interpreter to be embedded to the SALOME study's GUI.
31
32   There is only one Python interpreter for the whole SALOME environment.
33
34   Call the initialize() method defined in the base class PyInterp_Interp,
35   to initialize the interpreter after instance creation.
36
37   The method initialize() calls virtuals methods
38   - initPython()  to initialize global Python interpreter
39   - initContext() to initialize interpreter internal context
40   - initRun()     to prepare interpreter for running commands
41
42   See PyInterp_Interp class for more details.
43 */
44
45 /*!
46   \brief Constructor.
47
48   Creates new python interpreter.
49 */
50 PyConsole_Interp::PyConsole_Interp()
51   : PyInterp_Interp()
52 {
53 }
54
55 /*!
56   \brief Destructor.
57 */
58 PyConsole_Interp::~PyConsole_Interp()
59 {
60 }
61
62 /*!
63   \brief Performs specific actions before each Python command
64   
65   Sets the variable "__IN_SALOME_GUI_CONSOLE" to True.
66   This is not attached to a module (like salome_iapp.IN_SALOME_GUI_CONSOLE)
67   since modules are shared across all interpreters in SALOME.
68
69   \note GIL is already acquired here.
70 */
71 int PyConsole_Interp::beforeRun()
72 {
73   return PyRun_SimpleString("__builtins__.__IN_SALOME_GUI_CONSOLE=True");
74 }
75  
76 /*!
77   \brief Performs specific actions after each Python command
78
79   Sets the variable "__IN_SALOME_GUI_CONSOLE" to False.
80   \sa beforeRun()
81
82   \note GIL is already acquired here.
83 */
84 int PyConsole_Interp::afterRun()
85 {
86   return PyRun_SimpleString("__builtins__.__IN_SALOME_GUI_CONSOLE=False");
87 }
88
89 /*!
90   \brief Run Python dir() command to get matches.
91   \internal
92   \param dirArgument Python expression to pass to the dir command. The parsing of what the
93   user actually started typing is dedicated to the caller
94   \param startMatch string representing the beginning of the pattern to be completed. For example, when
95   the user types "a_string_variable.rsp <TAB>", this is "rsp".
96   \param[out] matches resulting list of matches
97   \param[out] docString resulting docstring of single match
98   \return \true if completion succeeded, \c false otherwise
99 */
100 bool PyConsole_Interp::runDirCommand( const QString& dirArgument, const QString& startMatch, 
101                                       QStringList& matches, QString& docString )
102 {
103   static QStringList keywords;
104   if ( keywords.isEmpty() ) {
105     keywords << "and" << "as" << "assert" << "break" << "class" << "continue"
106              << "def" << "del" << "elif" << "else" << "except" << "exec"
107              << "finally" << "for" << "from" << "global" << "if" << "import"
108              << "in" << "is" << "lambda" << "not" << "or" << "pass" << "print" << "raise"
109              << "return" << "try" << "while" << "with" << "yield";
110   }
111   
112   // run dir() command and extract completions
113   if ( !runDirAndExtract( dirArgument, startMatch, matches ) )
114     return false;
115   
116   // If dirArgument is empty, we append the __builtins__
117   if ( dirArgument.isEmpty() ) {
118     if ( !runDirAndExtract( QString( "__builtins__" ), startMatch, matches, false ) )
119       return false;
120     
121     // ... and we match on Python's keywords as well
122     foreach( QString keyword, keywords ) {
123       if ( keyword.startsWith( startMatch ) )
124         matches.append( keyword );
125     }
126   }
127   
128   // Try to get doc string of the first match
129   if ( matches.size() > 0 ) {
130     QString cmd = QString( "%1.__doc__" ).arg( matches[0] );
131     if ( !dirArgument.trimmed().isEmpty() )
132       cmd.prepend( QString( "%1." ).arg( dirArgument ) );
133     
134     PyObject* str = PyRun_String( cmd.toStdString().c_str(), Py_eval_input, _global_context, _local_context );
135     if ( !str || str == Py_None || !PyUnicode_Check( str ) )
136       {
137         if ( !str )
138           PyErr_Clear();
139       }
140     else {
141       docString = QString( PyUnicode_AsUTF8( str ) );
142     }
143     Py_XDECREF( str );
144   }
145   return true;
146 }
147
148 /*!
149   \internal
150   \sa runDirCommand()
151   \param dirArgument see runDirCommand()
152   \param startMatch  see runDirCommand()
153   \param[out] result resulting list of matches
154   \param discardSwig if \c true, a regular expression is used to discard all static method generated
155   by SWIG. Typically: MEDCouplingUMesh_Blabla
156   \return \c true if the call to dir() and parsing of the result succeeded, \false otherwise.
157 */
158 bool PyConsole_Interp::runDirAndExtract( const QString& dirArgument,
159                                          const QString& startMatch,
160                                          QStringList& result,
161                                          bool discardSwig ) const
162 {
163   QRegularExpression re( "^[A-Z].+_[A-Z]+[a-z]+.+$" ); // REX to discard SWIG static method, e.g. MEDCouplingUMesh_Blabla
164
165   // Execute dir() command
166   QString command( "dir(" + dirArgument + ")" );
167   PyObject* plst = PyRun_String( command.toStdString().c_str(), Py_eval_input, _global_context, _local_context );
168   if ( !plst || plst == Py_None ) {
169     if ( !plst )
170       PyErr_Clear();
171     Py_XDECREF( plst );
172     return false;
173   }
174
175   // Check result
176   if ( !PySequence_Check( plst ) ) {
177     // Should never happen ...
178     Py_XDECREF( plst );
179     return false;
180   }
181
182   // Extract the returned list
183   int n = PySequence_Length( plst );
184   for ( int i = 0; i < n; i++ ) {
185     PyObject* it;
186     it = PySequence_GetItem( plst, i );
187     QString s( PyUnicode_AsUTF8( it ) );
188     // if the method is not from swig, not static (guessed from the reg exp) and matches
189     // what is already there
190     if ( s.startsWith( startMatch ) ) {
191       if ( !discardSwig || ( !re.match( s ).hasMatch() && !s.contains( "swig" ) ) )
192         result.append( s );
193     }
194     Py_DECREF( it );
195   }
196   Py_DECREF( plst );
197   
198   return true;
199 }