rdkcentral / ut-control

Control & KVP Module to support vDevice
Apache License 2.0
1 stars 0 forks source link

ut_control: support include marco in yaml parsing #10

Open Ulrond opened 1 month ago

Ulrond commented 1 month ago

While libfyaml itself doesn't directly support including other YAML files. We need to create a mechanism for including files.

Here's an approach that may work.

1. Manual Inclusion and Parsing:

2. Preprocessing:

3. Custom Tag Support:

Example (Manual Inclusion):

#include <yaml.h>
#include <stdio.h>
#include <string.h>

// ... (error handling and other code)

FILE *fh = fopen("main.yaml", "r");
yaml_parser_t parser;
yaml_event_t event;

// Parse main file, looking for !include tags

while (yaml_parser_parse(&parser, &event)) {
    if (event.type == YAML_SCALAR_EVENT && 
        !strcmp((char *)event.data.scalar.value, "!include")) {

        // Get filename from the next event
        yaml_event_delete(&event);
        if (!yaml_parser_parse(&parser, &event)) { /* handle error */ }
        char *filename = (char *)event.data.scalar.value;

        // Open and parse included file
        FILE *included_fh = fopen(filename, "r");
        yaml_parser_t included_parser;
        yaml_parser_initialize(&included_parser);
        yaml_parser_set_input_file(&included_parser, included_fh);

        // Process events from the included file
        while (yaml_parser_parse(&included_parser, &event)) {
            // ... process events
        }

        yaml_parser_delete(&included_parser);
        fclose(included_fh);
    } 
    // ... process other events
}

// ...
hdmicec:
 extendedfeatures: false

!include <URL>
!include <file:relativeToThisFile>
!include <file:myFile.yml>
myFeatures:
 enabled: true

!include <URL>
!include <file:relativeToThisFile>
UT_KVP_PROFILE_ASSERT_TRUE( "myFeatures.enabled" );
Ulrond commented 1 month ago

functionality to support reading from either a local file or a remote one, using the curl library ->

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>

// Function to get the size of a remote file
long get_remote_file_size(const char *url) {
    CURL *curl;
    CURLcode res;
    double filesize = 0.0;

    curl = curl_easy_init();
    if(curl) {
        curl_easy_setopt(curl, CURLOPT_URL, url);
        curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
        curl_easy_setopt(curl, CURLOPT_HEADER, 1L);
        curl_easy_setopt(curl, CURLOPT_FILETIME, 1L);

        res = curl_easy_perform(curl);
        if(res == CURLE_OK) {
            res = curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &filesize);
            if((res == CURLE_OK) && (filesize > 0.0)) {
                curl_easy_cleanup(curl);
                return (long)filesize;
            }
        }
        curl_easy_cleanup(curl);
    }
    return -1;
}

// Function to get the size of a local file
long get_local_file_size(const char *filename) {
    FILE *file = fopen(filename, "rb");
    if(file == NULL) {
        perror("Error opening file");
        return -1;
    }
    fseek(file, 0, SEEK_END);
    long filesize = ftell(file);
    fclose(file);
    return filesize;
}

// Callback function to write data into a buffer
size_t write_data(void *ptr, size_t size, size_t nmemb, void *data) {
    size_t total_size = size * nmemb;
    char **buffer = (char **)data;
    *buffer = realloc(*buffer, strlen(*buffer) + total_size + 1);
    strncat(*buffer, ptr, total_size);
    return total_size;
}

// Function to download a file using libcurl and store its content in a buffer
int download_file(const char *url, char **buffer) {
    CURL *curl;
    CURLcode res;

    *buffer = malloc(1);
    **buffer = '\0';

    curl = curl_easy_init();
    if(curl) {
        curl_easy_setopt(curl, CURLOPT_URL, url);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, buffer);
        res = curl_easy_perform(curl);
        curl_easy_cleanup(curl);

        if(res != CURLE_OK) {
            fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
            free(*buffer);
            return -1;
        }
        return 0;
    }
    free(*buffer);
    return -1;
}

// Function to read content from a local file into a buffer
int read_local_file(const char *filename, char **buffer) {
    long filesize = get_local_file_size(filename);
    if (filesize == -1) {
        return -1;
    }

    FILE *file = fopen(filename, "rb");
    if (file == NULL) {
        perror("Error opening file");
        return -1;
    }

    *buffer = malloc(filesize + 1);
    if (*buffer == NULL) {
        perror("Error allocating memory");
        fclose(file);
        return -1;
    }

    fread(*buffer, 1, filesize, file);
    (*buffer)[filesize] = '\0';  // Null-terminate the buffer

    fclose(file);
    return 0;
}

