Skip to content

Commit cd6cbea

Browse files
feat: add appendLine methods
1 parent 8fc18f7 commit cd6cbea

5 files changed

Lines changed: 238 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44

55
### Added
66

7-
- NIL
7+
- Line-based operations: `appendLine(str)` and `appendLn(str)` - append string with automatic newline character
88

99
### Changed
1010

11-
- NIL
11+
- chore(deps): bump google-benchmark from 1.9.4 to 1.9.5
1212

1313
### Deprecated
1414

TODO.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ Project roadmap and task tracking for the nfx-stringbuilder library.
55
### Todo
66

77
- [ ] Add convenience methods for common operations
8-
- [ ] `appendLine(str)` / `appendLn(str)` - append with newline character
98
- [ ] `join(container, delimiter)` - join collection elements with delimiter
109
- [ ] `substr(pos, len)` for zero-copy access to portions
1110
- [ ] `replace(pos, len, str)` - in-place replacement
@@ -20,4 +19,4 @@ Project roadmap and task tracking for the nfx-stringbuilder library.
2019

2120
### Done ✓
2221

23-
- NIL
22+
- [x] `appendLine(str)` / `appendLn(str)` - append with newline character (2026-02-08)

include/nfx/detail/string/StringBuilder.inl

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,48 @@ namespace nfx::string
320320
return *this;
321321
}
322322

323+
//----------------------------------------------
324+
// Line operations
325+
//----------------------------------------------
326+
327+
inline StringBuilder& StringBuilder::appendLine( std::string_view str )
328+
{
329+
const size_t len = str.size();
330+
const size_t newSize = m_size + len + 1; // +1 for newline
331+
332+
if( newSize > m_capacity ) [[unlikely]]
333+
{
334+
ensureCapacity( newSize );
335+
}
336+
337+
// Append string content
338+
if( len > 0 )
339+
{
340+
std::memcpy( m_buffer + m_size, str.data(), len );
341+
}
342+
343+
// Append newline
344+
m_buffer[m_size + len] = '\n';
345+
m_size = newSize;
346+
347+
return *this;
348+
}
349+
350+
inline StringBuilder& StringBuilder::appendLine( const std::string& str )
351+
{
352+
return appendLine( std::string_view{ str } );
353+
}
354+
355+
inline StringBuilder& StringBuilder::appendLine( const char* str )
356+
{
357+
if( str ) [[likely]]
358+
{
359+
return appendLine( std::string_view{ str, strlen( str ) } );
360+
}
361+
// Just append newline if str is null
362+
return append( '\n' );
363+
}
364+
323365
//----------------------------------------------
324366
// Prepend operations
325367
//----------------------------------------------

include/nfx/string/StringBuilder.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,41 @@ namespace nfx::string
359359
*/
360360
inline StringBuilder& append( double value );
361361

362+
//----------------------------------------------
363+
// Line operations
364+
//----------------------------------------------
365+
366+
/**
367+
* @brief Appends string_view contents followed by a newline character
368+
* @param str String view to append (default: empty)
369+
* @return Reference to this StringBuilder for chaining
370+
* @details Efficiently appends the string and newline in one operation.
371+
* Useful for building line-based formats like logs, CSV, or ISO 19848 messages.
372+
*
373+
* Example:
374+
* @code
375+
* builder.appendLine("Header")
376+
* .appendLine("Data1")
377+
* .appendLine("Data2");
378+
* // Result: "Header\nData1\nData2\n"
379+
* @endcode
380+
*/
381+
inline StringBuilder& appendLine( std::string_view str = "" );
382+
383+
/**
384+
* @brief Appends std::string contents followed by a newline character
385+
* @param str String to append
386+
* @return Reference to this StringBuilder for chaining
387+
*/
388+
inline StringBuilder& appendLine( const std::string& str );
389+
390+
/**
391+
* @brief Appends null-terminated C-string followed by a newline character
392+
* @param str Null-terminated C-string to append (null pointer handled gracefully)
393+
* @return Reference to this StringBuilder for chaining
394+
*/
395+
inline StringBuilder& appendLine( const char* str );
396+
362397
//----------------------------------------------
363398
// Prepend operations
364399
//----------------------------------------------

samples/Sample_StringBuilder.cpp

Lines changed: 158 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,27 @@
4040
#include <vector>
4141
#include <thread>
4242

43+
//=====================================================================
44+
// Helper function: Calculate NMEA-0183 checksum
45+
//=====================================================================
46+
/**
47+
* @brief Calculate NMEA-0183 checksum (XOR of all characters between $ and *)
48+
* @param sentence NMEA sentence without $ prefix and * suffix
49+
* @return Checksum as 2-digit uppercase hex string
50+
*/
51+
std::string calculateNmeaChecksum( std::string_view sentence )
52+
{
53+
uint8_t checksum = 0;
54+
for( char c : sentence )
55+
{
56+
checksum ^= static_cast<uint8_t>( c );
57+
}
58+
59+
char result[3];
60+
std::snprintf( result, sizeof( result ), "%02X", checksum );
61+
return std::string{ result };
62+
}
63+
4364
int main()
4465
{
4566
using namespace nfx::string;
@@ -619,10 +640,145 @@ int main()
619640
}
620641

