16 #ifndef BOOST_ADAPTBX_PYTHON_STREAMBUF_H
17 #define BOOST_ADAPTBX_PYTHON_STREAMBUF_H
19 #include <boost/python/object.hpp>
20 #include <boost/python/str.hpp>
21 #include <boost/python/extract.hpp>
23 #include <boost/optional.hpp>
24 #include <boost/utility/typed_in_place_factory.hpp>
37 namespace bp = boost::python;
114 typedef std::basic_streambuf<char> base_t;
139 streambuf(bp::object& python_file_obj, std::size_t buffer_size_ = 0)
140 : py_read(getattr(python_file_obj,
"read", bp::object())),
141 py_write(getattr(python_file_obj,
"write", bp::object())),
142 py_seek(getattr(python_file_obj,
"seek", bp::object())),
143 py_tell(getattr(python_file_obj,
"tell", bp::object())),
145 write_buffer(nullptr),
146 pos_of_read_buffer_end_in_py_file(0),
147 pos_of_write_buffer_end_in_py_file(buffer_size),
148 farthest_pptr(nullptr) {
154 if (py_tell != bp::object()) {
156 off_type py_pos = bp::extract<off_type>(py_tell());
157 if (py_seek != bp::object()) {
164 }
catch (bp::error_already_set&) {
165 py_tell = bp::object();
166 py_seek = bp::object();
174 if (py_write != bp::object()) {
176 write_buffer =
new char[buffer_size + 1];
177 write_buffer[buffer_size] =
'\0';
178 setp(write_buffer, write_buffer + buffer_size);
179 farthest_pptr = pptr();
182 setp(
nullptr,
nullptr);
185 if (py_tell != bp::object()) {
186 off_type py_pos = bp::extract<off_type>(py_tell());
187 pos_of_read_buffer_end_in_py_file = py_pos;
188 pos_of_write_buffer_end_in_py_file = py_pos;
194 std::size_t buffer_size_ = 0)
195 :
streambuf(python_file_obj, buffer_size_) {
197 bp::object io_mod = bp::import(
"io");
199 bp::object iobase = io_mod.attr(
"TextIOBase");
206 static bp::object io_mod = bp::object();
207 static bp::object iobase = bp::object();
208 if (!io_mod) io_mod = bp::import(
"io");
209 if (io_mod && !iobase) iobase = io_mod.attr(
"TextIOBase");
214 df_isTextMode = PyObject_IsInstance(python_file_obj.ptr(), iobase.ptr());
220 "Need a text mode file object like StringIO or a file opened "
226 "Need a binary mode file object like BytesIO or a file opened "
230 throw std::invalid_argument(
"bad mode character");
236 if (write_buffer)
delete[] write_buffer;
244 int_type const failure = traits_type::eof();
246 if (status == failure)
return -1;
247 return egptr() - gptr();
252 int_type const failure = traits_type::eof();
253 if (py_read == bp::object()) {
254 throw std::invalid_argument(
255 "That Python file object has no 'read' attribute");
257 read_buffer = py_read(buffer_size);
258 char* read_buffer_data;
259 bp::ssize_t py_n_read;
260 if (PyBytes_AsStringAndSize(read_buffer.ptr(), &read_buffer_data,
262 setg(
nullptr,
nullptr,
nullptr);
263 throw std::invalid_argument(
264 "The method 'read' of the Python file object "
265 "did not return a string.");
268 pos_of_read_buffer_end_in_py_file += n_read;
269 setg(read_buffer_data, read_buffer_data, read_buffer_data + n_read);
271 if (n_read == 0)
return failure;
272 return traits_type::to_int_type(read_buffer_data[0]);
277 if (py_write == bp::object()) {
278 throw std::invalid_argument(
279 "That Python file object has no 'write' attribute");
281 farthest_pptr = std::max(farthest_pptr, pptr());
283 off_type orig_n_written = n_written;
284 const unsigned int STD_ASCII = 0x7F;
285 if (df_isTextMode &&
static_cast<unsigned int>(c) > STD_ASCII) {
289 while (n_written > 0 &&
static_cast<unsigned int>(
290 write_buffer[n_written - 1]) > STD_ASCII) {
294 bp::str chunk(pbase(), pbase() + n_written);
297 if ((!df_isTextMode ||
static_cast<unsigned int>(c) <= STD_ASCII) &&
298 !traits_type::eq_int_type(c, traits_type::eof())) {
299 py_write(traits_type::to_char_type(c));
303 setp(pbase(), epptr());
305 farthest_pptr = pptr();
307 pos_of_write_buffer_end_in_py_file += n_written;
308 if (df_isTextMode &&
static_cast<unsigned int>(c) > STD_ASCII &&
309 !traits_type::eq_int_type(c, traits_type::eof())) {
310 size_t n_to_copy = orig_n_written - n_written;
312 for (
size_t i = 0; i < n_to_copy; ++i) {
313 sputc(write_buffer[n_written + i]);
320 return traits_type::eq_int_type(c, traits_type::eof())
321 ? traits_type::not_eof(c)
334 farthest_pptr = std::max(farthest_pptr, pptr());
335 if (farthest_pptr && farthest_pptr > pbase()) {
336 off_type delta = pptr() - farthest_pptr;
338 if (traits_type::eq_int_type(status, traits_type::eof())) result = -1;
339 if (py_seek != bp::object()) py_seek(delta, 1);
340 }
else if (gptr() && gptr() < egptr()) {
341 if (py_seek != bp::object()) py_seek(gptr() - egptr(), 1);
354 std::ios_base::openmode which =
355 std::ios_base::in | std::ios_base::out)
override {
363 if (py_seek == bp::object()) {
364 throw std::invalid_argument(
365 "That Python file object has no 'seek' attribute");
369 if (which == std::ios_base::in && !gptr()) {
370 if (traits_type::eq_int_type(
underflow(), traits_type::eof())) {
378 case std::ios_base::beg:
381 case std::ios_base::cur:
384 case std::ios_base::end:
392 boost::optional<off_type> result =
393 seekoff_without_calling_python(off, way, which);
396 if (which == std::ios_base::out)
overflow();
397 if (way == std::ios_base::cur) {
398 if (which == std::ios_base::in)
399 off -= egptr() - gptr();
400 else if (which == std::ios_base::out)
401 off += pptr() - pbase();
403 py_seek(off, whence);
404 result =
off_type(bp::extract<off_type>(py_tell()));
405 if (which == std::ios_base::in)
underflow();
412 std::ios_base::openmode which =
413 std::ios_base::in | std::ios_base::out)
override {
418 bp::object py_read, py_write, py_seek, py_tell;
420 std::size_t buffer_size;
427 bp::object read_buffer;
435 off_type pos_of_read_buffer_end_in_py_file,
436 pos_of_write_buffer_end_in_py_file;
441 boost::optional<off_type> seekoff_without_calling_python(
442 off_type off, std::ios_base::seekdir way, std::ios_base::openmode which) {
443 boost::optional<off_type>
const failure;
446 off_type buf_begin, buf_end, buf_cur, upper_bound;
447 off_type pos_of_buffer_end_in_py_file;
448 if (which == std::ios_base::in) {
449 pos_of_buffer_end_in_py_file = pos_of_read_buffer_end_in_py_file;
450 buf_begin =
reinterpret_cast<std::streamsize
>(eback());
451 buf_cur =
reinterpret_cast<std::streamsize
>(gptr());
452 buf_end =
reinterpret_cast<std::streamsize
>(egptr());
453 upper_bound = buf_end;
454 }
else if (which == std::ios_base::out) {
455 pos_of_buffer_end_in_py_file = pos_of_write_buffer_end_in_py_file;
456 buf_begin =
reinterpret_cast<std::streamsize
>(pbase());
457 buf_cur =
reinterpret_cast<std::streamsize
>(pptr());
458 buf_end =
reinterpret_cast<std::streamsize
>(epptr());
459 farthest_pptr = std::max(farthest_pptr, pptr());
460 upper_bound =
reinterpret_cast<std::streamsize
>(farthest_pptr) + 1;
467 if (way == std::ios_base::cur) {
468 buf_sought = buf_cur + off;
469 }
else if (way == std::ios_base::beg) {
470 buf_sought = buf_end + (off - pos_of_buffer_end_in_py_file);
471 }
else if (way == std::ios_base::end) {
478 if (buf_sought < buf_begin || buf_sought >= upper_bound)
return failure;
481 if (which == std::ios_base::in)
482 gbump(buf_sought - buf_cur);
483 else if (which == std::ios_base::out)
484 pbump(buf_sought - buf_cur);
485 return pos_of_buffer_end_in_py_file + (buf_sought - buf_end);
492 exceptions(std::ios_base::badbit);
508 exceptions(std::ios_base::badbit);
512 if (this->good()) this->flush();
527 ostream(bp::object& python_file_obj, std::size_t buffer_size = 0)
#define TEST_ASSERT(expr)
#define CHECK_INVARIANT(expr, mess)
Class to allow us to throw a ValueError from C++ and have it make it back to Python.
A stream buffer getting data from and putting data into a Python file object.
base_t::char_type char_type
~streambuf() override
Mundane destructor freeing the allocated resources.
base_t::off_type off_type
static const std::size_t default_buffer_size
The default size of the read and write buffer.
pos_type seekpos(pos_type sp, std::ios_base::openmode which=std::ios_base::in|std::ios_base::out) override
C.f. C++ standard section 27.5.2.4.2.
base_t::pos_type pos_type
static int traits_type_eof()
std::streamsize showmanyc() override
C.f. C++ standard section 27.5.2.4.3.
pos_type seekoff(off_type off, std::ios_base::seekdir way, std::ios_base::openmode which=std::ios_base::in|std::ios_base::out) override
C.f. C++ standard section 27.5.2.4.2.
base_t::int_type int_type
streambuf(bp::object &python_file_obj, char mode, std::size_t buffer_size_=0)
constructor to enforce a mode (binary or text)
int sync() override
Update the python file to reflect the state of this stream buffer.
base_t::traits_type traits_type
int_type overflow(int_type c=traits_type_eof()) override
C.f. C++ standard section 27.5.2.4.5.
int_type underflow() override
C.f. C++ standard section 27.5.2.4.3.
streambuf(bp::object &python_file_obj, std::size_t buffer_size_=0)
Construct from a Python file object.
ostream(bp::object &python_file_obj, std::size_t buffer_size=0)
~ostream() noexcept override
streambuf_capsule(bp::object &python_file_obj, std::size_t buffer_size=0)
streambuf python_streambuf