// Unified function to read from either a URL or a local file
int ut_read(const char *path, char **buffer) {
    // Check if the path is a URL (simple check for http:// or https://)
    if (strstr(path, "http://") || strstr(path, "https://")) {
        long filesize = get_remote_file_size(path);
        if (filesize == -1) {
            fprintf(stderr, "Failed to get remote file size.\n");
            return -1;
        }
        return download_file(path, buffer);
    } else {
        return read_local_file(path, buffer);
    }
}

int main(void) {
    const char *input = "https://example.com/file.txt"; // or "local_file.txt"
    char *buffer = NULL;

    if (ut_read(input, &buffer) == 0) {
        printf("File content:\n%s\n", buffer);
        free(buffer);  // Don't forget to free the allocated memory
    } else {
        printf("Failed to read file.\n");
    }

    return 0;
}
Ulrond commented 1 week ago

Recursive version

#include <libfyaml.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>

#define MAX_INCLUDE_DEPTH 10

struct MemoryStruct 
{
    char *memory;
    size_t size;
};

/* Function prototypes */
fy_node* process_include(fy_node *node, fy_parser *parser, int depth);
void merge_nodes(fy_node *dest, fy_node *src);

/* Processing functions */
size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
    size_t realsize = size * nmemb;
    struct MemoryStruct *mem = (struct MemoryStruct *)userp;

    char *ptr = realloc(mem->memory, mem->size + realsize + 1);
    if (!ptr) 
    {
        fprintf(stderr, "Error: Not enough memory (realloc returned NULL)\n");
        return 0;  
    }

    mem->memory = ptr;
    memcpy(&(mem->memory[mem->size]), contents, realsize);
    mem->size += realsize;
    mem->memory[mem->size] = 0; 
    return realsize;
}

fy_node* process_node(fy_node *node, fy_parser *parser, int depth) 
{
    if (!node) return NULL;

    if (fy_node_is_scalar(node)) 
    {
        fy_tag tag = fy_node_get_tag(node);
        if (strcmp(fy_tag_get_name(tag), "!include") == 0) 
        {
            fy_node *included = process_include(node, parser, depth);
            merge_nodes(node, included);
            fy_node_free(included); 

            // Remove the !include tag
            if (fy_node_is_mapping(node->parent)) 
            {
                fy_node *parent = node->parent;
                for (size_t i = 0; i < fy_node_get_mapping_pairs_length(parent); i++) 
                {
                    fy_node_pair pair = fy_node_get_mapping_pair_by_index(parent, i);
                    if (pair.value == node) 
                    {
                        fy_node_remove_mapping_pair_by_index(parent, i);
                        break; // Only remove the first match
                    }
                }
            }
        } 
    } else if (fy_node_is_sequence(node)) 
    {
        for (size_t i = 0; i < fy_node_get_sequence_items_length(node); i++) 
        {
            fy_node_set_sequence_item_by_index(node, i, 
                process_node(fy_node_get_sequence_item_by_index(node, i), parser, depth));
        }
    } else if (fy_node_is_mapping(node)) 
    {
        for (size_t i = 0; i < fy_node_get_mapping_pairs_length(node); i++) 
        {
            fy_node_pair pair = fy_node_get_mapping_pair_by_index(node, i);
            pair.key = process_node(pair.key, parser, depth);
            pair.value = process_node(pair.value, parser, depth);
            fy_node_set_mapping_pair_by_index(node, i, pair);
        }
    }
    return node;
}

void merge_nodes(fy_node *dest, fy_node *src) 
{
    if (!dest || !src) return;

    if (fy_node_is_scalar(dest)) 
    {
        fy_node_move(dest, src); // Move ownership, not copy
    } else if (fy_node_is_sequence(dest) && fy_node_is_sequence(src)) 
    {
        for (size_t i = 0; i < fy_node_get_sequence_items_length(src); i++) 
        {
            fy_node_append_sequence_item(dest, fy_node_get_sequence_item_by_index(src, i));
            fy_node_set_sequence_item_by_index(src, i, NULL); // Prevent double-free
        }
    } else if (fy_node_is_mapping(dest) && fy_node_is_mapping(src)) 
    {
        for (size_t i = 0; i < fy_node_get_mapping_pairs_length(src); i++) 
        {
            fy_node_pair pair = fy_node_get_mapping_pair_by_index(src, i);
            fy_node_add_mapping_pair(dest, pair.key, pair.value);
            pair.key = NULL;
            pair.value = NULL; // Prevent double-free
        }
    } else 
    {
        fprintf(stderr, "Warning: Cannot merge nodes of incompatible types\n");
    }
}

