From ce332e63723b36c0f5ecdbedffbe1130dd8953bd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 18:26:32 +0000 Subject: [PATCH 1/4] Initial plan From c3067a75eea02103e69b3bf016385a0c14a7ddc9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 18:32:14 +0000 Subject: [PATCH 2/4] Add iteration API for sections and keys (dmini_section_count, dmini_section_name, dmini_key_count, dmini_key_name) Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- README.md | 6 +++ apps/test_dmini/test_dmini.c | 85 +++++++++++++++++++++++++++++ docs/dmini.md | 26 +++++++++ include/dmini.h | 54 +++++++++++++++++++ src/dmini.c | 100 +++++++++++++++++++++++++++++++++++ 5 files changed, 271 insertions(+) diff --git a/README.md b/README.md index fb33d3c..b7c1adb 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,12 @@ dmini is a lightweight INI file parser/generator module designed for embedded sy - `dmini_has_section(ctx, section)` - Check if section exists - `dmini_has_key(ctx, section, key)` - Check if key exists +### Iteration +- `dmini_section_count(ctx)` - Get number of sections (including global) +- `dmini_section_name(ctx, index)` - Get section name at index (NULL for global section) +- `dmini_key_count(ctx, section)` - Get number of keys in a section +- `dmini_key_name(ctx, section, index)` - Get key name at index within a section + ### Section Visibility Restriction - `dmini_set_active_section(ctx, section, owner_token)` - Restrict the context to a single section - `dmini_clear_active_section(ctx, owner_token)` - Remove the section restriction diff --git a/apps/test_dmini/test_dmini.c b/apps/test_dmini/test_dmini.c index 9365067..7b647ae 100644 --- a/apps/test_dmini/test_dmini.c +++ b/apps/test_dmini/test_dmini.c @@ -333,6 +333,90 @@ static void test_inline_comments(void) TEST_PASS(); } +/** + * @brief Test: Iteration over sections and keys + */ +static void test_iteration(void) +{ + TEST_START("Iterate over sections and keys"); + + const char* ini_data = + "global_key=global_value\n" + "\n" + "[section1]\n" + "key1=value1\n" + "key2=value2\n" + "\n" + "[section2]\n" + "number=42\n"; + + dmini_context_t ctx = dmini_create(); + TEST_ASSERT(ctx != NULL, "Failed to create context"); + + int result = dmini_parse_string(ctx, ini_data); + TEST_ASSERT(result == DMINI_OK, "Failed to parse string"); + + /* Section count: global + section1 + section2 */ + int sc = dmini_section_count(ctx); + TEST_ASSERT(sc == 3, "Expected 3 sections"); + + /* Section names: index 0 = global (NULL), 1 = section1, 2 = section2 */ + const char* name0 = dmini_section_name(ctx, 0); + TEST_ASSERT(name0 == NULL, "Index 0 should be global section (NULL)"); + + const char* name1 = dmini_section_name(ctx, 1); + TEST_ASSERT(name1 != NULL && strcmp(name1, "section1") == 0, "Index 1 should be section1"); + + const char* name2 = dmini_section_name(ctx, 2); + TEST_ASSERT(name2 != NULL && strcmp(name2, "section2") == 0, "Index 2 should be section2"); + + /* Out-of-range index returns NULL */ + TEST_ASSERT(dmini_section_name(ctx, 3) == NULL, "Out-of-range index should return NULL"); + + /* Key count for global section */ + int kc = dmini_key_count(ctx, NULL); + TEST_ASSERT(kc == 1, "Global section should have 1 key"); + + /* Key count for section1 */ + kc = dmini_key_count(ctx, "section1"); + TEST_ASSERT(kc == 2, "section1 should have 2 keys"); + + /* Key names for section1 */ + const char* kname0 = dmini_key_name(ctx, "section1", 0); + TEST_ASSERT(kname0 != NULL && strcmp(kname0, "key1") == 0, "Index 0 key should be key1"); + + const char* kname1 = dmini_key_name(ctx, "section1", 1); + TEST_ASSERT(kname1 != NULL && strcmp(kname1, "key2") == 0, "Index 1 key should be key2"); + + /* Out-of-range key index returns NULL */ + TEST_ASSERT(dmini_key_name(ctx, "section1", 2) == NULL, "Out-of-range key index should return NULL"); + + /* Non-existent section returns DMINI_ERR_NOT_FOUND for key_count */ + TEST_ASSERT(dmini_key_count(ctx, "nonexistent") == DMINI_ERR_NOT_FOUND, + "Non-existent section should return DMINI_ERR_NOT_FOUND"); + + /* NULL ctx returns DMINI_ERR_INVALID */ + TEST_ASSERT(dmini_section_count(NULL) == DMINI_ERR_INVALID, "NULL ctx should return DMINI_ERR_INVALID"); + TEST_ASSERT(dmini_key_count(NULL, "section1") == DMINI_ERR_INVALID, "NULL ctx should return DMINI_ERR_INVALID"); + + /* Active section restriction: only active section is visible */ + result = dmini_set_active_section(ctx, "section1", 0); + TEST_ASSERT(result == DMINI_OK, "Failed to set active section"); + + TEST_ASSERT(dmini_section_count(ctx) == 1, "Only 1 section visible under restriction"); + TEST_ASSERT(dmini_section_name(ctx, 0) != NULL && + strcmp(dmini_section_name(ctx, 0), "section1") == 0, + "Active section should be section1"); + + TEST_ASSERT(dmini_key_count(ctx, "section1") == 2, "section1 keys still accessible by name"); + TEST_ASSERT(dmini_key_count(ctx, "section2") == DMINI_ERR_NOT_FOUND, + "section2 hidden under restriction"); + + dmini_clear_active_section(ctx, 0); + dmini_destroy(ctx); + TEST_PASS(); +} + /** * @brief Test: Active section restriction */ @@ -452,6 +536,7 @@ int main(int argc, char** argv) test_file_io(); test_comments_whitespace(); test_inline_comments(); + test_iteration(); test_active_section(); // Print summary diff --git a/docs/dmini.md b/docs/dmini.md index 6f0dc0d..3bc75f2 100644 --- a/docs/dmini.md +++ b/docs/dmini.md @@ -38,6 +38,11 @@ int dmini_remove_key(dmini_context_t ctx, const char* section, const char* key); int dmini_set_active_section(dmini_context_t ctx, const char* section, unsigned int owner_token); int dmini_clear_active_section(dmini_context_t ctx, unsigned int owner_token); + +int dmini_section_count(dmini_context_t ctx); +const char* dmini_section_name(dmini_context_t ctx, int index); +int dmini_key_count(dmini_context_t ctx, const char* section); +const char* dmini_key_name(dmini_context_t ctx, const char* section, int index); ``` ## DESCRIPTION @@ -125,6 +130,27 @@ context. Returns DMINI_OK on success or an error code on failure. NULL for section to remove from the global section. Returns DMINI_OK on success or an error code on failure. +### Iteration + +**dmini_section_count()** returns the total number of sections in the context, +including the global (unnamed) section. Respects the active-section restriction +when it is in effect. Returns DMINI_ERR_INVALID if ctx is NULL. + +**dmini_section_name()** returns the name of the section at the given +zero-based index. The global (unnamed) section is represented by NULL. Returns +NULL when the index is out of range. Use dmini_section_count() to determine +the valid range. Respects the active-section restriction when it is in effect. + +**dmini_key_count()** returns the number of key-value pairs in the specified +section. Pass NULL for section to query the global section. Returns +DMINI_ERR_INVALID if ctx is NULL, or DMINI_ERR_NOT_FOUND if the section does +not exist. Respects the active-section restriction when it is in effect. + +**dmini_key_name()** returns the name of the key at the given zero-based index +within the specified section. Returns NULL when the index is out of range or +the section does not exist. Use dmini_key_count() to determine the valid range. +Respects the active-section restriction when it is in effect. + ### Section Visibility Restriction **dmini_set_active_section()** restricts the context so that only the named diff --git a/include/dmini.h b/include/dmini.h index 6a92831..839da8b 100644 --- a/include/dmini.h +++ b/include/dmini.h @@ -208,6 +208,60 @@ dmod_dmini_api(1.0, int, _remove_key, (dmini_context_t ctx, const char* section, const char* key)); +/** + * @brief Get number of sections in the context + * + * Returns the total number of sections, including the global (unnamed) section. + * Respects the active-section restriction when it is in effect. + * + * @param ctx INI context + * @return Number of sections, or DMINI_ERR_INVALID if ctx is NULL + */ +dmod_dmini_api(1.0, int, _section_count, (dmini_context_t ctx)); + +/** + * @brief Get section name at the given index + * + * Returns the name of the section at position @p index (0-based). + * The global (unnamed) section is represented by NULL. + * Respects the active-section restriction when it is in effect. + * Use dmini_section_count() to determine the valid index range. + * + * @param ctx INI context + * @param index Zero-based section index + * @return Section name, NULL for the global section, or NULL if index is out of range + */ +dmod_dmini_api(1.0, const char*, _section_name, (dmini_context_t ctx, int index)); + +/** + * @brief Get number of keys in a section + * + * Returns the number of key-value pairs in the given section. + * Respects the active-section restriction when it is in effect. + * + * @param ctx INI context + * @param section Section name (NULL for global section) + * @return Number of keys, or DMINI_ERR_INVALID if ctx is NULL, + * or DMINI_ERR_NOT_FOUND if section does not exist + */ +dmod_dmini_api(1.0, int, _key_count, (dmini_context_t ctx, const char* section)); + +/** + * @brief Get key name at the given index within a section + * + * Returns the name of the key at position @p index (0-based) within @p section. + * Use dmini_key_count() to determine the valid index range. + * Respects the active-section restriction when it is in effect. + * + * @param ctx INI context + * @param section Section name (NULL for global section) + * @param index Zero-based key index + * @return Key name, or NULL if index is out of range or section not found + */ +dmod_dmini_api(1.0, const char*, _key_name, (dmini_context_t ctx, + const char* section, + int index)); + /** * @brief Initialize INI context with owner token * diff --git a/src/dmini.c b/src/dmini.c index ca68e43..06b6e71 100644 --- a/src/dmini.c +++ b/src/dmini.c @@ -1128,6 +1128,106 @@ int dmini_remove_key(dmini_context_t ctx, const char* section, const char* key) return DMINI_ERR_NOT_FOUND; } +int dmini_section_count(dmini_context_t ctx) +{ + if (!ctx) + { + return DMINI_ERR_INVALID; + } + + int count = 0; + dmini_section_t* section = ctx->sections; + while (section) + { + if (!ctx->active_section_locked || + section_names_equal(section->name, ctx->active_section)) + { + count++; + } + section = section->next; + } + + return count; +} + +const char* dmini_section_name(dmini_context_t ctx, int index) +{ + if (!ctx || index < 0) + { + return NULL; + } + + int current = 0; + dmini_section_t* section = ctx->sections; + while (section) + { + if (!ctx->active_section_locked || + section_names_equal(section->name, ctx->active_section)) + { + if (current == index) + { + return section->name; + } + current++; + } + section = section->next; + } + + return NULL; +} + +int dmini_key_count(dmini_context_t ctx, const char* section) +{ + if (!ctx) + { + return DMINI_ERR_INVALID; + } + + dmini_section_t* sec = find_section(ctx, section); + if (!sec) + { + return DMINI_ERR_NOT_FOUND; + } + + int count = 0; + dmini_pair_t* pair = sec->pairs; + while (pair) + { + count++; + pair = pair->next; + } + + return count; +} + +const char* dmini_key_name(dmini_context_t ctx, const char* section, int index) +{ + if (!ctx || index < 0) + { + return NULL; + } + + dmini_section_t* sec = find_section(ctx, section); + if (!sec) + { + return NULL; + } + + int current = 0; + dmini_pair_t* pair = sec->pairs; + while (pair) + { + if (current == index) + { + return pair->key; + } + current++; + pair = pair->next; + } + + return NULL; +} + int dmini_set_active_section(dmini_context_t ctx, const char* section, unsigned int owner_token) { if (!ctx) From aecbe7dd7760dd0b9958b2e2b87efb7dedf4953c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 19:37:46 +0000 Subject: [PATCH 3/4] Pin DMOD GIT_TAG to commit compatible with dmod:1.0.4 docker image Co-authored-by: JohnAmadis <17320783+JohnAmadis@users.noreply.github.com> --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d32fa98..997d205 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ include(FetchContent) FetchContent_Declare( dmod GIT_REPOSITORY https://github.com/choco-technologies/dmod.git - GIT_TAG develop + GIT_TAG 69a8f7446a377751d6e1d5be33ba77521eda84cc ) # ====================================================================== From e432de52929031889e069b04096e4f5dea210cad Mon Sep 17 00:00:00 2001 From: Patryk Kubiak Date: Mon, 9 Mar 2026 21:25:22 +0100 Subject: [PATCH 4/4] Update DMOD GIT_TAG to use the latest develop branch --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 997d205..d32fa98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ include(FetchContent) FetchContent_Declare( dmod GIT_REPOSITORY https://github.com/choco-technologies/dmod.git - GIT_TAG 69a8f7446a377751d6e1d5be33ba77521eda84cc + GIT_TAG develop ) # ======================================================================