Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# cstring - Changes <!-- omit in toc -->


## 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;
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
2 changes: 2 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions include/cstring/cstring.h
Original file line number Diff line number Diff line change
Expand Up @@ -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/
*
Expand Down Expand Up @@ -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 */


Expand Down Expand Up @@ -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
Expand All @@ -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 \
Expand Down
19 changes: 15 additions & 4 deletions src/cstring.core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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/
*
Expand Down Expand Up @@ -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);

Expand All @@ -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 (;;)
{
Expand Down
79 changes: 78 additions & 1 deletion test/component/test.component.cstring_readline/entry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Purpose: Component-tests `cstring_readline()`.
*
* Created: 23rd May 2009
* Updated: 27th September 2026
* Updated: 3rd October 2026
*
* ////////////////////////////////////////////////////////////////////// */

Expand Down Expand Up @@ -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*);
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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<FILE*> 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<FILE*> 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


Expand Down
Loading