Skip to content

Commit e117aaf

Browse files
committed
Console Fixes
- Console.run stderr now piped (oops, i forget to uncomment it!) - Console.run stdout/stderr now default to sys.stdout/sys.stderr - Console.run throws exception if CreateProcess failed - Console.run now returns the return code from the process
1 parent 09a4cc1 commit e117aaf

7 files changed

Lines changed: 114 additions & 27 deletions

File tree

PythonScript/src/NotepadPython.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,20 @@
33
#include "NotepadPython.h"
44
#include "Notepad_Plus_msgs.h"
55
#include "NotepadPlusWrapper.h"
6-
6+
#include "ProcessExecute.h"
77
using namespace boost::python;
88

9+
namespace PythonScript
10+
{
11+
void translateProcessStart(const process_start_exception &e)
12+
{
13+
PyErr_SetString(PyExc_RuntimeError, e.what());
14+
}
15+
}
16+
917
void export_notepad()
1018
{
19+
register_exception_translator<process_start_exception>(&PythonScript::translateProcessStart);
1120
class_<NotepadPlusWrapper>("Notepad", no_init)
1221
.def("new", &NotepadPlusWrapper::newDocument, "Create a new document")
1322
.def("save", &NotepadPlusWrapper::save, "Save the current file")

PythonScript/src/ProcessExecute.cpp

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "stdafx.h"
22
#include "ProcessExecute.h"
3+
#include "WcharMbcsConverter.h"
34

45
/* The Console Redirection is taken from the TagsView plugin from Vitaliy Dovgan.
56
* My thanks to him for pointing me in the right direction. :)
@@ -11,6 +12,7 @@
1112
#define PIPE_READBUFSIZE 4096
1213

1314
using namespace boost::python;
15+
using namespace std;
1416

1517
ProcessExecute::ProcessExecute()
1618
{
@@ -29,19 +31,24 @@ bool ProcessExecute::isWindowsNT()
2931
return (osv.dwPlatformId >= VER_PLATFORM_WIN32_NT);
3032
}
3133

32-
int ProcessExecute::execute(const TCHAR *commandLine, boost::python::object pyStdout, boost::python::object pyStderr, boost::python::object pyStdin)
34+
long ProcessExecute::execute(const TCHAR *commandLine, boost::python::object pyStdout, boost::python::object pyStderr, boost::python::object pyStdin)
3335
{
36+
DWORD returnValue = 0;
37+
3438
if (pyStdout.is_none())
35-
return 3;
39+
throw process_start_exception("stdout cannot be None");
3640
if (pyStderr.is_none())
37-
return 4;
41+
throw process_start_exception("stderr cannot be None");
3842

3943
// Create out, err, and in pipes (ignore in, initially)
4044
SECURITY_DESCRIPTOR sd;
4145
SECURITY_ATTRIBUTES sa;
46+
process_start_exception exceptionThrown("");
47+
bool thrown = false;
4248

4349
Py_BEGIN_ALLOW_THREADS
44-
50+
try
51+
{
4552
if (isWindowsNT())
4653
{
4754
::InitializeSecurityDescriptor( &sd, SECURITY_DESCRIPTOR_REVISION );
@@ -57,14 +64,12 @@ int ProcessExecute::execute(const TCHAR *commandLine, boost::python::object pySt
5764

5865
if (!::CreatePipe(&m_hStdOutReadPipe, &m_hStdOutWritePipe, &sa, DEFAULT_PIPE_SIZE))
5966
{
60-
// TODO throw exception
61-
return -1;
67+
throw process_start_exception("Error creating pipe for stdout");
6268
}
6369

6470
if (!::CreatePipe(&m_hStdErrReadPipe, &m_hStdErrWritePipe, &sa, DEFAULT_PIPE_SIZE))
6571
{
66-
// TODO throw exception
67-
return -1;
72+
throw process_start_exception("Error creating pipe for stderr");
6873
}
6974

7075
HANDLE stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
@@ -93,15 +98,14 @@ int ProcessExecute::execute(const TCHAR *commandLine, boost::python::object pySt
9398
(LPVOID) &stdoutReaderArgs, // thread parameter
9499
0, // not suspended
95100
&dwThreadId); // returns thread ID
96-
/*
101+
97102
HANDLE hStderrThread = CreateThread(
98103
NULL, // no security attribute
99104
0, // default stack size
100105
pipeReader, // thread proc
101106
(LPVOID) &stderrReaderArgs, // thread parameter
102107
0, // not suspended
103108
&dwThreadId); // returns thread ID
104-
*/
105109

106110

107111
// start process
@@ -119,12 +123,14 @@ int ProcessExecute::execute(const TCHAR *commandLine, boost::python::object pySt
119123
si.wShowWindow = SW_HIDE;
120124
si.hStdInput = NULL;
121125
si.hStdOutput = m_hStdOutWritePipe;
122-
si.hStdError = m_hStdOutWritePipe;
126+
si.hStdError = m_hStdErrWritePipe;
123127

124128
::ZeroMemory( &pi, sizeof(PROCESS_INFORMATION) );
129+
125130
int commandLineLength = _tcslen(commandLine) + 1;
126131
TCHAR *cmdLine = new TCHAR[commandLineLength];
127132
_tcscpy_s(cmdLine, commandLineLength, commandLine);
133+
bool processStartSuccess;
128134

129135
if ( ::CreateProcess(
130136
NULL,
@@ -141,18 +147,64 @@ int ProcessExecute::execute(const TCHAR *commandLine, boost::python::object pySt
141147
{
142148
// wait for process to exit
143149
::WaitForSingleObject(pi.hProcess, INFINITE);
144-
150+
CloseHandle(pi.hThread);
151+
152+
processStartSuccess = true;
153+
154+
}
155+
else
156+
{
157+
processStartSuccess = false;
145158
}
146159

147160

148-
// Abort / Stop somehow the reader threads
149-
SetEvent(stopEvent);
161+
162+
// Stop the reader threads
163+
SetEvent(stopEvent);
164+
// Wait for the pipe reader threads to complete
150165
HANDLE handles[] = { stdoutReaderArgs.completedEvent, stderrReaderArgs.completedEvent };
166+
WaitForMultipleObjects(2, handles, TRUE, INFINITE);
167+
CloseHandle(stdoutReaderArgs.completedEvent);
168+
CloseHandle(stderrReaderArgs.completedEvent);
169+
CloseHandle(hStdoutThread);
170+
CloseHandle(hStderrThread);
171+
CloseHandle(stopEvent);
172+
173+
if (processStartSuccess)
174+
{
175+
GetExitCodeProcess(pi.hProcess, &returnValue);
176+
CloseHandle(pi.hProcess);
177+
}
178+
else
179+
{
180+
DWORD errorNo = ::GetLastError();
181+
TCHAR *buffer;
182+
183+
::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, errorNo, 0, reinterpret_cast<LPTSTR>(&buffer), 0, NULL);
184+
185+
shared_ptr<char> message = WcharMbcsConverter::tchar2char(buffer);
186+
process_start_exception ex(message.get());
187+
188+
::LocalFree(buffer);
189+
throw ex;
190+
191+
}
192+
}
193+
catch(process_start_exception& ex)
194+
{
195+
exceptionThrown = ex;
196+
thrown = true;
197+
}
198+
151199

152-
// WaitForMultipleObjects(2, handles, TRUE, INFINITE);
153-
int returnedIndex = WaitForSingleObject(stdoutReaderArgs.completedEvent, INFINITE);
154200
Py_END_ALLOW_THREADS
155-
return 0;
201+
202+
if (thrown)
203+
{
204+
throw exceptionThrown;
205+
}
206+
207+
return returnValue;
156208
}
157209

158210

PythonScript/src/ProcessExecute.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class ProcessExecute
1010
ProcessExecute();
1111
~ProcessExecute();
1212

13-
int execute(const TCHAR *commandLine, boost::python::object pyStdout, boost::python::object pyStderr, boost::python::object pyStdin);
13+
long execute(const TCHAR *commandLine, boost::python::object pyStdout, boost::python::object pyStderr, boost::python::object pyStdin);
1414

1515
protected:
1616
static bool isWindowsNT();
@@ -33,5 +33,19 @@ struct PipeReaderArgs
3333
boost::python::object pythonFile;
3434
};
3535

36+
class process_start_exception
37+
{
38+
public:
39+
process_start_exception(const char *what)
40+
: m_what(what)
41+
{};
42+
43+
const char *what() const
44+
{ return m_what.c_str();
45+
}
46+
47+
private:
48+
std::string m_what;
49+
};
3650

3751
#endif

PythonScript/src/PythonConsole.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,11 @@ void PythonConsole::stopStatement()
130130

131131
}
132132