fy_node* process_include(fy_node *node, fy_parser *parser, int depth) 
{
    if (depth >= MAX_INCLUDE_DEPTH) 
    {
        fprintf(stderr, "Error: Maximum include depth exceeded.\n");
        exit(EXIT_FAILURE);
    }

    const char *filename = fy_node_get_scalar_str(node, NULL);

    if (strncmp(filename, "file:", 5) == 0) 
    {
        // Local file include
        filename += 5; // Skip the "file:" prefix
        FILE *file = fopen(filename, "r");
        if (!file) 
        {
            fprintf(stderr, "Error: Cannot open include file '%s'.\n", filename);
            exit(EXIT_FAILURE);
        }
        fy_parser_set_input_file(parser, file);
        fy_document doc;
        if (!fy_parse_document(parser, &doc)) 
        {
            fprintf(stderr, "Error: Cannot parse include file '%s'.\n", filename);
            exit(EXIT_FAILURE);
        }

        fy_node *root = fy_document_get_root_node(&doc);
        root = process_node(root, parser, depth + 1);
        fy_document_destroy(&doc);
        fclose(file);
        return root;

    } else if (strncmp(filename, "http:", 5) == 0 || strncmp(filename, "https:", 6) == 0) 
    {
        // URL include
        struct MemoryStruct chunk = { .memory = malloc(1), .size = 0 };
        CURL *curl;
        CURLcode res;
        curl_global_init(CURL_GLOBAL_ALL);
        curl = curl_easy_init();
        if (!curl) 
        {
            fprintf(stderr, "Error: Could not initialize curl\n");
            exit(EXIT_FAILURE);
        }
        curl_easy_setopt(curl, CURLOPT_URL, filename);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
        res = curl_easy_perform(curl);
        if (res != CURLE_OK) 
        {
            fprintf(stderr, "Error: curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
            exit(EXIT_FAILURE);
        }
        long response_code;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
        if (response_code != 200) 
        {
            fprintf(stderr, "Error: HTTP request failed with code %ld\n", response_code);
            exit(EXIT_FAILURE);
        }
        fy_parser_set_input_string(parser, chunk.memory, chunk.size);
        fy_document doc;
        if (!fy_parse_document(parser, &doc)) 
        {
            fprintf(stderr, "Error: Cannot parse included content\n");
            exit(EXIT_FAILURE);
        }
        fy_node *root = fy_document_get_root_node(&doc);
        root = process_node(root, parser, depth + 1);
        fy_document_destroy(&doc);
Ulrond commented 1 week ago

Yaml file testing requirements

## Single include URL check

`1-single-include-url.yaml`

1:
 value: true

!include <https://github.com/rdkcentral/ut-control/tree/main/tests/src/assets/include/2s.yaml>
!include <https://github.com/rdkcentral/ut-control/tree/main/tests/src/assets/include/3s.yaml>
!include <https://github.com/rdkcentral/ut-control/tree/main/tests/src/assets/include/4s.yaml>

## Multi-include check file check

`1-single-include-file.yaml`

1:
 value: true

!include <file:2s.yaml>
!include <file:3s.yaml>
!include <file:4s.yaml>

`2s.yml`

2:
  value: true

3:
  value: true

4:
  value: true

5:
  value: true

## Include Depth Check

`depth_check.yml`

1:
  value: true
!include <file:2.yaml>

`2d.yaml`

2:
  value: true
!include <file:3.yaml>

`3d.yaml`

3: 
 value: true
!include <file:4.yaml>

`4d.yaml`

4: 
 value: true
!include <file:5.yaml>

`5s.yaml`

5:
 value: true

OpenKVP( "./profiles/1.yml" );

UT_ASSERT_KVP_PROFILE_CHECK_BOOL( "4:value" , true);
UT_ASSERT_KVP_PROFILE_CHECK_BOOL( "3:value" , true);
UT_ASSERT_KVP_PROFILE_CHECK_BOOL( "2:value" , true);
UT_ASSERT_KVP_PROFILE_CHECK_BOOL( "1:value" , true);

## Master file would look like this in both cases

1:
 value: true
2:
 value: true
3: 
 value: true
4: 
 value: true

## Check for MAX INCLUDES - Set Max Includes 4