Line data Source code
1 : // Copyright (c) 2019-2026 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/snapdev
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program 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
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 : #pragma once
19 :
20 : /** \file
21 : * \brief Capture the output to an output stream in a buffer.
22 : *
23 : * We often use this template in our tests when we expect a function to
24 : * generate output in a given stream. This allows us to capture that output
25 : * and verify it once the function being tested returns.
26 : */
27 :
28 : // snapdev
29 : //
30 : #include <snapdev/not_used.h>
31 :
32 :
33 : // C++
34 : //
35 : #include <iostream>
36 : #include <ostream>
37 : //#include <sstream>
38 : //#include <string>
39 :
40 :
41 :
42 : namespace snapdev
43 : {
44 :
45 :
46 :
47 : /** \brief Create a streambuf where the data comes from a vector.
48 : *
49 : * This class is used to create a streambuf from the data of a vector.
50 : * If you are looking at capturing the output of a stream, you may be
51 : * interested by the ostream_to_buf instead. That other implementation
52 : * allows you to save the output of a stream in a string and then verify
53 : * that string.
54 : *
55 : * This implementation was created for input rather than output, allowing
56 : * you to create a buffer from the data of a vector.
57 : *
58 : * \code
59 : * {
60 : * // Say you have a vector with data
61 : * std::vector<char> data{ 1, 2, 3, 4, 5, 6, 7 };
62 : *
63 : * // you create a vector_streambuf this way
64 : * snapdev::vector_streambuf buf(data);
65 : *
66 : * // and then an input stream this way
67 : * std::istream in(&buf);
68 : *
69 : * // now you can read & seek as usual
70 : *
71 : * // if you want to be able to do updates, use an iostream insead
72 : * std::iostream in_out(&buf);
73 : * }
74 : * \endcode
75 : *
76 : * \note
77 : * The function expects the vector to only contain data that can be
78 : * read/written as such. This means no pointers, not classes with
79 : * virtual functions, etc.
80 : *
81 : * \warning
82 : * The vector must remain available and not change in size outside
83 : * of this stream buffer implementation.
84 : *
85 : * \todo
86 : * Verify that VecT is only composed of basic types.
87 : *
88 : * \tparam VecT The type of the vector used here.
89 : * \tparam CharT The type of characters used by the stream.
90 : * \tparam Traits The traits of the specified character type.
91 : */
92 : template<
93 : typename VecT
94 : , typename CharT = char
95 : , typename Traits = std::char_traits<CharT>
96 : > class vector_streambuf
97 : : public std::basic_streambuf<CharT, Traits>
98 : {
99 : public:
100 : typedef CharT char_type;
101 : typedef Traits traits_type;
102 : typedef typename Traits::int_type int_type;
103 : typedef typename Traits::pos_type pos_type;
104 : typedef typename Traits::off_type off_type;
105 :
106 : typedef std::basic_streambuf<char_type, traits_type> streambuf_type;
107 :
108 : /** \brief Initialize the streambuf from \p vec.
109 : *
110 : * This function creates a streambuf that you can then use to initialize
111 : * an input or output stream with.
112 : *
113 : * This version of the constructor allows to read and write to the
114 : * stream buffer (a.k.a. the vector). You can use it with input or
115 : * an output stream.
116 : *
117 : * Note that the reading and writing happen from the start of the
118 : * buffer unless a corresponding seek is used.
119 : *
120 : * \todo
121 : * Note that the overflow() function does not yet know how to grow
122 : * the vector. If you attempt to write past the end, an exception
123 : * is generated.
124 : *
125 : * \param[in] vec The vector to use with the streambuf.
126 : */
127 2 : vector_streambuf(VecT & vec)
128 2 : : f_vector(vec)
129 : {
130 2 : char_type * begin(const_cast<char_type *>(reinterpret_cast<char_type const *>(vec.data())));
131 2 : char_type * end(begin + vec.size() * sizeof(typename VecT::value_type));
132 2 : this->setg(begin, begin, end);
133 2 : this->setp(begin, end);
134 2 : }
135 :
136 : /** \brief Initialize the streambuf from \p vec.
137 : *
138 : * This function creates a read-only streambuf that you can then use
139 : * to initialize an input stream with. Trying to write to that stream
140 : * generates an exception.
141 : *
142 : * \param[in] vec The vector to use with the streambuf.
143 : */
144 1 : vector_streambuf(VecT const & vec)
145 1 : : f_read_only(true)
146 1 : , f_vector(const_cast<VecT &>(vec))
147 : {
148 1 : char_type * begin(const_cast<char_type *>(reinterpret_cast<char_type const *>(vec.data())));
149 1 : char_type * end(begin + vec.size() * sizeof(typename VecT::value_type));
150 1 : this->setg(begin, begin, end);
151 :
152 : // a write calls overflow() if the start & end pointers are the same
153 : //
154 1 : this->setp(begin, begin);
155 1 : }
156 :
157 : protected:
158 : /** \brief Function called each time the output is read.
159 : *
160 : * This function is called to write one element to the vector.
161 : * The function saves the input character to the vector.
162 : *
163 : * \exception std::ios_base::failure
164 : * If the vector is read-only (const when creating the streambuf)
165 : * then this exception is raised. At the moment, this function is
166 : * not capable to grow the vector. If the end of the vector is
167 : * reached and one more character is written, then this exception
168 : * is raised.
169 : *
170 : * \param[in] c The character to output.
171 : *
172 : * \return The input character \p c or '\0' of \p c is EOF.
173 : */
174 3 : int_type overflow(int_type c)
175 : {
176 3 : snapdev::NOT_USED(c);
177 : //if(f_read_only)
178 : {
179 3 : throw std::ios_base::failure("this buffer is read-only, writing to the buffer is not available.");
180 : }
181 :
182 : // TODO: implement growth whenever possible
183 :
184 : //if(traits_type::eq_int_type(c, Traits::eof()))
185 : //{
186 : // return 0;
187 : //}
188 :
189 : //std::size_t const size(this->pptr() - this->pbase());
190 : //if(size >= f_vector.size())
191 : //{
192 : // // TODO: grow the buffer
193 : // //f_vector.push_back(c);
194 : // //return 0;
195 : // throw std::ios_base::failure("the buffer is full.");
196 : //}
197 :
198 : //*this->pptr() = c;
199 : //this->pbump(1);
200 :
201 : //return c;
202 : }
203 :
204 45 : virtual pos_type seekoff(
205 : off_type const offset
206 : , std::ios_base::seekdir const dir
207 : , std::ios_base::openmode const mode) override
208 : {
209 45 : off_type result(-1);
210 :
211 45 : if((mode & std::ios_base::in) != 0)
212 : {
213 32 : char_type * pos = nullptr;
214 32 : switch(dir)
215 : {
216 24 : case std::ios_base::cur:
217 24 : pos = this->gptr() + offset;
218 24 : break;
219 :
220 1 : case std::ios_base::end:
221 1 : pos = this->egptr() + offset;
222 1 : break;
223 :
224 6 : case std::ios_base::beg:
225 6 : pos = this->eback() + offset;
226 6 : break;
227 :
228 1 : default:
229 1 : throw std::ios_base::failure("unknown direction in seekoff() -- in");
230 :
231 : }
232 31 : if(pos < this->eback())
233 : {
234 1 : pos = this->eback();
235 : }
236 31 : if(pos > this->egptr())
237 : {
238 1 : pos = this->egptr();
239 : }
240 31 : this->setg(this->eback(), pos, this->egptr());
241 31 : result = pos - this->eback();
242 : }
243 :
244 44 : if((mode & std::ios_base::out) != 0)
245 : {
246 13 : char_type * pos = nullptr;
247 13 : switch(dir)
248 : {
249 7 : case std::ios_base::cur:
250 7 : pos = this->pptr() + offset;
251 7 : break;
252 :
253 1 : case std::ios_base::end:
254 1 : pos = this->epptr() + offset;
255 1 : break;
256 :
257 4 : case std::ios_base::beg:
258 4 : pos = this->pbase() + offset;
259 4 : break;
260 :
261 1 : default:
262 1 : throw std::ios_base::failure("unknown direction in seekpos() -- out");
263 :
264 : }
265 12 : if(pos < this->pbase())
266 : {
267 1 : pos = this->pbase();
268 : }
269 12 : if(pos > this->epptr())
270 : {
271 2 : pos = this->epptr();
272 : }
273 12 : this->setp(this->pbase(), this->epptr());
274 12 : result = pos - this->pbase();
275 12 : this->pbump(result);
276 : }
277 :
278 86 : return result;
279 : }
280 :
281 10 : virtual pos_type seekpos(pos_type offset, std::ios_base::openmode mode) override
282 : {
283 10 : return seekoff(offset, std::ios_base::beg, mode);
284 : }
285 :
286 : private:
287 : /** \brief Whether the buffer is considered read-only or not.
288 : *
289 : * When creating the streambuf with a constant vector, this parameter
290 : * is set to true. This prevents writes to the file. In other words,
291 : * the vector will never be updated.
292 : */
293 : bool f_read_only = false;
294 :
295 : /** \brief A reference back to the user's vector.
296 : *
297 : * This holds the reference to the vector. It is used in case you
298 : * try to write more data than the existing vector supports. In that
299 : * case, we need access to the vector to grow it.
300 : */
301 : VecT & f_vector = VecT();
302 : };
303 :
304 :
305 :
306 : } // namespace snapdev
307 : // vim: ts=4 sw=4 et
|