diff --git a/package/c/hmpl/default.nix b/package/c/hmpl/default.nix index b32c839..3f39c5e 100644 --- a/package/c/hmpl/default.nix +++ b/package/c/hmpl/default.nix @@ -25,7 +25,7 @@ stdenv.mkDerivation { ''; meta = { - description = "hectic"; + description = "hmpl"; license = lib.licenses.mit; }; } diff --git a/package/c/hmpl/hmpl.c b/package/c/hmpl/hmpl.c index 2da8751..0988d33 100644 --- a/package/c/hmpl/hmpl.c +++ b/package/c/hmpl/hmpl.c @@ -66,180 +66,148 @@ void hmpl_render_interpolation_tags(Arena *arena, char **text_ptr, const Json * } } -// CREATE OR REPLACE FUNCTION common.render_template_loop_blocks(result TEXT, context JSONB) -// RETURNS TEXT LANGUAGE plpgsql AS $$ -// DECLARE -// loop_start INT; -// key_end INT; -// loop_end INT; -// loop_key TEXT; -// block TEXT; -// rendered_block TEXT; -// arr JSONB; -// item JSONB; -// item_text TEXT; -// BEGIN -// LOOP -// loop_start := strpos(result, '{{#'); -// EXIT WHEN loop_start = 0; -- Exit if no loop start found. -// -// -- Locate the end of the loop key marker. -// key_end := strpos(result, '}}', loop_start); -// IF key_end = 0 THEN -// RAISE EXCEPTION 'Malformed template: missing closing braces for loop start'; -// END IF; -// -// -- Extract the key used for the loop. -// loop_key := substr_cloneing(result from loop_start + 3 for key_end - loop_start - 3); -// -// RAISE DEBUG 'loop key %', loop_key; -// -// -- Find the matching loop end marker for this key. -// loop_end := strpos(result, '{{/#' || loop_key || '}}', key_end); -// IF loop_end = 0 THEN -// RAISE EXCEPTION 'Malformed template: missing loop end for key %', loop_key; -// END IF; -// -// -- Extract the inner block of the loop. -// block := substr_cloneing(result from key_end + 2 for loop_end - key_end - 2); -// -// -- Retrieve the JSON array from the context for the loop key. -// arr := eval_value(context, loop_key); -// rendered_block := ''; -// -// -- If an array is found, iterate over each element. -// IF arr IS NOT NULL AND jsonb_typeof(arr) = 'array' THEN -// FOR item IN SELECT * FROM jsonb_array_elements(arr) LOOP -// item_text := block; -- Begin with the raw block. -// IF jsonb_typeof(item) != 'object' THEN -// -- Replace interpolation for primitive values. -// item_text := replace(item_text, '{{.}}', item::text); -// ELSE -// -- For object values, iterate over each key/value. -// item_text := render_template_interpolations(item_text, item, '.'::CHAR(1)); -// item_text := render_template_conditions(item_text, item, '.'); -// END IF; -// rendered_block := rendered_block || item_text; -// END LOOP; -// END IF; -// -// -- Replace the entire loop block in the result with the rendered content. -// result := substr_cloneing(result from 1 for loop_start - 1) -// || rendered_block -// || substr_cloneing(result from loop_end + char_length('{{/#' || loop_key || '}}')); -// END LOOP; -// -// RETURN result; -// END $$; - -// {{#array_key}} -void hmpl_render_section_tags(Arena *arena, char **text_ptr, Json *context, const char * const prefix_start, const char * const prefix_end, const char * const separator_pattern){ - raise_debug("hmpl_render_section_tags(%p, %s, , %s, %s, %s)", arena, *text_ptr, prefix_start, prefix_end, separator_pattern); - char start_pattern[32]; - snprintf(start_pattern, sizeof(start_pattern), "{{%s", prefix_start); - int start_pattern_length = strlen(start_pattern); - - // TODO: rename close_tag_start_pattern - char end_pattern[32]; - snprintf(end_pattern, sizeof(end_pattern), "{{%s", prefix_end); - int end_pattern_length = strlen(end_pattern); - - int separator_pattern_length = strlen(separator_pattern); - if (!separator_pattern || separator_pattern_length == 0) { - raise_exception("Unexpected usage: separator pattern cannot be empty"); - } - - int offset = 0; - - while (1) { - char *current_text = *text_ptr; - char *opening_tag_start = strstr(current_text + offset, start_pattern); - if (!opening_tag_start) - break; - int start_index = opening_tag_start - current_text; - int relative_key_start = start_index + start_pattern_length; - - char *opening_tag_separator = strstr(opening_tag_start, separator_pattern); - if (!opening_tag_start) { - raise_exception("Malformed template: missing separator for section tag or not specifiet name for element"); - exit(1); - } - int separator_index = opening_tag_separator - current_text; - int element_name_start = separator_index + separator_pattern_length; - - char *opening_tag_end = strstr(opening_tag_separator, "}}"); - if (!opening_tag_end) { - raise_exception("Malformed template: missing closing braces for section tag"); - exit(1); - } - assert((size_t)opening_tag_end > (size_t)opening_tag_separator); - assert((size_t)opening_tag_separator > (size_t)opening_tag_start); - - int key_length = (opening_tag_separator - current_text) - relative_key_start; - assert(key_length > 0); +// {{item#array}}...{{/array}} +void hmpl_render_section_tags(Arena *arena, char **text_ptr, Json *context, const char * const prefix_start, const char * const prefix_end, const char * const separator_pattern) { + raise_debug("hmpl_render_section_tags(%p, %s, , %s, %s, %s)", arena, *text_ptr, prefix_start, prefix_end, separator_pattern); - char *key = arena_alloc(arena, key_length + 1); - substr_clone(current_text, key, relative_key_start, key_length); + // Create search patterns + char start_pattern[32]; + snprintf(start_pattern, sizeof(start_pattern), "{{%s", prefix_start); + Slice start_slice = slice_create(char, start_pattern, strlen(start_pattern), 0, strlen(start_pattern)); - int element_name_length = (opening_tag_end - current_text) - element_name_start; - assert(element_name_length > 0); - - char *element_name = arena_alloc(arena, element_name_length + 1); - substr_clone(current_text, element_name, element_name_start, element_name_length); - - int close_tag_patern_length = start_pattern_length + key_length + end_pattern_length; - char *close_tag_patern = arena_alloc(arena, close_tag_patern_length + 1); - snprintf(close_tag_patern, sizeof(*close_tag_patern), "%s%s%s", start_pattern, key, end_pattern); - - char *close_tag = strstr(opening_tag_end + offset + 1, close_tag_patern); - if (!close_tag) { - raise_exception("Malformed template: missing loop end for key %s", key); - exit(1); + // Create a mutable copy of separator_pattern + char separator_copy[32]; + strncpy(separator_copy, separator_pattern, sizeof(separator_copy) - 1); + separator_copy[sizeof(separator_copy) - 1] = '\0'; + Slice separator_slice = slice_create(char, separator_copy, strlen(separator_copy), 0, strlen(separator_copy)); + if (separator_slice.len == 0) { + raise_exception("Unexpected usage: separator pattern cannot be empty"); } - Json *arr = eval_object(arena, context, key); + // Create slice for the text + Slice text_slice = slice_create(char, *text_ptr, strlen(*text_ptr), 0, strlen(*text_ptr)); + size_t offset = 0; - if (arr && arr->type == JSON_ARRAY) { - size_t elem_count = 0; - for (Json *e = arr->child; e; e = e->next) elem_count++; - - char *replacement = arena_alloc(arena, MEM_KiB * elem_count); - size_t offset = 0; + while (1) { + // Find tag start + char *text_data = (char*)text_slice.data; + char *opening_tag_start = strstr(text_data + offset, (char*)start_slice.data); + if (!opening_tag_start) break; - char *block_buff = arena_alloc(arena, MEM_KiB); - size_t relative_block_start = (size_t)opening_tag_end + 2 - (size_t)current_text; - raise_trace("relative_block_start: %p = %p - 2 - %p", opening_tag_end, current_text); - size_t block_len = (size_t)opening_tag_end - (size_t)close_tag - 2; - raise_trace("block_len %p = %p - %p - 2", block_len, opening_tag_end, close_tag); - assert(block_len > 0); - substr_clone(current_text, block_buff, relative_block_start, block_len); - - for (Json *elem = arr->child; elem; elem = elem->next) { - char *block = arena_strdup(arena, block_buff); - - char *prefix = arena_alloc(arena, element_name_length + 2); - snprintf(prefix, element_name_length + 2, "%s.", element_name); - - hmpl_render_interpolation_tags(arena, &block, context, prefix); - raise_trace("block after: %s", block); - - size_t block_len = strlen(block); - memcpy(replacement + offset, block, block_len); - offset += block_len; + // Create slice for separator search + size_t start_index = opening_tag_start - text_data; + Slice remaining_slice = slice_subslice(text_slice, start_index, text_slice.len - start_index); + + // Find separator + char *opening_tag_separator = strstr((char*)remaining_slice.data, (char*)separator_slice.data); + if (!opening_tag_separator) { + raise_exception("Malformed template: missing separator for section tag or not specified name for element"); + exit(1); + } + + // Extract element name (now before separator) + size_t separator_index = opening_tag_separator - (char*)remaining_slice.data; + size_t element_name_start = start_slice.len; + size_t element_name_length = separator_index; + + char *element_name = arena_alloc(arena, element_name_length + 1); + substr_clone((char*)remaining_slice.data, element_name, element_name_start, element_name_length); + element_name[element_name_length] = '\0'; + + // Find closing braces + Slice after_separator = slice_subslice(remaining_slice, separator_index + separator_slice.len, + remaining_slice.len - separator_index - separator_slice.len); + char *opening_tag_end = strstr((char*)after_separator.data, "}}"); + if (!opening_tag_end) { + raise_exception("Malformed template: missing closing braces for section tag"); + exit(1); } - replacement[offset] = '\0'; - raise_trace("replacement: %s", replacement); + // Extract key (now after separator) + size_t key_start = 0; + size_t key_length = opening_tag_end - (char*)after_separator.data; + char *key = arena_alloc(arena, key_length + 1); + substr_clone((char*)after_separator.data, key, key_start, key_length); + key[key_length] = '\0'; - char *new_text = arena_repstr(arena, current_text, - (size_t)opening_tag_start - 1, - close_tag + close_tag_patern_length - opening_tag_start + 2, - replacement); + // Create pattern for closing tag + char *close_tag_pattern = arena_alloc(arena, start_slice.len + key_length + 3); // +3 for "{{" and "}}" + snprintf(close_tag_pattern, start_slice.len + key_length + 3, + "{{%s%s}}", prefix_end, key); - *text_ptr = new_text; + // Find closing tag + size_t after_opening_end = (opening_tag_end - (char*)after_separator.data) + 2; + Slice after_opening_slice = slice_subslice(after_separator, after_opening_end, + after_separator.len - after_opening_end); + + // Find the exact closing tag by checking for complete tag pattern + char *close_tag = NULL; + char *search_start = (char*)after_opening_slice.data; + while ((search_start = strstr(search_start, "{{")) != NULL) { + if (strncmp(search_start, close_tag_pattern, strlen(close_tag_pattern)) == 0) { + close_tag = search_start; + break; + } + search_start += 2; // Move past the "{{" we found + } + + if (!close_tag) { + raise_exception("Malformed template: missing loop end for key %s", key); + exit(1); + } + + // Get array from context + Json *arr = eval_object(arena, context, key); + + if (arr && arr->type == JSON_ARRAY) { + // Count array elements + size_t elem_count = 0; + for (Json *e = arr->child; e; e = e->next) elem_count++; + + // Allocate memory for replacement + char *replacement = arena_alloc(arena, MEM_KiB * elem_count); + size_t replacement_offset = 0; + + // Extract template block + size_t block_start = after_opening_end; + size_t block_length = (close_tag - (char*)after_opening_slice.data); + char *block_buff = arena_alloc(arena, block_length + 1); + substr_clone((char*)after_opening_slice.data, block_buff, block_start, block_length); + block_buff[block_length] = '\0'; + + // Process each array element + for (Json *elem = arr->child; elem; elem = elem->next) { + char *block = arena_strdup(arena, block_buff); + + char *prefix = arena_alloc(arena, element_name_length + 2); + snprintf(prefix, element_name_length + 2, "%s.", element_name); + + hmpl_render_interpolation_tags(arena, &block, context, prefix); + raise_trace("block after: %s", block); + + size_t block_len = strlen(block); + memcpy(replacement + replacement_offset, block, block_len); + replacement_offset += block_len; + } + + replacement[replacement_offset] = '\0'; + raise_trace("replacement: %s", replacement); + + // Calculate replacement positions + size_t replace_start = start_index; + size_t replace_length = (close_tag - (char*)after_opening_slice.data) + + start_slice.len + key_length + 2; + + // Perform replacement + char *new_text = arena_repstr(arena, (char*)text_slice.data, replace_start, replace_length, replacement); + *text_ptr = new_text; + + // Update text slice + text_slice = slice_create(char, new_text, strlen(new_text), 0, strlen(new_text)); + } + + offset = start_index; } - offset = start_index; - } } void hmpl_render_with_arena(Arena *arena, char **text, const Json * const context) { diff --git a/package/c/hmpl/make.sh b/package/c/hmpl/make.sh index aeb2484..44024e0 100644 --- a/package/c/hmpl/make.sh +++ b/package/c/hmpl/make.sh @@ -2,6 +2,8 @@ # Usage: make.sh [build|check] [--norun] [--debug] [--color] # Options: # build Build the library and app (default if no mode is provided). +# watch Build the library and app and watch for changes. +# run Build and run the app. # check Build tests; runs them unless --norun is specified. # --norun (check only) Build tests but do not run them. # --debug Build with -O0 (debug mode). @@ -9,7 +11,7 @@ # help, --help Show this help message. check_dependencies() { - for dep in cc ar; do + for dep in cc ar entr pager; do if ! command -v "$dep" >/dev/null 2>&1; then echo "Error: Required dependency '$dep' not found." >&2 exit 1 @@ -22,6 +24,8 @@ print_help() { cat <, #, /, ) +2025-03-27 21:50:28 TRACE hmpl.c:76 slice_create(, , , , ) +2025-03-27 21:50:28 TRACE hmpl.c:82 slice_create(, , , , ) +2025-03-27 21:50:28 TRACE hmpl.c:88 slice_create(, , , , ) +2025-03-27 21:50:28 TRACE hmpl.c:99 slice_subslice(, , ) +2025-03-27 21:50:28 TRACE hmpl.c:113 arena_alloc_or_null(0x7ffff5700020, 11) +2025-03-27 21:50:28 DEBUG hmpl.c:113 Arena 0x7ffff3dff800 (capacity 3145728) used 1088800 will allocate 11 bytes +2025-03-27 21:50:28 DEBUG hmpl.c:113 Allocated at 0x7ffff3f09520 +2025-03-27 21:50:28 TRACE hmpl.c:114 substr_cloning(src="{{#element array}} {{element.field.subfield}}{{/array}}", src_ptr=0x7ffff3f068e7, dest=0x7ffff3f09520, from=3, len=10) +2025-03-27 21:50:28 TRACE hmpl.c:114 Completed substr_cloning: result="element ar", copied_length=10 +2025-03-27 21:50:28 TRACE hmpl.c:118 slice_subslice(, , ) +2025-03-27 21:50:28 TRACE hmpl.c:129 arena_alloc_or_null(0x7ffff5700020, 6) +2025-03-27 21:50:28 DEBUG hmpl.c:129 Arena 0x7ffff3dff800 (capacity 3145728) used 1088811 will allocate 6 bytes +2025-03-27 21:50:28 DEBUG hmpl.c:129 Allocated at 0x7ffff3f0952b +2025-03-27 21:50:28 TRACE hmpl.c:130 substr_cloning(src="array}} {{element.field.subfield}}{{/array}}", src_ptr=0x7ffff3f068f2, dest=0x7ffff3f0952b, from=0, len=5) +2025-03-27 21:50:28 TRACE hmpl.c:130 Completed substr_cloning: result="array", copied_length=5 +2025-03-27 21:50:28 TRACE hmpl.c:134 arena_alloc_or_null(0x7ffff5700020, 11) +2025-03-27 21:50:28 DEBUG hmpl.c:134 Arena 0x7ffff3dff800 (capacity 3145728) used 1088817 will allocate 11 bytes +2025-03-27 21:50:28 DEBUG hmpl.c:134 Allocated at 0x7ffff3f09531 +2025-03-27 21:50:28 TRACE hmpl.c:140 slice_subslice(, , ) +2025-03-27 21:50:28 TRACE hmpl.c:7 arena_alloc_or_null(0x7ffff5700020, 6) +2025-03-27 21:50:28 DEBUG hmpl.c:7 Arena 0x7ffff3dff800 (capacity 3145728) used 1088828 will allocate 6 bytes +2025-03-27 21:50:28 DEBUG hmpl.c:7 Allocated at 0x7ffff3f0953c +2025-03-27 21:50:28 TRACE hectic.c:449 arena_alloc_or_null(0x7ffff5700020, 1024) +2025-03-27 21:50:28 DEBUG hectic.c:449 Arena 0x7ffff3dff800 (capacity 3145728) used 1088834 will allocate 1024 bytes +2025-03-27 21:50:28 DEBUG hectic.c:449 Allocated at 0x7ffff3f09542 +2025-03-27 21:50:28 TRACE hectic.c:449 arena_alloc_or_null(0x7ffff5700020, 1024) +2025-03-27 21:50:28 DEBUG hectic.c:449 Arena 0x7ffff3dff800 (capacity 3145728) used 1089858 will allocate 1024 bytes +2025-03-27 21:50:28 DEBUG hectic.c:449 Allocated at 0x7ffff3f09942 +2025-03-27 21:50:28 TRACE hectic.c:449 arena_alloc_or_null(0x7ffff5700020, 1024) +2025-03-27 21:50:28 DEBUG hectic.c:449 Arena 0x7ffff3dff800 (capacity 3145728) used 1090882 will allocate 1024 bytes +2025-03-27 21:50:28 DEBUG hectic.c:449 Allocated at 0x7ffff3f09d42 +2025-03-27 21:50:28 TRACE hectic.c:449 arena_alloc_or_null(0x7ffff5700020, 1024) +2025-03-27 21:50:28 DEBUG hectic.c:449 Arena 0x7ffff3dff800 (capacity 3145728) used 1091906 will allocate 1024 bytes +2025-03-27 21:50:28 DEBUG hectic.c:449 Allocated at 0x7ffff3f0a142 +2025-03-27 21:50:28 TRACE hectic.c:449 arena_alloc_or_null(0x7ffff5700020, 1024) +2025-03-27 21:50:28 DEBUG hectic.c:449 Arena 0x7ffff3dff800 (capacity 3145728) used 1092930 will allocate 1024 bytes +2025-03-27 21:50:28 DEBUG hectic.c:449 Allocated at 0x7ffff3f0a542 +2025-03-27 21:50:28 DEBUG hectic.c:490 Converted JSON to string: "value1" +2025-03-27 21:50:28 DEBUG hectic.c:490 Converted JSON to string: {"subfield":"value1"} +2025-03-27 21:50:28 DEBUG hectic.c:490 Converted JSON to string: {"field":{"subfield":"value1"}} +2025-03-27 21:50:28 TRACE hectic.c:449 arena_alloc_or_null(0x7ffff5700020, 1024) +2025-03-27 21:50:28 DEBUG hectic.c:449 Arena 0x7ffff3dff800 (capacity 3145728) used 1093954 will allocate 1024 bytes +2025-03-27 21:50:28 DEBUG hectic.c:449 Allocated at 0x7ffff3f0a942 +2025-03-27 21:50:28 TRACE hectic.c:449 arena_alloc_or_null(0x7ffff5700020, 1024) +2025-03-27 21:50:28 DEBUG hectic.c:449 Arena 0x7ffff3dff800 (capacity 3145728) used 1094978 will allocate 1024 bytes +2025-03-27 21:50:28 DEBUG hectic.c:449 Allocated at 0x7ffff3f0ad42 +2025-03-27 21:50:28 TRACE hectic.c:449 arena_alloc_or_null(0x7ffff5700020, 1024) +2025-03-27 21:50:28 DEBUG hectic.c:449 Arena 0x7ffff3dff800 (capacity 3145728) used 1096002 will allocate 1024 bytes +2025-03-27 21:50:28 DEBUG hectic.c:449 Allocated at 0x7ffff3f0b142 +2025-03-27 21:50:28 DEBUG hectic.c:490 Converted JSON to string: "value2" +2025-03-27 21:50:28 DEBUG hectic.c:490 Converted JSON to string: {"subfield":"value2"} +2025-03-27 21:50:28 DEBUG hectic.c:490 Converted JSON to string: {"field":{"subfield":"value2"}} +2025-03-27 21:50:28 TRACE hectic.c:449 arena_alloc_or_null(0x7ffff5700020, 1024) +2025-03-27 21:50:28 DEBUG hectic.c:449 Arena 0x7ffff3dff800 (capacity 3145728) used 1097026 will allocate 1024 bytes +2025-03-27 21:50:28 DEBUG hectic.c:449 Allocated at 0x7ffff3f0b542 +2025-03-27 21:50:28 TRACE hectic.c:449 arena_alloc_or_null(0x7ffff5700020, 1024) +2025-03-27 21:50:28 DEBUG hectic.c:449 Arena 0x7ffff3dff800 (capacity 3145728) used 1098050 will allocate 1024 bytes +2025-03-27 21:50:28 DEBUG hectic.c:449 Allocated at 0x7ffff3f0b942 +2025-03-27 21:50:28 TRACE hectic.c:449 arena_alloc_or_null(0x7ffff5700020, 1024) +2025-03-27 21:50:28 DEBUG hectic.c:449 Arena 0x7ffff3dff800 (capacity 3145728) used 1099074 will allocate 1024 bytes +2025-03-27 21:50:28 DEBUG hectic.c:449 Allocated at 0x7ffff3f0bd42 +2025-03-27 21:50:28 DEBUG hectic.c:490 Converted JSON to string: "value3" +2025-03-27 21:50:28 DEBUG hectic.c:490 Converted JSON to string: {"subfield":"value3"} +2025-03-27 21:50:28 DEBUG hectic.c:490 Converted JSON to string: {"field":{"subfield":"value3"}} +2025-03-27 21:50:28 DEBUG hectic.c:490 Converted JSON to string: [{"field":{"subfield":"value1"}},{"field":{"subfield":"value2"}},{"field":{"subfield":"value3"}}] +2025-03-27 21:50:28 DEBUG hectic.c:490 Converted JSON to string: {"array":[{"field":{"subfield":"value1"}},{"field":{"subfield":"value2"}},{"field":{"subfield":"value3"}}]} +2025-03-27 21:50:28 DEBUG hmpl.c:18 res: {"array":[{"field":{"subfield":"value1"}},{"field":{"subfield":"value2"}},{"field":{"subfield":"value3"}}]}, key: array, query: array +2025-03-27 21:50:28 DEBUG hectic.c:496 json_get_object_item: Searching for key "array" +2025-03-27 21:50:28 DEBUG hectic.c:503 Comparing child key "array" with "array" +2025-03-27 21:50:28 DEBUG hectic.c:505 Key "array" found +2025-03-27 21:50:28 TRACE hmpl.c:157 arena_alloc_or_null(0x7ffff5700020, 3072) +2025-03-27 21:50:28 DEBUG hmpl.c:157 Arena 0x7ffff3dff800 (capacity 3145728) used 1100098 will allocate 3072 bytes +2025-03-27 21:50:28 DEBUG hmpl.c:157 Allocated at 0x7ffff3f0c142 +2025-03-27 21:50:28 TRACE hmpl.c:163 arena_alloc_or_null(0x7ffff5700020, 29) +2025-03-27 21:50:28 DEBUG hmpl.c:163 Arena 0x7ffff3dff800 (capacity 3145728) used 1103170 will allocate 29 bytes +2025-03-27 21:50:28 DEBUG hmpl.c:163 Allocated at 0x7ffff3f0cd42 +2025-03-27 21:50:28 TRACE hmpl.c:164 substr_cloning(src=" {{element.field.subfield}}{{/array}}", src_ptr=0x7ffff3f068f9, dest=0x7ffff3f0cd42, from=7, len=28) +2025-03-27 21:50:28 TRACE hmpl.c:164 Completed substr_cloning: result="ment.field.subfield}}{{/arra", copied_length=28 +2025-03-27 21:50:28 TRACE hmpl.c:169 arena_alloc_or_null(0x7ffff5700020, 29) +2025-03-27 21:50:28 DEBUG hmpl.c:169 Arena 0x7ffff3dff800 (capacity 3145728) used 1103199 will allocate 29 bytes +2025-03-27 21:50:28 DEBUG hmpl.c:169 Allocated at 0x7ffff3f0cd5f +2025-03-27 21:50:28 TRACE hmpl.c:171 arena_alloc_or_null(0x7ffff5700020, 12) +2025-03-27 21:50:28 DEBUG hmpl.c:171 Arena 0x7ffff3dff800 (capacity 3145728) used 1103228 will allocate 12 bytes +2025-03-27 21:50:28 DEBUG hmpl.c:171 Allocated at 0x7ffff3f0cd7c +2025-03-27 21:50:28 DEBUG hmpl.c:32 hmpl_render_interpolation_tags +2025-03-27 21:50:28 TRACE hmpl.c:175 block after: ment.field.subfield}}{{/arra +2025-03-27 21:50:28 TRACE hmpl.c:169 arena_alloc_or_null(0x7ffff5700020, 29) +2025-03-27 21:50:28 DEBUG hmpl.c:169 Arena 0x7ffff3dff800 (capacity 3145728) used 1103240 will allocate 29 bytes +2025-03-27 21:50:28 DEBUG hmpl.c:169 Allocated at 0x7ffff3f0cd88 +2025-03-27 21:50:28 TRACE hmpl.c:171 arena_alloc_or_null(0x7ffff5700020, 12) +2025-03-27 21:50:28 DEBUG hmpl.c:171 Arena 0x7ffff3dff800 (capacity 3145728) used 1103269 will allocate 12 bytes +2025-03-27 21:50:28 DEBUG hmpl.c:171 Allocated at 0x7ffff3f0cda5 +2025-03-27 21:50:28 DEBUG hmpl.c:32 hmpl_render_interpolation_tags +2025-03-27 21:50:28 TRACE hmpl.c:175 block after: ment.field.subfield}}{{/arra +2025-03-27 21:50:28 TRACE hmpl.c:169 arena_alloc_or_null(0x7ffff5700020, 29) +2025-03-27 21:50:28 DEBUG hmpl.c:169 Arena 0x7ffff3dff800 (capacity 3145728) used 1103281 will allocate 29 bytes +2025-03-27 21:50:28 DEBUG hmpl.c:169 Allocated at 0x7ffff3f0cdb1 +2025-03-27 21:50:28 TRACE hmpl.c:171 arena_alloc_or_null(0x7ffff5700020, 12) +2025-03-27 21:50:28 DEBUG hmpl.c:171 Arena 0x7ffff3dff800 (capacity 3145728) used 1103310 will allocate 12 bytes +2025-03-27 21:50:28 DEBUG hmpl.c:171 Allocated at 0x7ffff3f0cdce +2025-03-27 21:50:28 DEBUG hmpl.c:32 hmpl_render_interpolation_tags +2025-03-27 21:50:28 TRACE hmpl.c:175 block after: ment.field.subfield}}{{/arra +2025-03-27 21:50:28 TRACE hmpl.c:183 replacement: ment.field.subfield}}{{/arrament.field.subfield}}{{/arrament.field.subfield}}{{/arra +2025-03-27 21:50:28 TRACE hmpl.c:191 arena_repstr__(0x7ffff3f068e7, (nil), 38, "ment.field.subfield}}{{/arrament.field.subfield}}{{/arrament.field.subfield}}{{/arra") +2025-03-27 21:50:28 TRACE hmpl.c:191 arena_alloc_or_null(0x7ffff5700020, 103) +2025-03-27 21:50:28 DEBUG hmpl.c:191 Arena 0x7ffff3dff800 (capacity 3145728) used 1103322 will allocate 103 bytes +2025-03-27 21:50:28 DEBUG hmpl.c:191 Allocated at 0x7ffff3f0cdda +2025-03-27 21:50:28 TRACE hmpl.c:195 slice_create(, , , , ) +2025-03-27 21:50:28 NOTICE test/test.c:197 Result: +ment.field.subfield}}{{/arrament.field.subfield}}{{/arrament.field.subfield}}{{/arrabfield}}{{/array}} +test: test/test.c:198: test_render_section_tags: Assertion `strcmp(text, TEST_DATA_SIMPLE_SECTION_ITERATION_RESULT) == 0' failed. +make.sh: line 91: 218973 Aborted "$exe" diff --git a/package/c/hmpl/test/test.c b/package/c/hmpl/test/test.c index 577c7c7..14ee214 100644 --- a/package/c/hmpl/test/test.c +++ b/package/c/hmpl/test/test.c @@ -101,7 +101,7 @@ "}" #define TEST_DATA_SIMPLE_SECTION_ITERATION_TEMPLATE \ - "{{#array element}}" \ + "{{#element array}}" \ " {{element.field.subfield}}" \ "{{/array}}" @@ -110,78 +110,130 @@ "value2" \ "value3" +#define TEST_DATA_COMPLEX_SECTION_ITERATION_CONTEXT \ + "{" \ + " \"users\": [" \ + " { \"name\": \"John\", \"age\": 30 }," \ + " { \"name\": \"Jane\", \"age\": 25 }" \ + " ]" \ + "}" + +#define TEST_DATA_COMPLEX_SECTION_ITERATION_TEMPLATE \ + "{{#user users}}" \ + " Name: {{user.name}}, Age: {{user.age}}\n" \ + "{{/users}}" + +#define TEST_DATA_COMPLEX_SECTION_ITERATION_RESULT \ + " Name: John, Age: 30\n" \ + " Name: Jane, Age: 25\n" + void test_eval_single_level_key(Arena *arena) { + raise_notice("Testing single level key evaluation"); const char *context_text = arena_strdup(arena, "{\"name\": \"world\"}"); Json *context = json_parse(arena, &context_text); if (!context) { raise_exception("Malformed json"); exit(1); } char *result = eval_string(arena, context, "name"); - raise_debug("eval result: %s", result); + raise_notice("Context: %s", json_to_string(arena, context)); + raise_notice("Query: name"); + raise_notice("Result: %s", result); assert(result && strcmp(result, "world") == 0); } void test_eval_nested_key(Arena *arena) { + raise_notice("Testing nested key evaluation"); const char *context_text = arena_strdup(arena, "{\"person\": {\"name\": \"Alice\"}}"); Json *context = json_parse(arena, &context_text); if (!context) { raise_exception("Malformed json"); exit(1); } char *result = eval_string(arena, context, "person.name"); - raise_notice("context: %s, eval result: %s", json_to_string(arena, context), result); + raise_notice("Context: %s", json_to_string(arena, context)); + raise_notice("Query: person.name"); + raise_notice("Result: %s", result); assert(result && strcmp(result, "Alice") == 0); } void test_render_interpolation_tags(Arena *arena) { - raise_trace("test_render_interpolation_tags(arena)"); + raise_notice("Testing interpolation tags without prefix"); const char *context_text = arena_strdup(arena, TEST_DATA_INTERPOLATION_CONTEXT); Json *context = json_parse(arena, &context_text); if (!context) { raise_exception("Malformed json"); exit(1); } char *text = arena_strdup(arena, TEST_DATA_INTERPOLATION_TEMPLATE); + raise_notice("Template:\n%s", text); + raise_notice("Context: %s", json_to_string(arena, context)); hmpl_render_interpolation_tags(arena, &text, context, ""); - raise_trace("text: %s", text); + raise_notice("Result:\n%s", text); assert(strcmp(text, TEST_DATA_INTERPOLATION_RESULT) == 0); } void test_render_interpolation_tags_with_prefix(Arena *arena) { + raise_notice("Testing interpolation tags with prefix"); const char *context_text = arena_strdup(arena, TEST_DATA_INTERPOLATION_WITH_PREFIX_CONTEXT); Json *context = json_parse(arena, &context_text); if (!context) { raise_exception("Malformed json"); exit(1); } char *text = arena_strdup(arena, TEST_DATA_INTERPOLATION_WITH_PREFIX_TEMPLATE); + raise_notice("Template:\n%s", text); + raise_notice("Context: %s", json_to_string(arena, context)); hmpl_render_interpolation_tags(arena, &text, context, "."); + raise_notice("Result:\n%s", text); assert(strcmp(text, TEST_DATA_INTERPOLATION_WITH_PREFIX_RESULT) == 0); } void test_render_section_tags(Arena *arena) { + raise_notice("Testing simple section tags"); const char *context_text = arena_strdup(arena, TEST_DATA_SIMPLE_SECTION_ITERATION_CONTEXT); Json *context = json_parse(arena, &context_text); if (!context) { raise_exception("Malformed json"); exit(1); } char *text = arena_strdup(arena, TEST_DATA_SIMPLE_SECTION_ITERATION_TEMPLATE); + raise_notice("Template:\n%s", text); + raise_notice("Context: %s", json_to_string(arena, context)); hmpl_render_section_tags(arena, &text, context, "#", "/", " "); + raise_notice("Result:\n%s", text); assert(strcmp(text, TEST_DATA_SIMPLE_SECTION_ITERATION_RESULT) == 0); } +void test_render_complex_section_tags(Arena *arena) { + raise_notice("Testing complex section tags"); + const char *context_text = arena_strdup(arena, TEST_DATA_COMPLEX_SECTION_ITERATION_CONTEXT); + Json *context = json_parse(arena, &context_text); + if (!context) { raise_exception("Malformed json"); exit(1); } + + char *text = arena_strdup(arena, TEST_DATA_COMPLEX_SECTION_ITERATION_TEMPLATE); + raise_notice("Template:\n%s", text); + raise_notice("Context: %s", json_to_string(arena, context)); + + hmpl_render_section_tags(arena, &text, context, "#", "/", " "); + raise_notice("Result:\n%s", text); + assert(strcmp(text, TEST_DATA_COMPLEX_SECTION_ITERATION_RESULT) == 0); +} + int main(void) { init_logger(); + raise_notice("Starting HMPL tests"); Arena arena = arena_init(MEM_MiB * 3); // evaluation + raise_notice("=== Testing key evaluation ==="); test_eval_single_level_key(&arena); test_eval_nested_key(&arena); // interpolation tags + raise_notice("=== Testing interpolation tags ==="); test_render_interpolation_tags(&arena); test_render_interpolation_tags_with_prefix(&arena); // section tags + raise_notice("=== Testing section tags ==="); test_render_section_tags(&arena); + test_render_complex_section_tags(&arena); - printf("All tests passed.\n"); - + raise_notice("All tests passed successfully"); arena_free(&arena); return 0; } diff --git a/package/c/watch/default.nix b/package/c/watch/default.nix index 2cb8eb7..6b1937f 100644 --- a/package/c/watch/default.nix +++ b/package/c/watch/default.nix @@ -1,10 +1,10 @@ -{ stdenv, gcc, lib, hectic, bash }: +{ stdenv, gcc, lib, bash, gdb }: stdenv.mkDerivation { pname = "watch"; version = "1.0"; src = ./.; - doCheck = true; + doCheck = false; nativeBuildInputs = [ gcc gdb ]; @@ -17,10 +17,8 @@ stdenv.mkDerivation { ''; installPhase = '' - mkdir -p $out/bin $out/lib $out/include - cp target/hmpl $out/bin/hmpl - cp target/libhmpl.a $out/lib/ - cp hmpl.h $out/include/hmpl.h + mkdir -p $out/bin + cp target/watch $out/bin/watch ''; meta = { diff --git a/package/c/watch/main.c b/package/c/watch/main.c index b3350ba..63bd242 100755 --- a/package/c/watch/main.c +++ b/package/c/watch/main.c @@ -9,6 +9,8 @@ #include #include #include +#include +#include #ifndef PATH_MAX #define PATH_MAX 4096 @@ -19,15 +21,27 @@ #define MAX_PATTERNS 32 #define POLL_INTERVAL_MS 100 +// Global flag to indicate if we're running in pager mode +int pager_mode = 0; +FILE *output_stream = NULL; +int running = 1; + +void signal_handler(int sig) { + (void)sig; // Mark parameter as used to avoid warning + running = 0; +} + void print_usage(const char *prog_name) { fprintf(stderr, "Usage: %s [-p ] [-p ] ... [dir2] ...\n", prog_name); fprintf(stderr, " or: find . -type d | %s [-p ] [-p ] ...\n", prog_name); fprintf(stderr, "Options:\n"); fprintf(stderr, " -p File pattern to watch (can be used multiple times)\n"); + fprintf(stderr, " -P Enable pager-friendly output (refresh mode)\n"); fprintf(stderr, " -h Show this help message\n"); fprintf(stderr, "Examples:\n"); fprintf(stderr, " %s 'make' -p '*.c' -p '*.h' ./src\n", prog_name); fprintf(stderr, " find . -type d | %s 'echo changed' -p '*.py'\n", prog_name); + fprintf(stderr, " %s -P 'make' -p '*.c' -p '*.h' ./src | less\n", prog_name); exit(EXIT_FAILURE); } @@ -148,6 +162,10 @@ int match_any_pattern(const char *filename, char **patterns, int num_patterns) { } int main(int argc, char *argv[]) { + // Register signal handlers + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + if (argc < 2) { print_usage(argv[0]); } @@ -159,7 +177,7 @@ int main(int argc, char *argv[]) { optind = 2; int opt; - while ((opt = getopt(argc, argv, "p:h")) != -1) { + while ((opt = getopt(argc, argv, "p:Ph")) != -1) { switch (opt) { case 'p': if (num_patterns < MAX_PATTERNS) { @@ -169,6 +187,9 @@ int main(int argc, char *argv[]) { exit(EXIT_FAILURE); } break; + case 'P': + pager_mode = 1; + break; case 'h': print_usage(argv[0]); break; @@ -183,6 +204,15 @@ int main(int argc, char *argv[]) { print_usage(argv[0]); } + // Set output stream - stderr for normal mode, stdout for pager mode + output_stream = pager_mode ? stdout : stderr; + + // Check if stdout is being piped and we're not in pager mode + if (!pager_mode && !isatty(STDOUT_FILENO)) { + pager_mode = 1; + output_stream = stdout; + } + struct dir_info dirs[MAX_DIRS]; int num_dirs = 0; struct file_hash files; @@ -242,13 +272,13 @@ int main(int argc, char *argv[]) { } // Print the directories and patterns we're watching - fprintf(stderr, "Watching %d directories for files matching: ", num_dirs); + fprintf(output_stream, "Watching %d directories for files matching: ", num_dirs); for (int i = 0; i < num_patterns; i++) { - fprintf(stderr, "'%s'%s", patterns[i], (i < num_patterns - 1) ? ", " : "\n"); + fprintf(output_stream, "'%s'%s", patterns[i], (i < num_patterns - 1) ? ", " : "\n"); } for (int i = 0; i < num_dirs; i++) { - fprintf(stderr, " %s\n", dirs[i].path); + fprintf(output_stream, " %s\n", dirs[i].path); } for (int i = 0; i < num_dirs; i++) { @@ -281,8 +311,9 @@ int main(int argc, char *argv[]) { closedir(dir); } - fprintf(stderr, "Initially found %d matching files\n", files.count); - fprintf(stderr, "Waiting for file modifications...\n"); + fprintf(output_stream, "Initially found %d matching files\n", files.count); + fprintf(output_stream, "Waiting for file modifications...\n"); + fflush(output_stream); struct timeval tv; tv.tv_sec = 0; @@ -302,7 +333,8 @@ int main(int argc, char *argv[]) { int first_scan = 1; - while (1) { + // Main watch loop + while (running) { select(0, NULL, NULL, NULL, &tv); int any_changes = 0; @@ -337,14 +369,14 @@ int main(int argc, char *argv[]) { existing->exists = 1; if (existing->mtime != st.st_mtime) { if (!first_scan) { - fprintf(stderr, "File modified: %s\n", filepath); + fprintf(output_stream, "File modified: %s\n", filepath); any_changes = 1; } existing->mtime = st.st_mtime; } } else { if (!first_scan) { - fprintf(stderr, "New file: %s\n", filepath); + fprintf(output_stream, "New file: %s\n", filepath); any_changes = 1; } struct file_info *file = malloc(sizeof(struct file_info)); @@ -361,7 +393,7 @@ int main(int argc, char *argv[]) { for (int i = 0; i < files.size; i++) { if (files.items[i] != NULL && files.items[i]->exists == 0) { if (!first_scan) { - fprintf(stderr, "File deleted: %s\n", files.items[i]->path); + fprintf(output_stream, "File deleted: %s\n", files.items[i]->path); any_changes = 1; } } @@ -369,8 +401,42 @@ int main(int argc, char *argv[]) { hash_remove_nonexistent(&files); + // If in pager mode and any changes occur, clear screen and reprint status + if (pager_mode && any_changes) { + // Use ANSI escape codes to clear screen + fprintf(output_stream, "\033[2J\033[H"); + + // Reprint header + fprintf(output_stream, "Watching %d directories for files matching: ", num_dirs); + for (int i = 0; i < num_patterns; i++) { + fprintf(output_stream, "'%s'%s", patterns[i], (i < num_patterns - 1) ? ", " : "\n"); + } + + // List current files + fprintf(output_stream, "Currently watching %d files:\n", files.count); + int file_count = 0; + for (int i = 0; i < files.size && file_count < files.count; i++) { + if (files.items[i] != NULL) { + fprintf(output_stream, " %s\n", files.items[i]->path); + file_count++; + } + } + + // Show last action + time_t current_time = time(NULL); + fprintf(output_stream, "\nLast event: Command executed at %s", ctime(¤t_time)); + fprintf(output_stream, "Command: %s\n", command); + + // Make sure output is flushed to the pager + fflush(output_stream); + } else if (any_changes) { + // In normal mode, just show status messages + fprintf(output_stream, "Executing command: %s\n", command); + fflush(output_stream); + } + + // Execute command if changes detected if (any_changes) { - fprintf(stderr, "Executing command: %s\n", command); int res = system(command); if (res != 0) { perror("system"); @@ -393,5 +459,6 @@ int main(int argc, char *argv[]) { free(patterns); hash_free(&files); + fprintf(output_stream, "Watch terminated.\n"); return 0; } \ No newline at end of file diff --git a/package/nvim-pager.nix b/package/nvim-pager.nix index 562d6e3..7624e17 100644 --- a/package/nvim-pager.nix +++ b/package/nvim-pager.nix @@ -2,7 +2,7 @@ pkgs.writeShellScriptBin "pager" '' nvim -R --clean -c 'set buftype=nofile' -c 'nnoremap q :q!' -c 'set nowrap' \ -c 'set runtimepath^=${pkgs.vimPlugins.vim-plugin-AnsiEsc}' \ - -c 'runtime! plugin/*.vim' -c 'AnsiEsc' - + -c 'runtime! plugin/*.vim' -c 'AnsiEsc' - $@ # ^^^^^^^^^^^^^^^^^^^^ # Prevents Neovim from treating the buffer as a file # ^^^^^^^^^^^^^^^^^^^^