Lector v1.0.0
C++ library for parsing command line arguments.
text.hpp
1// Copyright © 2026, Alexandre Coderre-Chabot.
2
3// This file is part of Lector (https://github.com/acodcha/lector), a C++ library for parsing
4// command line arguments. Lector is licensed under the MIT License (https://mit-license.org).
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
7// associated documentation files (the "Software"), to deal in the Software without restriction,
8// including without limitation the rights to use, copy, modify, merge, publish, distribute,
9// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11// - The above copyright notice and this permission notice shall be included in all copies or
12// substantial portions of the Software.
13// - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
14// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
16// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM
17// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18
19#ifndef LECTOR_TEXT_HPP
20#define LECTOR_TEXT_HPP
21
22#include <algorithm>
23#include <cctype>
24#include <cstddef>
25#include <stdexcept>
26#include <string>
27#include <string_view>
28#include <utility>
29#include <vector>
30
31/// @brief The Lector library's namespace.
32namespace lector {
33
34/// @brief Returns whether a given character is the leading byte of a UTF-8 character. All UTF-8
35/// characters measure either one, two, three, or four bytes. UTF-8 characters that measure only one
36/// byte are the ASCII characters. UTF-8 character that measure two, three, or four bytes are
37/// multi-byte characters and consist of a leading byte with a specific binary pattern and one or
38/// more continuation bytes of the binary pattern 10xxxxxx.
39///
40/// 1. One-byte UTF-8 characters are the ASCII characters. Their first bit is 0 and their binary
41/// pattern is therefore 0xxxxxxx.
42///
43/// 2. Two-byte UTF-8 characters have a leading byte with the binary pattern 110xxxxx and one
44/// continuation byte with the binary pattern 10xxxxxx. Together, the two bytes therefore have
45/// the binary pattern 110xxxxx 10xxxxxx.
46///
47/// 3. Three-byte UTF-8 characters have a leading byte with the binary pattern 1110xxxx and two
48/// continuation bytes with the binary pattern 10xxxxxx. Together, the three bytes therefore have
49/// the binary pattern 1110xxxx 10xxxxxx 10xxxxxx.
50///
51/// 4. Four-byte UTF-8 characters have a leading byte with the binary pattern 11110xxx and three
52/// continuation bytes with the binary pattern 10xxxxxx. Together, the four bytes therefore have
53/// the binary pattern 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx.
54/// @param[in] character The character to check.
55/// @return True if the character is a leading byte; false if the character is a continuation byte.
56[[nodiscard]] inline bool is_leading_byte(const char character) {
57 // Cast to an unsigned character to avoid undefined behavior with bitwise operations on signed
58 // characters. The binary pattern 10xxxxxx that identifies a continuation byte ranges from 0x80 to
59 // 0xBF in hexadecimal notation.
60 return (static_cast<unsigned char>(character) & static_cast<unsigned char>(0xC0))
61 != static_cast<unsigned char>(0x80);
62}
63
64/// @brief Finds the exact byte [begin, end) index interval in a string of text where a specified
65/// code point resides.
66/// @param[in] text The string of text to parse.
67/// @param[in] code_point_index The index of the code point in the string of text.
68/// @return A pair that contains the begin and end byte indices of the specified code point. The end
69/// index is the classical C++ "one past the end" index. If the specified code point index is out of
70/// bounds, both returned indices are set to one past the end index of the string, which is the size
71/// of the string.
72[[nodiscard]] inline std::pair<std::size_t, std::size_t> byte_interval(
73 const std::string_view text, const std::size_t code_point_index) {
74 std::size_t current_code_point_index{0UL};
75 std::size_t begin_byte_index{text.size()};
76 for (std::size_t current_byte_index{0UL}; current_byte_index < text.size();
77 ++current_byte_index) {
78 if (lector::is_leading_byte(text.at(current_byte_index))) {
79 if (current_code_point_index == code_point_index) {
80 begin_byte_index = current_byte_index;
81 } else if (current_code_point_index == code_point_index + static_cast<std::size_t>(1UL)) {
82 // In this case, this is the start of the next code point, and therefore the end of the
83 // requested code point.
84 return std::pair<std::size_t, std::size_t>{begin_byte_index, current_byte_index};
85 }
86 ++current_code_point_index;
87 }
88 }
89 if (begin_byte_index < text.size()) {
90 // In this case, the requested code point is found, but it is the last code point in the string.
91 return std::pair<std::size_t, std::size_t>{begin_byte_index, text.size()};
92 }
93 // In this case, the requested code point index is out of bounds.
94 return std::pair<std::size_t, std::size_t>{text.size(), text.size()};
95}
96
97/// @brief Counts and returns the number of UTF-8 code points in a string of text. The number of
98/// UTF-8 code points is a useful approximation of the number of graphemes in the string, where
99/// ASCII characters and multi-byte UTF-8 characters are each counted as one unit of length.
100/// @param[in] text The string of text whose UTF-8 code points are to be counted.
101/// @return The number of UTF-8 code points in the string of text.
102[[nodiscard]] inline std::size_t count_code_points(const std::string_view text) {
103 std::size_t count{0UL};
104 for (const char character : text) {
105 if (lector::is_leading_byte(character)) {
106 ++count;
107 }
108 }
109 return count;
110}
111
112/// @brief Checks whether an ASCII character is one of the ASCII whitespace characters: space (' '),
113/// horizontal tab ('\t'), line feed ('\n'), vertical tab ('\v'), form feed ('\f'), or carriage
114/// return ('\r').
115/// @param[in] character The character to examine.
116/// @return True if the character is an ASCII whitespace character; false if it is not.
117[[nodiscard]] inline bool is_whitespace(const char character) {
118 return character == ' ' || character == '\t' || character == '\n' || character == '\v'
119 || character == '\f' || character == '\r';
120}
121
122/// @brief Checks whether a string of text contains any ASCII whitespace characters: space (' '),
123/// horizontal tab ('\t'), line feed ('\n'), vertical tab ('\v'), form feed ('\f'), or carriage
124/// return ('\r').
125/// @param[in] text The string of text to examine.
126/// @return True if the string of text contains any ASCII whitespace characters; false if it does
127/// not.
128[[nodiscard]] inline bool contains_whitespace(const std::string_view text) {
129 return std::any_of(text.begin(), text.end(), lector::is_whitespace);
130}
131
132/// @brief Computes and returns the length of the longest word in a string of text. The length of a
133/// word is measured by its number of UTF-8 code points.
134/// @param[in] text The string of text whose longest word length is to be computed.
135/// @return The length of the longest word in the string of text.
136[[nodiscard]] inline std::size_t longest_word_length(const std::string_view text) {
137 std::size_t current_longest_word_length{0UL};
138 std::size_t index{0UL};
139 while (index < text.length()) {
140 // Skip over any whitespaces.
141 while (index < text.length() && lector::is_whitespace(text.at(index))) {
142 ++index;
143 }
144 // Return if the end of the string has been reached after skipping whitespaces.
145 if (index >= text.length()) {
146 break;
147 }
148 // The index now points to the start of the current word.
149 const std::size_t current_word_start{index};
150 // Find the end of the current word.
151 while (index < text.length() && !lector::is_whitespace(text.at(index))) {
152 ++index;
153 }
154 // Obtain the current word.
155 const std::string_view current_word{
156 text.substr(current_word_start, index - current_word_start)};
157 // Compute the length of the current word.
158 const std::size_t current_word_length{lector::count_code_points(current_word)};
159 // Update the longest word length.
160 current_longest_word_length = std::max(current_longest_word_length, current_word_length);
161 }
162 return current_longest_word_length;
163}
164
165/// @brief Tokenizes a string of text into a vector of strings of text, where each string in the
166/// vector corresponds to a word in the original string. Words are defined as sequences of
167/// non-whitespace characters, and whitespace characters are used as delimiters. The function does
168/// not modify the original string and returns views into it, so the original string must remain
169/// valid for the lifetime of the returned vector.
170/// @param[in] text The string of text to be tokenized.
171/// @return A vector of strings of text, each corresponding to a word in the original string.
172[[nodiscard]] inline std::vector<std::string_view> tokenize(const std::string_view text) {
173 std::vector<std::string_view> words;
174 std::size_t begin_index{0UL};
175 while (begin_index < text.size()) {
176 while (begin_index < text.size() && lector::is_whitespace(text.at(begin_index))) {
177 ++begin_index;
178 }
179 if (begin_index == text.size()) {
180 break;
181 }
182 std::size_t end_index{begin_index};
183 while (end_index < text.size() && !lector::is_whitespace(text.at(end_index))) {
184 ++end_index;
185 }
186 words.push_back(text.substr(begin_index, end_index - begin_index));
187 begin_index = end_index;
188 }
189 return words;
190}
191
192/// @brief Encloses a string of text in quotes. Either single or double quotes are used depending on
193/// which type of quote is not present in the string of text, with double quotes preferred if
194/// neither type of quote is present. If the string of text already begins and ends with either
195/// single or double quotes, no additional quotes are added. If the string of text is empty, an
196/// empty string is returned.
197/// @param[in] text The string of text to enclose in quotes.
198/// @return The string of text enclosed in quotes.
199/// @throws std::invalid_argument if the string of text contains both single and double quotes.
200[[nodiscard]] inline std::string quote(const std::string_view text) {
201 if (text.empty()) {
202 return std::string{""};
203 }
204 if (text.size() >= static_cast<std::size_t>(2UL) && (text.front() == '"' || text.front() == '\'')
205 && text.front() == text.back()) {
206 return std::string{text};
207 }
208 bool contains_single_quotes{false};
209 bool contains_double_quotes{false};
210 for (const char character : text) {
211 if (character == '\'') {
212 contains_single_quotes = true;
213 } else if (character == '"') {
214 contains_double_quotes = true;
215 }
216 if (contains_single_quotes && contains_double_quotes) {
217 throw std::invalid_argument(
218 "String contains both single and double quotes: " + std::string{text});
219 }
220 }
221 const char quote_character{contains_double_quotes ? '\'' : '"'};
222 std::string result;
223 result.reserve(text.size() + 2);
224 result.push_back(quote_character);
225 result.append(text);
226 result.push_back(quote_character);
227 return result;
228}
229
230/// @brief Encloses a string of text in quotes if it contains any whitespace. Either single or
231/// double quotes are used depending on which type of quote is not present in the string of text,
232/// with double quotes preferred if neither type of quote is present. If the string of text already
233/// begins and ends with either single or double quotes, no additional quotes are added. If the
234/// string of text is empty, an empty string is returned.
235/// @param[in] text The string of text to possibly enclose in quotes.
236/// @return The string of text possibly enclosed in quotes.
237/// @throws std::invalid_argument if the string of text contains both single and double quotes.
238[[nodiscard]] inline std::string quote_if_contains_whitespace(const std::string_view text) {
239 if (lector::contains_whitespace(text)) {
240 return lector::quote(text);
241 }
242 return std::string{text};
243}
244
245/// @brief Pads a string of text with spaces to reach a specified length. The padding is added from
246/// the right such that the text becomes left-aligned. If the string of text is longer than the
247/// specified length, it is returned unchanged.
248/// @param[in] text The string of text to pad.
249/// @param[in] length The minimum length of the padded string of text.
250/// @return The padded string of text.
251[[nodiscard]] inline std::string pad_and_align_left(
252 const std::string_view text, const std::size_t length) {
253 const std::size_t text_length{lector::count_code_points(text)};
254 if (text_length >= length) {
255 return std::string{text};
256 }
257 const std::size_t padding{length - text_length};
258 std::string result;
259 result.reserve(text.size() + padding);
260 result.append(text);
261 result.append(padding, ' ');
262 return result;
263}
264
265/// @brief Pads a string of text with spaces to reach a specified length. The padding is added from
266/// the left such that the text becomes right-aligned. If the string of text is longer than the
267/// specified length, it is returned unchanged.
268/// @param[in] text The string of text to pad.
269/// @param[in] length The minimum length of the padded string of text.
270/// @return The padded string of text.
271[[nodiscard]] inline std::string pad_and_align_right(
272 const std::string_view text, const std::size_t length) {
273 const std::size_t text_length{lector::count_code_points(text)};
274 if (text_length >= length) {
275 return std::string{text};
276 }
277 const std::size_t padding{length - text_length};
278 std::string result;
279 result.reserve(text.size() + padding);
280 result.append(padding, ' ');
281 result.append(text);
282 return result;
283}
284
285/// @brief Pads a string of text with spaces to reach a specified length. The padding is added from
286/// both the left and right such that the text becomes centre-aligned. If the total required
287/// centre-aligning padding is odd, the text is biased by one space towards the left. If the string
288/// of text is longer than the specified length, it is returned unchanged.
289/// @param[in] text The string of text to pad.
290/// @param[in] length The minimum length of the padded string of text.
291/// @return The padded string of text.
292[[nodiscard]] inline std::string pad_and_align_centre_left(
293 const std::string_view text, const std::size_t length) {
294 const std::size_t text_length{lector::count_code_points(text)};
295 if (text_length >= length) {
296 return std::string{text};
297 }
298 const std::size_t total_padding{length - text_length};
299 const std::size_t left_padding{total_padding / static_cast<std::size_t>(2UL)};
300 const std::size_t right_padding{total_padding - left_padding};
301 std::string result;
302 result.reserve(text.size() + total_padding);
303 result.append(left_padding, ' ');
304 result.append(text);
305 result.append(right_padding, ' ');
306 return result;
307}
308
309/// @brief Pads a string of text with spaces to reach a specified length. The padding is added from
310/// both the left and right such that the text becomes centre-aligned. If the total required
311/// centre-aligning padding is odd, the text is biased by one space towards the right. If the string
312/// of text is longer than the specified length, it is returned unchanged.
313/// @param[in] text The string of text to pad.
314/// @param[in] length The minimum length of the padded string of text.
315/// @return The padded string of text.
316[[nodiscard]] inline std::string pad_and_align_centre_right(
317 const std::string_view text, const std::size_t length) {
318 const std::size_t text_length{lector::count_code_points(text)};
319 if (text_length >= length) {
320 return std::string{text};
321 }
322 const std::size_t total_padding{length - text_length};
323 const std::size_t right_padding{total_padding / static_cast<std::size_t>(2UL)};
324 const std::size_t left_padding{total_padding - right_padding};
325 std::string result;
326 result.reserve(text.size() + total_padding);
327 result.append(left_padding, ' ');
328 result.append(text);
329 result.append(right_padding, ' ');
330 return result;
331}
332
333/// @brief Truncates a string of text to a specified length by removing characters from the string's
334/// left end. If the string of text is already shorter than or equal to the specified length, it is
335/// returned unchanged.
336/// @param[in] text The string of text to truncate.
337/// @param[in] length The maximum length of the truncated string of text.
338/// @return The truncated string of text.
339[[nodiscard]] inline std::string truncate_from_left(
340 const std::string_view text, const std::size_t length) {
341 if (length == static_cast<std::size_t>(0UL)) {
342 return std::string{};
343 }
344 std::size_t count{0UL};
345 for (std::size_t reverse_index{0UL}; reverse_index < text.size(); ++reverse_index) {
346 const std::size_t index{text.size() - static_cast<std::size_t>(1UL) - reverse_index};
347 if (lector::is_leading_byte(text.at(index))) {
348 ++count;
349 if (count == length) {
350 return std::string{text.substr(index)};
351 }
352 }
353 }
354 return std::string{text};
355}
356
357/// @brief Truncates a string of text to a specified length by removing characters from the string's
358/// right end. If the string of text is already shorter than or equal to the specified length, it is
359/// returned unchanged.
360/// @param[in] text The string of text to truncate.
361/// @param[in] length The maximum length of the truncated string of text.
362/// @return The truncated string of text.
363[[nodiscard]] inline std::string truncate_from_right(
364 const std::string_view text, const std::size_t length) {
365 std::size_t count{0UL};
366 for (std::size_t index{0UL}; index < text.size(); ++index) {
367 if (lector::is_leading_byte(text.at(index))) {
368 if (count == length) {
369 return std::string{text.substr(static_cast<std::size_t>(0UL), index)};
370 }
371 ++count;
372 }
373 }
374 return std::string{text};
375}
376
377/// @brief Truncates a string of text to a specified length by removing characters from both ends of
378/// the string. If the total number of characters to be removed is odd, one fewer character is
379/// removed from the left. If the string of text is already shorter than or equal to the specified
380/// length, it is returned unchanged.
381/// @param[in] text The string of text to truncate.
382/// @param[in] length The maximum length of the truncated string of text.
383/// @return The truncated string of text.
384[[nodiscard]] inline std::string truncate_to_centre_left(
385 const std::string_view text, const std::size_t length) {
386 if (length == static_cast<std::size_t>(0UL)) {
387 return std::string{};
388 }
389 const std::size_t total_code_points{lector::count_code_points(text)};
390 if (total_code_points <= length) {
391 return std::string{text};
392 }
393 const std::size_t code_points_to_remove{total_code_points - length};
394 // Bias the result to the left. Drop fewer characters from the left end.
395 const std::size_t code_points_to_remove_from_left{
396 code_points_to_remove / static_cast<std::size_t>(2UL)};
397 const std::size_t begin_code_point_index{code_points_to_remove_from_left};
398 const std::size_t end_code_point_index{code_points_to_remove_from_left + length};
399 std::size_t begin_byte_index{0UL};
400 std::size_t end_byte_index{text.size()};
401 std::size_t code_point_count{0UL};
402 for (std::size_t i{0UL}; i < text.size(); ++i) {
403 if (lector::is_leading_byte(text[i])) {
404 if (code_point_count == begin_code_point_index) {
405 begin_byte_index = i;
406 }
407 if (code_point_count == end_code_point_index) {
408 end_byte_index = i;
409 break;
410 }
411 ++code_point_count;
412 }
413 }
414 return std::string{text.substr(begin_byte_index, end_byte_index - begin_byte_index)};
415}
416
417/// @brief Truncates a string of text to a specified length by removing characters from both ends of
418/// the string. If the total number of characters to be removed is odd, one fewer character is
419/// removed from the right. If the string of text is already shorter than or equal to the specified
420/// length, it is returned unchanged.
421/// @param[in] text The string of text to truncate.
422/// @param[in] length The maximum length of the truncated string of text.
423/// @return The truncated string of text.
424[[nodiscard]] inline std::string truncate_to_centre_right(
425 const std::string_view text, const std::size_t length) {
426 if (length == static_cast<std::size_t>(0UL)) {
427 return std::string{};
428 }
429 const std::size_t total_code_points{lector::count_code_points(text)};
430 if (total_code_points <= length) {
431 return std::string{text};
432 }
433 const std::size_t code_points_to_remove{total_code_points - length};
434 // Bias the result to the right. Drop fewer characters from the right end.
435 const std::size_t code_points_to_remove_from_right{
436 code_points_to_remove / static_cast<std::size_t>(2UL)};
437 const std::size_t code_points_to_remove_from_left{
438 code_points_to_remove - code_points_to_remove_from_right};
439 const std::size_t begin_code_point_index{code_points_to_remove_from_left};
440 const std::size_t end_code_point_index{code_points_to_remove_from_left + length};
441 std::size_t begin_byte_index{0UL};
442 std::size_t end_byte_index{text.size()};
443 std::size_t code_point_count{0UL};
444 for (std::size_t i{0UL}; i < text.size(); ++i) {
445 if (lector::is_leading_byte(text[i])) {
446 if (code_point_count == begin_code_point_index) {
447 begin_byte_index = i;
448 }
449 if (code_point_count == end_code_point_index) {
450 end_byte_index = i;
451 break;
452 }
453 ++code_point_count;
454 }
455 }
456 return std::string{text.substr(begin_byte_index, end_byte_index - begin_byte_index)};
457}
458
459/// @brief Wraps a string of text to a line length and returns the result as a sequence of strings
460/// of text where each string in the sequence represents one line of text.
461/// @param[in] text The string of text to wrap.
462/// @param[in] line_length The desired line length to use when wrapping. Must be strictly greater
463/// than zero. Very long words whose lengths exceed this line length are hyphenated.
464/// @return The sequence of strings of text that contains one string per line.
465/// @throws std::invalid_argument if the desired line length is zero.
466[[nodiscard]] inline std::vector<std::string> wrap(
467 const std::string_view text, const std::size_t line_length) {
468 // Ensure the line length is valid.
469 if (line_length <= static_cast<std::size_t>(0UL)) {
470 throw std::invalid_argument("Invalid line length. Must be strictly greater than zero.");
471 }
472 // Tokenize the input string of text.
473 const std::vector<std::string_view> words{lector::tokenize(text)};
474 // Process the tokenized input string of text and assemble the wrapped lines.
475 std::vector<std::string> lines;
476 std::string current_line;
477 std::size_t current_line_code_point_size{0UL};
478 for (const std::string_view current_word : words) {
479 // Measure the current word.
480 const std::size_t current_word_code_point_size{lector::count_code_points(current_word)};
481 const std::size_t space_needed_for_hyphen{
482 (current_line_code_point_size > static_cast<std::size_t>(0UL)) ?
483 static_cast<std::size_t>(1UL) :
484 static_cast<std::size_t>(0UL)};
485 // Check if the current word fits on the current line.
486 if (current_line_code_point_size > static_cast<std::size_t>(0UL)
487 && current_line_code_point_size + current_word_code_point_size + space_needed_for_hyphen
488 <= line_length) {
489 // In this case, the current word fits on the current line.
490 current_line.push_back(' ');
491 current_line.append(current_word);
492 current_line_code_point_size += current_word_code_point_size + space_needed_for_hyphen;
493 } else {
494 // In this case, the current word does not fit on the current line and must be wrapped to the
495 // next line.
496 if (current_line_code_point_size > static_cast<std::size_t>(0UL)) {
497 lines.push_back(std::move(current_line));
498 current_line.clear();
499 current_line_code_point_size = static_cast<std::size_t>(0UL);
500 }
501 // Check if the current word needs to be hyphenated.
502 if (current_word_code_point_size <= line_length) {
503 // In this case, the current word fits completely on an empty line and does not need to be
504 // hyphenated.
505 current_line = current_word;
506 current_line_code_point_size = current_word_code_point_size;
507 } else {
508 // In this case, the current word is too long and must be hyphenated.
509 std::string_view remaining_word{current_word};
510 std::size_t remaining_code_point_size{current_word_code_point_size};
511 // Iterate until the remaining portion of the current word fits on a line, and repeat as
512 // necessary; a very long word might need to be hyphenated multiple times.
513 while (remaining_code_point_size > line_length) {
514 // If the line_length is 1, no hyphen is used. Otherwise, take "line length - 1" code
515 // points to save 1 character for the hyphen.
516 const std::size_t chunk_code_point_size{line_length == static_cast<std::size_t>(1UL) ?
517 static_cast<std::size_t>(1UL) :
518 line_length - static_cast<std::size_t>(1UL)};
519 const std::size_t split_byte_index{
520 lector::byte_interval(remaining_word, chunk_code_point_size).first};
521 std::string split_line(
522 remaining_word.substr(static_cast<std::size_t>(0UL), split_byte_index));
523 if (line_length > static_cast<std::size_t>(1UL)) {
524 split_line.push_back('-');
525 }
526 lines.push_back(std::move(split_line));
527 remaining_word = remaining_word.substr(split_byte_index);
528 remaining_code_point_size -= chunk_code_point_size;
529 }
530 // The remaining slice of the word seeds the subsequent line.
531 if (remaining_code_point_size > static_cast<std::size_t>(0UL)) {
532 current_line = remaining_word;
533 current_line_code_point_size = remaining_code_point_size;
534 }
535 }
536 }
537 }
538 // Push the final built line if it is not empty.
539 if (current_line_code_point_size > static_cast<std::size_t>(0UL)) {
540 lines.push_back(std::move(current_line));
541 }
542 // Return the wrapped lines.
543 return lines;
544}
545
546/// @brief Joins a vector of strings where each string corresponds to a line of text into a single
547/// string of text, with newline characters inserted between the lines, and the lines left-aligned.
548/// @param[in] lines Vector of strings to be joined and left-aligned.
549/// @return The joined and left-aligned string of text.
550[[nodiscard]] inline std::string join_and_align_left(const std::vector<std::string>& lines) {
551 // Handle the empty case immediately to prevent underflow later.
552 if (lines.empty()) {
553 return std::string{};
554 }
555 // Calculate the exact total size.
556 std::size_t total_size{0UL};
557 for (const std::string& line : lines) {
558 total_size += line.size();
559 }
560 // Add space for the newline separators (one less than the total number of lines).
561 total_size += lines.size() - static_cast<std::size_t>(1UL);
562 // Create and allocate the resulting text.
563 std::string text;
564 text.reserve(total_size);
565 // Append the first line.
566 text.append(lines.front());
567 // Append subsequent lines prefixed by a newline.
568 for (std::size_t line_index{1UL}; line_index < lines.size(); ++line_index) {
569 text.push_back('\n');
570 text.append(lines.at(line_index));
571 }
572 return text;
573}
574
575/// @brief Joins a vector of strings where each string corresponds to a line of text into a single
576/// string of text, with newline characters inserted between the lines, and the lines right-aligned.
577/// @param[in] lines Vector of strings to be joined and right-aligned.
578/// @return The joined and right-aligned string of text.
579[[nodiscard]] inline std::string join_and_align_right(const std::vector<std::string>& lines) {
580 // Handle the empty case immediately to prevent underflow later.
581 if (lines.empty()) {
582 return std::string{};
583 }
584 // Compute the line lengths and find the maximum line length.
585 std::vector<std::size_t> line_lengths;
586 line_lengths.reserve(lines.size());
587 std::size_t longest_line_length{0UL};
588 for (const std::string& line : lines) {
589 const std::size_t length{lector::count_code_points(line)};
590 line_lengths.push_back(length);
591 longest_line_length = std::max(length, longest_line_length);
592 }
593 // Compute the exact total byte size.
594 std::size_t total_size{0UL};
595 for (std::size_t line_index{0UL}; line_index < lines.size(); ++line_index) {
596 const std::size_t padding{longest_line_length - line_lengths.at(line_index)};
597 total_size += lines.at(line_index).size() + padding;
598 }
599 total_size += lines.size() - static_cast<std::size_t>(1UL);
600 // Create and allocate the resulting text.
601 std::string text;
602 text.reserve(total_size);
603 // Append lines with padding.
604 for (std::size_t line_index{0UL}; line_index < lines.size(); ++line_index) {
605 if (line_index > 0UL) {
606 text.push_back('\n');
607 }
608 const std::size_t padding{longest_line_length - line_lengths.at(line_index)};
609 text.append(padding, ' ');
610 text.append(lines.at(line_index));
611 }
612 return text;
613}
614
615/// @brief Joins a vector of strings where each string corresponds to a line of text into a single
616/// string of text, with newline characters inserted between the lines, and the lines
617/// centre-aligned. If the total required centre-aligning padding is odd, the text is biased by one
618/// space towards the left.
619/// @param[in] lines Vector of strings to be joined and centre-aligned.
620/// @return The joined and centre-aligned string of text.
621[[nodiscard]] inline std::string join_and_align_centre_left(const std::vector<std::string>& lines) {
622 // Handle the empty case immediately to prevent underflow later.
623 if (lines.empty()) {
624 return std::string{};
625 }
626 // Compute the line lengths and find the maximum line length.
627 std::vector<std::size_t> line_lengths;
628 line_lengths.reserve(lines.size());
629 std::size_t longest_line_length{0UL};
630 for (const std::string& line : lines) {
631 const std::size_t length{lector::count_code_points(line)};
632 line_lengths.push_back(length);
633 longest_line_length = std::max(length, longest_line_length);
634 }
635 // Compute the exact total byte size.
636 std::size_t total_size{0UL};
637 for (std::size_t line_index{0UL}; line_index < lines.size(); ++line_index) {
638 const std::size_t total_padding{longest_line_length - line_lengths.at(line_index)};
639 // Bias left. When the total number of padding spaces is odd, integer division rounds down,
640 // giving one less padding space to the left.
641 const std::size_t left_padding{total_padding / 2UL};
642 total_size += lines.at(line_index).size() + left_padding;
643 }
644 total_size += lines.size() - static_cast<std::size_t>(1UL);
645 // Create and allocate the resulting text.
646 std::string text;
647 text.reserve(total_size);
648 // Append lines with padding.
649 for (std::size_t line_index{0UL}; line_index < lines.size(); ++line_index) {
650 if (line_index > 0UL) {
651 text.push_back('\n');
652 }
653 const std::size_t total_padding{longest_line_length - line_lengths.at(line_index)};
654 const std::size_t left_padding{total_padding / 2UL};
655 text.append(left_padding, ' ');
656 text.append(lines.at(line_index));
657 }
658 return text;
659}
660
661/// @brief Joins a vector of strings where each string corresponds to a line of text into a single
662/// string of text, with newline characters inserted between the lines, and the lines
663/// centre-aligned. If the total required centre-aligning padding is odd, the text is biased by one
664/// space towards the right.
665/// @param[in] lines Vector of strings to be joined and centre-aligned.
666/// @return The joined and centre-aligned string of text.
667[[nodiscard]] inline std::string join_and_align_centre_right(
668 const std::vector<std::string>& lines) {
669 // Handle the empty case immediately to prevent underflow later.
670 if (lines.empty()) {
671 return std::string{};
672 }
673 // Compute the line lengths and find the maximum line length.
674 std::vector<std::size_t> line_lengths;
675 line_lengths.reserve(lines.size());
676 std::size_t longest_line_length{0UL};
677 for (const std::string& line : lines) {
678 const std::size_t length{lector::count_code_points(line)};
679 line_lengths.push_back(length);
680 longest_line_length = std::max(length, longest_line_length);
681 }
682 // Compute the exact total byte size.
683 std::size_t total_size{0UL};
684 for (std::size_t line_index{0UL}; line_index < lines.size(); ++line_index) {
685 const std::size_t total_padding{longest_line_length - line_lengths.at(line_index)};
686 // Bias right. When the total number of padding spaces is odd, adding one more space before
687 // performing the integer division rounds it up, giving one more padding space to the left.
688 const std::size_t left_padding{(total_padding + 1UL) / 2UL};
689 total_size += lines.at(line_index).size() + left_padding;
690 }
691 total_size += lines.size() - static_cast<std::size_t>(1UL);
692 // Create and allocate the resulting text.
693 std::string text;
694 text.reserve(total_size);
695 // Append lines with padding.
696 for (std::size_t line_index{0UL}; line_index < lines.size(); ++line_index) {
697 if (line_index > 0UL) {
698 text.push_back('\n');
699 }
700 const std::size_t total_padding{longest_line_length - line_lengths.at(line_index)};
701 const std::size_t left_padding{(total_padding + 1UL) / 2UL};
702 text.append(left_padding, ' ');
703 text.append(lines.at(line_index));
704 }
705 return text;
706}
707
708/// @brief Wraps and left-aligns a string of text to a line length.
709/// @param[in] text The string of text to wrap and left-align.
710/// @param[in] line_length The desired line length to use when wrapping. Must be strictly greater
711/// than zero. Very long words whose lengths exceed this line length are hyphenated.
712/// @return The wrapped and left-aligned string of text.
713/// @throws std::invalid_argument if the desired line length is zero.
714[[nodiscard]] inline std::string wrap_and_align_left(
715 const std::string_view text, const std::size_t line_length) {
716 return lector::join_and_align_left(lector::wrap(text, line_length));
717}
718
719/// @brief Wraps and right-aligns a string of text to a line length.
720/// @param[in] text The string of text to wrap and right-align.
721/// @param[in] line_length The desired line length to use when wrapping. Must be strictly greater
722/// than zero. Very long words whose lengths exceed this line length are hyphenated.
723/// @return The wrapped and right-aligned string of text.
724/// @throws std::invalid_argument if the desired line length is zero.
725[[nodiscard]] inline std::string wrap_and_align_right(
726 const std::string_view text, const std::size_t line_length) {
727 return lector::join_and_align_right(lector::wrap(text, line_length));
728}
729
730/// @brief Wraps and centre-aligns a string of text to a line length. If the total required
731/// centre-aligning padding is odd, the text is biased by one space towards the left.
732/// @param[in] text The string of text to wrap and centre-align.
733/// @param[in] line_length The desired line length to use when wrapping. Must be strictly greater
734/// than zero. Very long words whose lengths exceed this line length are hyphenated.
735/// @return The wrapped and centre-aligned string of text.
736/// @throws std::invalid_argument if the desired line length is zero.
737[[nodiscard]] inline std::string wrap_and_align_centre_left(
738 const std::string_view text, const std::size_t line_length) {
739 return lector::join_and_align_centre_left(lector::wrap(text, line_length));
740}
741
742/// @brief Wraps and centre-aligns a string of text to a line length. If the total required
743/// centre-aligning padding is odd, the text is biased by one space towards the right.
744/// @param[in] text The string of text to wrap and centre-align.
745/// @param[in] line_length The desired line length to use when wrapping. Must be strictly greater
746/// than zero. Very long words whose lengths exceed this line length are hyphenated.
747/// @return The wrapped and centre-aligned string of text.
748/// @throws std::invalid_argument if the desired line length is zero.
749[[nodiscard]] inline std::string wrap_and_align_centre_right(
750 const std::string_view text, const std::size_t line_length) {
751 return lector::join_and_align_centre_right(lector::wrap(text, line_length));
752}
753
754/// @brief Collates two strings of text, each representing a column, into a single string that
755/// contains newline-separated lines of text, with the lines formatted such that the two columns are
756/// left-aligned and spaced a short distance apart.
757/// @param[in] first_column_text The string of text for the first column.
758/// @param[in] first_column_width The desired width of the first column. Very long words whose
759/// length exceeds this width are hyphenated.
760/// @param[in] second_column_text The string of text for the second column.
761/// @param[in] second_column_width The desired width of the second column. Very long words whose
762/// length exceeds this width are hyphenated.
763/// @return The string that contains the collated text.
764/// @throws std::invalid_argument if either desired column width is zero.
765[[nodiscard]] inline std::string collate_and_align_left(
766 const std::string_view first_column_text, const std::size_t first_column_width,
767 const std::string_view second_column_text, const std::size_t second_column_width) {
768 // Use a gutter width of two spaces.
769 constexpr std::size_t gutter_width{2UL};
770 // Wrap and split both columns.
771 const std::vector<std::string> first_column{lector::wrap(first_column_text, first_column_width)};
772 const std::vector<std::string> second_column{
773 lector::wrap(second_column_text, second_column_width)};
774 // Determine the total number of rows required.
775 const std::size_t rows{std::max(first_column.size(), second_column.size())};
776 // Pre-allocate memory for the result. A safe and highly efficient upper bound is the byte size of
777 // both original input strings, plus the maximum possible padding spaces and newlines per row.
778 std::string result;
779 result.reserve(first_column_text.length() + second_column_text.length()
780 + (rows * (first_column_width + gutter_width + static_cast<std::size_t>(1UL))));
781 // Collate the rows line by line.
782 for (std::size_t row_index{0UL}; row_index < rows; ++row_index) {
783 // Append a newline character for every row after the first to separate them without leaving a
784 // trailing newline at the very end of the string.
785 if (row_index > static_cast<std::size_t>(0UL)) {
786 result.push_back('\n');
787 }
788 // Grab the string for the first column if it exists on this row; otherwise, use an empty
789 // string.
790 const std::string_view first_cell{
791 row_index < first_column.size() ? std::string_view{first_column.at(row_index)} :
792 std::string_view{}};
793 result.append(first_cell);
794 // If the second column has text on this row, pad the first column and append the second column.
795 // Otherwise, if the second column is exhausted, skip this padding to avoid unnecessary trailing
796 // whitespace.
797 if (row_index < second_column.size()) {
798 const std::size_t first_cell_length{lector::count_code_points(first_cell)};
799 const std::size_t padding{first_column_width + gutter_width - first_cell_length};
800 result.append(padding, ' ');
801 result.append(second_column.at(row_index));
802 }
803 }
804 return result;
805}
806
807/// @brief Collates two strings of text, each representing a column, into a single string that
808/// contains newline-separated lines of text, with the lines formatted such that the two columns are
809/// right-aligned and spaced a short distance apart.
810/// @param[in] first_column_text The string of text for the first column.
811/// @param[in] first_column_width The desired width of the first column. Very long words whose
812/// length exceeds this width are hyphenated.
813/// @param[in] second_column_text The string of text for the second column.
814/// @param[in] second_column_width The desired width of the second column. Very long words whose
815/// length exceeds this width are hyphenated.
816/// @return The string that contains the collated text.
817/// @throws std::invalid_argument if either desired column width is zero.
818[[nodiscard]] inline std::string collate_and_align_right(
819 const std::string_view first_column_text, const std::size_t first_column_width,
820 const std::string_view second_column_text, const std::size_t second_column_width) {
821 // Use a gutter width of two spaces.
822 constexpr std::size_t gutter_width{2UL};
823 // Wrap and split both columns.
824 const std::vector<std::string> first_column{lector::wrap(first_column_text, first_column_width)};
825 const std::vector<std::string> second_column{
826 lector::wrap(second_column_text, second_column_width)};
827 // Determine the total number of rows required.
828 const std::size_t rows{std::max(first_column.size(), second_column.size())};
829 // Pre-allocate memory for the result. A safe and highly efficient upper bound is the byte size of
830 // both original input strings, plus the maximum possible padding spaces and newlines per row.
831 std::string result;
832 result.reserve(first_column_text.length() + second_column_text.length()
833 + (rows
834 * (first_column_width + gutter_width + second_column_width
835 + static_cast<std::size_t>(1UL))));
836 // Collate the rows line by line.
837 for (std::size_t row_index{0UL}; row_index < rows; ++row_index) {
838 // Append a newline character for every row after the first to separate them without leaving a
839 // trailing newline at the very end of the string.
840 if (row_index > static_cast<std::size_t>(0UL)) {
841 result.push_back('\n');
842 }
843 // Grab the string for the first column if it exists on this row; otherwise, use an empty
844 // string.
845 const std::string_view first_cell{
846 row_index < first_column.size() ? std::string_view{first_column.at(row_index)} :
847 std::string_view{}};
848 const std::size_t first_cell_length{lector::count_code_points(first_cell)};
849 // Calculate the leading padding. The ternary operator protects against std::size_t underflow in
850 // the extremely unlikely event a cell exceeds the column width.
851 const std::size_t first_cell_padding{
852 first_column_width > first_cell_length ? first_column_width - first_cell_length :
853 static_cast<std::size_t>(0UL)};
854 // Right-align the first column by prepending the required padding.
855 result.append(first_cell_padding, ' ');
856 result.append(first_cell);
857 // If the second column has text on this row, append the gutter, pad the second column, and
858 // append it. Otherwise, if the second column is exhausted, skip this padding to avoid
859 // unnecessary trailing whitespace.
860 if (row_index < second_column.size()) {
861 const std::string_view second_cell{second_column.at(row_index)};
862 const std::size_t second_cell_length{lector::count_code_points(second_cell)};
863 const std::size_t second_cell_padding{
864 second_column_width > second_cell_length ? second_column_width - second_cell_length :
865 static_cast<std::size_t>(0UL)};
866 result.append(gutter_width, ' ');
867 result.append(second_cell_padding, ' ');
868 result.append(second_cell);
869 }
870 }
871 return result;
872}
873
874/// @brief Collates two strings of text, each representing a column, into a single string that
875/// contains newline-separated lines of text, with the lines formatted such that the two columns are
876/// centre-aligned and spaced a short distance apart. If the total required centre-aligning padding
877/// is odd, the text is biased by one space towards the left.
878/// @param[in] first_column_text The string of text for the first column.
879/// @param[in] first_column_width The desired width of the first column. Very long words whose
880/// length exceeds this width are hyphenated.
881/// @param[in] second_column_text The string of text for the second column.
882/// @param[in] second_column_width The desired width of the second column. Very long words whose
883/// length exceeds this width are hyphenated.
884/// @return The string that contains the collated text.
885/// @throws std::invalid_argument if either desired column width is zero.
886[[nodiscard]] inline std::string collate_and_align_centre_left(
887 const std::string_view first_column_text, const std::size_t first_column_width,
888 const std::string_view second_column_text, const std::size_t second_column_width) {
889 // Use a gutter width of two spaces.
890 constexpr std::size_t gutter_width{2UL};
891 // Wrap and split both columns.
892 const std::vector<std::string> first_column{lector::wrap(first_column_text, first_column_width)};
893 const std::vector<std::string> second_column{
894 lector::wrap(second_column_text, second_column_width)};
895 // Determine the total number of rows required.
896 const std::size_t rows{std::max(first_column.size(), second_column.size())};
897 // Pre-allocate memory for the result. A safe and highly efficient upper bound is the byte size of
898 // both original input strings, plus the maximum possible padding spaces and newlines per row.
899 std::string result;
900 result.reserve(first_column_text.length() + second_column_text.length()
901 + (rows
902 * (first_column_width + gutter_width + second_column_width
903 + static_cast<std::size_t>(1UL))));
904 // Collate the rows line by line.
905 for (std::size_t row_index{0UL}; row_index < rows; ++row_index) {
906 // Append a newline character for every row after the first to separate them without leaving a
907 // trailing newline at the very end of the string.
908 if (row_index > static_cast<std::size_t>(0UL)) {
909 result.push_back('\n');
910 }
911 // Grab the string for the first column if it exists on this row; otherwise, use an empty
912 // string.
913 const std::string_view first_cell{
914 row_index < first_column.size() ? std::string_view{first_column.at(row_index)} :
915 std::string_view{}};
916 const std::size_t first_cell_length{lector::count_code_points(first_cell)};
917 // Calculate the total padding. The ternary operator protects against std::size_t underflow in
918 // the extremely unlikely event a cell exceeds the column width.
919 const std::size_t first_cell_total_padding{
920 first_column_width > first_cell_length ? first_column_width - first_cell_length :
921 static_cast<std::size_t>(0UL)};
922 // Bias left. When the total number of padding spaces is odd, integer division rounds down,
923 // giving one less padding space to the left.
924 const std::size_t first_cell_left_padding{first_cell_total_padding / 2UL};
925 const std::size_t first_cell_right_padding{first_cell_total_padding - first_cell_left_padding};
926 // Append the left padding and the first cell.
927 result.append(first_cell_left_padding, ' ');
928 result.append(first_cell);
929 // If the second column has text on this row, calculate its padding, append the central padding
930 // (first cell right padding + gutter + second cell left padding), and append the second cell.
931 if (row_index < second_column.size()) {
932 const std::string_view second_cell{second_column.at(row_index)};
933 const std::size_t second_cell_length{lector::count_code_points(second_cell)};
934 const std::size_t second_cell_total_padding{
935 second_column_width > second_cell_length ? second_column_width - second_cell_length :
936 static_cast<std::size_t>(0UL)};
937 const std::size_t second_cell_left_padding{second_cell_total_padding / 2UL};
938 const std::size_t central_padding{
939 first_cell_right_padding + gutter_width + second_cell_left_padding};
940 result.append(central_padding, ' ');
941 result.append(second_cell);
942 }
943 }
944 return result;
945}
946
947/// @brief Collates two strings of text, each representing a column, into a single string that
948/// contains newline-separated lines of text, with the lines formatted such that the two columns are
949/// centre-aligned and spaced a short distance apart. If the total required centre-aligning padding
950/// is odd, the text is biased by one space towards the right.
951/// @param[in] first_column_text The string of text for the first column.
952/// @param[in] first_column_width The desired width of the first column. Very long words whose
953/// length exceeds this width are hyphenated.
954/// @param[in] second_column_text The string of text for the second column.
955/// @param[in] second_column_width The desired width of the second column. Very long words whose
956/// length exceeds this width are hyphenated.
957/// @return The string that contains the collated text.
958/// @throws std::invalid_argument if either desired column width is zero.
959[[nodiscard]] inline std::string collate_and_align_centre_right(
960 const std::string_view first_column_text, const std::size_t first_column_width,
961 const std::string_view second_column_text, const std::size_t second_column_width) {
962 // Use a gutter width of two spaces.
963 constexpr std::size_t gutter_width{2UL};
964 // Wrap and split both columns.
965 const std::vector<std::string> first_column{lector::wrap(first_column_text, first_column_width)};
966 const std::vector<std::string> second_column{
967 lector::wrap(second_column_text, second_column_width)};
968 // Determine the total number of rows required.
969 const std::size_t rows{std::max(first_column.size(), second_column.size())};
970 // Pre-allocate memory for the result. A safe and highly efficient upper bound is the byte size of
971 // both original input strings, plus the maximum possible padding spaces and newlines per row.
972 std::string result;
973 result.reserve(first_column_text.length() + second_column_text.length()
974 + (rows
975 * (first_column_width + gutter_width + second_column_width
976 + static_cast<std::size_t>(1UL))));
977 // Collate the rows line by line.
978 for (std::size_t row_index{0UL}; row_index < rows; ++row_index) {
979 // Append a newline character for every row after the first to separate them without leaving a
980 // trailing newline at the very end of the string.
981 if (row_index > static_cast<std::size_t>(0UL)) {
982 result.push_back('\n');
983 }
984 // Grab the string for the first column if it exists on this row; otherwise, use an empty
985 // string.
986 const std::string_view first_cell{
987 row_index < first_column.size() ? std::string_view{first_column.at(row_index)} :
988 std::string_view{}};
989 const std::size_t first_cell_length{lector::count_code_points(first_cell)};
990 // Calculate the total padding. The ternary operator protects against std::size_t underflow in
991 // the extremely unlikely event a cell exceeds the column width.
992 const std::size_t first_cell_total_padding{
993 first_column_width > first_cell_length ? first_column_width - first_cell_length :
994 static_cast<std::size_t>(0UL)};
995 // Bias right. When the total number of padding spaces is odd, adding one more space before
996 // performing the integer division rounds it up, giving one more padding space to the left.
997 const std::size_t first_cell_left_padding{(first_cell_total_padding + 1UL) / 2UL};
998 const std::size_t first_cell_right_padding{first_cell_total_padding - first_cell_left_padding};
999 // Append the left padding and the first cell.
1000 result.append(first_cell_left_padding, ' ');
1001 result.append(first_cell);
1002 // If the second column has text on this row, calculate its padding, append the central padding
1003 // (first cell right padding + gutter + second cell left padding), and append the second cell.
1004 if (row_index < second_column.size()) {
1005 const std::string_view second_cell{second_column.at(row_index)};
1006 const std::size_t second_cell_length{lector::count_code_points(second_cell)};
1007 const std::size_t second_cell_total_padding{
1008 second_column_width > second_cell_length ? second_column_width - second_cell_length :
1009 static_cast<std::size_t>(0UL)};
1010 const std::size_t second_cell_left_padding{(second_cell_total_padding + 1UL) / 2UL};
1011 const std::size_t central_padding{
1012 first_cell_right_padding + gutter_width + second_cell_left_padding};
1013 result.append(central_padding, ' ');
1014 result.append(second_cell);
1015 }
1016 }
1017 return result;
1018}
1019
1020} // namespace lector
1021
1022#endif // LECTOR_TEXT_HPP
The Lector library's namespace.
Definition arguments.hpp:43
std::string quote_if_contains_whitespace(const std::string_view text)
Encloses a string of text in quotes if it contains any whitespace. Either single or double quotes are...
Definition text.hpp:238
bool is_leading_byte(const char character)
Returns whether a given character is the leading byte of a UTF-8 character. All UTF-8 characters meas...
Definition text.hpp:56
std::string pad_and_align_centre_right(const std::string_view text, const std::size_t length)
Pads a string of text with spaces to reach a specified length. The padding is added from both the lef...
Definition text.hpp:316
std::string truncate_to_centre_right(const std::string_view text, const std::size_t length)
Truncates a string of text to a specified length by removing characters from both ends of the string....
Definition text.hpp:424
std::string wrap_and_align_left(const std::string_view text, const std::size_t line_length)
Wraps and left-aligns a string of text to a line length.
Definition text.hpp:714
std::string wrap_and_align_centre_right(const std::string_view text, const std::size_t line_length)
Wraps and centre-aligns a string of text to a line length. If the total required centre-aligning padd...
Definition text.hpp:749
std::string wrap_and_align_centre_left(const std::string_view text, const std::size_t line_length)
Wraps and centre-aligns a string of text to a line length. If the total required centre-aligning padd...
Definition text.hpp:737
std::string pad_and_align_centre_left(const std::string_view text, const std::size_t length)
Pads a string of text with spaces to reach a specified length. The padding is added from both the lef...
Definition text.hpp:292
std::size_t longest_word_length(const std::string_view text)
Computes and returns the length of the longest word in a string of text. The length of a word is meas...
Definition text.hpp:136
std::pair< std::size_t, std::size_t > byte_interval(const std::string_view text, const std::size_t code_point_index)
Finds the exact byte [begin, end) index interval in a string of text where a specified code point res...
Definition text.hpp:72
std::string truncate_from_left(const std::string_view text, const std::size_t length)
Truncates a string of text to a specified length by removing characters from the string's left end....
Definition text.hpp:339
std::size_t count_code_points(const std::string_view text)
Counts and returns the number of UTF-8 code points in a string of text. The number of UTF-8 code poin...
Definition text.hpp:102
std::string truncate_to_centre_left(const std::string_view text, const std::size_t length)
Truncates a string of text to a specified length by removing characters from both ends of the string....
Definition text.hpp:384
std::string truncate_from_right(const std::string_view text, const std::size_t length)
Truncates a string of text to a specified length by removing characters from the string's right end....
Definition text.hpp:363
std::vector< std::string_view > tokenize(const std::string_view text)
Tokenizes a string of text into a vector of strings of text, where each string in the vector correspo...
Definition text.hpp:172
std::string collate_and_align_right(const std::string_view first_column_text, const std::size_t first_column_width, const std::string_view second_column_text, const std::size_t second_column_width)
Collates two strings of text, each representing a column, into a single string that contains newline-...
Definition text.hpp:818
std::string join_and_align_centre_left(const std::vector< std::string > &lines)
Joins a vector of strings where each string corresponds to a line of text into a single string of tex...
Definition text.hpp:621
std::string join_and_align_left(const std::vector< std::string > &lines)
Joins a vector of strings where each string corresponds to a line of text into a single string of tex...
Definition text.hpp:550
std::string pad_and_align_left(const std::string_view text, const std::size_t length)
Pads a string of text with spaces to reach a specified length. The padding is added from the right su...
Definition text.hpp:251
std::string collate_and_align_centre_right(const std::string_view first_column_text, const std::size_t first_column_width, const std::string_view second_column_text, const std::size_t second_column_width)
Collates two strings of text, each representing a column, into a single string that contains newline-...
Definition text.hpp:959
std::string pad_and_align_right(const std::string_view text, const std::size_t length)
Pads a string of text with spaces to reach a specified length. The padding is added from the left suc...
Definition text.hpp:271
std::string quote(const std::string_view text)
Encloses a string of text in quotes. Either single or double quotes are used depending on which type ...
Definition text.hpp:200
std::string join_and_align_centre_right(const std::vector< std::string > &lines)
Joins a vector of strings where each string corresponds to a line of text into a single string of tex...
Definition text.hpp:667
std::vector< std::string > wrap(const std::string_view text, const std::size_t line_length)
Wraps a string of text to a line length and returns the result as a sequence of strings of text where...
Definition text.hpp:466
std::string collate_and_align_centre_left(const std::string_view first_column_text, const std::size_t first_column_width, const std::string_view second_column_text, const std::size_t second_column_width)
Collates two strings of text, each representing a column, into a single string that contains newline-...
Definition text.hpp:886
bool contains_whitespace(const std::string_view text)
Checks whether a string of text contains any ASCII whitespace characters: space (' '),...
Definition text.hpp:128
std::string wrap_and_align_right(const std::string_view text, const std::size_t line_length)
Wraps and right-aligns a string of text to a line length.
Definition text.hpp:725
bool is_whitespace(const char character)
Checks whether an ASCII character is one of the ASCII whitespace characters: space (' '),...
Definition text.hpp:117
std::string join_and_align_right(const std::vector< std::string > &lines)
Joins a vector of strings where each string corresponds to a line of text into a single string of tex...
Definition text.hpp:579
std::string collate_and_align_left(const std::string_view first_column_text, const std::size_t first_column_width, const std::string_view second_column_text, const std::size_t second_column_width)
Collates two strings of text, each representing a column, into a single string that contains newline-...
Definition text.hpp:765