Updated API and made it faster, and suports more features

This commit is contained in:
OusmBlueNinja 2025-04-09 11:29:19 -05:00
parent 0ad6954f92
commit 2e2c2afe7a
5 changed files with 660 additions and 543 deletions

BIN
a.exe

Binary file not shown.

View File

@ -1,31 +1,198 @@
#define CYAML_IMPLEMENTATION
#include "../include/cyaml.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define YAML_IMPLEMENTATION
#include "../include/cyaml.h"
// Define a simple User struct.
typedef struct {
int id;
char name[100];
char email[100];
} User;
#define MAX_USERS 100
User users[MAX_USERS];
size_t user_count = 0;
// Display the list of users.
void list_users(void) {
printf("\n-- User Database --\n");
if (user_count == 0) {
printf("No users found.\n");
return;
}
for (size_t i = 0; i < user_count; i++) {
printf("ID %d: %s, %s\n", users[i].id, users[i].name, users[i].email);
}
}
// Save the in-memory user database to a YAML file.
// The output YAML file will have a mapping with the key "users"
// which contains a sequence of mappings (each representing a user).
int save_to_file(const char *filename) {
// Create a new document with a mapping as root.
YAMLDoc *doc = (YAMLDoc*)malloc(sizeof(YAMLDoc));
if (!doc) return -1;
doc->root = yaml_new_map();
if (!doc->root) {
free(doc);
return -1;
}
// Create a sequence to hold all user mappings.
YAMLNode *userSeq = yaml_new_seq();
if (!userSeq) {
yaml_doc_destroy(doc);
return -1;
}
// For each user, create a mapping and add it to the sequence.
for (size_t i = 0; i < user_count; i++) {
YAMLNode *userMap = yaml_new_map();
char idStr[20];
snprintf(idStr, sizeof(idStr), "%d", users[i].id);
yaml_map_set(userMap, "id", yaml_new_str(idStr));
yaml_map_set(userMap, "name", yaml_new_str(users[i].name));
yaml_map_set(userMap, "email", yaml_new_str(users[i].email));
yaml_seq_append(userSeq, userMap);
}
yaml_map_set(doc->root, "users", userSeq);
// Create an emitter and write the document to the file.
YAMLEmitter *emitter = yaml_emitter_create();
if (!emitter) {
yaml_doc_destroy(doc);
return -1;
}
if (yaml_emit(emitter, doc) != 0) {
yaml_emitter_destroy(emitter);
yaml_doc_destroy(doc);
return -1;
}
int ret = yaml_emit_to_file(emitter, filename);
// Clean up.
yaml_emitter_destroy(emitter);
yaml_doc_destroy(doc);
return ret;
}
// Add a new user via CLI.
void add_user(void) {
if (user_count >= MAX_USERS) {
printf("User database is full.\n");
return;
}
User newUser;
newUser.id = user_count + 1; // Simple sequential id assignment.
printf("Enter user name: ");
if (!fgets(newUser.name, sizeof(newUser.name), stdin)) {
printf("Error reading name.\n");
return;
}
newUser.name[strcspn(newUser.name, "\n")] = '\0'; // Remove newline.
printf("Enter user email: ");
if (!fgets(newUser.email, sizeof(newUser.email), stdin)) {
printf("Error reading email.\n");
return;
}
newUser.email[strcspn(newUser.email, "\n")] = '\0';
users[user_count++] = newUser;
printf("User added successfully.\n");
}
// Edit an existing user via CLI.
void edit_user(void) {
list_users();
printf("Enter the user ID to edit: ");
int id;
if (scanf("%d", &id) != 1) {
printf("Invalid input.\n");
while(getchar() != '\n'); // Flush input buffer.
return;
}
while(getchar() != '\n'); // Consume newline.
int found = 0;
for (size_t i = 0; i < user_count; i++) {
if (users[i].id == id) {
found = 1;
printf("Editing user (ID %d): %s, %s\n", users[i].id, users[i].name, users[i].email);
printf("Enter new name (leave empty to keep unchanged): ");
char newName[100];
if (fgets(newName, sizeof(newName), stdin)) {
newName[strcspn(newName, "\n")] = '\0';
if (strlen(newName) > 0)
strncpy(users[i].name, newName, sizeof(users[i].name));
}
printf("Enter new email (leave empty to keep unchanged): ");
char newEmail[100];
if (fgets(newEmail, sizeof(newEmail), stdin)) {
newEmail[strcspn(newEmail, "\n")] = '\0';
if (strlen(newEmail) > 0)
strncpy(users[i].email, newEmail, sizeof(users[i].email));
}
printf("User updated successfully.\n");
break;
}
}
if (!found)
printf("User with ID %d not found.\n", id);
}
int main(void) {
// Load a YAML document from a string.
cyaml_document_t *doc = cyaml_load_string("example: Hello, cyaml!");
if (!doc) {
fprintf(stderr, "Failed to load YAML.\n");
return 1;
int running = 1;
while (running) {
printf("\n--- User Database CLI ---\n");
printf("1. List users\n");
printf("2. Add user\n");
printf("3. Edit user\n");
printf("4. Save database to file\n");
printf("5. Exit\n");
printf("Enter choice: ");
int choice;
if (scanf("%d", &choice) != 1) {
printf("Invalid input. Please enter a number.\n");
while(getchar() != '\n'); // Clear the invalid input.
continue;
}
while(getchar() != '\n'); // Consume newline.
switch (choice) {
case 1:
list_users();
break;
case 2:
add_user();
break;
case 3:
edit_user();
break;
case 4: {
char filename[256];
printf("Enter filename to save the database: ");
if (fgets(filename, sizeof(filename), stdin)) {
filename[strcspn(filename, "\n")] = '\0';
if (save_to_file(filename) == 0)
printf("Database saved successfully to '%s'.\n", filename);
else
printf("Error saving database to '%s'.\n", filename);
}
break;
}
case 5:
running = 0;
break;
default:
printf("Invalid choice. Please try again.\n");
}
}
// Get and print the root node's scalar value.
const cyaml_node_t *root = cyaml_document_get_root(doc);
if (root && cyaml_node_get_type(root) == CYAML_NODE_SCALAR) {
printf("YAML Content: %s\n", cyaml_node_as_string(root));
}
// Create an emitter and output the document.
cyaml_emitter_t *emitter = cyaml_emitter_create();
if (cyaml_emitter_emit(emitter, doc) == 0) {
char *output = cyaml_emitter_to_string(emitter);
printf("Emitted YAML:\n%s\n", output);
free(output);
}
// Cleanup.
cyaml_emitter_destroy(emitter);
cyaml_document_destroy(doc);
return 0;
}

View File

@ -2,363 +2,110 @@
#include <stdlib.h>
#include <string.h>
#define CYAML_IMPLEMENTATION
#include "../include/cyaml.h" // Adjust this path as needed
#define YAML_IMPLEMENTATION
#include "../include/cyaml.h"
/* ANSI color definitions for pretty terminal output */
#define COLOR_RED "\033[31m"
#define COLOR_GREEN "\033[32m"
#define COLOR_YELLOW "\033[33m"
#define COLOR_RESET "\033[0m"
/* Global counters for tests */
static int tests_passed = 0;
static int tests_run = 0;
/* Test assertion macro */
#define TEST_ASSERT(cond, msg) do { \
tests_run++; \
if (!(cond)) { \
printf(COLOR_RED "[FAIL]" COLOR_RESET " %s\n", msg); \
} else { \
tests_passed++; \
printf(COLOR_GREEN "[PASS]" COLOR_RESET " %s\n", msg); \
} \
} while(0)
/*---------------------------------------------------------
* Utility Functions to Clean Up Manually Created Nodes
* (These are needed because our cyaml library currently
* only auto-frees scalar nodes from cyaml_load_* functions.)
*---------------------------------------------------------*/
static void free_mapping_node(cyaml_node_t *node) {
if (node->keys) {
for (size_t i = 0; i < node->mapping_size; i++) {
free(node->keys[i]);
}
free(node->keys);
}
if (node->values) {
for (size_t i = 0; i < node->mapping_size; i++) {
if (node->values[i]) {
if (node->values[i]->scalar)
free(node->values[i]->scalar);
free(node->values[i]);
}
}
free(node->values);
}
free(node);
}
static void free_sequence_node(cyaml_node_t *node) {
if (node->sequence) {
for (size_t i = 0; i < node->sequence_size; i++) {
if (node->sequence[i]) {
if (node->sequence[i]->scalar)
free(node->sequence[i]->scalar);
free(node->sequence[i]);
}
}
free(node->sequence);
}
free(node);
}
/*---------------------------------------------------------
* Test 1: Load YAML String (Scalar)
*---------------------------------------------------------*/
void test_load_string() {
const char *yaml = "Hello, cyaml!";
cyaml_document_t *doc = cyaml_load_string(yaml);
TEST_ASSERT(doc != NULL, "cyaml_load_string should not return NULL");
if (doc) {
const cyaml_node_t *root = cyaml_document_get_root(doc);
TEST_ASSERT(root != NULL, "Root node should not be NULL");
if (root) {
const char *str = cyaml_node_as_string(root);
TEST_ASSERT(str != NULL, "Root node should be a scalar string");
if (str) {
TEST_ASSERT(strcmp(str, yaml) == 0, "Scalar value should match input string");
}
}
cyaml_document_destroy(doc);
}
}
/*---------------------------------------------------------
* Test 2: Emitter for Scalar YAML
*---------------------------------------------------------*/
void test_emitter_scalar() {
const char *yaml = "Emitter Test!";
cyaml_document_t *doc = cyaml_load_string(yaml);
TEST_ASSERT(doc != NULL, "cyaml_load_string for emitter test should not return NULL");
if (doc) {
cyaml_emitter_t *emitter = cyaml_emitter_create();
TEST_ASSERT(emitter != NULL, "cyaml_emitter_create should not return NULL");
if (emitter) {
int emit_result = cyaml_emitter_emit(emitter, doc);
TEST_ASSERT(emit_result == 0, "cyaml_emitter_emit should succeed");
char *emitted_str = cyaml_emitter_to_string(emitter);
TEST_ASSERT(emitted_str != NULL, "cyaml_emitter_to_string should not return NULL");
if (emitted_str) {
TEST_ASSERT(strcmp(emitted_str, yaml) == 0, "Emitted YAML should match original scalar");
free(emitted_str);
}
cyaml_emitter_destroy(emitter);
}
cyaml_document_destroy(doc);
}
}
/*---------------------------------------------------------
* Test 3: Load YAML from a File (Scalar)
*---------------------------------------------------------*/
void test_load_file() {
const char *filename = "test.yaml";
const char *yaml = "File Test!";
/* Create a temporary file with YAML content */
FILE *file = fopen(filename, "w");
if (!file) {
printf(COLOR_YELLOW "Warning: Could not create temporary test file.\n" COLOR_RESET);
return;
}
fputs(yaml, file);
fclose(file);
cyaml_document_t *doc = cyaml_load_file(filename);
TEST_ASSERT(doc != NULL, "cyaml_load_file should not return NULL");
if (doc) {
const cyaml_node_t *root = cyaml_document_get_root(doc);
TEST_ASSERT(root != NULL, "Root node from file should not be NULL");
if (root) {
const char *str = cyaml_node_as_string(root);
TEST_ASSERT(str != NULL, "Root node from file should be a scalar string");
if (str) {
TEST_ASSERT(strcmp(str, yaml) == 0, "File content should match expected YAML");
}
}
cyaml_document_destroy(doc);
}
/* Remove the temporary file */
remove(filename);
}
/*---------------------------------------------------------
* Test 4: Numeric Conversions
*---------------------------------------------------------*/
void test_numeric_conversion() {
// Integer conversion test
const char *int_str = "12345";
cyaml_document_t *doc_int = cyaml_load_string(int_str);
TEST_ASSERT(doc_int != NULL, "Load integer string for conversion");
if (doc_int) {
const cyaml_node_t *node = cyaml_document_get_root(doc_int);
int value = cyaml_node_as_int(node);
TEST_ASSERT(value == 12345, "Integer conversion: value should be 12345");
cyaml_document_destroy(doc_int);
}
// Double conversion test
const char *double_str = "3.14159";
cyaml_document_t *doc_double = cyaml_load_string(double_str);
TEST_ASSERT(doc_double != NULL, "Load double string for conversion");
if (doc_double) {
const cyaml_node_t *node = cyaml_document_get_root(doc_double);
double dvalue = cyaml_node_as_double(node);
TEST_ASSERT(dvalue > 3.14158 && dvalue < 3.14160, "Double conversion: value should be approx 3.14159");
cyaml_document_destroy(doc_double);
}
}
/*---------------------------------------------------------
* Test 5: Mapping Node Functionality
*---------------------------------------------------------*/
void test_mapping_node() {
// Manually create a mapping node document
cyaml_document_t *doc = (cyaml_document_t*)malloc(sizeof(cyaml_document_t));
doc->root = (cyaml_node_t*)malloc(sizeof(cyaml_node_t));
cyaml_node_t *map = doc->root;
map->type = CYAML_NODE_MAPPING;
map->mapping_size = 3;
map->keys = (char**)malloc(sizeof(char*) * 3);
map->values = (cyaml_node_t**)malloc(sizeof(cyaml_node_t*) * 3);
// Key-value pair 1: "name": "cyaml"
map->keys[0] = strdup("name");
map->values[0] = (cyaml_node_t*)malloc(sizeof(cyaml_node_t));
map->values[0]->type = CYAML_NODE_SCALAR;
map->values[0]->scalar = strdup("cyaml");
// Key-value pair 2: "version": "1.0"
map->keys[1] = strdup("version");
map->values[1] = (cyaml_node_t*)malloc(sizeof(cyaml_node_t));
map->values[1]->type = CYAML_NODE_SCALAR;
map->values[1]->scalar = strdup("1.0");
// Key-value pair 3: "count": "42"
map->keys[2] = strdup("count");
map->values[2] = (cyaml_node_t*)malloc(sizeof(cyaml_node_t));
map->values[2]->type = CYAML_NODE_SCALAR;
map->values[2]->scalar = strdup("42");
TEST_ASSERT(cyaml_node_size(map) == 3, "Mapping node size should be 3");
const cyaml_node_t *node_name = cyaml_node_get(map, "name");
TEST_ASSERT(node_name != NULL, "Mapping node: key 'name' should exist");
if (node_name) {
TEST_ASSERT(strcmp(cyaml_node_as_string(node_name), "cyaml") == 0, "Mapping node: 'name' value should be 'cyaml'");
}
const cyaml_node_t *node_version = cyaml_node_get(map, "version");
TEST_ASSERT(node_version != NULL, "Mapping node: key 'version' should exist");
if (node_version) {
TEST_ASSERT(strcmp(cyaml_node_as_string(node_version), "1.0") == 0, "Mapping node: 'version' value should be '1.0'");
}
const cyaml_node_t *node_count = cyaml_node_get(map, "count");
TEST_ASSERT(node_count != NULL, "Mapping node: key 'count' should exist");
if (node_count) {
int count = cyaml_node_as_int(node_count);
TEST_ASSERT(count == 42, "Mapping node: 'count' value should be 42");
}
// Clean up manually created mapping node
free_mapping_node(map);
free(doc);
}
/*---------------------------------------------------------
* Test 6: Sequence Node Functionality
*---------------------------------------------------------*/
void test_sequence_node() {
// Manually create a sequence node document
cyaml_document_t *doc = (cyaml_document_t*)malloc(sizeof(cyaml_document_t));
doc->root = (cyaml_node_t*)malloc(sizeof(cyaml_node_t));
cyaml_node_t *seq = doc->root;
seq->type = CYAML_NODE_SEQUENCE;
seq->sequence_size = 4;
seq->sequence = (cyaml_node_t**)malloc(sizeof(cyaml_node_t*) * 4);
const char *items[] = { "one", "two", "three", "four" };
for (size_t i = 0; i < 4; i++) {
seq->sequence[i] = (cyaml_node_t*)malloc(sizeof(cyaml_node_t));
seq->sequence[i]->type = CYAML_NODE_SCALAR;
seq->sequence[i]->scalar = strdup(items[i]);
}
TEST_ASSERT(cyaml_node_size(seq) == 4, "Sequence node size should be 4");
for (size_t i = 0; i < 4; i++) {
const cyaml_node_t *item = cyaml_node_index(seq, i);
char msg[128];
snprintf(msg, sizeof(msg), "Sequence node: item %zu should not be NULL", i);
TEST_ASSERT(item != NULL, msg);
if (item) {
snprintf(msg, sizeof(msg), "Sequence node: item %zu should equal '%s'", i, items[i]);
TEST_ASSERT(strcmp(cyaml_node_as_string(item), items[i]) == 0, msg);
}
}
// Clean up manually created sequence node
free_sequence_node(seq);
free(doc);
}
/*---------------------------------------------------------
* Test 7: Bulk YAML String Loading (100 iterations)
*---------------------------------------------------------*/
void test_bulk_load_string() {
for (int i = 0; i < 100; i++) {
char buffer[64];
snprintf(buffer, sizeof(buffer), "Bulk test %d", i);
cyaml_document_t *doc = cyaml_load_string(buffer);
char test_msg[128];
snprintf(test_msg, sizeof(test_msg), "Bulk load string test %d: document should not be NULL", i);
TEST_ASSERT(doc != NULL, test_msg);
if (doc) {
const cyaml_node_t *root = cyaml_document_get_root(doc);
snprintf(test_msg, sizeof(test_msg), "Bulk load string test %d: root should not be NULL", i);
TEST_ASSERT(root != NULL, test_msg);
if (root) {
snprintf(test_msg, sizeof(test_msg), "Bulk load string test %d: value should match", i);
TEST_ASSERT(strcmp(cyaml_node_as_string(root), buffer) == 0, test_msg);
}
cyaml_document_destroy(doc);
}
}
}
/*---------------------------------------------------------
* Test 8: Bulk File Read/Write (20 iterations)
*---------------------------------------------------------*/
void test_bulk_file_rw() {
for (int i = 0; i < 20; i++) {
char filename[64];
char content[64];
snprintf(filename, sizeof(filename), "temp_test_%d.yaml", i);
snprintf(content, sizeof(content), "File bulk test %d", i);
/* Write to file */
FILE *file = fopen(filename, "w");
if (!file) {
printf(COLOR_YELLOW "Warning: Could not create temporary file %s.\n" COLOR_RESET, filename);
continue;
}
fputs(content, file);
fclose(file);
/* Read file and validate */
cyaml_document_t *doc = cyaml_load_file(filename);
char test_msg[128];
snprintf(test_msg, sizeof(test_msg), "Bulk file test %d: document should not be NULL", i);
TEST_ASSERT(doc != NULL, test_msg);
if (doc) {
const cyaml_node_t *root = cyaml_document_get_root(doc);
snprintf(test_msg, sizeof(test_msg), "Bulk file test %d: root should not be NULL", i);
TEST_ASSERT(root != NULL, test_msg);
if (root) {
snprintf(test_msg, sizeof(test_msg), "Bulk file test %d: content should match", i);
TEST_ASSERT(strcmp(cyaml_node_as_string(root), content) == 0, test_msg);
}
cyaml_document_destroy(doc);
}
/* Remove the temporary file */
remove(filename);
}
}
/*---------------------------------------------------------
* Main Function: Run All Tests and Display Summary
*---------------------------------------------------------*/
int main(void) {
printf("========================================\n");
printf(" " COLOR_YELLOW "cyaml Unit Test Suite" COLOR_RESET "\n");
printf("========================================\n\n");
int fail_count = 0;
// --- Unit Test 1: Building and Emitting a YAML Document ---
printf("Unit Test 1: Document creation and emission\n");
// Create a new document with a mapping as the root.
YAMLDoc *doc = (YAMLDoc*)malloc(sizeof(YAMLDoc));
if (!doc) {
fprintf(stderr, "Memory allocation failure.\n");
return EXIT_FAILURE;
}
doc->root = yaml_new_map();
if (!doc->root) {
fprintf(stderr, "Failed to create root node.\n");
free(doc);
return EXIT_FAILURE;
}
// Set "name" to a scalar value.
if (yaml_map_set(doc->root, "name", yaml_new_str("Alice")) != 0) {
fprintf(stderr, "Error setting key 'name'.\n");
yaml_doc_destroy(doc);
return EXIT_FAILURE;
}
// Create a sequence for "skills".
YAMLNode *skills = yaml_new_seq();
if (!skills) {
fprintf(stderr, "Failed to create skills sequence.\n");
yaml_doc_destroy(doc);
return EXIT_FAILURE;
}
yaml_seq_append(skills, yaml_new_str("C"));
yaml_seq_append(skills, yaml_new_str("Python"));
if (yaml_map_set(doc->root, "skills", skills) != 0) {
fprintf(stderr, "Error setting key 'skills'.\n");
yaml_doc_destroy(doc);
return EXIT_FAILURE;
}
// Emit the document.
YAMLEmitter *emitter = yaml_emitter_create();
if (!emitter) {
fprintf(stderr, "Failed to create emitter.\n");
yaml_doc_destroy(doc);
return EXIT_FAILURE;
}
if (yaml_emit(emitter, doc) != 0) {
fprintf(stderr, "Error emitting YAML document.\n");
yaml_emitter_destroy(emitter);
yaml_doc_destroy(doc);
return EXIT_FAILURE;
}
char *output = yaml_emit_to_str(emitter);
if (!output) {
fprintf(stderr, "Failed to retrieve emitter string.\n");
yaml_emitter_destroy(emitter);
yaml_doc_destroy(doc);
return EXIT_FAILURE;
}
printf("Emitted YAML:\n%s\n", output);
// Check expected content.
if (strstr(output, "Alice") == NULL ||
strstr(output, "skills") == NULL ||
strstr(output, "- Python") == NULL)
{
printf("Unit Test 1 Failed: Emitted YAML output is missing expected content.\n");
fail_count++;
} else {
printf("Unit Test 1 Passed.\n");
}
test_load_string();
test_emitter_scalar();
test_load_file();
test_numeric_conversion();
test_mapping_node();
test_sequence_node();
test_bulk_load_string();
test_bulk_file_rw();
printf("\n========================================\n");
printf("Test Summary:\n");
printf("Total tests run: %d\n", tests_run);
printf(COLOR_GREEN "Passed: %d" COLOR_RESET "\n", tests_passed);
printf(COLOR_RED "Failed: %d" COLOR_RESET "\n", tests_run - tests_passed);
printf("========================================\n");
return (tests_run - tests_passed) == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
free(output);
yaml_emitter_destroy(emitter);
yaml_doc_destroy(doc);
// --- Unit Test 2: Loading a YAML Document from a String ---
printf("\nUnit Test 2: YAML load from string\n");
const char *yaml_input = "Hello YAML!\nThis is a test.";
YAMLDoc *doc2 = yaml_load_str(yaml_input);
if (!doc2) {
fprintf(stderr, "Error loading YAML from string.\n");
return EXIT_FAILURE;
}
const YAMLNode *root2 = yaml_doc_root(doc2);
if (!root2 || strcmp(yaml_as_str(root2), yaml_input) != 0) {
printf("Unit Test 2 Failed: Loaded YAML does not match input string.\n");
fail_count++;
} else {
printf("Unit Test 2 Passed.\n");
}
yaml_doc_destroy(doc2);
// --- Finalize Unit Tests ---
if (fail_count == 0) {
printf("\nAll unit tests passed successfully.\n");
return EXIT_SUCCESS;
} else {
printf("\n%d unit test(s) failed.\n", fail_count);
return EXIT_FAILURE;
}
}

