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 - 2025-Jan-05 v1.00 Added c_str(), data(), and append(char*, int) methods,
39 and the rline(std::string, std::string) constructor
40 - 2025-Jan-09 v1.00 Added @ref eol(char, char) and @ref eol(std::string)
41 methods for configuring a specific EoL sequence
42 - 2025-Jan-12 v1.00 Changed std::length_error to std::invalid_argument in
43 response to length being specified as a negative value
44 so this can more easily be handled with seperate code;
45 added the @c at() method, and [], ==, and != operators
46 - 2025-Jan-13 v1.00 Improved internal support for EoL flag indication
47 @version 1.00
48 @author Randolf Richardson
49 *///=========================================================================
50 class rline {
51 private:
52 std::string __line; // Text data without EoL sequence
53 char __eol_detected[3] = {'\0', '\0', '\0'}; // EoL sequence detected (default = none)
54 int __eol_detected_size = 0; // EoL sequence detected size (default = 0)
55 char __eol_standard[3] = {'\0', '\0', '\0'}; // EoL sequence standard (default = none/autodetect)
56 int __eol_standard_size = 0; // EoL sequence standard size (default = 0)
57
58 /*======================================================================*//**
59 @brief
60 Internal line-of-text intake method used by constructors and other methods.
61 @returns TRUE = EoL sequence was encountered@n
62 FALSE = EoL sequence is not present
63 *///=========================================================================
64 bool __rline(
65 /// Line of text to embrace
66 const char* line,
67 /// Length of data (-1 = ASCIIZ string)
68 size_t len = -1) noexcept {
69
70 // --------------------------------------------------------------------------
71 // Syntax checks.
72 // --------------------------------------------------------------------------
73 if (line == nullptr) return false; // A null string was provided, so we can avoid a lot of processing
74
75//std::cout << "__rline len=" << __line.size() << " data=[" << data << "]" << std::endl;
76 // --------------------------------------------------------------------------
77 // Measure size of line if an ASCIIZ string was indicated.
78 // --------------------------------------------------------------------------
79 if (len == -1) len = std::strlen(line);
80 if (len == 0) return false; // Empty line provided, so this is an easy finish
81
82 // --------------------------------------------------------------------------
83 // Pre-allocate memory in std::string so that we can write directly to the
84 // underlying array, which is the most efficient (fastest) way.
85 // --------------------------------------------------------------------------
86 int offset = __line.size();
87 __line.resize(offset + len); // This must be done before calling __line.data()
88 char* __line_data = __line.data();
89
90//std::cout << "__rline len=" << len << " offset=" << offset << " line=[" << line << "]" << std::endl;
91//std::cout << "__rline len=" << len << " offset=" << offset << std::endl;
92 // --------------------------------------------------------------------------
93 // Detect existing (as in already-defined) EoL sequence while copying data.
94 // --------------------------------------------------------------------------
95 if (__eol_standard_size != 0) {
96//std::cout << "__eol_sequence_size != 0" << std::endl;
97
98 // --------------------------------------------------------------------------
99 // Internal variables.
100 // --------------------------------------------------------------------------
101 char c;
102 char e = __eol_standard[0];
103 int i;
104
105 // --------------------------------------------------------------------------
106 // Check for edge case with 2-character EoL sequence carries over from
107 // existing line data.
108 // --------------------------------------------------------------------------
109 if (offset > 0 && __eol_standard_size == 2) {
110 if (__line[offset - 1] == e && line[0] == __eol_standard[1]) {
111 __line.resize(__line.size() - 1); // Crop previous character
112 __eol_detected[0] = e;
113 __eol_detected[1] = line[0];
114 __eol_detected_size = __eol_standard_size;
115 return true;
116 } // -x- if e -x-
117 } // -x- if !offset -x-
118
119//std::cout << "__rline len=" << len << " offset=" << offset << " line=[" << line << "]" << std::endl;
120 // --------------------------------------------------------------------------
121 // Copy data.
122 // --------------------------------------------------------------------------
123 for (i = 0; i < len; i++) {
124
125 // --------------------------------------------------------------------------
126 // Obtain next character to absorb.
127 // --------------------------------------------------------------------------
128 c = line[i];
129
130 // --------------------------------------------------------------------------
131 // EoL sequence detected. If this is a one-character EoL sequence, then it
132 // matches and we're done; or if this is a two-character EoL sequence, then
133 // it also must match; otherwise the sequence will be ignored and treated as
134 // a normal part of the text.
135 // --------------------------------------------------------------------------
136 if (c == e) {
137 if (__eol_standard_size == 1) {
138 __eol_detected[0] = __eol_standard[0];
139 __eol_detected[1] = __eol_standard[1];
140 __eol_detected_size = 1;
141 break;
142 } // -x- if 1 -x-
143 if ((i + 1) < len && line[i + 1] == __eol_standard[1]) {
144 __eol_detected[0] = __eol_standard[0];
145 __eol_detected[1] = __eol_standard[1];
146 __eol_detected_size = 2;
147 break;
148 } // -x- if 2 -x-
149
150 } // -x- if c -x-
151
152 // --------------------------------------------------------------------------
153 // Append character to internal __line by writing directly to the underlying
154 // array (which is the most efficienty way to store it), then advance the
155 // offset index.
156 // --------------------------------------------------------------------------
157 __line_data[offset++] = c;
158
159 } // -x- for i -x-
160
161 // --------------------------------------------------------------------------
162 // Automatic EoL sequence detection while copying data.
163 // --------------------------------------------------------------------------
164 } else {
165//std::cout << "__eol_sequence_size == 0" << std::endl;
166
167 // --------------------------------------------------------------------------
168 // Internal variables.
169 // --------------------------------------------------------------------------
170 char c;
171 int i;
172
173//std::cout << "__rline len=" << len << " offset=" << offset << " line=[" << line << "]" << std::endl;
174 // --------------------------------------------------------------------------
175 // Copy data.
176 // --------------------------------------------------------------------------
177 for (i = 0; i < len; i++) {
178
179 // --------------------------------------------------------------------------
180 // Obtain next character to absorb.
181 // --------------------------------------------------------------------------
182 c = line[i];
183
184 // --------------------------------------------------------------------------
185 // LF detected, so save it and exit the loop, while also advancing
186 // __eol_sequence_size (in two places if this is a broken LFCR sequence). We
187 // do need to compare "i" against "len" to prevent a 1-byte index overflow.
188 // --------------------------------------------------------------------------
189 if (c == 10) { // LF detected
190 __eol_detected[__eol_detected_size++] = __eol_standard[__eol_standard_size++] = c;
191 if ((i + 1) < len && line[i + 1] == 13) { // CR detected, so save it too
192 __eol_detected[__eol_detected_size++] = __eol_standard[__eol_standard_size++] = 13;
193 } // -x- if CR -x-
194//std::cout << "__eol_detected_size<LF>=" << __eol_detected_size << std::endl;
195 break;
196
197 // --------------------------------------------------------------------------
198 // CR detected, so save it and exit the loop, while also advancing
199 // __eol_sequence_size (in two places if this is a valid CRLF sequence). We
200 // do need to compare "i" against "len" to prevent a 1-byte index overflow.
201 // --------------------------------------------------------------------------
202 } else if (c == 13) { // CR detected
203 __eol_detected[__eol_detected_size++] = c;
204 __eol_standard[__eol_standard_size++] = c;
205 if ((i + 1) < len && line[i + 1] == 10) { // LF detected, so save it too
206 __eol_detected[__eol_detected_size++] = 10; // LF detected, so save it too
207 __eol_standard[__eol_standard_size++] = 10; // LF detected, so save it too
208 } // -x- if LF -x-
209//std::cout << "__eol_detected_size<CR>=" << __eol_detected_size << std::endl;
210 break;
211
212 } // -x- if CR/LF -x-
213
214 // --------------------------------------------------------------------------
215 // Append character to internal __line by writing directly to the underlying
216 // array (which is the most efficienty way to store it), then advance the
217 // offset index.
218 // --------------------------------------------------------------------------
219 __line_data[offset++] = c;
220//std::cout << "c=" << c << " offset=" << offset << std::endl;
221
222 } // -x- for i -x-
223
224 } // -x- if !__eol_standar_size -x-
225
226 // --------------------------------------------------------------------------
227 // The "offset" variable was updated by the loop to ensure it holds the
228 // accurate length of __line internally. The "i" variable is loop-local.
229 // --------------------------------------------------------------------------
230 __line.resize(offset);
231//std::cout << "offset=" << offset << " __line=[" << get() << "]" << std::endl;
232
233 return __eol_standard_size > 0; // TRUE = EoL sequence was encountered
234 } // -x- void __rline -x-
235
236 public:
237 /*======================================================================*//**
238 @brief
239 Instantiate an empty rline, with no EoL sequences present.
240 *///=========================================================================
241 rline() noexcept {} // -x- constructor rline -x-
242
243 /*======================================================================*//**
244 @brief
245 Instantiate a new rline based on the supplied line of text, ending early upon
246 encountering the first EoL sequence (use either the @ref size(bool) or the
247 @ref size_with_eol() method to obtain the total quantity of characters that
248 were absorbed, including the EoL sequence {if present}).
249
250 If no EoL sequence was encountered, the @ref has_eol() method will return
251 `false` even though the contents of the supplied line were copied (and are
252 still accessible) -- this is useful in loops reading lines of text that are
253 too long to store in memory with a single allocation (and either interpreting
254 the absorbed data somehow, or using a simple loop to skip through to the end
255 of the line before issuing an error that indicates the total line length).
256 @note
257 If @c line is a @c nullptr then it will be treated as an empty string without
258 an EoL sequence, instead of throwing a Null Pointer Exception. This provides
259 an opportunity to write simpler code (see the @ref is_null method for the way
260 to check for this condition, which also happens to be the result of using the
261 default constructor or calling @ref clear).
262 @see assign
263 @see clear
264 @see has_eol
265 @see is_null
266 @see size
267 @see size_with_eol
268 *///=========================================================================
269 rline(
270 /// Line of text to embrace
271 char* line,
272 /// Length of line (-1 == ASCIIZ string)
273 size_t len = -1) noexcept {
274 __rline(line, len);
275 } // -x- constructor rline -x-
276
277 /*======================================================================*//**
278 @brief
279 Instantiate a new rline based on the supplied line of text, ending early upon
280 encountering the first EoL sequence (use either the `size(true)` or the
281 `size_full()` method to obtain the total quantity of characters that were
282 absorbed, including the EoL sequence {if present}).
283
284 If no EoL sequence was encountered, the @ref has_eol() method will return
285 `false` even though the contents of the supplied line were copied (and are
286 still accessible) -- this is useful in loops reading lines of text that are
287 too long to store in memory with a single allocation (and either interpreting
288 the absorbed data somehow, or using a simple loop to skip through to the end
289 of the line before issuing an error that indicates the total line length).
290 @see assign
291 @see has_eol
292 @see is_null
293 @see size
294 @see size_with_eol
295 *///=========================================================================
296 rline(
297 /// Line of text to embrace
298 std::string line,
299 /// Length of line (-1 == defer to length of line provided)
300 size_t len = -1) noexcept {
301 __rline(line.data(), len == -1 ? line.length() : len);
302 } // -x- constructor rline -x-
303
304 /*======================================================================*//**
305 @brief
306 Instantiate a new rline based on the supplied line of text, ending early upon
307 encountering the first EoL sequence (the `size(true)` and `size_full()`
308 methods obtain the total quantity of characters that were absorbed, including
309 the EoL sequence {if present}).
310
311 If no EoL sequence was encountered, the @ref has_eol() method will return
312 `false` even though the contents of the supplied line were copied (and are
313 still accessible) -- this is useful in loops reading lines of text that are
314 too long to store in memory with a single allocation (and either interpreting
315 the absorbed data somehow, or using a simple loop to skip through to the end
316 of the line before issuing an error that indicates the total line length).
317 @throws std::length_error if the EoL sequence is longer than 2 characters
318 @see assign
319 @see has_eol
320 @see is_null
321 @see size
322 @see size_with_eol
323 *///=========================================================================
324 rline(
325 /// Line of text to embrace, without the EoL sequence characters
326 std::string line,
327 /// The EoL sequence to impose as if it had been detected (may be only 1 or 2
328 /// characters long)
329 std::string eol_sequence) {
330
331 // --------------------------------------------------------------------------
332 // Save EoL sequence while also performing a simple syntax check.
333 // --------------------------------------------------------------------------
334 switch (eol_sequence.size()) {
335 case 2:
336 __eol_detected[0] = __eol_standard[0] = eol_sequence[0];
337 __eol_detected[1] = __eol_standard[1] = eol_sequence[1];
338 __eol_detected_size = __eol_standard_size = 2;
339 break;
340 case 1:
341 __eol_detected[0] = __eol_standard[0] = eol_sequence[0];
342 __eol_detected[1] = __eol_standard[1] = '\0';
343 __eol_detected_size = __eol_standard_size = 1;
344 break;
345 case 0:
346 __eol_detected[0] = __eol_standard[0] = '\0';
347 __eol_detected[1] = __eol_standard[1] = '\0';
348 __eol_detected_size = __eol_standard_size = 0;
349 break;
350 deafult: // Any other length is invalid
351 throw std::length_error("eol_sequence is too long (maximum length is 2 characters)");
352 } // -x- switch eol_sequence.size -x-
353
354 // --------------------------------------------------------------------------
355 // Set line of text.
356 // --------------------------------------------------------------------------
357 __rline(line.data(), line.length());
358
359 } // -x- constructor rline -x-
360
361 /*======================================================================*//**
362 @brief
363 Add the specified string (without checking whether it contains any EoL
364 character sequences) onto the line of text (before the EoL sequence).
365 @returns The same rline object so as to facilitate stacking
366 @see assign
367 @see insert
368 *///=========================================================================
369 rline* append(
370 /// The string to insert
371 const char* line,
372 /// Length of line (-1 == ASCIIZ string)
373 size_t len = -1) {
374
375 // --------------------------------------------------------------------------
376 // Measure size of line if an ASCIIZ string was indicated.
377 // --------------------------------------------------------------------------
378// if (len == -1) len = std::strlen(line);
379// if (len == 0) return this; // Empty line provided, so do nothing
380
381 // --------------------------------------------------------------------------
382 // Add to line of text.
383 // --------------------------------------------------------------------------
384// __line.append(line, len);
385
386 // --------------------------------------------------------------------------
387 // Set line of text.
388 // --------------------------------------------------------------------------
389//std::cout << "__rline append()*=[" << line << "]" << " len=" << len << std::endl;
390 __rline(line, len);
391//std::cout << " __line=[" << __line << "]" << std::endl;
392
393 return this;
394 } // -x- rline* append -x-
395
396 /*======================================================================*//**
397 @brief
398 Add the specified string (without checking whether it contains any EoL
399 character sequences) onto the line of text (before the EoL sequence).
400 @returns The same rline object so as to facilitate stacking
401 @see assign
402 @see insert
403 *///=========================================================================
404 rline* append(
405 /// The string to insert
406 const std::string line,
407 /// Length of line (-1 == defer to length of line provided)
408 size_t len = -1) {
409 //__line.append(line, len == -1 ? line.length() : len);
410//std::cout << "__rline append()=[" << line << "]" << " len=" << len << std::endl;
411 __rline(line.data(), len == -1 ? line.length() : len);
412 return this;
413 } // -x- rline* append -x-
414
415 /*======================================================================*//**
416 @brief
417 Assign a new rline based on the supplied line of text, ending early upon
418 encountering the first EoL sequence (use either the @ref size(bool) or the
419 @ref size_with_eol() method to obtain the total quantity of characters that
420 were absorbed, including the EoL sequence {if present}).
421
422 If no EoL sequence was encountered, the @ref has_eol() method will return
423 `false` even though the contents of the supplied line were copied (and are
424 still accessible) -- this is useful in loops reading lines of text that are
425 too long to store in memory with a single allocation (and either interpreting
426 the absorbed data somehow, or using a simple loop to skip through to the end
427 of the line before issuing an error that indicates the total line length).
428 @note
429 If @c line is a @c nullptr then it will be treated as an empty string without
430 an EoL sequence, instead of throwing a Null Pointer Exception. This provides
431 an opportunity to write simpler code (see the @ref is_null method for the way
432 to check for this condition, which also happens to be the result of using the
433 default constructor or calling @ref clear).
434 @returns The same rline object so as to facilitate stacking
435 @see append
436 @see clear
437 @see has_eol
438 @see insert
439 @see is_null
440 @see size
441 @see size_with_eol
442 *///=========================================================================
443 rline* assign(
444 /// Line of text to embrace
445 char* line,
446 /// Length of line (-1 == ASCIIZ string)
447 size_t len = -1) noexcept {
448
449 // --------------------------------------------------------------------------
450 // Reset internal variables.
451 // --------------------------------------------------------------------------
452 __eol_detected[0] = __eol_standard[0] = '\0';
453 __eol_detected[1] = __eol_standard[1] = '\0';
454 __eol_detected_size = __eol_standard_size = 0;
455 __line.clear();
456
457 // --------------------------------------------------------------------------
458 // Replace line of text.
459 // --------------------------------------------------------------------------
460 __rline(line, len);
461
462 return this;
463 } // -x- rline* assign -x-
464
465 /*======================================================================*//**
466 @brief
467 Assign a new rline based on the supplied line of text, ending early upon
468 encountering the first EoL sequence (use either the @ref size(bool) or the
469 @ref size_with_eol() method to obtain the total quantity of characters that
470 were absorbed, including the EoL sequence {if present}).
471
472 If no EoL sequence was encountered, the @ref has_eol() method will return
473 `false` even though the contents of the supplied line were copied (and are
474 still accessible) -- this is useful in loops reading lines of text that are
475 too long to store in memory with a single allocation (and either interpreting
476 the absorbed data somehow, or using a simple loop to skip through to the end
477 of the line before issuing an error that indicates the total line length).
478 @returns The same rline object so as to facilitate stacking
479 @see append
480 @see has_eol
481 @see insert
482 @see is_null
483 @see size
484 @see size_with_eol
485 *///=========================================================================
486 rline* assign(
487 /// Line of text to embrace
488 std::string line,
489 /// Length of line (-1 == defer to length of line provided)
490 size_t len = -1
491 ) noexcept {
492
493 // --------------------------------------------------------------------------
494 // Reset internal variables.
495 // --------------------------------------------------------------------------
496 __eol_detected[0] = __eol_standard[0] = '\0';
497 __eol_detected[1] = __eol_standard[1] = '\0';
498 __eol_detected_size = __eol_standard_size = 0;
499 __line.clear();
500
501 // --------------------------------------------------------------------------
502 // Replace line of text.
503 // --------------------------------------------------------------------------
504 __rline(line.data(), len == -1 ? line.length() : len);
505
506 return this;
507 } // -x- rline* assign -x-
508
509 /*======================================================================*//**
510 @brief
511 Array-style access to the underlying std::string (without the EoL sequence).
512
513 The first element is at index 0.
514 @throws std::out_of_range if the index is out-of-range
515 @returns char
516 @see operator[]
517 *///=========================================================================
518 char at(
519 /// Index of character to access (0 = first element; negative index values
520 /// are calculated in reverse, starting with -1 as the final position)
521 int index) {
522
523 // --------------------------------------------------------------------------
524 // Internal variables.
525 // --------------------------------------------------------------------------
526 int MAX = __line.length();
527
528 // --------------------------------------------------------------------------
529 // Syntax checks.
530 // --------------------------------------------------------------------------
531 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) + ")");
532
533 return __line[index >= 0 ? index : MAX - index];
534 } // -x- char at -x-
535
536 /*======================================================================*//**
537 @brief
538 Reset all data and internal flags to the original state of the constructor
539 that has no parameters.
540 @returns The same rline object so as to facilitate stacking
541 *///=========================================================================
542 rline* clear() noexcept {
543
544 // --------------------------------------------------------------------------
545 // Reset internal variables.
546 // --------------------------------------------------------------------------
547 __eol_detected[0] = __eol_standard[0] = '\0';
548 __eol_detected[1] = __eol_standard[1] = '\0';
549 __eol_detected_size = __eol_standard_size = 0;
550 __line.clear();
551
552 return this;
553 } // -x- rline* clear -x-
554
555 /*======================================================================*//**
556 @brief
557 Returns a pointer to the underlying string array, without the EoL sequence.
558 @returns Pointer to buffer (null terminated)
559 @see data
560 *///=========================================================================
561 const char* c_str() noexcept {
562 return __line.c_str();
563 } // -x- char* c_str -x-
564
565 /*======================================================================*//**
566 @brief
567 Returns a pointer to the underlying string array, without the EoL sequence.
568 @returns Pointer to buffer (not null terminated)
569 @see c_str
570 *///=========================================================================
571 char* data() noexcept {
572 return __line.data();
573 } // -x- char* data -x-
574
575 /*======================================================================*//**
576 @brief
577 Returns a pointer to the underlying std::string object, without the EoL
578 sequence.
579 @returns Pointer to buffer (not null terminated)
580 @see c_str
581 *///=========================================================================
582 std::string* data_string() noexcept {
583 return &__line;
584 } // -x- std::string* data -x-
585
586 /*======================================================================*//**
587 @brief
588 Indicate whether the line of text is empty. Whether an EoL sequence is
589 present is incidental since this only measures the size of the line of text
590 without regard for the presence of an EoL sequence.
591 @returns TRUE = Line of text is empty@n
592 FALSE = Line of text is not empty
593 @see has_eol()
594 @see is_null()
595 *///=========================================================================
596 bool empty() noexcept {
597 return __line.empty();
598 } // -x- bool empty -x-
599
600 /*======================================================================*//**
601 @brief
602 Obtain a copy of the EoL sequence that was detected.
603 @returns The EoL sequence as a string
604 @see get
605 *///=========================================================================
606 const std::string eol(
607 /// TRUE = return the detected EoL sequence (default) @n
608 /// FALSE = return the EoL sequence to use when detecting an EoL sequence
609 const bool detected = true) noexcept {
610 return detected ? __eol_detected : __eol_standard;
611 } // -x- std::string eol -x-
612
613 /*======================================================================*//**
614 @brief
615 Define the EoL sequence to use when detecting an EoL sequence (also clears
616 the detected EoL sequence, if present).
617 @note
618 This method is the most efficient @c eol method to use for defining the
619 specific EoL sequence to use.
620 @returns The same rline object so as to facilitate stacking
621 @see eol
622 *///=========================================================================
623 rline* eol(
624 /// The first character of the EoL sequence (typically 10 or 13)@c
625 /// 10 = LF (Line Feed) @n
626 /// 13 = CR (Carriage Return) @n
627 /// 0 = enable EoL sequence auto-detection (this is the default in all
628 /// constructors, and is also set by the @ref clear method)
629 const char c1,
630 /// The second character of the EoL sequence (typically 10 or 13)@c
631 /// 10 = LF (Line Feed) @n
632 /// 13 = CR (Carriage Return) @n
633 /// 0 = indicate that this is not a two-character EoL sequence
634 const char c2 = 0) noexcept {
635
636 // --------------------------------------------------------------------------
637 // Save EoL sequence.
638 // --------------------------------------------------------------------------
639 __eol_detected[0] = __eol_detected[1] = 0;
640 __eol_standard[0] = c1;
641 __eol_standard[1] = c1 == 0 ? 0 : c2;
642
643 // --------------------------------------------------------------------------
644 // Calculate the size of the EoL sequence.
645 // --------------------------------------------------------------------------
646 __eol_detected_size = 0;
647 __eol_standard_size = 2;
648 if (c2 == 0) __eol_standard_size = 1;
649 if (c1 == 0) __eol_standard_size = 0;
650
651 return this;
652 } // -x- rline* eol -x-
653
654 /*======================================================================*//**
655 @brief
656 Define the EoL sequence to use when detecting an EoL sequence (also clears
657 the detected EoL sequence, if present).
658 @throws std::length_error if the EoL sequence is longer than 2 characters
659 @returns The same rline object so as to facilitate stacking
660 @see eol
661 *///=========================================================================
662 rline* eol(
663 /// The EoL sequence (may be only 1 or 2 characters long; or 0 characters to
664 /// enable EoL auto-detection, which is also the default in all constructors)
665 const std::string eol_sequence) {
666
667 // --------------------------------------------------------------------------
668 // Save EoL sequence while also performing a simple syntax check.
669 // --------------------------------------------------------------------------
670 switch (eol_sequence.size()) {
671 case 2:
672 __eol_detected[0] = __eol_detected[1] = 0;
673 __eol_standard[0] = eol_sequence[0];
674 __eol_standard[1] = eol_sequence[1];
675 __eol_detected_size = 0;
676 __eol_standard_size = 2;
677 break;
678 case 1:
679 __eol_detected[0] = __eol_detected[1] = 0;
680 __eol_standard[0] = eol_sequence[0];
681 __eol_standard[1] = '\0';
682 __eol_detected_size = 0;
683 __eol_standard_size = 1;
684 break;
685 case 0:
686 __eol_detected[0] = __eol_detected[1] = 0;
687 __eol_standard[0] = '\0';
688 __eol_standard[1] = '\0';
689 __eol_detected_size = 0;
690 __eol_standard_size = 0;
691 break;
692 deafult: // Any other length is invalid
693 throw std::length_error("eol_sequence is too long (maximum length is 2 characters)");
694 } // -x- switch eol_sequence.size -x-
695
696 return this;
697 } // -x- rline* eol -x-
698
699 /*======================================================================*//**
700 @brief
701 Obtain a copy of the entire line of text, without the EoL sequence (albeit by
702 default, this can also be included by setting the @c include_eol flag).
703 @returns The line of text as a string
704 @see eol
705 *///=========================================================================
706 const std::string get(
707 /// Whether to include the EoL detected (default = FALSE)
708 const bool include_eol = false) noexcept {
709 return include_eol ? __line + __eol_detected : __line;
710 } // -x- std::string get -x-
711
712 /*======================================================================*//**
713 @brief
714 Indicate whether an EoL sequence was detected.
715 @returns TRUE = EoL sequence is present@n
716 FALSE = No EoL sequence
717 @see empty
718 *///=========================================================================
719 bool has_eol() noexcept {
720 return __eol_detected_size != 0;
721 } // -x- bool has_eol -x-
722
723 /*======================================================================*//**
724 @brief
725 Indicate whether a broken EoL sequence detected of `<LFCR>` is being used as
726 the EoL sequence.
727 @returns TRUE = Broken EoL sequence is present@n
728 FALSE = No broken EoL sequence
729 @see empty
730 *///=========================================================================
731 bool has_LFCR(
732 /// TRUE = report on whether a broken EoL sequence was detected (default) @n
733 /// FALSE = report on whether a broken EoL sequence was specifically defined
734 /// as the EoL sequence to detect
735 const bool detected = true) noexcept {
736 if (true) return __eol_detected_size == 2 && __eol_detected[0] == 10 && __eol_detected[1] == 13;
737 else return __eol_standard_size == 2 && __eol_standard[0] == 10 && __eol_standard[1] == 13;
738 } // -x- bool has_LFCR -x-
739
740 /*======================================================================*//**
741 @brief
742 Insert the specified string (without checking whether it contains any EoL
743 character sequences) into the line of text at the specified position.
744 @exception std::out_of_range When the position (@c pos) provided is out of
745 range (e.g., position is larger than the entire
746 size of the string)
747 @returns The same rline object so as to facilitate stacking
748 @see append
749 @see assign
750 *///=========================================================================
751 rline* insert(
752 /// Where to insert the supplied string (0 = insert before first character;
753 /// and use a negative number to set the position relative to the end of the
754 /// line of text)
755 int position, // Must be "int" and not "size_t" because size_t is unsigned
756 /// The string to insert
757 const std::string str,
758 /// Length of string (-1 == defer to length of string provided)
759 size_t len = -1) {
760 __line.insert(position >= 0 ? position : (__line.size() + position),
761 len == -1 ? str : str.substr(0, len));
762 return this;
763 } // -x- rline* insert -x-
764
765 /*======================================================================*//**
766 @brief
767 Indicate whether this line is non-existent, which means there is no text and
768 no detected EoL sequence. This is less than an an @ref empty() string when
769 it confirms that there literally are no characters at all.
770 @returns TRUE = null string@n
771 FALSE = not null (has text and/or an EoL
772 sequence)
773 @see empty
774 *///=========================================================================
775 bool is_null() noexcept {
776 return (__line.size() + __eol_detected_size) == 0;
777 } // -x- bool is_null -x-
778
779 /*======================================================================*//**
780 @brief
781 Provides the length of the line of text without the EoL sequence.
782
783 This method is identital to the @ref size method.
784 @returns Number of bytes
785 @see size
786 *///=========================================================================
787 size_t length(
788 /// Whether to include the EoL sequence (default = FALSE)
789 const bool include_eol = false) noexcept {
790 return include_eol ? __line.size() + __eol_detected_size : __line.size();
791 } // -x- size_t length -x-
792
793 /*======================================================================*//**
794 @brief
795 Provides the length of the line of text without the EoL sequence.
796
797 This method is identital to the @ref length method.
798 @returns Number of bytes
799 @see length
800 @see size_eol
801 @see size_with_eol
802 *///=========================================================================
803 size_t size(
804 /// Whether to include the EoL sequence (default = FALSE)
805 const bool include_eol = false) noexcept {
806 return include_eol ? __line.size() + __eol_detected_size : __line.size();
807 } // -x- size_t size -x-
808
809 /*======================================================================*//**
810 @brief
811 Provides the length of the detected EoL sequence.
812 @returns Number of bytes (0 = no EoL string)
813 @see size
814 *///=========================================================================
815 size_t size_eol(
816 /// TRUE = report on the size of the detected EoL sequence (default) @n
817 /// FALSE = report on the size of the EoL sequence to use when detecting an
818 /// EoL sequence
819 const bool detected = true) noexcept {
820 return detected ? __eol_detected_size : __eol_standard_size;
821 } // -x- size_t size_eol -x-
822
823 /*======================================================================*//**
824 @brief
825 Provides the length of the line of text, including the EoL sequence (if one
826 is present).
827 @returns Number of bytes
828 @see length
829 @see size
830 *///=========================================================================
831 size_t size_with_eol() noexcept {
832 return __line.size() + __eol_detected_size;
833 } // -x- size_t size_with_eol -x-
834
835 /*======================================================================*//**
836 @brief
837 Convert this @c rline to an @c std::string without the EoL sequence.
838
839 @code{.cpp}
840 #include <iostream> // std::cout, std::cerr, std::endl, etc.
841 #include <string> // std::string
842 #include <randolf/rline>
843
844 int main(int argc, char *argv[]) {
845 randolf::rline rl("This is an example.\n");
846 std::string s = rl;
847 std::cout << "\"" << s << "\"" << std::endl;
848 return EXIT_SUCCESS;
849 } // -x- int main -x-
850 @endcode
851
852 @returns The line of text as an std::string object
853 @see get
854 *///=========================================================================
855 operator std::string() const noexcept {
856 return __line;
857 } // -x- operator std::string -x-
858
859 /*======================================================================*//**
860 @brief
861 Compare with the underlying std::string (without the EoL sequence).
862 @returns TRUE = doesn't match@n
863 FALSE = match
864 *///=========================================================================
865 bool operator!=(
866 /// String to compare with
867 const std::string s) {
868 return __line != s;
869 } // -x- bool operator== -x-
870
871 /*======================================================================*//**
872 @brief
873 Compare with the underlying std::string (without the EoL sequence).
874 @returns TRUE = match@n
875 FALSE = doesn't match
876 *///=========================================================================
877 bool operator==(
878 /// String to compare with
879 const std::string s) {
880 return __line == s;
881 } // -x- bool operator== -x-
882
883 /*======================================================================*//**
884 @brief
885 Array-style access to the underlying std::string (without the EoL sequence).
886
887 The first element is at index 0.
888 @throws std::out_of_range if the index is out-of-range
889 @returns char
890 @see at
891 *///=========================================================================
892 char operator[](
893 /// Index of character to access (0 = first element; negative index values
894 /// are calculated in reverse, starting with -1 as the final position)
895 int index) {
896
897 // --------------------------------------------------------------------------
898 // Internal variables.
899 // --------------------------------------------------------------------------
900 int MAX = __line.length();
901
902 // --------------------------------------------------------------------------
903 // Syntax checks.
904 // --------------------------------------------------------------------------
905 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) + ")");
906
907 return __line[index >= 0 ? index : MAX - index];
908 } // -x- char operator[] -x-
909
910 /*======================================================================*//**
911 @brief
912 Support convenient streaming usage with std::cout, std::cerr, and friends.
913
914 @code{.cpp}
915 #include <iostream> // std::cout, std::cerr, std::endl, etc.
916 #include <string> // std::string
917 #include <randolf/rline>
918
919 int main(int argc, char *argv[]) {
920 randolf::rline rl("This is an example.\n");
921 std::cout << "\"" << rl << "\"" << std::endl;
922 return EXIT_SUCCESS;
923 } // -x- int main -x-
924 @endcode
925
926 @returns Line of text, without EoL sequence
927 *///=========================================================================
928 friend std::ostream& operator<< (
929 /// Output stream (provided automatically by std::cout and std::cerr)
930 std::ostream& o,
931 /// Object class (matched by compiler)
932 rline const& c) {
933 return o << (char*)c.__line.data();
934 } // -x- std::ostream& operator<< -x-
935
936 }; // -x- class rline -x-
937
938}; // -x- namespace randolf -x-