diff --git a/CHANGES.md b/CHANGES.md index ad45af0..1b2a41c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,12 @@ # cstring - Changes +## 4.0.19 - 3rd October 2026 + +* `cstring_readline()` returns the result of its opening `cstring_truncate()`, so a readonly destination yields `CSTRING_RC_READONLY` for an empty line or immediate end of file, and the payload and stream position are left unchanged; +* Added **test.component.cstring_readline** case `TEST_cstring_readline_READONLY_RETAINS_PAYLOAD`; + + ## 4.0.18 - 29th September 2026 * Gated Windows arena flags and WinAPI allocators on `_WIN32` in **cstring.h** and **cstring.core.c**, so 32- and 64-bit Windows builds expose them without a `WIN32` or `WIN64` define; diff --git a/CMakeLists.txt b/CMakeLists.txt index aca4361..330b3e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -214,7 +214,7 @@ endif(BUILD_TESTING) if(BUILD_TESTING) - set(xTests_REQUIRED_VERSION_ 0.26) + set(xTests_REQUIRED_VERSION_ 0.26.5) find_package(xTests ${xTests_REQUIRED_VERSION_} REQUIRED) diff --git a/Doxyfile b/Doxyfile index 361b9a9..b549b16 100644 --- a/Doxyfile +++ b/Doxyfile @@ -2,7 +2,7 @@ PROJECT_NAME = "cstring" PROJECT_BRIEF = "Extensible C-style strings and vectors of such, for Unix and Windows" -PROJECT_NUMBER = 4.0.18 +PROJECT_NUMBER = 4.0.19 # Prefer SIS_CMAKE_BUILD_DIR-aligned output (same convention as Diagnosticism). # ./dox/ remains gitignored for legacy/local runs that override OUTPUT_DIRECTORY. diff --git a/NEWS.md b/NEWS.md index b1106c3..02d9c20 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,7 @@ | Date | News Item | Details | | ------------------- | -------------------------------- | ------- | | Available from [**cstring** project on GitHub](https://synesissoftware.com/cstring): | +| 3rd October 2026 | Release of [cstring 4.0.19](https://github.com/synesissoftware/cstring/releases/tag/4.0.19) | `cstring_readline()` reports a failed readonly clear | | 29th September 2026 | Release of [cstring 4.0.18](https://github.com/synesissoftware/cstring/releases/tag/4.0.18) | `_WIN32` arena gate; **win.c**; static `CoTaskMem*` (**ole32**) | | 29th September 2026 | Release of [cstring 4.0.17](https://github.com/synesissoftware/cstring/releases/tag/4.0.17) | Perf tests; `insertAt` fix; component I/O; Windows arena rename | | 27th September 2026 | Release of [cstring 4.0.16](https://github.com/synesissoftware/cstring/releases/tag/4.0.16) | Phase 4b helpers, native `.cmd`, CI dogfood | diff --git a/TODO.md b/TODO.md index 4f1564b..cf57dcf 100644 --- a/TODO.md +++ b/TODO.md @@ -17,6 +17,8 @@ * [x] ~~~discriminate on `_WIN32` in implementation (and maybe also in API)~~~ - ✅; * [x] ~~~check `CSTRING_USE_WINAPI_`~~~; * [ ] custom arena(s); +* [ ] `cstring_vector_readlineEx()` that takes a flag to prevent truncate, thereby allowing client code to add to an existing string; +* [ ] when go to 5.x, change the name of `cstring_vector_readLines()` to `cstring_vector_readlines()`; ## Performance improvements diff --git a/include/cstring/cstring.h b/include/cstring/cstring.h index edd5026..663ba1c 100644 --- a/include/cstring/cstring.h +++ b/include/cstring/cstring.h @@ -4,7 +4,7 @@ * Purpose: Definition of the cstring.core API. * * Created: 16th June 1994 - * Updated: 29th September 2026 + * Updated: 3rd October 2026 * * Home: http://synesis.com.au/software/ * @@ -55,8 +55,8 @@ #ifndef CSTRING_DOCUMENTATION_SKIP_SECTION # define CSTRING_VER_CSTRING_H_CSTRING_MAJOR 3 # define CSTRING_VER_CSTRING_H_CSTRING_MINOR 12 -# define CSTRING_VER_CSTRING_H_CSTRING_REVISION 1 -# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 90 +# define CSTRING_VER_CSTRING_H_CSTRING_REVISION 2 +# define CSTRING_VER_CSTRING_H_CSTRING_EDIT 91 #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ @@ -103,6 +103,7 @@ # define CSTRING_VER_4_0_16 0x040010ff # define CSTRING_VER_4_0_17 0x040011ff # define CSTRING_VER_4_0_18 0x040012ff +# define CSTRING_VER_4_0_19 0x040013ff #endif /* !CSTRING_DOCUMENTATION_SKIP_SECTION */ /** \def CSTRING_VER_MAJOR @@ -123,7 +124,7 @@ #define CSTRING_VER_MAJOR 4 #define CSTRING_VER_MINOR 0 -#define CSTRING_VER_PATCH 18 +#define CSTRING_VER_PATCH 19 #define CSTRING_VER_ALPHABETA 0xFF #define CSTRING_VER \ diff --git a/src/cstring.core.c b/src/cstring.core.c index 5bee299..7fbfa3f 100644 --- a/src/cstring.core.c +++ b/src/cstring.core.c @@ -4,7 +4,7 @@ * Purpose: The implementation of the cstring core API * * Created: 16th June 1994 - * Updated: 29th September 2026 + * Updated: 3rd October 2026 * * Home: http://synesis.com.au/software/ * @@ -1363,8 +1363,9 @@ cstring_readline( , size_t* numRead /* = NULL */ ) { - int previous = '\0'; - size_t dummy; + int previous = '\0'; + size_t dummy; + CSTRING_RC rc0; CSTRING_ASSERT(NULL != pcs); @@ -1381,7 +1382,17 @@ cstring_readline( *numRead = 0u; - cstring_truncate(pcs, 0); + rc0 = cstring_truncate(pcs, 0); + + if (CSTRING_RC_SUCCESS != rc0) + { + /* NOTE: have to check for failure here, because a non-empty + * readonly string could be left if the stream is empty or its first + * character is LF. + */ + + return rc0; + } for (;;) { diff --git a/test/component/test.component.cstring_readline/entry.cpp b/test/component/test.component.cstring_readline/entry.cpp index 7bb3c7c..ad5d765 100644 --- a/test/component/test.component.cstring_readline/entry.cpp +++ b/test/component/test.component.cstring_readline/entry.cpp @@ -4,7 +4,7 @@ * Purpose: Component-tests `cstring_readline()`. * * Created: 23rd May 2009 - * Updated: 27th September 2026 + * Updated: 3rd October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -56,6 +56,7 @@ namespace static void TEST_cstring_readline_LONG_LINES(void); static void TEST_cstring_readline_MANY_SHORT_LINES(void); static void TEST_cstring_readline_REUSE_AFTER_LONG_LINE(void); + static void TEST_cstring_readline_READONLY_RETAINS_PAYLOAD(void); int setup(void*); int teardown(void*); @@ -104,6 +105,7 @@ int main(int argc, char* argv[]) XTESTS_RUN_CASE(TEST_cstring_readline_LONG_LINES); XTESTS_RUN_CASE(TEST_cstring_readline_MANY_SHORT_LINES); XTESTS_RUN_CASE(TEST_cstring_readline_REUSE_AFTER_LONG_LINE); + XTESTS_RUN_CASE(TEST_cstring_readline_READONLY_RETAINS_PAYLOAD); XTESTS_PRINT_RESULTS(); @@ -578,6 +580,81 @@ static void TEST_cstring_readline_REUSE_AFTER_LONG_LINE() cstring_destroy(&cs); } + +static void TEST_cstring_readline_READONLY_RETAINS_PAYLOAD() +{ + /* non-empty readonly string: a failed clear must be reported, and the + * stream must not be consumed + */ + + { + write_string_bytes(TEST_FILE_NAME, std::string("\nnext\n")); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs; + CSTRING_RC rc; + + rc = cstring_createEx(&cs, "stale", CSTRING_F_MEMORY_IS_READONLY, NULL, 0); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + + size_t n = 99u; + + rc = cstring_readline(f, &cs, &n); + + TEST_ENUM_EQ(CSTRING_RC_READONLY, rc); + TEST_INT_EQ(5u, cs.len); + TEST_MS_EQ("stale", cs); + + cstring_t fresh = cstring_t_DEFAULT; + + rc = cstring_readline(f, &fresh, &n); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + TEST_INT_EQ(0u, n); + TEST_MS_EQ("", fresh); + + cstring_destroy(&fresh); + cstring_destroy(&cs); + } + + { + write_bytes(TEST_FILE_NAME, "", 0); + + FILE* f = fopen_or_throw(TEST_FILE_NAME, "rb"); + + stlsoft::scoped_handle scoper(f, ::fclose); + + cstring_t cs; + CSTRING_RC rc; + + rc = cstring_createEx(&cs, "stale", CSTRING_F_MEMORY_IS_READONLY, NULL, 0); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_SUCCESS, rc)); + + size_t n = 99u; + + rc = cstring_readline(f, &cs, &n); + + TEST_ENUM_EQ(CSTRING_RC_READONLY, rc); + TEST_INT_EQ(5u, cs.len); + TEST_MS_EQ("stale", cs); + + cstring_t fresh = cstring_t_DEFAULT; + + rc = cstring_readline(f, &fresh, &n); + + REQUIRE(TEST_ENUM_EQ(CSTRING_RC_EOF, rc)); + TEST_INT_EQ(0u, n); + TEST_INT_EQ(0u, fresh.len); + + cstring_destroy(&fresh); + cstring_destroy(&cs); + } +} } // anonymous namespace