621642
//=====================================================================
622-
// 15. Prepend operations - Building strings in reverse
643+
// 15. Line-based operations - appendLine() / appendLn()
644+
//=====================================================================
645+
{
646+
std::cout << "15. Line-based operations - appendLine() / appendLn()\n";
647+
std::cout << "------------------------------------------------------\n";
648+
649+
// Basic line appending
650+
std::cout << "Basic line appending:\n";
651+
{
652+
StringBuilder builder;
653+
builder.appendLine( "First line" ).appendLine( "Second line" ).appendLine( "Third line" );
654+
655+
std::cout << " Result:\n" << builder.toString();
656+
std::cout << " Note: Each appendLine() adds '\\n' automatically\n";
657+
std::cout << "\n";
658+
}
659+
660+
// Building CSV-like data
661+
std::cout << "Building CSV-like data:\n";
662+
{
663+
StringBuilder builder;
664+
builder.appendLine( "Name,Age,City" )
665+
.appendLine( "Alice,30,Paris" )
666+
.appendLine( "Bob,25,London" )
667+
.appendLine( "Charlie,35,Berlin" );
668+
669+
std::cout << " Result:\n" << builder.toString();
670+
std::cout << "\n";
671+
}
672+
673+
std::cout << "Building NMEA-0183 messages with checksums:\n";
674+
{
675+
StringBuilder builder;
676+
677+
// Helper lambda to build NMEA sentence with checksum
678+
auto buildNmeaSentence = [&builder]( std::string_view sentence ) {
679+
builder.append( "$" ).append( sentence );
680+
std::string checksum = calculateNmeaChecksum( sentence );
681+
builder.append( "*" ).append( checksum );
682+
builder.appendLine();
683+
};
684+
685+
// GPS Fix Data (GPGGA)
686+
buildNmeaSentence( "GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,," );
687+
688+
// GPS DOP and active satellites (GPGSA)
689+
buildNmeaSentence( "GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1" );
690+
691+
// Satellites in view (GPGSV)
692+
buildNmeaSentence( "GPGSV,2,1,08,01,40,083,46,02,17,308,41,12,07,344,39,14,22,228,45" );
693+
694+
// Meteorological Composite (WIMDA) - Full message
695+
buildNmeaSentence( "WIMDA,29.92,I,1.013,B,15.5,C,10.2,C,45.0,,12.5,C,270.0,T,270.0,M,5.2,N,2.7,M" );
696+
697+
// Wind Speed and Angle (WIMWV)
698+
buildNmeaSentence( "WIMWV,270.0,T,5.2,N,A" );
699+
700+
// Water Temperature (WIMTW)
701+
buildNmeaSentence( "WIMTW,10.2,C" );
702+
703+
std::cout << " Result:\n" << builder.toString();
704+
std::cout << " Note: All messages include valid NMEA-0183 checksums\n";
705+
std::cout << " Format: $SENTENCE*XX where XX is XOR checksum\n";
706+
std::cout << "\n";
707+
}
708+
709+
// Building a realistic NMEA-0183 data stream
710+
std::cout << "Building realistic NMEA-0183 data stream:\n";
711+
{
712+
StringBuilder builder;
713+
714+
// Simulate a GPS/Weather station output
715+
auto addNmea = [&builder]( std::string_view sentence ) {
716+
builder.append( "$" ).append( sentence );
717+
builder.append( "*" ).append( calculateNmeaChecksum( sentence ) );
718+
builder.appendLine();
719+
};
720+
721+
// Position update
722+
addNmea( "GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W" );
723+
addNmea( "GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,," );
724+
725+
// Weather data
726+
addNmea( "WIMDA,30.12,I,1.020,B,18.3,C,12.5,C,52.0,,10.8,C,285.0,T,285.0,M,12.5,N,6.4,M" );
727+
addNmea( "WIMWV,285.0,T,12.5,N,A" );
728+
729+
// Depth sounder
730+
addNmea( "SDDBT,45.2,f,13.8,M,7.5,F" );
731+
732+
std::cout << " Result:\n" << builder.toString();
733+
std::cout << "\n";
734+
}
735+
736+
// Building log entries
737+
std::cout << "Building multi-line log entries:\n";
738+
{
739+
StringBuilder builder;
740+
741+
auto now = std::chrono::system_clock::now();
742+
auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>( now.time_since_epoch() ).count();
743+
744+
builder.appendLine( "[INFO] Application started" )
745+
.append( "[DEBUG] Timestamp: " )
746+
.append( timestamp )
747+
.appendLine()
748+
.appendLine( "[INFO] Configuration loaded" )
749+
.appendLine( "[WARN] Cache not found, creating new" );
750+
751+
std::cout << " Result:\n" << builder.toString();
752+
std::cout << "\n";
753+
}
754+
755+
// Building JSON-like structure (simplified)
756+
std::cout << "Building structured text:\n";
757+
{
758+
StringBuilder builder;
759+
builder.appendLine( "{" )
760+
.appendLine( " \"name\": \"StringBuilder\"," )
761+
.appendLine( " \"version\": \"0.5.0\"," )
762+
.appendLine( " \"features\": [" )
763+
.appendLine( " \"SBO\"," )
764+
.appendLine( " \"Zero-copy\"," )
765+
.appendLine( " \"High-performance\"" )
766+
.appendLine( " ]" )
767+
.appendLine( "}" );
768+
769+
std::cout << " Result:\n" << builder.toString();
770+
std::cout << " Note: Indentation handled manually\n";
771+
std::cout << "\n";
772+
}
773+
774+
std::cout << "\n";
775+
}
776+
777+
//=====================================================================
778+
// 16. Prepend operations - Building strings in reverse
623779
//=====================================================================
624780
{
625-
std::cout << "15. Prepend operations - Building strings in reverse\n";
781+
std::cout << "16. Prepend operations - Building strings in reverse\n";
626782
std::cout << "----------------------------------------------------\n";
627783

628784
// Basic prepend

0 commit comments

Comments
 (0)