randolf.ca  1.00
Randolf Richardson's C++ classes
Loading...
Searching...
No Matches
rline
1#pragma once
2
3#include <cstring>
4#include <vector>
5
6namespace randolf {
7
8 /*======================================================================*//**
9 @brief
10 One line of text is stored in this class, with additional metadata to track
11 whether the text was terminated by an EoL (End of Line) sequence, and, if so,
12 what the EoL sequence was.
13
14 This is particularly useful for reading lines of text from a file or a socket
15 without having to go to additional efforts to track whether the final line is
16 terminated with an EoL sequence (since this class takes care of this detail).
17
18 By default, the EoL sequence may be &lt;CR&gt;, &lt;LF&gt;, &lt;CRLF&gt;, or
19 &lt;LFCR&gt;.
20
21 @par Use case
22 One of the challenges with the @c std::string class is that it doesn't
23 provide a way to differentiate between a empty line and @c NULL, unless one
24 uses pointers to @c std::string objects and sets thoes pointers to @c nullptr
25 which entails having to test for that, resulting in more code in various
26 places to test for multiple conditions, thus providing opportunities for more
27 unintended situations (a.k.a., bugs) ... and then there's the matter of how
28 to handle the variety of EoL sequences that persist due to the diversity of
29 Operating System standards, which complicates matters even more.
30
31 This class handles all of these details gracefully, and provides a thorough
32 variety of clearly-documented methods that ensure consistency whilst handling
33 the complexity of EoL sequences that may be 1 or 2 characters long and also
34 differentiating between blank and NULL lines, all without causing significant
35 increases in complexity in code.
36
37 @par History
38 - 2024-Nov-16 v1.00 Initial version
39 - 2025-Jan-05 v1.00 Added c_str(), data(), and append(char*, int) methods,
40 and the rline(std::string, std::string) constructor
41 - 2025-Jan-09 v1.00 Added @ref eol(char, char) and @ref eol(std::string)
42 methods for configuring a specific EoL sequence
43 - 2025-Jan-12 v1.00 Changed std::length_error to std::invalid_argument in
44 response to length being specified as a negative value
45 so this can more easily be handled with seperate code;
46 added the @c at() method, and [], ==, and != operators
47 - 2025-Jan-13 v1.00 Improved internal support for EoL flag indication
48 - 2025-Feb-03 v1.00 Increased use of references and pointers
49 - 2025-Feb-09 v1.00 Added a constructor, and @ref append(), @ref assign(),
50 and @ref to_vector(), methods, and, finally, also an
51 `operator std::vector<char>` method, all of which make
52 easy to work with data in `std::vector<char>` arrays
53 - 2025-Feb-10 v1.00 Added @ref clear_all() method (which complements the
54 @ref clear() method)
55 - 2025-Feb-13 v1.00 Added @ref append(const char) method, and @ref first()
56 and @ref last() methods
57 @version 1.00
58 @author Randolf Richardson
59 *///=========================================================================
60 class rline {
61 private:
62 std::string __line; // Text data without EoL sequence
63 char __eol_detected[3] = {'\0', '\0', '\0'}; // EoL sequence detected (default = none)
64 int __eol_detected_size = 0; // EoL sequence detected size (default = 0)
65 char __eol_standard[3] = {'\0', '\0', '\0'}; // EoL sequence standard (default = none/autodetect)
66 int __eol_standard_size = 0; // EoL sequence standard size (default = 0)
67
68 /*======================================================================*//**
69 @brief
70 Internal line-of-text intake method used by constructors and other methods.
71 @returns TRUE = EoL sequence was encountered@n
72 FALSE = EoL sequence is not present
73 *///=========================================================================
74 bool __rline(
75 /// Line of text to embrace
76 const char* line,
77 /// Length of data (-1 = ASCIIZ string)
78 int len = -1) noexcept {
79
80 // --------------------------------------------------------------------------
81 // Syntax checks.
82 // --------------------------------------------------------------------------
83 if (line == nullptr) return false; // A null string was provided, so we can avoid a lot of processing
84
85//std::cout << "__rline len=" << __line.size() << " data=[" << data << "]" << std::endl;
86 // --------------------------------------------------------------------------
87 // Measure size of line if an ASCIIZ string was indicated.
88 // --------------------------------------------------------------------------
89 if (len == -1) len = std::strlen(line);
90 if (len == 0) return false; // Empty line provided, so this is an easy finish
91
92 // --------------------------------------------------------------------------
93 // Pre-allocate memory in std::string so that we can write directly to the
94 // underlying array, which is the most efficient (fastest) way.
95 // --------------------------------------------------------------------------
96 int offset = __line.size();
97 __line.resize(offset + len); // In case of a dymamic realloc(), this must be done before calling __line.data()
98 char* __line_data = __line.data();
99
100//std::cout << "__rline len=" << len << " offset=" << offset << " line=[" << line << "]" << std::endl;
101//std::cout << "__rline len=" << len << " offset=" << offset << std::endl;
102 // --------------------------------------------------------------------------
103 // Detect existing (as in already-defined) EoL sequence while copying data.
104 // --------------------------------------------------------------------------
105 if (__eol_standard_size != 0) {
106//std::cout << "__eol_sequence_size != 0" << std::endl;
107
108 // --------------------------------------------------------------------------
109 // Internal variables.
110 // --------------------------------------------------------------------------
111 char c;
112 char e = __eol_standard[0];
113 int i;
114
115 // --------------------------------------------------------------------------
116 // Check for edge case with 2-character EoL sequence carries over from
117 // existing line data.
118 // --------------------------------------------------------------------------
119 if (offset > 0 && __eol_standard_size == 2) {
120 if (__line[offset - 1] == e && line[0] == __eol_standard[1]) {
121 __line.resize(__line.size() - 1); // Crop previous character
122 __eol_detected[0] = e;
123 __eol_detected[1] = line[0];
124 __eol_detected_size = __eol_standard_size;
125 return true;
126 } // -x- if e -x-
127 } // -x- if !offset -x-
128
129//std::cout << "__rline len=" << len << " offset=" << offset << " line=[" << line << "]" << std::endl;
130 // --------------------------------------------------------------------------
131 // Copy data.
132 // --------------------------------------------------------------------------
133 for (i = 0; i < len; i++) {
134
135 // --------------------------------------------------------------------------
136 // Obtain next character to absorb.
137 // --------------------------------------------------------------------------
138 c = line[i];
139
140 // --------------------------------------------------------------------------
141 // EoL sequence detected. If this is a one-character EoL sequence, then it
142 // matches and we're done; or if this is a two-character EoL sequence, then
143 // it also must match; otherwise the sequence will be ignored and treated as
144 // a normal part of the text.
145 // --------------------------------------------------------------------------
146 if (c == e) {
147 if (__eol_standard_size == 1) {
148 __eol_detected[0] = e;//__eol_standard[0];
149 __eol_detected[1] = 0;//__eol_standard[1];
150 __eol_detected_size = 1;
151 break;
152 } // -x- if 1 -x-
153 if ((i + 1) < len && line[i + 1] == __eol_standard[1]) {
154 __eol_detected[0] = e;
155 __eol_detected[1] = __eol_standard[1];
156 __eol_detected_size = 2;
157 break;
158 } // -x- if 2 -x-
159
160 } // -x- if c -x-
161
162 // --------------------------------------------------------------------------
163 // Append character to internal __line by writing directly to the underlying
164 // array (which is the most efficienty way to store it), then advance the
165 // offset index.
166 // --------------------------------------------------------------------------
167 __line_data[offset++] = c;
168
169 } // -x- for i -x-
170
171 // --------------------------------------------------------------------------
172 // Automatic EoL sequence detection while copying data.
173 // --------------------------------------------------------------------------
174 } else { // This "else" statement is effectively the same as: if (eol_standard_size == 0)
175//std::cout << "__eol_sequence_size == 0" << std::endl;
176
177 // --------------------------------------------------------------------------
178 // Internal variables.
179 // --------------------------------------------------------------------------
180 char c;
181 int i;
182
183//std::cout << "__rline len=" << len << " offset=" << offset << " line=[" << line << "]" << std::endl;
184 // --------------------------------------------------------------------------
185 // Copy data.
186 // --------------------------------------------------------------------------
187 for (i = 0; i < len; i++) {
188
189 // --------------------------------------------------------------------------
190 // Obtain next character to absorb.
191 // --------------------------------------------------------------------------
192 c = line[i];
193
194 // --------------------------------------------------------------------------
195 // LF detected, so save it and exit the loop, while also advancing
196 // __eol_sequence_size (in two places if this is a broken LFCR sequence). We
197 // do need to compare "i" against "len" to prevent a 1-byte index overflow.
198 // --------------------------------------------------------------------------
199 if (c == 10) { // LF detected
200 __eol_detected_size = __eol_standard_size;
201 __eol_detected[__eol_detected_size++] = c;
202 __eol_standard[__eol_standard_size++] = c;
203 if ((i + 1) < len && line[i + 1] == 13) { // CR detected, so save it too
204 __eol_detected[__eol_detected_size++] = 13;
205 __eol_standard[__eol_standard_size++] = 13;
206 } // -x- if CR -x-
207//std::cout << "__eol_detected_size<LF>=" << __eol_detected_size << std::endl;
208 break;
209
210 // --------------------------------------------------------------------------
211 // CR detected, so save it and exit the loop, while also advancing
212 // __eol_sequence_size (in two places if this is a valid CRLF sequence). We
213 // do need to compare "i" against "len" to prevent a 1-byte index overflow.
214 // --------------------------------------------------------------------------
215 } else if (c == 13) { // CR detected
216 __eol_detected_size = __eol_standard_size;
217 __eol_detected[__eol_detected_size++] = c;
218 __eol_standard[__eol_standard_size++] = c;
219 if ((i + 1) < len && line[i + 1] == 10) { // LF detected, so save it too
220 __eol_detected[__eol_detected_size++] = 10; // LF detected, so save it too
221 __eol_standard[__eol_standard_size++] = 10; // LF detected, so save it too
222 } // -x- if LF -x-
223//std::cout << "__eol_detected_size<CR>=" << __eol_detected_size << std::endl;
224 break;
225
226 } // -x- if CR/LF -x-
227
228 // --------------------------------------------------------------------------
229 // Append character to internal __line by writing directly to the underlying
230 // array (which is the most efficienty way to store it), then advance the
231 // offset index.
232 // --------------------------------------------------------------------------
233 __line_data[offset++] = c;
234//std::cout << "c=" << c << " offset=" << offset << std::endl;
235
236 } // -x- for i -x-
237
238 } // -x- if !__eol_standar_size -x-
239
240 // --------------------------------------------------------------------------
241 // The "offset" variable was updated by the loop to ensure it holds the
242 // accurate length of __line internally. The "i" variable was loop-local.
243 // --------------------------------------------------------------------------
244 __line.resize(offset);
245//std::cout << "offset=" << offset << " __line=[" << get() << "]" << std::endl;
246
247 return __eol_standard_size > 0; // TRUE = EoL sequence was encountered
248 } // -x- bool __rline -x-
249
250 public:
251 /*======================================================================*//**
252 @brief
253 Instantiate an empty rline, with no EoL sequences present.
254 *///=========================================================================
255 rline() noexcept {} // -x- constructor rline -x-
256
257 /*======================================================================*//**
258 @brief
259 Instantiate a new rline based on the supplied line of text, ending early upon
260 encountering the first EoL sequence (use either the @ref size(bool) or the
261 @ref size_with_eol() method to obtain the total quantity of characters that
262 were absorbed, including the EoL sequence {if present}).
263
264 If no EoL sequence was encountered, the @ref has_eol() method will return
265 `false` even though the contents of the supplied line were copied (and are
266 still accessible) -- this is useful in loops reading lines of text that are
267 too long to store in memory with a single allocation (and either interpreting
268 the absorbed data somehow, or using a simple loop to skip through to the end
269 of the line before issuing an error that indicates the total line length).
270 @note
271 If @c line is a @c nullptr then it will be treated as an empty string without
272 an EoL sequence, instead of throwing a Null Pointer Exception. This provides
273 an opportunity to write simpler code (see the @ref is_null method for the way
274 to check for this condition, which also happens to be the result of using the
275 default constructor or calling @ref clear).
276 @see assign
277 @see clear
278 @see has_eol
279 @see is_null
280 @see size
281 @see size_with_eol
282 *///=========================================================================
283 rline(
284 /// Line of text to embrace
285 const char* line,
286 /// Length of line (-1 == ASCIIZ string)
287 const size_t len = -1) noexcept {
288 __rline(line, len);
289 } // -x- constructor rline -x-
290
291 /*======================================================================*//**
292 @brief
293 Instantiate a new rline based on the supplied line of text, ending early upon
294 encountering the first EoL sequence (use either the `size(true)` or the
295 `size_full()` method to obtain the total quantity of characters that were
296 absorbed, including the EoL sequence {if present}).
297
298 If no EoL sequence was encountered, the @ref has_eol() method will return
299 `false` even though the contents of the supplied line were copied (and are
300 still accessible) -- this is useful in loops reading lines of text that are
301 too long to store in memory with a single allocation (and either interpreting
302 the absorbed data somehow, or using a simple loop to skip through to the end
303 of the line before issuing an error that indicates the total line length).
304 @see assign
305 @see has_eol
306 @see is_null
307 @see size
308 @see size_with_eol
309 *///=========================================================================
310 rline(
311 /// Line of text to embrace
312 const std::string& line,
313 /// Length of line (-1 == defer to length of line provided)
314 const size_t len = -1) noexcept {
315 __rline(line.data(), len == -1 ? line.length() : len);
316 } // -x- constructor rline -x-
317
318 /*======================================================================*//**
319 @brief
320 Instantiate a new rline based on the supplied line of text, ending early upon
321 encountering the first EoL sequence (the `size(true)` and `size_full()`
322 methods obtain the total quantity of characters that were absorbed, including
323 the EoL sequence {if present}).
324
325 If no EoL sequence was encountered, the @ref has_eol() method will return
326 `false` even though the contents of the supplied line were copied (and are
327 still accessible) -- this is useful in loops reading lines of text that are
328 too long to store in memory with a single allocation (and either interpreting
329 the absorbed data somehow, or using a simple loop to skip through to the end
330 of the line before issuing an error that indicates the total line length).
331 @throws std::length_error if the EoL sequence is longer than 2 characters
332 @see assign
333 @see has_eol
334 @see is_null
335 @see size
336 @see size_with_eol
337 *///=========================================================================
338 rline(
339 /// Line of text to embrace, without the EoL sequence characters
340 const std::string& line,
341 /// The EoL sequence to impose as if it had been detected (may be only 1 or 2
342 /// characters long)
343 const std::string& eol_sequence) {
344
345 // --------------------------------------------------------------------------
346 // Save EoL sequence while also performing a simple syntax check.
347 // --------------------------------------------------------------------------
348 switch (eol_sequence.size()) {
349 case 2:
350 __eol_detected[0] = __eol_standard[0] = eol_sequence[0];
351 __eol_detected[1] = __eol_standard[1] = eol_sequence[1];
352 __eol_detected_size = __eol_standard_size = 2;
353 break;
354 case 1:
355 __eol_detected[0] = __eol_standard[0] = eol_sequence[0];
356 __eol_detected[1] = __eol_standard[1] = '\0';
357 __eol_detected_size = __eol_standard_size = 1;
358 break;
359 case 0:
360 __eol_detected[0] = __eol_standard[0] = '\0';
361 __eol_detected[1] = __eol_standard[1] = '\0';
362 __eol_detected_size = __eol_standard_size = 0;
363 break;
364 deafult: // Any other length is invalid
365 throw std::length_error("eol_sequence is too long (maximum length is 2 characters)");
366 } // -x- switch eol_sequence.size -x-
367
368 // --------------------------------------------------------------------------
369 // Set line of text.
370 // --------------------------------------------------------------------------
371 __rline(line.data(), line.length());
372
373 } // -x- constructor rline -x-
374
375 /*======================================================================*//**
376 @brief
377 Instantiate a new rline based on the supplied line of text, ending early upon
378 encountering the first EoL sequence (use either the `size(true)` or the
379 `size_full()` method to obtain the total quantity of characters that were
380 absorbed, including the EoL sequence {if present}).
381
382 If no EoL sequence was encountered, the @ref has_eol() method will return
383 `false` even though the contents of the supplied line were copied (and are
384 still accessible) -- this is useful in loops reading lines of text that are
385 too long to store in memory with a single allocation (and either interpreting
386 the absorbed data somehow, or using a simple loop to skip through to the end
387 of the line before issuing an error that indicates the total line length).
388 @see assign
389 @see has_eol
390 @see is_null
391 @see size
392 @see size_with_eol
393 *///=========================================================================
394 rline(
395 /// Line of text to embrace
396 const std::vector<char>& line,
397 /// Length of line (-1 == defer to length of line provided)
398 const size_t len = -1) noexcept {
399 __rline(line.data(), len == -1 ? line.size() : len);
400 } // -x- constructor rline -x-
401
402 /*======================================================================*//**
403 @brief
404 Add the specified character (without checking whether it's part of an EoL
405 character sequence) onto the line of text (before the EoL sequence).
406 @returns The same rline object so as to facilitate stacking
407 @see assign
408 @see insert
409 *///=========================================================================
410 rline& append(
411 /// The string to insert
412 const char character) {
413 char ch[] = { character, 0 };
414 __rline(ch, 1);
415 return *this;
416 } // -x- rline& append -x-
417
418 /*======================================================================*//**
419 @brief
420 Add the specified string (without checking whether it contains any EoL
421 character sequences) onto the line of text (before the EoL sequence).
422 @returns The same rline object so as to facilitate stacking
423 @see assign
424 @see insert
425 *///=========================================================================
426 rline& append(
427 /// The string to insert
428 const char* line,
429 /// Length of line (-1 == ASCIIZ string)
430 const size_t len = -1) {
431 __rline(line, len);
432 return *this;
433 } // -x- rline& append -x-
434
435 /*======================================================================*//**
436 @brief
437 Add the specified std::string (without checking whether it contains any EoL
438 character sequences) onto the line of text (before the EoL sequence).
439 @returns The same rline object so as to facilitate stacking
440 @see assign
441 @see insert
442 *///=========================================================================
443 rline& append(
444 /// The string to insert
445 const std::string& line,
446 /// Length of line (-1 == defer to length of line provided)
447 const size_t len = -1) {
448 __rline(line.data(), len == -1 ? line.length() : len);
449 return *this;
450 } // -x- rline& append -x-
451
452 /*======================================================================*//**
453 @brief
454 Add the specified std::vector<char> (without checking whether it contains any
455 EoL character sequences) onto the line of text (before the EoL sequence).
456 @returns The same rline object so as to facilitate stacking
457 @see assign
458 @see insert
459 *///=========================================================================
460 rline& append(
461 /// The data to insert
462 const std::vector<char>& line,
463 /// Length of line (-1 == defer to length of line provided)
464 const size_t len = -1) {
465 __rline(line.data(), len == -1 ? line.size() : len);
466 return *this;
467 } // -x- rline& append -x-
468
469 /*======================================================================*//**
470 @brief
471 Assign a new rline based on the supplied line of text, ending early upon
472 encountering the first EoL sequence (use either the @ref size(bool) or the
473 @ref size_with_eol() method to obtain the total quantity of characters that
474 were absorbed, including the EoL sequence {if present}).
475
476 If no EoL sequence was encountered, the @ref has_eol() method will return
477 `false` even though the contents of the supplied line were copied (and are
478 still accessible) -- this is useful in loops reading lines of text that are
479 too long to store in memory with a single allocation (and either interpreting
480 the absorbed data somehow, or using a simple loop to skip through to the end
481 of the line before issuing an error that indicates the total line length).
482 @note
483 If @c line is a @c nullptr then it will be treated as an empty string without
484 an EoL sequence, instead of throwing a Null Pointer Exception. This provides
485 an opportunity to write simpler code (see the @ref is_null method for the way
486 to check for this condition, which also happens to be the result of using the
487 default constructor or calling @ref clear).
488 @returns The same rline object so as to facilitate stacking
489 @see append
490 @see clear
491 @see has_eol
492 @see insert
493 @see is_null
494 @see size
495 @see size_with_eol
496 *///=========================================================================
497 rline& assign(
498 /// Line of text to embrace
499 const char* line,
500 /// Length of line (-1 == ASCIIZ string)
501 const size_t len = -1) noexcept {
502
503 // --------------------------------------------------------------------------
504 // Reset internal variables.
505 // --------------------------------------------------------------------------
506 __line.clear();
507
508 // --------------------------------------------------------------------------
509 // Replace line of text.
510 // --------------------------------------------------------------------------
511 __rline(line, len);
512
513 return *this;
514 } // -x- rline& assign -x-
515
516 /*======================================================================*//**
517 @brief
518 Assign a new rline based on the supplied line of text, ending early upon
519 encountering the first EoL sequence (use either the @ref size(bool) or the
520 @ref size_with_eol() method to obtain the total quantity of characters that
521 were absorbed, including the EoL sequence {if present}).
522
523 If no EoL sequence was encountered, the @ref has_eol() method will return
524 `false` even though the contents of the supplied line were copied (and are
525 still accessible) -- this is useful in loops reading lines of text that are
526 too long to store in memory with a single allocation (and either interpreting
527 the absorbed data somehow, or using a simple loop to skip through to the end
528 of the line before issuing an error that indicates the total line length).
529 @returns The same rline object so as to facilitate stacking
530 @see append
531 @see has_eol
532 @see insert
533 @see is_null
534 @see size
535 @see size_with_eol
536 *///=========================================================================
537 rline& assign(
538 /// Line of text to embrace
539 const std::string& line,
540 /// Length of line (-1 == defer to length of line provided)
541 const size_t len = -1) noexcept {
542
543 // --------------------------------------------------------------------------
544 // Reset internal variables.
545 // --------------------------------------------------------------------------
546 __line.clear();
547
548 // --------------------------------------------------------------------------
549 // Replace line of text.
550 // --------------------------------------------------------------------------
551 __rline(line.data(), len == -1 ? line.length() : len);
552
553 return *this;
554 } // -x- rline& assign -x-
555
556 /*======================================================================*//**
557 @brief
558 Assign a new rline based on the supplied line of text, ending early upon
559 encountering the first EoL sequence (use either the @ref size(bool) or the
560 @ref size_with_eol() method to obtain the total quantity of characters that
561 were absorbed, including the EoL sequence {if present}).
562
563 If no EoL sequence was encountered, the @ref has_eol() method will return
564 `false` even though the contents of the supplied line were copied (and are
565 still accessible) -- this is useful in loops reading lines of text that are
566 too long to store in memory with a single allocation (and either interpreting
567 the absorbed data somehow, or using a simple loop to skip through to the end
568 of the line before issuing an error that indicates the total line length).
569 @returns The same rline object so as to facilitate stacking
570 @see append
571 @see has_eol
572 @see insert
573 @see is_null
574 @see size
575 @see size_with_eol
576 *///=========================================================================
577 rline& assign(
578 /// Line of text to embrace
579 const std::vector<char>& line,
580 /// Length of line (-1 == defer to length of line provided)
581 const size_t len = -1) noexcept {
582
583 // --------------------------------------------------------------------------
584 // Reset internal variables.
585 // --------------------------------------------------------------------------
586 __line.clear();
587
588 // --------------------------------------------------------------------------
589 // Replace line of text.
590 // --------------------------------------------------------------------------
591 __rline(line.data(), len == -1 ? line.size() : len);
592
593 return *this;
594 } // -x- rline& assign -x-
595
596 /*======================================================================*//**
597 @brief
598 Array-style access to the underlying std::string (without the EoL sequence).
599
600 The first element is at index 0.
601 @throws std::out_of_range if the index is out-of-range
602 @returns char
603 @see first
604 @see last
605 @see operator[]
606 *///=========================================================================
607 char at(
608 /// Index of character to access (0 = first element; negative index values
609 /// are calculated in reverse, starting with -1 as the final position)
610 const int index) {
611
612 // --------------------------------------------------------------------------
613 // Internal variables.
614 // --------------------------------------------------------------------------
615 int MAX = __line.length();
616
617 // --------------------------------------------------------------------------
618 // Syntax checks.
619 // --------------------------------------------------------------------------
620 if (index >= MAX || 0 - index > MAX) throw std::out_of_range("index (position=" + std::to_string(index) + ") falls outside of the underlying string (length=" + std::to_string(MAX) + ")");
621
622 return __line[index >= 0 ? index : MAX - index];
623 } // -x- char at -x-
624
625 /*======================================================================*//**
626 @brief
627 Reset all data (including the detected EoL sequence) while preserving
628 internal flags (including the established EoL standard).
629 @returns The same rline object so as to facilitate stacking
630 *///=========================================================================
631 rline& clear() noexcept {
632
633 // --------------------------------------------------------------------------
634 // Reset internal variables.
635 // --------------------------------------------------------------------------
636 __eol_detected[0] = '\0';
637 __eol_detected[1] = '\0';
638 __eol_detected_size = 0;
639 __line.clear();
640
641 return *this;
642 } // -x- rline& clear -x-
643
644 /*======================================================================*//**
645 @brief
646 Reset all data (including the detected EoL sequence) and reset internal flags
647 (including the established EoL standard) to be like the original state of the
648 constructor that has no parameters.
649 @returns The same rline object so as to facilitate stacking
650 *///=========================================================================
651 rline& clear_all() noexcept {
652
653 // --------------------------------------------------------------------------
654 // Reset internal variables.
655 // --------------------------------------------------------------------------
656 __eol_detected[0] = __eol_standard[0] = '\0';
657 __eol_detected[1] = __eol_standard[1] = '\0';
658 __eol_detected_size = __eol_standard_size = 0;
659 __line.clear();
660
661 return *this;
662 } // -x- rline& clear_all -x-
663
664 /*======================================================================*//**
665 @brief
666 Returns a pointer to the underlying string array, without the EoL sequence.
667 @returns Pointer to buffer (null terminated)
668 @see data
669 *///=========================================================================
670 const char* c_str() noexcept {
671 return __line.c_str();
672 } // -x- char* c_str -x-
673
674 /*======================================================================*//**
675 @brief
676 Returns a pointer to the underlying string array, without the EoL sequence.
677 @returns Pointer to buffer (not null terminated)
678 @see c_str
679 *///=========================================================================
680 char* data() noexcept {
681 return __line.data();
682 } // -x- char* data -x-
683
684 /*======================================================================*//**
685 @brief
686 Obtain a copy of the EoL sequence that was detected.
687 @returns A pointer to the EoL sequence as a @c char[] array
688 @see eol
689 @see get
690 *///=========================================================================
691 const char* data_eol(
692 /// TRUE = return the detected EoL sequence (default) @n
693 /// FALSE = return the EoL sequence to use when detecting an EoL sequence
694 const bool detected = true) noexcept {
695 return detected ? __eol_detected : __eol_standard;
696 } // -x- char* data_eol -x-
697
698 /*======================================================================*//**
699 @brief
700 Returns a pointer to the underlying std::string object, without the EoL
701 sequence.
702 @returns Pointer to buffer (not null terminated)
703 @see c_str
704 @see to_vector
705 *///=========================================================================
706 std::string* data_string() noexcept {
707 return &__line;
708 } // -x- std::string* data -x-
709
710 /*======================================================================*//**
711 @brief
712 Indicate whether the line of text is empty. Whether an EoL sequence is
713 present is incidental since this only measures the size of the line of text
714 without regard for the presence of an EoL sequence.
715 @returns TRUE = Line of text is empty@n
716 FALSE = Line of text is not empty
717 @see has_eol()
718 @see is_null()
719 *///=========================================================================
720 bool empty() noexcept {
721 return __line.empty();
722 } // -x- bool empty -x-
723
724 /*======================================================================*//**
725 @brief
726 Obtain a copy of the EoL sequence that was detected.
727 @returns The EoL sequence as a string
728 @see data_eol
729 @see get
730 *///=========================================================================
731 const std::string eol(
732 /// TRUE = return the detected EoL sequence (default) @n
733 /// FALSE = return the EoL sequence to use when detecting an EoL sequence
734 const bool detected = true) noexcept {
735 return std::string(detected ? __eol_detected : __eol_standard);
736 } // -x- std::string& eol -x-
737
738 /*======================================================================*//**
739 @brief
740 Define the EoL sequence to use when detecting an EoL sequence (also clears
741 the detected EoL sequence, if present).
742 @note
743 This method is the most efficient @c eol method to use for defining the
744 specific EoL sequence to use.
745 @returns The same rline object so as to facilitate stacking
746 @see eol
747 *///=========================================================================
748 rline& eol(
749 /// The first character of the EoL sequence (typically 10 or 13)@c
750 /// 10 = LF (Line Feed) @n
751 /// 13 = CR (Carriage Return) @n
752 /// 0 = enable EoL sequence auto-detection (this is the default in all
753 /// constructors, and is also set by the @ref clear method)
754 const char c1,
755 /// The second character of the EoL sequence (typically 10 or 13)@c
756 /// 10 = LF (Line Feed) @n
757 /// 13 = CR (Carriage Return) @n
758 /// 0 = indicate that this is not a two-character EoL sequence
759 const char c2 = 0) noexcept {
760
761 // --------------------------------------------------------------------------
762 // Save EoL sequence.
763 // --------------------------------------------------------------------------
764 __eol_detected[0] = __eol_detected[1] = 0;
765 __eol_standard[0] = c1;
766 __eol_standard[1] = c1 == 0 ? 0 : c2;
767
768 // --------------------------------------------------------------------------
769 // Calculate the size of the EoL sequence.
770 // --------------------------------------------------------------------------
771 __eol_detected_size = 0;
772 __eol_standard_size = 2;
773 if (c2 == 0) __eol_standard_size = 1;
774 if (c1 == 0) __eol_standard_size = 0;
775
776 return *this;
777 } // -x- rline& eol -x-
778
779 /*======================================================================*//**
780 @brief
781 Define the EoL sequence to use when detecting an EoL sequence (also clears
782 the detected EoL sequence, if present).
783 @throws std::length_error if the EoL sequence is longer than 2 characters
784 @returns The same rline object so as to facilitate stacking
785 @see eol
786 *///=========================================================================
787 rline& eol(
788 /// The EoL sequence (may be only 1 or 2 characters long; or 0 characters to
789 /// enable EoL auto-detection, which is also the default in all constructors)
790 const std::string& eol_sequence) {
791
792 // --------------------------------------------------------------------------
793 // Save EoL sequence while also performing a simple syntax check.
794 // --------------------------------------------------------------------------
795 switch (eol_sequence.size()) {
796 case 2:
797 __eol_detected[0] = __eol_detected[1] = 0;
798 __eol_standard[0] = eol_sequence[0];
799 __eol_standard[1] = eol_sequence[1];
800 __eol_detected_size = 0;
801 __eol_standard_size = 2;
802 break;
803 case 1:
804 __eol_detected[0] = __eol_detected[1] = 0;
805 __eol_standard[0] = eol_sequence[0];
806 __eol_standard[1] = '\0';
807 __eol_detected_size = 0;
808 __eol_standard_size = 1;
809 break;
810 case 0:
811 __eol_detected[0] = __eol_detected[1] = 0;
812 __eol_standard[0] = '\0';
813 __eol_standard[1] = '\0';
814 __eol_detected_size = 0;
815 __eol_standard_size = 0;
816 break;
817 deafult: // Any other length is invalid
818 throw std::length_error("eol_sequence is too long (maximum length is 2 characters)");
819 } // -x- switch eol_sequence.size -x-
820
821 return *this;
822 } // -x- rline& eol -x-
823
824 /*======================================================================*//**
825 @brief
826 Return the first character from the underlying std::string (not including the
827 EoL sequence).
828 @throws std::out_of_range if the underlying string is empty
829 @returns char
830 @see at
831 @see last
832 @see operator[]
833 *///=========================================================================
834 char first() {
835
836 // --------------------------------------------------------------------------
837 // Syntax checks.
838 // --------------------------------------------------------------------------
839 if (__line.empty()) throw std::out_of_range("first() is out of range because rline is empty");
840
841 return __line.front();
842 } // -x- char first -x-
843
844 /*======================================================================*//**
845 @brief
846 Obtain a copy of the entire line of text, without the EoL sequence (albeit by
847 default, this can also be included by setting the @c include_eol flag).
848 @returns The line of text as a string
849 @see eol
850 @see data_eol
851 *///=========================================================================
852 const std::string get(
853 /// Whether to include the EoL detected (default = FALSE)
854 const bool include_eol = false) noexcept {
855 return std::string(include_eol ? __line + __eol_detected : __line);
856 } // -x- std::string get -x-
857
858 /*======================================================================*//**
859 @brief
860 Indicate whether an EoL sequence was detected.
861 @returns TRUE = EoL sequence is present@n
862 FALSE = No EoL sequence
863 @see empty
864 *///=========================================================================
865 bool has_eol() noexcept {
866 return __eol_detected_size != 0;
867 } // -x- bool has_eol -x-
868
869 /*======================================================================*//**
870 @brief
871 Indicate whether a broken EoL sequence detected of `<LFCR>` is being used as
872 the EoL sequence.
873 @returns TRUE = Broken EoL sequence is present@n
874 FALSE = No broken EoL sequence
875 @see empty
876 *///=========================================================================
877 bool has_LFCR(
878 /// TRUE = report on whether a broken EoL sequence was detected (default) @n
879 /// FALSE = report on whether a broken EoL sequence was specifically defined
880 /// as the EoL sequence to detect
881 const bool detected = true) noexcept {
882 if (true) return __eol_detected_size == 2 && __eol_detected[0] == 10 && __eol_detected[1] == 13;
883 else return __eol_standard_size == 2 && __eol_standard[0] == 10 && __eol_standard[1] == 13;
884 } // -x- bool has_LFCR -x-
885
886 /*======================================================================*//**
887 @brief
888 Insert the specified string (without checking whether it contains any EoL
889 character sequences) into the line of text at the specified position.
890 @exception std::out_of_range When the position (@c pos) provided is out of
891 range (e.g., position is larger than the entire
892 size of the string)
893 @returns The same rline object so as to facilitate stacking
894 @see append
895 @see assign
896 *///=========================================================================
897 rline& insert(
898 /// Where to insert the supplied string (0 = insert before first character;
899 /// and use a negative number to set the position relative to the end of the
900 /// line of text)
901 const int position, // Must be "int" and not "size_t" because size_t is unsigned
902 /// The string to insert
903 const std::string& str,
904 /// Length of string (-1 == defer to length of string provided)
905 const size_t len = -1) {
906//std::cout << "str=[" << str << "] position=" << (position >= 0 ? position : __line.size() + position) << std::endl;
907 __line.insert((position >= 0 ? position : __line.size() + position),
908 str,
909 0,
910 len == -1 ? str.size() : len);
911//std::cout << "__line=[" << __line << "]" << std::endl;
912 return *this;
913 } // -x- rline& insert -x-
914
915 /*======================================================================*//**
916 @brief
917 Indicate whether this line is non-existent, which means there is no text and
918 no detected EoL sequence. This is less than an an @ref empty() string when
919 it confirms that there literally are no characters at all.
920 @returns TRUE = null string@n
921 FALSE = not null (has text and/or an EoL
922 sequence)
923 @see empty
924 *///=========================================================================
925 bool is_null() noexcept {
926 return (__line.size() + __eol_detected_size) == 0;
927 } // -x- bool is_null -x-
928
929 /*======================================================================*//**
930 @brief
931 Return the last character from the underlying std::string (not including the
932 EoL sequence).
933 @throws std::out_of_range if the underlying string is empty
934 @returns char
935 @see at
936 @see first
937 @see operator[]
938 *///=========================================================================
939 char last() {
940
941 // --------------------------------------------------------------------------
942 // Syntax checks.
943 // --------------------------------------------------------------------------
944 if (__line.empty()) throw std::out_of_range("last() is out of range because rline is empty");
945
946 return __line.back();
947 } // -x- char last -x-
948
949 /*======================================================================*//**
950 @brief
951 Provides the length of the line of text without the EoL sequence.
952
953 This method is identital to the @ref size method.
954 @returns Number of bytes
955 @see size
956 *///=========================================================================
957 size_t length(
958 /// Whether to include the EoL sequence (default = FALSE)
959 const bool include_eol = false) noexcept {
960 return include_eol ? __line.size() + __eol_detected_size : __line.size();
961 } // -x- size_t length -x-
962
963 /*======================================================================*//**
964 @brief
965 Provides the length of the line of text without the EoL sequence.
966
967 This method is identital to the @ref length method.
968 @returns Number of bytes
969 @see length
970 @see size_eol
971 @see size_with_eol
972 *///=========================================================================
973 size_t size(
974 /// Whether to include the EoL sequence (default = FALSE)
975 const bool include_eol = false) noexcept {
976 return include_eol ? __line.size() + __eol_detected_size : __line.size();
977 } // -x- size_t size -x-
978
979 /*======================================================================*//**
980 @brief
981 Provides the length of the detected EoL sequence.
982 @returns Number of bytes (0 = no EoL string)
983 @see size
984 *///=========================================================================
985 size_t size_eol(
986 /// TRUE = report on the size of the detected EoL sequence (default) @n
987 /// FALSE = report on the size of the EoL sequence to use when detecting an
988 /// EoL sequence
989 const bool detected = true) noexcept {
990 return detected ? __eol_detected_size : __eol_standard_size;
991 } // -x- size_t size_eol -x-
992
993 /*======================================================================*//**
994 @brief
995 Provides the length of the line of text, including the EoL sequence (if one
996 is present).
997 @returns Number of bytes
998 @see length
999 @see size
1000 *///=========================================================================
1001 size_t size_with_eol() noexcept {
1002 return __line.size() + __eol_detected_size;
1003 } // -x- size_t size_with_eol -x-
1004
1005 /*======================================================================*//**
1006 @brief
1007 Converts the underlying std::string object to `std::vector<char>` without
1008 the EoL sequence, and returns it.
1009 @returns std::vector<char> (not null terminated)
1010 @see c_str
1011 @see to_vector
1012 *///=========================================================================
1013 std::vector<char> to_vector() noexcept {
1014 return std::vector<char>(__line.data(), __line.data() + __line.size());
1015 } // -x- std::vector<char> to_vector -x-
1016
1017 /*======================================================================*//**
1018 @brief
1019 Convert this @c rline to an @c std::string without the EoL sequence.
1020
1021 @code{.cpp}
1022 #include <iostream> // std::cout, std::cerr, std::endl, etc.
1023 #include <string> // std::string
1024 #include <randolf/rline>
1025
1026 int main(int argc, char *argv[]) {
1027 randolf::rline rl("This is an example.\n");
1028 std::string s = rl;
1029 std::cout << "\"" << s << "\"" << std::endl;
1030 return EXIT_SUCCESS;
1031 } // -x- int main -x-
1032 @endcode
1033
1034 @returns The line of text as an std::string object
1035 @see get
1036 *///=========================================================================
1037 operator std::string() const noexcept {
1038 return __line;
1039 } // -x- operator std::string -x-
1040
1041 /*======================================================================*//**
1042 @brief
1043 Convert this @c rline to an `std::vector<char>` without the EoL sequence.
1044
1045 @code{.cpp}
1046 #include <iostream> // std::cout, std::cerr, std::endl, etc.
1047 #include <string> // std::string
1048 #include <randolf/rline>
1049
1050 int main(int argc, char *argv[]) {
1051 randolf::rline rl("This is an example.\n");
1052 std::vector<char> v = rl;
1053 std::cout << "\"" << v[0] << "\"" << std::endl;
1054 return EXIT_SUCCESS;
1055 } // -x- int main -x-
1056 @endcode
1057
1058 @returns The line of text as an std::vector<char> object
1059 @see get
1060 *///=========================================================================
1061 operator std::vector<char>() const noexcept {
1062 return std::vector<char>(__line.data(), __line.data() + __line.size());
1063 } // -x- operator std::vector<char> -x-
1064
1065 /*======================================================================*//**
1066 @brief
1067 Compare with the underlying std::string (without the EoL sequence).
1068 @returns TRUE = doesn't match@n
1069 FALSE = match
1070 *///=========================================================================
1071 bool operator!=(
1072 /// String to compare with
1073 const std::string& s) {
1074 return __line != s;
1075 } // -x- bool operator== -x-
1076
1077 /*======================================================================*//**
1078 @brief
1079 Compare with the underlying std::string (without the EoL sequence).
1080 @returns TRUE = match@n
1081 FALSE = doesn't match
1082 *///=========================================================================
1083 bool operator==(
1084 /// String to compare with
1085 const std::string& s) {
1086 return __line == s;
1087 } // -x- bool operator== -x-
1088
1089 /*======================================================================*//**
1090 @brief
1091 Array-style access to the underlying std::string (without the EoL sequence).
1092
1093 The first element is at index 0.
1094 @throws std::out_of_range if the index is out-of-range
1095 @returns char
1096 @see at
1097 @see first
1098 @see last
1099 *///=========================================================================
1100 char operator[](
1101 /// Index of character to access (0 = first element; negative index values
1102 /// are calculated in reverse, starting with -1 as the final position)
1103 int index) {
1104
1105 // --------------------------------------------------------------------------
1106 // Internal variables.
1107 // --------------------------------------------------------------------------
1108 int MAX = __line.length();
1109
1110 // --------------------------------------------------------------------------
1111 // Syntax checks.
1112 // --------------------------------------------------------------------------
1113 if (index >= MAX || 0 - index > MAX) throw std::out_of_range("index (position=" + std::to_string(index) + ") falls outside of the underlying string (length=" + std::to_string(MAX) + ")");
1114
1115 return __line[index >= 0 ? index : MAX - index];
1116 } // -x- char operator[] -x-
1117
1118 /*======================================================================*//**
1119 @brief
1120 Support convenient streaming usage with std::cout, std::cerr, and friends.
1121
1122 @code{.cpp}
1123 #include <iostream> // std::cout, std::cerr, std::endl, etc.
1124 #include <string> // std::string
1125 #include <randolf/rline>
1126
1127 int main(int argc, char *argv[]) {
1128 randolf::rline rl("This is an example.\n");
1129 std::cout << "\"" << rl << "\"" << std::endl;
1130 return EXIT_SUCCESS;
1131 } // -x- int main -x-
1132 @endcode
1133
1134 @returns Line of text, without EoL sequence
1135 *///=========================================================================
1136 friend std::ostream& operator<< (
1137 /// Output stream (provided automatically by std::cout and std::cerr)
1138 std::ostream& o,
1139 /// Object class (matched by compiler)
1140 rline const& c) {
1141 return o << &c.__line;
1142 } // -x- std::ostream& operator<< -x-
1143
1144 }; // -x- class rline -x-
1145
1146}; // -x- namespace randolf -x-