133-
void PythonConsole::runCommand(str text, boost::python::object pyStdout, boost::python::object pyStderr)
133+
long PythonConsole::runCommand(str text, boost::python::object pyStdout, boost::python::object pyStderr)
134134
{
135135
ProcessExecute process;
136136
shared_ptr<TCHAR> cmdLine = WcharMbcsConverter::char2tchar(extract<const char *>(text));
137-
process.execute(cmdLine.get(), pyStdout, pyStderr, object());
137+
return process.execute(cmdLine.get(), pyStdout, pyStderr, object());
138138
}
139139

140140

PythonScript/src/PythonConsole.h

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,19 @@ class PythonConsole : public NppPythonScript::PyProducerConsumer<const char *>,
3939
bool runStatementWorker(const char *statement);
4040
virtual void consume(const char *statement);
4141

42-
void runCommand(boost::python::str text, boost::python::object pyStdout, boost::python::object pyStderr);
43-
void runCommandNoStderr(boost::python::str text, boost::python::object pyStdout)
44-
{ runCommand(text, pyStdout, boost::python::object(boost::python::ptr(this))); }
45-
void runCommandNoStdout(boost::python::str text)
46-
{ runCommand(text, boost::python::object(boost::python::ptr(this)), boost::python::object(boost::python::ptr(this))); }
42+
long runCommand(boost::python::str text, boost::python::object pyStdout, boost::python::object pyStderr);
43+
long runCommandNoStderr(boost::python::str text, boost::python::object pyStdout)
44+
{
45+
boost::python::object sys_module( (boost::python::handle<>(PyImport_ImportModule("sys"))) );
46+
boost::python::object sys_namespace = sys_module.attr("__dict__");
47+
return runCommand(text, pyStdout, sys_namespace["stderr"]);
48+
}
49+
long runCommandNoStdout(boost::python::str text)
50+
{
51+
boost::python::object sys_module( (boost::python::handle<>(PyImport_ImportModule("sys"))) );
52+
boost::python::object sys_namespace = sys_module.attr("__dict__");
53+
return runCommand(text, sys_namespace["stdout"], sys_namespace["stderr"]);
54+
}
4755

4856

4957

PythonScript/src/ScintillaWrapper.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include "ScintillaCells.h"
99
#include "PyProducerConsumer.h"
10+
#include "ProcessExecute.h"
1011

1112
using namespace std;
1213
using namespace boost::python;
@@ -737,7 +738,8 @@ void ScintillaWrapper::pyreplace(boost::python::object searchExp, boost::python:
737738
{
738739
delete[] range.lpstrText;
739740
}
740-
return;
741+
742+
throw boost::python::error_already_set();
741743
}
742744
}
743745
previousLine = line;

docs/source/console.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ Console Object
3030

3131
Runs an external program, with output optionally directed to the given objects (which must support a `write` method).
3232

33+
Not supplying stdout or stderr means that the default values from sys.stdout and sys.stderr (normally the console) are used.
34+
3335
e.g.::
3436
3537
# The basic form, run a normal command line.

0 commit comments

Comments
 (0)