View File

@ -1,5 +1,5 @@
#ifndef CYAML_H
#define CYAML_H
#ifndef YAML_H
#define YAML_H
#ifdef __cplusplus
extern "C" {
@ -7,124 +7,164 @@ extern "C" {
#include <stddef.h>
/* --- API Declarations --- */
// Forward declarations for opaque types.
typedef struct cyaml_node_t cyaml_node_t;
typedef struct cyaml_document_t cyaml_document_t;
typedef struct cyaml_emitter_t cyaml_emitter_t;
/* --- Public Types --- */
// Node types.
typedef enum {
CYAML_NODE_UNDEFINED,
CYAML_NODE_SCALAR,
CYAML_NODE_SEQUENCE,
CYAML_NODE_MAPPING
} cyaml_node_type_t;
YAML_UNDEF, // Undefined
YAML_STR, // Scalar string
YAML_SEQ, // Sequence (list)
YAML_MAP // Mapping (dictionary)
} YAMLType;
/* Document Loading and Parsing */
// Opaque types.
typedef struct YAMLNode YAMLNode;
typedef struct YAMLDoc YAMLDoc;
typedef struct YAMLEmitter YAMLEmitter;
// Load YAML document from a file.
// Returns a pointer to a cyaml_document_t or NULL on error.
cyaml_document_t* cyaml_load_file(const char *filename);
/* --- Document Loading and Parsing --- */
// Load YAML document from a string.
// Returns a pointer to a cyaml_document_t or NULL on error.
cyaml_document_t* cyaml_load_string(const char *yaml_string);
// Load a YAML document from a file. For demonstration, the file content is treated as a scalar.
YAMLDoc* yaml_load_file(const char *filename);
// Free the document and all associated nodes.
void cyaml_document_destroy(cyaml_document_t *doc);
// Load a YAML document from a string. (Currently, the entire string is parsed as a scalar.)
YAMLDoc* yaml_load_str(const char *yaml_string);
// Destroy the document and free all associated memory.
void yaml_doc_destroy(YAMLDoc *doc);
// Get the root node of the document.
const cyaml_node_t* cyaml_document_get_root(const cyaml_document_t *doc);
const YAMLNode* yaml_doc_root(const YAMLDoc *doc);
/* Node Querying and Access */
/* --- Node Querying and Access --- */
// Get the type of a node.
cyaml_node_type_t cyaml_node_get_type(const cyaml_node_t *node);
// Get the type of a YAML node.
YAMLType yaml_node_type(const YAMLNode *node);
// For scalar nodes: retrieve as a string. Returns NULL if not convertible.
const char* cyaml_node_as_string(const cyaml_node_t *node);
// Retrieve the scalar value of a node (if it is a scalar), or NULL.
const char* yaml_as_str(const YAMLNode *node);
// For scalar nodes: retrieve as an integer.
int cyaml_node_as_int(const cyaml_node_t *node);
// Convert a scalar node to int. Returns 0 if not a scalar.
int yaml_as_int(const YAMLNode *node);
// For scalar nodes: retrieve as a double.
double cyaml_node_as_double(const cyaml_node_t *node);
// Convert a scalar node to double. Returns 0.0 if not a scalar.
double yaml_as_double(const YAMLNode *node);
// For mapping nodes: retrieve a child node by key. Returns NULL if not found.
const cyaml_node_t* cyaml_node_get(const cyaml_node_t *node, const char *key);
// For mapping nodes: get a child node by key (returns NULL if not found).
const YAMLNode* yaml_map_get(const YAMLNode *node, const char *key);
// For sequence nodes: return the number of child nodes.
size_t cyaml_node_size(const cyaml_node_t *node);
size_t yaml_seq_size(const YAMLNode *node);
// For sequence nodes: retrieve a child node by index.
const cyaml_node_t* cyaml_node_index(const cyaml_node_t *node, size_t index);
// For sequence nodes: retrieve a child node by index (returns NULL if out of range).
const YAMLNode* yaml_seq_index(const YAMLNode *node, size_t index);
/* Emitting YAML */
/* --- Node Creation and Modification --- */
// Create an emitter object.
cyaml_emitter_t* cyaml_emitter_create(void);
// Create a new scalar node with the given string value.
YAMLNode* yaml_new_str(const char *value);
// Emit a document into the emitter's internal buffer.
int cyaml_emitter_emit(cyaml_emitter_t *emitter, const cyaml_document_t *doc);
// Create a new, empty sequence node.
YAMLNode* yaml_new_seq(void);
// Write the emitter's contents to a file.
// Returns 0 on success, non-zero on error.
int cyaml_emitter_to_file(cyaml_emitter_t *emitter, const char *filename);
// Create a new, empty mapping node.
YAMLNode* yaml_new_map(void);
// Retrieve a string copy of the emitter's contents.
// The caller is responsible for freeing the returned string.
char* cyaml_emitter_to_string(cyaml_emitter_t *emitter);
// Append a child node to a sequence node.
int yaml_seq_append(YAMLNode *node, YAMLNode *child);
// Destroy the emitter and free associated memory.
void cyaml_emitter_destroy(cyaml_emitter_t *emitter);
// Set a key/value pair in a mapping node (the key is copied).
int yaml_map_set(YAMLNode *node, const char *key, YAMLNode *child);
// Recursively free a YAML node and its children.
void yaml_node_destroy(YAMLNode *node);
/* --- YAML Emitting --- */
// Create a new YAML emitter.
YAMLEmitter* yaml_emitter_create(void);
// Emit the YAML document into the emitter's buffer.
int yaml_emit(YAMLEmitter *emitter, const YAMLDoc *doc);
// Write the emitter's content to a file (returns 0 on success).
int yaml_emit_to_file(YAMLEmitter *emitter, const char *filename);
// Get a string copy of the emitter's buffer (caller must free it).
char* yaml_emit_to_str(YAMLEmitter *emitter);
// Destroy the emitter and free its memory.
void yaml_emitter_destroy(YAMLEmitter *emitter);
#ifdef __cplusplus
}
#endif
/* --- Implementation --- */
/* To include the implementation, define CYAML_IMPLEMENTATION in *one* source file before including "cyaml.h". */
/* To compile the implementation, define YAML_IMPLEMENTATION in one source file before including "yaml.h". */
#ifdef CYAML_IMPLEMENTATION
#ifdef YAML_IMPLEMENTATION
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Internal structures */
/* --- Internal Structures --- */
struct cyaml_document_t {
cyaml_node_t *root;
struct YAMLDoc {
YAMLNode *root;
};
struct cyaml_node_t {
cyaml_node_type_t type;
char *scalar;
// For sequence nodes.
cyaml_node_t **sequence;
size_t sequence_size;
// For mapping nodes.
struct YAMLNode {
YAMLType type;
// For scalar nodes:
char *str;
// For sequence nodes:
YAMLNode **seq;
size_t seq_size;
// For mapping nodes:
char **keys;
cyaml_node_t **values;
size_t mapping_size;
YAMLNode **vals;
size_t map_size;
};
struct cyaml_emitter_t {
struct YAMLEmitter {
char *buffer;
size_t size;
size_t capacity;
};
#define CYAML_EMITTER_INITIAL_CAPACITY 256
#define YAMLEMIT_INITIAL_CAPACITY 256
/* --- Document Loading and Parsing --- */
// Loads the entire file into a buffer and then calls cyaml_load_string().
cyaml_document_t* cyaml_load_file(const char *filename) {
// For demonstration purposes, our "parser" treats the entire input as a scalar node.
// In a full implementation, you would integrate a complete YAML parser.
YAMLDoc* yaml_load_str(const char *yaml_string) {
if (!yaml_string)
return NULL;
YAMLDoc *doc = (YAMLDoc*)malloc(sizeof(YAMLDoc));
if (!doc)
return NULL;
doc->root = (YAMLNode*)malloc(sizeof(YAMLNode));
if (!doc->root) {
free(doc);
return NULL;
}
doc->root->type = YAML_STR;
doc->root->str = strdup(yaml_string);
doc->root->seq = NULL;
doc->root->seq_size = 0;
doc->root->keys = NULL;
doc->root->vals = NULL;
doc->root->map_size = 0;
return doc;
}
YAMLDoc* yaml_load_file(const char *filename) {
FILE *file = fopen(filename, "rb");
if (!file) return NULL;
if (!file)
return NULL;
fseek(file, 0, SEEK_END);
long length = ftell(file);
rewind(file);
@ -136,160 +176,323 @@ cyaml_document_t* cyaml_load_file(const char *filename) {
fread(content, 1, length, file);
content[length] = '\0';
fclose(file);
cyaml_document_t *doc = cyaml_load_string(content);
YAMLDoc *doc = yaml_load_str(content);
free(content);
return doc;
}
// For demonstration, cyaml_load_string() creates a document with a single scalar node.
cyaml_document_t* cyaml_load_string(const char *yaml_string) {
cyaml_document_t *doc = (cyaml_document_t*)malloc(sizeof(cyaml_document_t));
if (!doc) return NULL;
doc->root = (cyaml_node_t*)malloc(sizeof(cyaml_node_t));
if (!doc->root) {
free(doc);
return NULL;
}
doc->root->type = CYAML_NODE_SCALAR;
doc->root->scalar = strdup(yaml_string);
doc->root->sequence = NULL;
doc->root->sequence_size = 0;
doc->root->keys = NULL;
doc->root->values = NULL;
doc->root->mapping_size = 0;
return doc;
}
void cyaml_document_destroy(cyaml_document_t *doc) {
if (!doc) return;
// For this demonstration, assume only a scalar root node exists.
if (doc->root) {
if (doc->root->scalar)
free(doc->root->scalar);
free(doc->root);
}
void yaml_doc_destroy(YAMLDoc *doc) {
if (!doc)
return;
if (doc->root)
yaml_node_destroy(doc->root);
free(doc);
}
const cyaml_node_t* cyaml_document_get_root(const cyaml_document_t *doc) {
if (!doc) return NULL;
const YAMLNode* yaml_doc_root(const YAMLDoc *doc) {
if (!doc)
return NULL;
return doc->root;
}
/* --- Node Querying and Access --- */
cyaml_node_type_t cyaml_node_get_type(const cyaml_node_t *node) {
if (!node) return CYAML_NODE_UNDEFINED;
YAMLType yaml_node_type(const YAMLNode *node) {
if (!node)
return YAML_UNDEF;
return node->type;
}
const char* cyaml_node_as_string(const cyaml_node_t *node) {
if (!node || node->type != CYAML_NODE_SCALAR) return NULL;
return node->scalar;
const char* yaml_as_str(const YAMLNode *node) {
if (!node || node->type != YAML_STR)
return NULL;
return node->str;
}
int cyaml_node_as_int(const cyaml_node_t *node) {
if (!node || node->type != CYAML_NODE_SCALAR) return 0;
return atoi(node->scalar);
int yaml_as_int(const YAMLNode *node) {
if (!node || node->type != YAML_STR)
return 0;
return atoi(node->str);
}
double cyaml_node_as_double(const cyaml_node_t *node) {
if (!node || node->type != CYAML_NODE_SCALAR) return 0.0;
return atof(node->scalar);
double yaml_as_double(const YAMLNode *node) {
if (!node || node->type != YAML_STR)
return 0.0;
return atof(node->str);
}
// Dummy mapping lookup: searches keys in a mapping node.
const cyaml_node_t* cyaml_node_get(const cyaml_node_t *node, const char *key) {
if (!node || node->type != CYAML_NODE_MAPPING) return NULL;
for (size_t i = 0; i < node->mapping_size; i++) {
if (strcmp(node->keys[i], key) == 0) {
return node->values[i];
}
const YAMLNode* yaml_map_get(const YAMLNode *node, const char *key) {
if (!node || node->type != YAML_MAP)
return NULL;
for (size_t i = 0; i < node->map_size; i++) {
if (strcmp(node->keys[i], key) == 0)
return node->vals[i];
}
return NULL;
}
size_t cyaml_node_size(const cyaml_node_t *node) {
if (!node) return 0;
if (node->type == CYAML_NODE_SEQUENCE)
return node->sequence_size;
if (node->type == CYAML_NODE_MAPPING)
return node->mapping_size;
size_t yaml_seq_size(const YAMLNode *node) {
if (!node || node->type != YAML_SEQ)
return 0;
return node->seq_size;
}
const YAMLNode* yaml_seq_index(const YAMLNode *node, size_t index) {
if (!node || node->type != YAML_SEQ || index >= node->seq_size)
return NULL;
return node->seq[index];
}
/* --- Node Creation and Modification --- */
YAMLNode* yaml_new_str(const char *value) {
YAMLNode *node = (YAMLNode*)malloc(sizeof(YAMLNode));
if (!node)
return NULL;
node->type = YAML_STR;
node->str = strdup(value ? value : "");
node->seq = NULL;
node->seq_size = 0;
node->keys = NULL;
node->vals = NULL;
node->map_size = 0;
return node;
}
YAMLNode* yaml_new_seq(void) {
YAMLNode *node = (YAMLNode*)malloc(sizeof(YAMLNode));
if (!node)
return NULL;
node->type = YAML_SEQ;
node->str = NULL;
node->seq = NULL;
node->seq_size = 0;
node->keys = NULL;
node->vals = NULL;
node->map_size = 0;
return node;
}
YAMLNode* yaml_new_map(void) {
YAMLNode *node = (YAMLNode*)malloc(sizeof(YAMLNode));
if (!node)
return NULL;
node->type = YAML_MAP;
node->str = NULL;
node->seq = NULL;
node->seq_size = 0;
node->keys = NULL;
node->vals = NULL;
node->map_size = 0;
return node;
}
int yaml_seq_append(YAMLNode *node, YAMLNode *child) {
if (!node || node->type != YAML_SEQ)
return -1;
YAMLNode **new_seq = (YAMLNode**)realloc(node->seq, sizeof(YAMLNode*) * (node->seq_size + 1));
if (!new_seq)
return -1;
node->seq = new_seq;
node->seq[node->seq_size] = child;
node->seq_size++;
return 0;
}
const cyaml_node_t* cyaml_node_index(const cyaml_node_t *node, size_t index) {
if (!node) return NULL;
if (node->type == CYAML_NODE_SEQUENCE && index < node->sequence_size)
return node->sequence[index];
return NULL;
int yaml_map_set(YAMLNode *node, const char *key, YAMLNode *child) {
if (!node || node->type != YAML_MAP)
return -1;
// Append new key/value pair
char **new_keys = (char**)realloc(node->keys, sizeof(char*) * (node->map_size + 1));
if (!new_keys)
return -1;
node->keys = new_keys;
node->keys[node->map_size] = strdup(key);
YAMLNode **new_vals = (YAMLNode**)realloc(node->vals, sizeof(YAMLNode*) * (node->map_size + 1));
if (!new_vals)
return -1;
node->vals = new_vals;
node->vals[node->map_size] = child;
node->map_size++;
return 0;
}
void yaml_node_destroy(YAMLNode *node) {
if (!node)
return;
if (node->type == YAML_STR) {
free(node->str);
} else if (node->type == YAML_SEQ) {
for (size_t i = 0; i < node->seq_size; i++) {
yaml_node_destroy(node->seq[i]);
}
free(node->seq);
} else if (node->type == YAML_MAP) {
for (size_t i = 0; i < node->map_size; i++) {
free(node->keys[i]);
yaml_node_destroy(node->vals[i]);
}
free(node->keys);
free(node->vals);
}
free(node);
}
/* --- YAML Emitting --- */
cyaml_emitter_t* cyaml_emitter_create(void) {
cyaml_emitter_t *emitter = (cyaml_emitter_t*)malloc(sizeof(cyaml_emitter_t));
if (!emitter) return NULL;
emitter->buffer = (char*)malloc(CYAML_EMITTER_INITIAL_CAPACITY);
YAMLEmitter* yaml_emitter_create(void) {
YAMLEmitter *emitter = (YAMLEmitter*)malloc(sizeof(YAMLEmitter));
if (!emitter)
return NULL;
emitter->buffer = (char*)malloc(YAMLEMIT_INITIAL_CAPACITY);
if (!emitter->buffer) {
free(emitter);
return NULL;
}
emitter->buffer[0] = '\0';
emitter->size = 0;
emitter->capacity = CYAML_EMITTER_INITIAL_CAPACITY;
emitter->capacity = YAMLEMIT_INITIAL_CAPACITY;
return emitter;
}
// Internal helper to append text to the emitter's buffer.
static int cyaml_emitter_append(cyaml_emitter_t *emitter, const char *str) {
if (!emitter || !str) return -1;
// Internal function: append string to emitter's buffer.
static int yaml_emitter_append(YAMLEmitter *emitter, const char *str) {
if (!emitter || !str)
return -1;
size_t len = strlen(str);
if (emitter->size + len + 1 > emitter->capacity) {
size_t new_capacity = emitter->capacity * 2;
while (emitter->size + len + 1 > new_capacity) {
new_capacity *= 2;
}
char *new_buffer = (char*)realloc(emitter->buffer, new_capacity);
if (!new_buffer) return -1;
emitter->buffer = new_buffer;
emitter->capacity = new_capacity;
size_t new_cap = emitter->capacity * 2;
while (emitter->size + len + 1 > new_cap)
new_cap *= 2;
char *new_buf = (char*)realloc(emitter->buffer, new_cap);
if (!new_buf)
return -1;
emitter->buffer = new_buf;
emitter->capacity = new_cap;
}
memcpy(emitter->buffer + emitter->size, str, len + 1);
memcpy(emitter->buffer + emitter->size, str, len);
emitter->size += len;
emitter->buffer[emitter->size] = '\0';
return 0;
}
// A simple emitter that outputs the root scalar.
int cyaml_emitter_emit(cyaml_emitter_t *emitter, const cyaml_document_t *doc) {
if (!emitter || !doc) return -1;
if (doc->root && doc->root->type == CYAML_NODE_SCALAR) {
return cyaml_emitter_append(emitter, doc->root->scalar);
// Recursive function to emit a YAML node with proper indentation.
static int yaml_emit_node(YAMLEmitter *emitter, const YAMLNode *node, int indent) {
char indent_str[256];
if (indent < (int)sizeof(indent_str)) {
memset(indent_str, ' ', indent);
indent_str[indent] = '\0';
} else {
indent_str[0] = '\0';
}
return -1;
char line_buf[1024];
int ret;
if (!node)
return -1;
switch (node->type) {
case YAML_STR:
// Use block scalar style if the string contains newline(s).
if (strchr(node->str, '\n')) {
ret = yaml_emitter_append(emitter, "|-\n");
if (ret)
return ret;
char *copy = strdup(node->str);
char *line = strtok(copy, "\n");
while (line) {
snprintf(line_buf, sizeof(line_buf), "%s%s\n", indent_str, line);
ret = yaml_emitter_append(emitter, line_buf);
if (ret) {
free(copy);
return ret;
}
line = strtok(NULL, "\n");
}
free(copy);
} else {
snprintf(line_buf, sizeof(line_buf), "%s%s\n", indent_str, node->str);
ret = yaml_emitter_append(emitter, line_buf);
if (ret)
return ret;
}
break;
case YAML_SEQ:
for (size_t i = 0; i < node->seq_size; i++) {
snprintf(line_buf, sizeof(line_buf), "%s- ", indent_str);
ret = yaml_emitter_append(emitter, line_buf);
if (ret)
return ret;
if (node->seq[i]->type == YAML_STR) {
ret = yaml_emitter_append(emitter, node->seq[i]->str);
ret = yaml_emitter_append(emitter, "\n");
if (ret)
return ret;
} else {
ret = yaml_emitter_append(emitter, "\n");
if (ret)
return ret;
ret = yaml_emit_node(emitter, node->seq[i], indent + 2);
if (ret)
return ret;
}
}
break;
case YAML_MAP:
for (size_t i = 0; i < node->map_size; i++) {
snprintf(line_buf, sizeof(line_buf), "%s%s: ", indent_str, node->keys[i]);
ret = yaml_emitter_append(emitter, line_buf);
if (ret)
return ret;
if (node->vals[i]->type == YAML_STR) {
ret = yaml_emitter_append(emitter, node->vals[i]->str);
ret = yaml_emitter_append(emitter, "\n");
if (ret)
return ret;
} else {
ret = yaml_emitter_append(emitter, "\n");
if (ret)
return ret;
ret = yaml_emit_node(emitter, node->vals[i], indent + 2);
if (ret)
return ret;
}
}
break;
default:
break;
}
return 0;
}
int cyaml_emitter_to_file(cyaml_emitter_t *emitter, const char *filename) {
if (!emitter || !filename) return -1;
int yaml_emit(YAMLEmitter *emitter, const YAMLDoc *doc) {
if (!emitter || !doc || !doc->root)
return -1;
return yaml_emit_node(emitter, doc->root, 0);
}
int yaml_emit_to_file(YAMLEmitter *emitter, const char *filename) {
if (!emitter || !filename)
return -1;
FILE *file = fopen(filename, "w");
if (!file) return -1;
if (!file)
return -1;
fwrite(emitter->buffer, 1, emitter->size, file);
fclose(file);
return 0;
}
char* cyaml_emitter_to_string(cyaml_emitter_t *emitter) {
if (!emitter) return NULL;
char* yaml_emit_to_str(YAMLEmitter *emitter) {
if (!emitter)
return NULL;
return strdup(emitter->buffer);
}
void cyaml_emitter_destroy(cyaml_emitter_t *emitter) {
if (!emitter) return;
if (emitter->buffer)
free(emitter->buffer);
void yaml_emitter_destroy(YAMLEmitter *emitter) {
if (!emitter)
return;
free(emitter->buffer);
free(emitter);
}
#endif // CYAML_IMPLEMENTATION
#endif // YAML_IMPLEMENTATION
#endif // CYAML_H
#endif // YAML_H

0
users.yaml Normal file
View File