randolf.ca  1.00
Randolf Richardson's C++ classes
Loading...
Searching...
No Matches
rline
1#pragma once
2
3#include <cstring>
4
5namespace randolf {
6
7 /*======================================================================*//**
8 @brief
9 One line of text is stored in this class, with additional metadata to track
10 whether the text was terminated by an EoL (End of Line) sequence, and, if so,
11 what the EoL sequence was.
12
13 This is particularly useful for reading lines of text from a file or a socket
14 without having to go to additional efforts to track whether the final line is
15 terminated with an EoL sequence (since this class takes care of this detail).
16
17 By default, the EoL sequence may be &lt;CR&gt;, &lt;LF&gt;, &lt;CRLF&gt;, or
18 &lt;LFCR&gt;.
19
20 @par Use case
21 One of the challenges with the @c std::string class is that it doesn't
22 provide a way to differentiate between a empty line and @c NULL, unless one
23 uses pointers to @c std::string objects and sets thoes pointers to @c nullptr
24 which entails having to test for that, resulting in more code in various
25 places to test for multiple conditions, thus providing opportunities for more
26 unintended situations (a.k.a., bugs) ... and then there's the matter of how
27 to handle the variety of EoL sequences that persist due to the diversity of
28 Operating System standards, which complicates matters even more.
29
30 This class handles all of these details gracefully, and provides a thorough
31 variety of clearly-documented methods that ensure consistency whilst handling
32 the complexity of EoL sequences that may be 1 or 2 characters long and also
33 differentiating between blank and NULL lines, all without causing significant
34 increases in complexity in code.
35
36 @par History
37 - 2024-Nov-16 v1.00 Initial version
38 @version 1.00
39 @author Randolf Richardson
40 *///=========================================================================
41 class rline {
42 private:
43 char _eol_sequence[3] = {'\0', '\0', '\0'}; // EoL sequence (default = none)
44 int _eol_size = 0; // Size of EoL sequence (default = 0)
45 bool _eol_LFCR = false; // Only TRUE if EoL is broken <LFCR> sequence
46 std::string _line; // Text data without EoL sequence
47
48 /*======================================================================*//**
49 @brief
50 Internal line-of-text intake method used by constructors and other methods.
51 *///=========================================================================
52 void _rline(
53 /// Line of text to embrace
54 const char* line,
55 /// Length of data (-1 = ASCIIZ string)
56 size_t len = -1
57 ) noexcept {
58
59 // --------------------------------------------------------------------------
60 // Syntax checks.
61 // --------------------------------------------------------------------------
62 if (line == nullptr) { // A null string was provided, so just do a clear() to prevent a Null Pointer Exception
63 _eol_sequence[0] = '\0';
64 _eol_sequence[1] = '\0';
65 _eol_size = 0;
66 _eol_LFCR = false;
67 _line.clear();
68 return;
69 } // -x- if !line -x-
70
71 // --------------------------------------------------------------------------
72 // Measure size of line if an ASCIIZ string was indicated.
73 // --------------------------------------------------------------------------
74 if (len == -1) len = std::strlen(line);
75 if (len == 0) return; // Empty line provided, so our class-defaults will be correct
76
77 // --------------------------------------------------------------------------
78 // Locate single-character EoL sequence.
79 // --------------------------------------------------------------------------
80 char ch1 = line[len - 1];
81 if ((ch1 == 10) || (ch1 == 13)) {
82 _eol_sequence[0] = ch1;
83 _eol_size++;
84 } // -x- if ch1 -x-
85 if (len == 1) return; // Total string length is 1 character, so we only needed to check for CR or LF
86
87 // --------------------------------------------------------------------------
88 // Locate double-character of EoL sequence (ch1 is actually the last/second
89 // character, which means that ch0 is the penultimate character, but only if
90 // ch0 is CR or LF).
91 // --------------------------------------------------------------------------
92 char ch0 = line[len - 2];
93 if (((ch0 == 10) && (ch1 == 13)) || ((ch0 == 13) && (ch1 == 10))) {
94 _eol_sequence[0] = ch0;
95 _eol_sequence[1] = ch1;
96 _eol_size++;
97 if (ch0 == 10) _eol_LFCR = true;
98 } // -x- if ch0 -x-
99
100 // --------------------------------------------------------------------------
101 // Assign line of text, without EoL sequence (if any) to the internal string.
102 // --------------------------------------------------------------------------
103 _line.assign(line, len - _eol_size);
104
105 }; // -x- void _rline -x-
106
107 public:
108 /*======================================================================*//**
109 @brief
110 Instantiate an empty rline, with no EoL sequences present.
111 *///=========================================================================
112 rline() noexcept {}; // -x- constructor rline -x-
113
114 /*======================================================================*//**
115 @brief
116 Instantiate a new rline based on the supplied line of text. Multiple lines
117 embedded in the supplied string will be treated as a single line, and ignored
118 therefore.
119 @note
120 If @c line is a @c nullptr then it will be treated as an empty string without
121 any EoL sequence instead of throwing a Null Pointer Exception. This provides
122 an opportunity to write simpler code (see the @ref is_null method for the way
123 to check for this condition, which also happens to be the result of using the
124 default constructor or calling @ref clear).
125 @see is_null
126 *///=========================================================================
127 rline(
128 /// Line of text to embrace
129 char* line,
130 /// Length of line (-1 == ASCIIZ string)
131 size_t len = -1
132 ) noexcept { _rline(line, len); }; // -x- constructor rline -x-
133
134 /*======================================================================*//**
135 @brief
136 Instantiate a new rline based on the supplied line of text. Multiple lines
137 embedded in the supplied string will be treated as a single line, and ignored
138 therefore.
139 *///=========================================================================
140 rline(
141 /// Line of text to embrace
142 std::string line
143 ) noexcept { _rline(line.c_str(), line.length()); }; // -x- constructor rline -x-
144
145 /*======================================================================*//**
146 @brief
147 Add the specified string (without checking whether it contains any EoL
148 character sequences) onto the line of text (before the EoL sequence).
149 @returns The same rline object so as to facilitate stacking
150 @see assign
151 @see insert
152 *///=========================================================================
153 rline* append(
154 /// The string to insert
155 const std::string str) {
156 _line.append(str);
157 return this;
158 }; // -x- rline* append -x-
159
160 /*======================================================================*//**
161 @brief
162 Assign a new line of text, the same way the constructor assigns it.
163 @note
164 If @c line is a @c nullptr then it will be treated as an empty string without
165 any EoL sequence instead of throwing a Null Pointer Exception. This provides
166 an opportunity to write simpler code (see the @ref is_null method for the way
167 to check for this condition, which also happens to be the result of using the
168 default constructor or calling @ref clear).
169 @returns The same rline object so as to facilitate stacking
170 @see append
171 @see insert
172 *///=========================================================================
173 rline* assign(
174 /// Line of text to embrace
175 char* line,
176 /// Length of line (-1 == ASCIIZ string)
177 size_t len = -1
178 ) noexcept {
179
180 // --------------------------------------------------------------------------
181 // Reset internal variables.
182 // --------------------------------------------------------------------------
183 _eol_sequence[0] = '\0';
184 _eol_sequence[1] = '\0';
185 _eol_size = 0;
186 _eol_LFCR = false;
187
188 // --------------------------------------------------------------------------
189 // Replace line of text.
190 // --------------------------------------------------------------------------
191 _rline(line, len);
192
193 return this;
194 }; // -x- rline* assign -x-
195
196 /*======================================================================*//**
197 @brief
198 Assign a new line of text, the same way the constructor assigns it.
199 @returns The same rline object so as to facilitate stacking
200 @see append
201 @see insert
202 *///=========================================================================
203 rline* assign(
204 /// Line of text to embrace
205 std::string line
206 ) noexcept {
207
208 // --------------------------------------------------------------------------
209 // Reset internal variables.
210 // --------------------------------------------------------------------------
211 _eol_sequence[0] = '\0';
212 _eol_sequence[1] = '\0';
213 _eol_size = 0;
214 _eol_LFCR = false;
215
216 // --------------------------------------------------------------------------
217 // Replace line of text.
218 // --------------------------------------------------------------------------
219 _rline(line.c_str(), line.length());
220
221 return this;
222 }; // -x- rline* assign -x-
223
224 /*======================================================================*//**
225 @brief
226 Reset all data and internal flags to the original state of the constructor
227 that has no parameters.
228 @returns The same rline object so as to facilitate stacking
229 *///=========================================================================
230 rline* clear() noexcept {
231
232 // --------------------------------------------------------------------------
233 // Reset internal variables.
234 // --------------------------------------------------------------------------
235 _eol_sequence[0] = '\0';
236 _eol_sequence[1] = '\0';
237 _eol_size = 0;
238 _eol_LFCR = false;
239 _line.clear();
240
241 return this;
242 }; // -x- rline* clear -x-
243
244 /*======================================================================*//**
245 @brief
246 Indicate whether the line of text is empty. Whether an EoL sequence is
247 present is incidental since this only measures the size of the line of text
248 without regard for the presence of an EoL sequence.
249 @returns TRUE = Line of text is empty@nFALSE = Line of text is not empty
250 @see has_eol()
251 @see is_null()
252 *///=========================================================================
253 bool empty() noexcept { return _line.empty(); }; // -x- bool empty -x-
254
255 /*======================================================================*//**
256 @brief
257 Obtain a copy of the EoL sequence.
258 @returns The EoL sequence as a string
259 @see get
260 *///=========================================================================
261 const std::string eol() noexcept { return _eol_sequence; }; // -x- std::string eol -x-
262
263 /*======================================================================*//**
264 @brief
265 Obtain a copy of the entire line of text, without the EoL sequence (albeit by
266 default, this can also be included by setting the @c include_eol flag).
267 @returns The line of text as a string
268 @see eol
269 *///=========================================================================
270 const std::string get(
271 /// Whether to include the EoL sequence (default = FALSE)
272 const bool include_eol = false
273 ) noexcept { return include_eol ? _line + _eol_sequence : _line; }; // -x- std::string get -x-
274
275 /*======================================================================*//**
276 @brief
277 Indicate whether an EoL sequence was detected.
278 @returns TRUE = EoL sequence is present@nFALSE = No EoL sequence
279 @see empty
280 *///=========================================================================
281 bool has_eol() noexcept { return _eol_size != 0; }; // -x- bool has_eol -x-
282
283 /*======================================================================*//**
284 @brief
285 Insert the specified string (without checking whether it contains any EoL
286 character sequences) into the line of text at the specified position.
287 @exception std::out_of_range When the position (@c pos) provided is out of
288 range (e.g., position is larger than the entire
289 size of the string)
290 @returns The same rline object so as to facilitate stacking
291 @see append
292 @see assign
293 *///=========================================================================
294 rline* insert(
295 /// Where to insert the supplied string (0 = insert before first character;
296 /// and use a negative number to set the position relative to the end of the
297 /// line of text)
298 int position, // Must be "int" and not "size_t" because we size_t is unsigned
299 /// The string to insert
300 const std::string str) {
301 _line.insert(position >= 0 ? position : (_line.size() + position), str);
302 return this;
303 }; // -x- rline* insert -x-
304
305 /*======================================================================*//**
306 @brief
307 Indicate whether this line is non-existent, which means there is no text and
308 no EoL sequence. This is less than an an @ref empty() string when it
309 confirms that there literally are no characters at all.
310 @returns TRUE = null string@nFALSE = not null (has text and/or an EoL
311 sequence)
312 @see empty
313 *///=========================================================================
314 bool is_null() noexcept { return (_line.size() + _eol_size) == 0; }; // -x- bool is_null -x-
315
316 /*======================================================================*//**
317 @brief
318 Provides the length of the line of text without the EoL sequence.
319
320 This method is identital to the @ref size method.
321 @returns Number of bytes
322 @see size
323 *///=========================================================================
324 size_t length(
325 /// Whether to include the EoL sequence (default = FALSE)
326 const bool include_eol = false
327 ) noexcept { return include_eol ? _line.size() + _eol_size : _line.size(); }; // -x- size_t length -x-
328
329 /*======================================================================*//**
330 @brief
331 Provides the length of the line of text without the EoL sequence.
332
333 This method is identital to the @ref length method.
334 @returns Number of bytes
335 @see length
336 *///=========================================================================
337 size_t size(
338 /// Whether to include the EoL sequence (default = FALSE)
339 const bool include_eol = false
340 ) noexcept { return include_eol ? _line.size() + _eol_size : _line.size(); }; // -x- size_t size -x-
341
342 /*======================================================================*//**
343 @brief
344 Provides the length of the EoL sequence.
345 @returns Number of bytes (0 = no EoL string)
346 *///=========================================================================
347 size_t size_eol() noexcept { return _eol_size; }; // -x- size_t size_eol -x-
348
349 /*======================================================================*//**
350 @brief
351 Convert this @c rline to an @c std::string without the EoL sequence.
352
353 @code{.cpp}
354 #include <iostream> // std::cout, std::cerr, std::endl, etc.
355 #include <string> // std::string
356 #include <randolf/rline>
357
358 int main(int argc, char *argv[]) {
359 rline rl("This is an example.\n");
360 std::string s = rl;
361 std::cout << "\"" << s << "\"" << std::endl;
362 return EXIT_SUCCESS;
363 } // -x- int main -x-
364 @endcode
365
366 @returns The line of text as an std::string object
367 @see get
368 *///=========================================================================
369 operator std::string() const noexcept { return _line; }; // -x- operator std::string -x-
370
371 /*======================================================================*//**
372 @brief
373 Support convenient streaming usage with std::cout, std::cerr, and friends.
374 @returns Line of text, without EoL sequence
375 *///=========================================================================
376 friend std::ostream& operator<< (
377 /// Output stream (provided automatically by std::cout and std::cerr)
378 std::ostream& o,
379 /// Object class (matched by compiler)
380 rline const& c) { return o << (char*)c._line.c_str(); }; // -x- std::ostream& operator<< -x-
381
382 }; // -x- class rline -x-
383
384}; // -x- namespace randolf -x-