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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
85 changes: 85 additions & 0 deletions apps/test_dmini/test_dmini.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions docs/dmini.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
54 changes: 54 additions & 0 deletions include/dmini.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
100 changes: 100 additions & 0 deletions src/dmini.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down