feat: postgreact: hello world
This commit is contained in:
33
flake.nix
33
flake.nix
@@ -145,6 +145,15 @@
|
|||||||
buildInputs = (with pkgs; [ inotify-tools gdb gcc ]) ++ (with self.packages.${system}; [ c-hectic nvim-pager watch ]);
|
buildInputs = (with pkgs; [ inotify-tools gdb gcc ]) ++ (with self.packages.${system}; [ c-hectic nvim-pager watch ]);
|
||||||
PAGER = "${self.packages.${system}.nvim-pager}/bin/pager";
|
PAGER = "${self.packages.${system}.nvim-pager}/bin/pager";
|
||||||
};
|
};
|
||||||
|
postgres-c = pkgs.mkShell {
|
||||||
|
buildInputs = (with pkgs; [ inotify-tools postgresql_15 ]) ++ (with self.packages.${system}; [ nvim-pager ]) ++ (with pkgs-unstable; [ gdb gcc ]);
|
||||||
|
PAGER = "${self.packages.${system}.nvim-pager}/bin/pager";
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
export PATH=${pkgs-unstable.gcc}/bin:$PATH
|
||||||
|
export PAGER="${self.packages.${system}.nvim-pager}/bin/pager"
|
||||||
|
'';
|
||||||
|
};
|
||||||
pure-c = pkgs.mkShell {
|
pure-c = pkgs.mkShell {
|
||||||
buildInputs = (with pkgs; [ inotify-tools ]) ++ (with self.packages.${system}; [ nvim-pager ]) ++ (with pkgs-unstable; [ gdb gcc ]);
|
buildInputs = (with pkgs; [ inotify-tools ]) ++ (with self.packages.${system}; [ nvim-pager ]) ++ (with pkgs-unstable; [ gdb gcc ]);
|
||||||
PAGER = "${self.packages.${system}.nvim-pager}/bin/pager";
|
PAGER = "${self.packages.${system}.nvim-pager}/bin/pager";
|
||||||
@@ -434,11 +443,25 @@
|
|||||||
http = buildHttpExt "17";
|
http = buildHttpExt "17";
|
||||||
pg_smtp_client = buildSmtpExt "17";
|
pg_smtp_client = buildSmtpExt "17";
|
||||||
plhaskell = buildPlHaskellExt "15";
|
plhaskell = buildPlHaskellExt "15";
|
||||||
|
postgreact = prev.callPackage ./package/c/postgreact/default.nix { postgresql = prev.postgresql_17; };
|
||||||
};};
|
};};
|
||||||
postgresql_16 = prev.postgresql_16 // {pkgs = prev.postgresql_16.pkgs // {
|
postgresql_16 = prev.postgresql_16 // {pkgs = prev.postgresql_16.pkgs // {
|
||||||
http = buildHttpExt "16";
|
http = buildHttpExt "16";
|
||||||
pg_smtp_client = buildSmtpExt "16";
|
pg_smtp_client = buildSmtpExt "16";
|
||||||
plhaskell = buildPlHaskellExt "15";
|
plhaskell = buildPlHaskellExt "15";
|
||||||
|
postgreact = prev.callPackage ./package/c/postgreact/default.nix { postgresql = prev.postgresql_16; };
|
||||||
|
};};
|
||||||
|
postgresql_15 = prev.postgresql_15 // {pkgs = prev.postgresql_15.pkgs // {
|
||||||
|
http = buildHttpExt "15";
|
||||||
|
pg_smtp_client = buildSmtpExt "15";
|
||||||
|
plhaskell = buildPlHaskellExt "15";
|
||||||
|
postgreact = prev.callPackage ./package/c/postgreact/default.nix { postgresql = prev.postgresql_15; };
|
||||||
|
};};
|
||||||
|
postgresql_14 = prev.postgresql_14 // {pkgs = prev.postgresql_14.pkgs // {
|
||||||
|
http = buildHttpExt "14";
|
||||||
|
pg_smtp_client = buildSmtpExt "14";
|
||||||
|
plhaskell = buildPlHaskellExt "15";
|
||||||
|
postgreact = prev.callPackage ./package/c/postgreact/default.nix { postgresql = prev.postgresql_14; };
|
||||||
};};
|
};};
|
||||||
writers = let
|
writers = let
|
||||||
writeC =
|
writeC =
|
||||||
@@ -484,16 +507,6 @@
|
|||||||
writeMinCBin = name: includes: body: writeMinC "/bin/${name}" includes body;
|
writeMinCBin = name: includes: body: writeMinC "/bin/${name}" includes body;
|
||||||
writeMinC = writeMinC;
|
writeMinC = writeMinC;
|
||||||
};
|
};
|
||||||
postgresql_15 = prev.postgresql_15 // {pkgs = prev.postgresql_15.pkgs // {
|
|
||||||
http = buildHttpExt "15";
|
|
||||||
pg_smtp_client = buildSmtpExt "15";
|
|
||||||
plhaskell = buildPlHaskellExt "15";
|
|
||||||
};};
|
|
||||||
postgresql_14 = prev.postgresql_14 // {pkgs = prev.postgresql_14.pkgs // {
|
|
||||||
http = buildHttpExt "14";
|
|
||||||
pg_smtp_client = buildSmtpExt "14";
|
|
||||||
plhaskell = buildPlHaskellExt "15";
|
|
||||||
};};
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
lib = {
|
lib = {
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
|
|
||||||
// External color mode variable declaration
|
|
||||||
extern ColorMode color_mode;
|
|
||||||
extern ColorMode debug_color_mode;
|
|
||||||
|
|
||||||
const char* color_mode_to_string(ColorMode mode);
|
|
||||||
|
|
||||||
// Function to set color mode
|
|
||||||
void set_output_color_mode(ColorMode mode);
|
|
||||||
|
|
||||||
// Macros for detecting terminal and color usage
|
|
||||||
#define IS_TERMINAL() (isatty(fileno(stderr)))
|
|
||||||
|
|
||||||
/*
|
|
||||||
* USE_COLOR() is true if color is forced or if color is auto and the output is a terminal.
|
|
||||||
* used for all colorized output
|
|
||||||
*/
|
|
||||||
#define USE_COLOR() ((color_mode == COLOR_MODE_FORCE) || (color_mode == COLOR_MODE_AUTO && IS_TERMINAL()))
|
|
||||||
|
|
||||||
/*
|
|
||||||
* DEBUG_COLOR_MODE is the color mode for debug output after USE_COLOR() check.
|
|
||||||
* used for debug colorized output
|
|
||||||
*/
|
|
||||||
#define USE_COLOR_IN_DEBUG() (color_mode == COLOR_MODE_AUTO ? ((debug_color_mode == COLOR_MODE_FORCE) || (debug_color_mode == COLOR_MODE_AUTO && IS_TERMINAL())) : USE_COLOR())
|
|
||||||
|
|
||||||
#define COLOR_RED "\033[1;31m"
|
|
||||||
#define COLOR_GREEN "\033[1;32m"
|
|
||||||
#define COLOR_YELLOW "\033[1;33m"
|
|
||||||
#define COLOR_BLUE "\033[1;34m"
|
|
||||||
#define COLOR_MAGENTA "\033[1;35m"
|
|
||||||
#define COLOR_CYAN "\033[1;36m"
|
|
||||||
#define COLOR_WHITE "\033[1;37m"
|
|
||||||
#define COLOR_RESET "\033[0m"
|
|
||||||
|
|
||||||
#define OPTIONAL_COLOR(color) (USE_COLOR() ? color : "")
|
|
||||||
#define DEBUG_COLOR(color) (USE_COLOR_IN_DEBUG() ? color : "")
|
|
||||||
|
|
||||||
">>>>"
|
|
||||||
DEBUG_COLOR(COLOR_RED) "Hello" DEBUG_COLOR(COLOR_RESET)
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
## interpolation tag
|
|
||||||
context
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "Ioan",
|
|
||||||
"person": { "name": "Oleg" },
|
|
||||||
"family": {"person": { "name": "Taras" }},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
template
|
|
||||||
```hmpl
|
|
||||||
{{name}}
|
|
||||||
{{person.name}}
|
|
||||||
{{family.person.name}}
|
|
||||||
```
|
|
||||||
|
|
||||||
result
|
|
||||||
```hmpl
|
|
||||||
Ioan
|
|
||||||
Oleg
|
|
||||||
Taras
|
|
||||||
```
|
|
||||||
|
|
||||||
## section/iteration tag
|
|
||||||
context
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"person": {"name": "persons"},
|
|
||||||
"persons": [
|
|
||||||
{"name": "Ioan", "number": 2},
|
|
||||||
{"name": "Oleg", "number": 1},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
template # raise_exception
|
|
||||||
```hmpl
|
|
||||||
{{#{{person.name}} p}}
|
|
||||||
{{p.name}} is {{p.number}}
|
|
||||||
{{/persons}}
|
|
||||||
```
|
|
||||||
|
|
||||||
result
|
|
||||||
```hmpl
|
|
||||||
Ioan is 2
|
|
||||||
Oleg is 1
|
|
||||||
```
|
|
||||||
|
|
||||||
## include tag
|
|
||||||
json
|
|
||||||
```json
|
|
||||||
```
|
|
||||||
|
|
||||||
template
|
|
||||||
```hmpl
|
|
||||||
{{>template_name}}
|
|
||||||
```
|
|
||||||
|
|
||||||
result
|
|
||||||
```hmpl
|
|
||||||
```
|
|
||||||
|
|
||||||
## Order
|
|
||||||
used plain render
|
|
||||||
interpolation->section->include
|
|
||||||
|
|
||||||
so you cannot render interpolation in interpolation or section
|
|
||||||
|
|
||||||
Not allowed:
|
|
||||||
```hmpl
|
|
||||||
{{name_{{subname}}}}
|
|
||||||
{{#array_{{subname}}}}
|
|
||||||
```
|
|
||||||
|
|
||||||
But:
|
|
||||||
```hmpl
|
|
||||||
{{>{{template_name}}}}
|
|
||||||
```
|
|
||||||
allowed;
|
|
||||||
|
|
||||||
№ эксепшн на нераскрытые
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
{ stdenv, gcc, lib, c-hectic, bash }:
|
|
||||||
|
|
||||||
stdenv.mkDerivation {
|
|
||||||
pname = "hmpl";
|
|
||||||
version = "1.0";
|
|
||||||
src = ./.;
|
|
||||||
doCheck = true;
|
|
||||||
|
|
||||||
buildInputs = [ c-hectic ];
|
|
||||||
nativeBuildInputs = [ gcc ];
|
|
||||||
|
|
||||||
buildPhase = ''
|
|
||||||
${bash}/bin/sh ./make.sh build
|
|
||||||
'';
|
|
||||||
|
|
||||||
checkPhase = ''
|
|
||||||
${bash}/bin/sh ./make.sh check
|
|
||||||
'';
|
|
||||||
|
|
||||||
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
|
|
||||||
'';
|
|
||||||
|
|
||||||
meta = {
|
|
||||||
description = "hmpl";
|
|
||||||
license = lib.licenses.mit;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,273 +0,0 @@
|
|||||||
#include "hmpl.h"
|
|
||||||
|
|
||||||
Json *eval_object(Arena *arena, const Json * const context, const char * const query) {
|
|
||||||
raise_debug("eval_object(%p, %s, %s)", arena, json_to_string(DISPOSABLE_ARENA, context), query);
|
|
||||||
if (!context || !query) return NULL;
|
|
||||||
|
|
||||||
const Json *res = context;
|
|
||||||
char *dot, *key = arena_strdup(arena, query);
|
|
||||||
|
|
||||||
while ((dot = strchr(key, '.')) != NULL) {
|
|
||||||
*dot = '\0';
|
|
||||||
raise_debug("eval_object: key: %s", key);
|
|
||||||
res = json_get_object_item(res, key);
|
|
||||||
if (!res)
|
|
||||||
return NULL;
|
|
||||||
key = dot + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
raise_debug("eval_object: final key: %s", key);
|
|
||||||
return json_get_object_item(res, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
char *eval_string(Arena *arena, const Json * const context, const char * const query) {
|
|
||||||
Json *res = eval_object(arena, context, query);
|
|
||||||
if (!res)
|
|
||||||
return NULL;
|
|
||||||
return json_to_string_with_opts(arena, res, JSON_RAW);
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{[prefix]key}}
|
|
||||||
void hmpl_render_interpolation_tags(Arena *arena, char **text_ptr, const Json * const context, const char * const prefix) {
|
|
||||||
raise_debug("hmpl_render_interpolation_tags(%p, %s, %s)", arena, *text_ptr, prefix);
|
|
||||||
char start_pattern[256];
|
|
||||||
snprintf(start_pattern, sizeof(start_pattern), "{{%s", prefix);
|
|
||||||
int start_pattern_length = strlen(start_pattern);
|
|
||||||
int offset = 0;
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
char *current_text = *text_ptr;
|
|
||||||
char *start = strstr(current_text + offset, start_pattern);
|
|
||||||
if (!start)
|
|
||||||
break;
|
|
||||||
|
|
||||||
int start_index = start - current_text;
|
|
||||||
int key_start = start_index + start_pattern_length;
|
|
||||||
|
|
||||||
char *end = strstr(start, "}}");
|
|
||||||
if (!end)
|
|
||||||
raise_exception("Malformed template: missing closing braces for interpolation tag");
|
|
||||||
int key_length = (end - current_text) - key_start;
|
|
||||||
char *key = arena_alloc(arena, key_length + 1);
|
|
||||||
substr_clone(current_text, key, key_start, key_length);
|
|
||||||
|
|
||||||
char *replacement = eval_string(arena, context, key);
|
|
||||||
if (!replacement) {
|
|
||||||
raise_debug("no replacement for key: `%s`", key);
|
|
||||||
offset = (end - current_text) + 2;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the replacement length from the beginning of {{[prefix] to the end }}
|
|
||||||
int replace_length = (end - start) + 2; // +2 for "}}"
|
|
||||||
|
|
||||||
char *new_text = arena_repstr(arena, current_text,
|
|
||||||
start_index,
|
|
||||||
replace_length,
|
|
||||||
replacement);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
*text_ptr = new_text;
|
|
||||||
offset = start_index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void hmpl_render_interpolation_tags_opts(Arena *arena, char **text_ptr, const Json *context, const HmplInterpolationTagsOptions *options) {
|
|
||||||
hmpl_render_interpolation_tags(arena, text_ptr, context, options->prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
// {{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, <optimized>, %s, %s, %s)", arena, *text_ptr, prefix_start, prefix_end, separator_pattern);
|
|
||||||
|
|
||||||
// prefix_start and prefix_end must be different
|
|
||||||
assert(strcmp(prefix_start, prefix_end) != 0);
|
|
||||||
|
|
||||||
// prefix_start, prefix_end and separator_pattern must be less than 28 characters
|
|
||||||
assert(strlen(prefix_start) < 28);
|
|
||||||
assert(0 < strlen(separator_pattern) && strlen(prefix_end) < 28);
|
|
||||||
assert(0 < strlen(separator_pattern) && strlen(separator_pattern) < 28);
|
|
||||||
|
|
||||||
// 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));
|
|
||||||
|
|
||||||
|
|
||||||
// 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");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create slice for the text
|
|
||||||
Slice text_slice = slice_create(char, *text_ptr, strlen(*text_ptr), 0, strlen(*text_ptr));
|
|
||||||
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;
|
|
||||||
|
|
||||||
// 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 - strlen(start_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);
|
|
||||||
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract key (now after separator)
|
|
||||||
size_t key_start = strlen(start_slice.data);
|
|
||||||
size_t key_length = opening_tag_end - (char*)after_separator.data - strlen(start_slice.data);
|
|
||||||
char *key = arena_alloc(arena, key_length + 1);
|
|
||||||
substr_clone((char*)after_separator.data, key, key_start, key_length);
|
|
||||||
|
|
||||||
key[key_length] = '\0';
|
|
||||||
|
|
||||||
// +2 for "{{" and "}}" + 1 for null terminator
|
|
||||||
size_t close_tag_pattern_length = key_length + strlen(prefix_end) + 2 + 2 + 1;
|
|
||||||
char *close_tag_pattern = arena_alloc(arena, close_tag_pattern_length);
|
|
||||||
snprintf(
|
|
||||||
close_tag_pattern,
|
|
||||||
close_tag_pattern_length,
|
|
||||||
"{{%s%s}}",
|
|
||||||
prefix_end,
|
|
||||||
key
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
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 directly searching for the closing tag pattern
|
|
||||||
char *close_tag = strstr(after_opening_slice.data, close_tag_pattern);
|
|
||||||
|
|
||||||
|
|
||||||
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_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, 0, 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);
|
|
||||||
|
|
||||||
raise_debug("Processing element with prefix: %s", prefix);
|
|
||||||
raise_debug("Block before processing: %s", block);
|
|
||||||
|
|
||||||
hmpl_render_interpolation_tags(arena, &block, elem, prefix);
|
|
||||||
raise_debug("Block after interpolation: %s", block);
|
|
||||||
|
|
||||||
// Recursively process nested sections
|
|
||||||
hmpl_render_section_tags(arena, &block, elem, prefix_start, prefix_end, separator_pattern);
|
|
||||||
raise_debug("Block after section processing: %s", block);
|
|
||||||
|
|
||||||
size_t block_len = strlen(block);
|
|
||||||
memcpy(replacement + replacement_offset, block, block_len);
|
|
||||||
replacement_offset += block_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
replacement[replacement_offset] = '\0';
|
|
||||||
|
|
||||||
|
|
||||||
// Calculate replacement positions
|
|
||||||
size_t replace_start = start_index;
|
|
||||||
size_t replace_length = (close_tag - opening_tag_start) + strlen(close_tag_pattern);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void hmpl_render_section_tags_opts(Arena *arena, char **text_ptr, const Json *context, const HmplSectionTagsOptions *options) {
|
|
||||||
// Create a copy of the context without const qualifier for compatibility with hmpl_render_section_tags
|
|
||||||
hmpl_render_section_tags(arena, text_ptr, (Json*)context, options->prefix_start, options->prefix_end, options->separator_pattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
void hmpl_render_with_arena(Arena *arena, char **text, const Json * const context, const HmplOptions * const options) {
|
|
||||||
if (context->type != JSON_OBJECT) {
|
|
||||||
raise_exception("Malformed context: context is not json");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
hmpl_render_interpolation_tags_opts(arena, text, context, &options->interpolation_tags_options);
|
|
||||||
hmpl_render_section_tags_opts(arena, text, context, &options->section_tags_options);
|
|
||||||
}
|
|
||||||
|
|
||||||
void hmpl_render(char **text, const Json * const context, const HmplOptions * const options) {
|
|
||||||
Arena arena = arena_init(MEM_MiB);
|
|
||||||
|
|
||||||
hmpl_render_with_arena(&arena, text, context, options);
|
|
||||||
|
|
||||||
arena_free(&arena);
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
#ifndef EPRINTF_HMPL
|
|
||||||
#define EPRINTF_HMPL
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include "hectic.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char *prefix_start;
|
|
||||||
char *prefix_end;
|
|
||||||
char *separator_pattern;
|
|
||||||
} HmplSectionTagsOptions;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char *prefix;
|
|
||||||
} HmplInterpolationTagsOptions;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
HmplSectionTagsOptions section_tags_options;
|
|
||||||
HmplInterpolationTagsOptions interpolation_tags_options;
|
|
||||||
} HmplOptions;
|
|
||||||
|
|
||||||
static const HmplOptions DEFAULT_OPTIONS = {
|
|
||||||
.section_tags_options = {
|
|
||||||
.prefix_start = "",
|
|
||||||
.prefix_end = "/",
|
|
||||||
.separator_pattern = "#"
|
|
||||||
},
|
|
||||||
.interpolation_tags_options = {
|
|
||||||
.prefix = ""
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void init_cjson_with_arenas(Arena *arena);
|
|
||||||
|
|
||||||
char *eval_string(Arena *arena, const Json * const context, const char * const key);
|
|
||||||
|
|
||||||
/* Modified: text is passed by reference so we can update it and free old allocations */
|
|
||||||
void hmpl_render_interpolation_tags(Arena *arena, char **text_ptr, const Json * const context, const char * const prefix);
|
|
||||||
|
|
||||||
void hmpl_render_interpolation_tags_opts(Arena *arena, char **text_ptr, const Json *context, const HmplInterpolationTagsOptions *options);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
void hmpl_render_section_tags_opts(Arena *arena, char **text_ptr, const Json *context, const HmplSectionTagsOptions *options);
|
|
||||||
|
|
||||||
void hmpl_render_with_arena(Arena *arena, char **text, const Json * const context, const HmplOptions * const options);
|
|
||||||
|
|
||||||
void hmpl_render(char **text, const Json * const context, const HmplOptions * const options);
|
|
||||||
|
|
||||||
#endif // EPRINTF_HMPL
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include "hectic.h"
|
|
||||||
#include "hmpl.h"
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
init_logger();
|
|
||||||
raise_info("start");
|
|
||||||
|
|
||||||
Arena arena = arena_init(MEM_MiB);
|
|
||||||
|
|
||||||
raise_info("read the arguments");
|
|
||||||
char *text = NULL;
|
|
||||||
|
|
||||||
const char *json_input = (argc > 1 ? argv[1] : "{}");
|
|
||||||
Json *context = json_parse(&arena, &json_input);
|
|
||||||
|
|
||||||
if (!context) {
|
|
||||||
fprintf(stderr, "Error parsing JSON\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argc > 2) {
|
|
||||||
text = arena_strdup(&arena, argv[2]);
|
|
||||||
} else if (!isatty(fileno(stdin))) {
|
|
||||||
size_t size = 0;
|
|
||||||
char *heap_text = NULL;
|
|
||||||
ssize_t len = getdelim(&heap_text, &size, '\0', stdin);
|
|
||||||
if (len < 0) {
|
|
||||||
perror("read stdin");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
text = arena_strdup(&arena, heap_text);
|
|
||||||
free(heap_text); // free temporary heap allocation
|
|
||||||
} else {
|
|
||||||
text = arena_strdup(&arena, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
hmpl_render_with_arena(&arena, &text, context, &DEFAULT_OPTIONS);
|
|
||||||
printf("%s", text);
|
|
||||||
|
|
||||||
arena_free(&arena);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# 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).
|
|
||||||
# --color Pass -fdiagnostics-color=always to compiler.
|
|
||||||
# help, --help Show this help message.
|
|
||||||
|
|
||||||
check_dependencies() {
|
|
||||||
for dep in cc ar pager; do
|
|
||||||
if ! command -v "$dep" >/dev/null 2>&1; then
|
|
||||||
echo "Error: Required dependency '$dep' not found." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
check_dependencies
|
|
||||||
|
|
||||||
print_help() {
|
|
||||||
cat <<EOF
|
|
||||||
Usage: $0 [build|check] [--norun] [--debug] [--color]
|
|
||||||
build Build the library and app (default).
|
|
||||||
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 debug flags (-O0).
|
|
||||||
--color Force colored compiler diagnostics.
|
|
||||||
help, --help Display this help message.
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# Show help if requested
|
|
||||||
case "$1" in
|
|
||||||
help|--help)
|
|
||||||
print_help
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Default flags
|
|
||||||
RUN_TESTS=1
|
|
||||||
OPTFLAGS="-O2"
|
|
||||||
CFLAGS="-Wall -Wextra -Werror -pedantic -fsanitize=address"
|
|
||||||
LDFLAGS="-lhectic"
|
|
||||||
STD_FLAGS="-std=c99"
|
|
||||||
COLOR_FLAG=""
|
|
||||||
|
|
||||||
MODE="${1:-build}"
|
|
||||||
shift
|
|
||||||
|
|
||||||
# Process options
|
|
||||||
while [ $# -gt 0 ]; do
|
|
||||||
case "$1" in
|
|
||||||
--norun)
|
|
||||||
RUN_TESTS=0
|
|
||||||
;;
|
|
||||||
--debug)
|
|
||||||
OPTFLAGS="-O0"
|
|
||||||
;;
|
|
||||||
--color)
|
|
||||||
COLOR_FLAG="-fdiagnostics-color=always"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Unknown option: $1"
|
|
||||||
print_help
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
shift
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -n "$COLOR_FLAG" ]; then
|
|
||||||
CFLAGS="$CFLAGS $COLOR_FLAG"
|
|
||||||
fi
|
|
||||||
|
|
||||||
build() {
|
|
||||||
mkdir -p target
|
|
||||||
echo "# Build library"
|
|
||||||
# shellcheck disable=SC2086
|
|
||||||
cc $CFLAGS $OPTFLAGS $STD_FLAGS -c hmpl.c -lhectic -o target/hmpl.o
|
|
||||||
ar rcs target/libhmpl.a target/hmpl.o
|
|
||||||
|
|
||||||
echo "# Build app"
|
|
||||||
# shellcheck disable=SC2086
|
|
||||||
cc $CFLAGS $OPTFLAGS main.c -Ltarget -lhmpl $LDFLAGS -o target/hmpl
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$MODE" in
|
|
||||||
watch)
|
|
||||||
find . -type d | nix run .#watch -- 'sh ./make.sh build && sh ./make.sh check' -i -p '*.c' -p '*.h' 2>&1
|
|
||||||
;;
|
|
||||||
build)
|
|
||||||
build
|
|
||||||
;;
|
|
||||||
run)
|
|
||||||
build && ./target/hmpl
|
|
||||||
;;
|
|
||||||
check)
|
|
||||||
mkdir -p target/test
|
|
||||||
for test_file in test/*.c; do
|
|
||||||
exe="target/test/$(basename "${test_file%.c}")"
|
|
||||||
# shellcheck disable=SC2086
|
|
||||||
cc $CFLAGS $OPTFLAGS -pedantic -I. "$test_file" -Ltarget -lhmpl $LDFLAGS -o "$exe"
|
|
||||||
if [ "$RUN_TESTS" -eq 1 ]; then
|
|
||||||
LOG_LEVEL=TRACE ./"$exe"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
print_help
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,266 +0,0 @@
|
|||||||
#include <assert.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "hmpl.h"
|
|
||||||
#include "hectic.h"
|
|
||||||
|
|
||||||
// --------------------------------
|
|
||||||
// -- Single key evaluation test --
|
|
||||||
// --------------------------------
|
|
||||||
|
|
||||||
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_notice("Context: %s", json_to_string(arena, context));
|
|
||||||
raise_notice("Query: name");
|
|
||||||
raise_notice("Result: %s", result);
|
|
||||||
assert(result && strcmp(result, "world") == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------
|
|
||||||
// -- Nested key evaluation test --
|
|
||||||
// --------------------------------
|
|
||||||
|
|
||||||
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", json_to_string(arena, context));
|
|
||||||
raise_notice("Query: person.name");
|
|
||||||
raise_notice("Result: %s", result);
|
|
||||||
assert(result && strcmp(result, "Alice") == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------
|
|
||||||
// -- Interpolation tags test --
|
|
||||||
// -----------------------------
|
|
||||||
|
|
||||||
#define TEST_DATA_INTERPOLATION_CONTEXT \
|
|
||||||
"{\n" \
|
|
||||||
" \"persona\": {\n" \
|
|
||||||
" \"name\": \"John\",\n" \
|
|
||||||
" \"surname\": \"Doe\",\n" \
|
|
||||||
" \"address\": {\n" \
|
|
||||||
" \"home\": {\n" \
|
|
||||||
" \"street\": \"123 Main St\",\n" \
|
|
||||||
" \"city\": \"Springfield\",\n" \
|
|
||||||
" \"zip\": \"12345\"\n" \
|
|
||||||
" },\n" \
|
|
||||||
" \"work\": {\n" \
|
|
||||||
" \"street\": \"456 Business Rd\",\n" \
|
|
||||||
" \"city\": \"Metropolis\",\n" \
|
|
||||||
" \"zip\": \"67890\"\n" \
|
|
||||||
" }\n" \
|
|
||||||
" },\n" \
|
|
||||||
" \"contact\": {\n" \
|
|
||||||
" \"email\": \"john@example.com\",\n" \
|
|
||||||
" \"phone\": {\n" \
|
|
||||||
" \"home\": \"555-1234\",\n" \
|
|
||||||
" \"mobile\": \"555-5678\"\n" \
|
|
||||||
" }\n" \
|
|
||||||
" }\n" \
|
|
||||||
" }\n" \
|
|
||||||
"}"
|
|
||||||
|
|
||||||
#define TEST_DATA_INTERPOLATION_TEMPLATE \
|
|
||||||
"Hello {{persona.name}} {{persona.surname}},\n" \
|
|
||||||
"\n" \
|
|
||||||
"Your home address:\n" \
|
|
||||||
"{{persona.address.home.street}},\n" \
|
|
||||||
"{{persona.address.home.city}},\n" \
|
|
||||||
"{{persona.address.home.zip}}\n" \
|
|
||||||
"\n" \
|
|
||||||
"Your work address:\n" \
|
|
||||||
"{{persona.address.work.street}},\n" \
|
|
||||||
"{{persona.address.work.city}},\n" \
|
|
||||||
"{{persona.address.work.zip}}\n" \
|
|
||||||
"\n" \
|
|
||||||
"Contact information:\n" \
|
|
||||||
"Email: {{persona.contact.email}}\n" \
|
|
||||||
"Home Phone: {{persona.contact.phone.home}}\n" \
|
|
||||||
"Mobile Phone: {{persona.contact.phone.mobile}}\n"
|
|
||||||
|
|
||||||
#define TEST_DATA_INTERPOLATION_RESULT \
|
|
||||||
"Hello John Doe,\n" \
|
|
||||||
"\n" \
|
|
||||||
"Your home address:\n" \
|
|
||||||
"123 Main St,\n" \
|
|
||||||
"Springfield,\n" \
|
|
||||||
"12345\n" \
|
|
||||||
"\n" \
|
|
||||||
"Your work address:\n" \
|
|
||||||
"456 Business Rd,\n" \
|
|
||||||
"Metropolis,\n" \
|
|
||||||
"67890\n" \
|
|
||||||
"\n" \
|
|
||||||
"Contact information:\n" \
|
|
||||||
"Email: john@example.com\n" \
|
|
||||||
"Home Phone: 555-1234\n" \
|
|
||||||
"Mobile Phone: 555-5678\n"
|
|
||||||
|
|
||||||
void test_render_interpolation_tags(Arena *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_notice("Result:\n%s", text);
|
|
||||||
assert(strcmp(text, TEST_DATA_INTERPOLATION_RESULT) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------------------
|
|
||||||
// -- Interpolation tags test with prefix `{{.name}}` --
|
|
||||||
// -----------------------------------------------------
|
|
||||||
|
|
||||||
#define TEST_DATA_INTERPOLATION_WITH_PREFIX_CONTEXT \
|
|
||||||
TEST_DATA_INTERPOLATION_CONTEXT
|
|
||||||
|
|
||||||
#define TEST_DATA_INTERPOLATION_WITH_PREFIX_TEMPLATE \
|
|
||||||
"Hello {{.persona.name}} {{.persona.surname}},\n" \
|
|
||||||
"\n" \
|
|
||||||
"Your home address:\n" \
|
|
||||||
"{{.persona.address.home.street}},\n" \
|
|
||||||
"{{.persona.address.home.city}},\n" \
|
|
||||||
"{{.persona.address.home.zip}}\n" \
|
|
||||||
"\n" \
|
|
||||||
"Your work address:\n" \
|
|
||||||
"{{.persona.address.work.street}},\n" \
|
|
||||||
"{{.persona.address.work.city}},\n" \
|
|
||||||
"{{.persona.address.work.zip}}\n" \
|
|
||||||
"\n" \
|
|
||||||
"Contact information:\n" \
|
|
||||||
"Email: {{.persona.contact.email}}\n" \
|
|
||||||
"Home Phone: {{.persona.contact.phone.home}}\n" \
|
|
||||||
"Mobile Phone: {{.persona.contact.phone.mobile}}\n"
|
|
||||||
|
|
||||||
#define TEST_DATA_INTERPOLATION_WITH_PREFIX_RESULT \
|
|
||||||
TEST_DATA_INTERPOLATION_RESULT
|
|
||||||
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------
|
|
||||||
// -- Section tags test `{{#element array}}...{{/array}}` --
|
|
||||||
// ----------------------------------------------------------
|
|
||||||
|
|
||||||
#define TEST_DATA_SIMPLE_SECTION_ITERATION_CONTEXT \
|
|
||||||
"{" \
|
|
||||||
" \"array\": [" \
|
|
||||||
" { \"field\": { \"subfield\": \"value1\" } }," \
|
|
||||||
" { \"field\": { \"subfield\": \"value2\" } }," \
|
|
||||||
" { \"field\": { \"subfield\": \"value3\" } }" \
|
|
||||||
" ]" \
|
|
||||||
"}"
|
|
||||||
|
|
||||||
#define TEST_DATA_SIMPLE_SECTION_ITERATION_TEMPLATE \
|
|
||||||
"{{#element array}}" \
|
|
||||||
" {{element.field.subfield}}" \
|
|
||||||
"{{/array}}"
|
|
||||||
|
|
||||||
#define TEST_DATA_SIMPLE_SECTION_ITERATION_RESULT \
|
|
||||||
" value1 value2 value3"
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
// -- Section tags test `{{user@users}}...{{#users}}` --
|
|
||||||
// ------------------------------------------------------------
|
|
||||||
|
|
||||||
#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_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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------
|
|
||||||
// -- Main test function --
|
|
||||||
// ------------------------
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
raise_notice("All tests passed successfully");
|
|
||||||
arena_free(&arena);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,313 +0,0 @@
|
|||||||
#include "../hmpl.h"
|
|
||||||
#include "hectic.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
const HmplOptions options = {
|
|
||||||
.section_tags_options = {
|
|
||||||
.prefix_start = "",
|
|
||||||
.prefix_end = "/",
|
|
||||||
.separator_pattern = "#"
|
|
||||||
},
|
|
||||||
.interpolation_tags_options = {
|
|
||||||
.prefix = ""
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function for comparing results
|
|
||||||
void assert_rendered(const char* template, const char* expected, const char* json_str) {
|
|
||||||
Arena arena = arena_init(MEM_KiB);
|
|
||||||
|
|
||||||
// Parse JSON string into an object
|
|
||||||
Json *json = json_parse(&arena, &json_str);
|
|
||||||
if (!json) {
|
|
||||||
printf("ERROR: Failed to parse JSON: %s\n", json_str);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a copy of the template for modification
|
|
||||||
char *result = arena_strdup(&arena, template);
|
|
||||||
|
|
||||||
// Render the template
|
|
||||||
hmpl_render_with_arena(&arena, &result, json, &options);
|
|
||||||
|
|
||||||
// Check the result
|
|
||||||
if (strcmp(result, expected) != 0) {
|
|
||||||
printf("ERROR:\n");
|
|
||||||
printf("Template: %s\n", template);
|
|
||||||
printf("Expected: %s\n", expected);
|
|
||||||
printf("Received: %s\n", result);
|
|
||||||
exit(1);
|
|
||||||
} else {
|
|
||||||
printf("SUCCESS: Template correctly rendered\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
arena_free(&arena);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------
|
|
||||||
// -- Simple section tag with surrounding text --
|
|
||||||
// --------------------------------------------------
|
|
||||||
|
|
||||||
#define TEST_SIMPLE_SECTION_CONTEXT \
|
|
||||||
"{" \
|
|
||||||
" \"users\": [" \
|
|
||||||
" {\"name\": \"John\", \"age\": 30}," \
|
|
||||||
" {\"name\": \"Mary\", \"age\": 25}," \
|
|
||||||
" {\"name\": \"Alex\", \"age\": 35}" \
|
|
||||||
" ]," \
|
|
||||||
" \"count\": 3" \
|
|
||||||
"}"
|
|
||||||
|
|
||||||
#define TEST_SIMPLE_SECTION_TEMPLATE \
|
|
||||||
"User list:\n" \
|
|
||||||
"{{item#users}}<li>{{item.name}}, age: {{item.age}}</li>{{/users}}\n" \
|
|
||||||
"Total users: {{count}}"
|
|
||||||
|
|
||||||
#define TEST_SIMPLE_SECTION_RESULT \
|
|
||||||
"User list:\n" \
|
|
||||||
"<li>John, age: 30</li><li>Mary, age: 25</li><li>Alex, age: 35</li>\n" \
|
|
||||||
"Total users: 3"
|
|
||||||
|
|
||||||
void test_simple_section_tags(Arena *arena) {
|
|
||||||
raise_notice("Testing simple section tag with surrounding text");
|
|
||||||
const char *context_text = arena_strdup(arena, TEST_SIMPLE_SECTION_CONTEXT);
|
|
||||||
Json *context = json_parse(arena, &context_text);
|
|
||||||
if (!context) { raise_exception("Malformed json"); exit(1); }
|
|
||||||
|
|
||||||
char *text = arena_strdup(arena, TEST_SIMPLE_SECTION_TEMPLATE);
|
|
||||||
raise_notice("Template:\n%s", text);
|
|
||||||
raise_notice("Context: %s", json_to_string(arena, context));
|
|
||||||
|
|
||||||
hmpl_render_with_arena(arena, &text, context, &options);
|
|
||||||
raise_notice("Result:\n%s", text);
|
|
||||||
|
|
||||||
assert(strcmp(text, TEST_SIMPLE_SECTION_RESULT) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------
|
|
||||||
// -- Nested section tags --
|
|
||||||
// -----------------------------------
|
|
||||||
|
|
||||||
#define TEST_NESTED_SECTION_CONTEXT \
|
|
||||||
"{" \
|
|
||||||
" \"department\": \"Development\"," \
|
|
||||||
" \"teams\": [" \
|
|
||||||
" {" \
|
|
||||||
" \"name\": \"Frontend\"," \
|
|
||||||
" \"members\": [" \
|
|
||||||
" {\"name\": \"John\", \"role\": \"Developer\"}," \
|
|
||||||
" {\"name\": \"Mary\", \"role\": \"Designer\"}" \
|
|
||||||
" ]" \
|
|
||||||
" }," \
|
|
||||||
" {" \
|
|
||||||
" \"name\": \"Backend\"," \
|
|
||||||
" \"members\": [" \
|
|
||||||
" {\"name\": \"Alex\", \"role\": \"Developer\"}," \
|
|
||||||
" {\"name\": \"Helen\", \"role\": \"Tester\"}" \
|
|
||||||
" ]" \
|
|
||||||
" }" \
|
|
||||||
" ]" \
|
|
||||||
"}"
|
|
||||||
|
|
||||||
#define TEST_NESTED_SECTION_TEMPLATE \
|
|
||||||
"Department: {{department}}\n" \
|
|
||||||
"{{item#teams}}Team: {{item.name}}\n" \
|
|
||||||
" {{item#item.members}}Member: {{item.name}} ({{item.role}}){{/item.members}}\n" \
|
|
||||||
"{{/teams}}"
|
|
||||||
|
|
||||||
#define TEST_NESTED_SECTION_RESULT \
|
|
||||||
"Department: Development\n" \
|
|
||||||
"Team: Frontend\n" \
|
|
||||||
" Member: John (Developer)Member: Mary (Designer)\n" \
|
|
||||||
"Team: Backend\n" \
|
|
||||||
" Member: Alex (Developer)Member: Helen (Tester)\n"
|
|
||||||
|
|
||||||
void test_nested_section_tags(Arena *arena) {
|
|
||||||
raise_notice("Testing nested section tags");
|
|
||||||
const char *context_text = arena_strdup(arena, TEST_NESTED_SECTION_CONTEXT);
|
|
||||||
Json *context = json_parse(arena, &context_text);
|
|
||||||
if (!context) { raise_exception("Malformed json"); exit(1); }
|
|
||||||
|
|
||||||
char *text = arena_strdup(arena, TEST_NESTED_SECTION_TEMPLATE);
|
|
||||||
raise_notice("Template:\n%s", text);
|
|
||||||
raise_notice("Context: %s", json_to_string(arena, context));
|
|
||||||
|
|
||||||
hmpl_render_with_arena(arena, &text, context, &options);
|
|
||||||
raise_notice("Result:\n%s", text);
|
|
||||||
|
|
||||||
assert(strcmp(text, TEST_NESTED_SECTION_RESULT) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------
|
|
||||||
// -- Empty array in section tag --
|
|
||||||
// -----------------------------------
|
|
||||||
|
|
||||||
#define TEST_EMPTY_ARRAY_CONTEXT \
|
|
||||||
"{" \
|
|
||||||
" \"tasks\": []" \
|
|
||||||
"}"
|
|
||||||
|
|
||||||
#define TEST_EMPTY_ARRAY_TEMPLATE \
|
|
||||||
"Tasks: {{item#tasks}}ID: {{item.id}} - {{item.description}}{{/tasks}}"
|
|
||||||
|
|
||||||
#define TEST_EMPTY_ARRAY_RESULT \
|
|
||||||
"Tasks: "
|
|
||||||
|
|
||||||
void test_empty_array_section_tags(Arena *arena) {
|
|
||||||
raise_notice("Testing empty array in section tag");
|
|
||||||
const char *context_text = arena_strdup(arena, TEST_EMPTY_ARRAY_CONTEXT);
|
|
||||||
Json *context = json_parse(arena, &context_text);
|
|
||||||
if (!context) { raise_exception("Malformed json"); exit(1); }
|
|
||||||
|
|
||||||
char *text = arena_strdup(arena, TEST_EMPTY_ARRAY_TEMPLATE);
|
|
||||||
raise_notice("Template:\n%s", text);
|
|
||||||
raise_notice("Context: %s", json_to_string(arena, context));
|
|
||||||
|
|
||||||
hmpl_render_with_arena(arena, &text, context, &options);
|
|
||||||
raise_notice("Result:\n%s", text);
|
|
||||||
|
|
||||||
assert(strcmp(text, TEST_EMPTY_ARRAY_RESULT) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------
|
|
||||||
// -- HTML template with section tags --
|
|
||||||
// -----------------------------------
|
|
||||||
|
|
||||||
#define TEST_HTML_CONTEXT \
|
|
||||||
"{" \
|
|
||||||
" \"title\": \"My List\"," \
|
|
||||||
" \"items\": [" \
|
|
||||||
" {\"name\": \"Item 1\", \"type\": \"important\"}," \
|
|
||||||
" {\"name\": \"Item 2\", \"type\": \"normal\"}," \
|
|
||||||
" {\"name\": \"Item 3\", \"type\": \"normal\"}" \
|
|
||||||
" ]," \
|
|
||||||
" \"footer\": \"© 2023\"" \
|
|
||||||
"}"
|
|
||||||
|
|
||||||
#define TEST_HTML_TEMPLATE \
|
|
||||||
"<!DOCTYPE html>\n" \
|
|
||||||
"<html>\n" \
|
|
||||||
"<head>\n" \
|
|
||||||
" <title>{{title}}</title>\n" \
|
|
||||||
"</head>\n" \
|
|
||||||
"<body>\n" \
|
|
||||||
" <h1>{{title}}</h1>\n" \
|
|
||||||
" <ul>\n" \
|
|
||||||
" {{item#items}}<li class=\"{{item.type}}\">{{item.name}}</li>{{/items}}\n" \
|
|
||||||
" </ul>\n" \
|
|
||||||
" <footer>{{footer}}</footer>\n" \
|
|
||||||
"</body>\n" \
|
|
||||||
"</html>"
|
|
||||||
|
|
||||||
#define TEST_HTML_RESULT \
|
|
||||||
"<!DOCTYPE html>\n" \
|
|
||||||
"<html>\n" \
|
|
||||||
"<head>\n" \
|
|
||||||
" <title>My List</title>\n" \
|
|
||||||
"</head>\n" \
|
|
||||||
"<body>\n" \
|
|
||||||
" <h1>My List</h1>\n" \
|
|
||||||
" <ul>\n" \
|
|
||||||
" <li class=\"important\">Item 1</li><li class=\"normal\">Item 2</li><li class=\"normal\">Item 3</li>\n" \
|
|
||||||
" </ul>\n" \
|
|
||||||
" <footer>© 2023</footer>\n" \
|
|
||||||
"</body>\n" \
|
|
||||||
"</html>"
|
|
||||||
|
|
||||||
void test_html_section_tags(Arena *arena) {
|
|
||||||
raise_notice("Testing HTML template with section tags");
|
|
||||||
const char *context_text = arena_strdup(arena, TEST_HTML_CONTEXT);
|
|
||||||
Json *context = json_parse(arena, &context_text);
|
|
||||||
if (!context) { raise_exception("Malformed json"); exit(1); }
|
|
||||||
|
|
||||||
char *text = arena_strdup(arena, TEST_HTML_TEMPLATE);
|
|
||||||
raise_notice("Template:\n%s", text);
|
|
||||||
raise_notice("Context: %s", json_to_string(arena, context));
|
|
||||||
|
|
||||||
hmpl_render_with_arena(arena, &text, context, &options);
|
|
||||||
raise_notice("Result:\n%s", text);
|
|
||||||
|
|
||||||
assert(strcmp(text, TEST_HTML_RESULT) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------
|
|
||||||
// -- Report with nested sections --
|
|
||||||
// -----------------------------------
|
|
||||||
|
|
||||||
#define TEST_REPORT_CONTEXT \
|
|
||||||
"{" \
|
|
||||||
" \"period\": \"March 2023\"," \
|
|
||||||
" \"data\": [" \
|
|
||||||
" {" \
|
|
||||||
" \"title\": \"Sales\"," \
|
|
||||||
" \"value\": 1000," \
|
|
||||||
" \"details\": [" \
|
|
||||||
" {\"name\": \"Product A\", \"value\": 500}," \
|
|
||||||
" {\"name\": \"Product B\", \"value\": 300}," \
|
|
||||||
" {\"name\": \"Product C\", \"value\": 200}" \
|
|
||||||
" ]" \
|
|
||||||
" }," \
|
|
||||||
" {" \
|
|
||||||
" \"title\": \"Expenses\"," \
|
|
||||||
" \"value\": 700," \
|
|
||||||
" \"details\": [" \
|
|
||||||
" {\"name\": \"Rent\", \"value\": 300}," \
|
|
||||||
" {\"name\": \"Salary\", \"value\": 400}" \
|
|
||||||
" ]" \
|
|
||||||
" }" \
|
|
||||||
" ]," \
|
|
||||||
" \"summary\": 300" \
|
|
||||||
"}"
|
|
||||||
|
|
||||||
#define TEST_REPORT_TEMPLATE \
|
|
||||||
"Report for {{period}}\n\n" \
|
|
||||||
"{{row#data}}* {{row.title}}: {{row.value}}\n" \
|
|
||||||
" {{detail#row.details}} - {{detail.name}}: {{detail.value}}\n{{/row.details}}\n" \
|
|
||||||
"{{/data}}\n" \
|
|
||||||
"Total: {{summary}}"
|
|
||||||
|
|
||||||
#define TEST_REPORT_RESULT \
|
|
||||||
"Report for March 2023\n\n" \
|
|
||||||
"* Sales: 1000\n" \
|
|
||||||
" - Product A: 500\n - Product B: 300\n - Product C: 200\n\n" \
|
|
||||||
"* Expenses: 700\n" \
|
|
||||||
" - Rent: 300\n - Salary: 400\n\n" \
|
|
||||||
"Total: 300"
|
|
||||||
|
|
||||||
void test_report_section_tags(Arena *arena) {
|
|
||||||
raise_notice("Testing report with nested sections");
|
|
||||||
const char *context_text = arena_strdup(arena, TEST_REPORT_CONTEXT);
|
|
||||||
Json *context = json_parse(arena, &context_text);
|
|
||||||
if (!context) { raise_exception("Malformed json"); exit(1); }
|
|
||||||
|
|
||||||
char *text = arena_strdup(arena, TEST_REPORT_TEMPLATE);
|
|
||||||
raise_notice("Template:\n%s", text);
|
|
||||||
raise_notice("Context: %s", json_to_string(arena, context));
|
|
||||||
|
|
||||||
hmpl_render_with_arena(arena, &text, context, &options);
|
|
||||||
raise_notice("Result:\n%s", text);
|
|
||||||
|
|
||||||
assert(strcmp(text, TEST_REPORT_RESULT) == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void) {
|
|
||||||
init_logger();
|
|
||||||
Arena arena = arena_init(MEM_MiB);
|
|
||||||
|
|
||||||
test_simple_section_tags(&arena);
|
|
||||||
arena_reset(&arena);
|
|
||||||
test_nested_section_tags(&arena);
|
|
||||||
//arena_reset(&arena);
|
|
||||||
//test_empty_array_section_tags(&arena);
|
|
||||||
//arena_reset(&arena);
|
|
||||||
//test_html_section_tags(&arena);
|
|
||||||
//arena_reset(&arena);
|
|
||||||
//test_report_section_tags(&arena);
|
|
||||||
|
|
||||||
printf("All tests passed successfully!\n");
|
|
||||||
|
|
||||||
arena_free(&arena);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
41
package/c/postgreact/default.nix
Executable file
41
package/c/postgreact/default.nix
Executable file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
stdenv,
|
||||||
|
postgresql,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
stdenv.mkDerivation {
|
||||||
|
pname = "postgreact";
|
||||||
|
version = "0.1";
|
||||||
|
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
postgresql
|
||||||
|
];
|
||||||
|
|
||||||
|
buildPhase = ''
|
||||||
|
mkdir -p target
|
||||||
|
sh ./make.sh build
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
mkdir -p $out/lib/postgresql $out/share/postgresql/extension
|
||||||
|
|
||||||
|
# Install compiled library
|
||||||
|
install -m 755 -D target/postgreact.so $out/lib/postgresql/postgreact.so
|
||||||
|
|
||||||
|
# Install control and SQL files
|
||||||
|
install -m 644 -D postgreact.control $out/share/postgresql/extension/postgreact.control
|
||||||
|
install -m 644 -D postgreact--0.1.sql $out/share/postgresql/extension/postgreact--0.1.sql
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "PostgreSQL extension for reactive functions";
|
||||||
|
homepage = "https://github.com/yukkop/util.nix";
|
||||||
|
license = licenses.mit;
|
||||||
|
platforms = postgresql.meta.platforms;
|
||||||
|
maintainers = with maintainers; [ ];
|
||||||
|
};
|
||||||
|
}
|
||||||
102
package/c/postgreact/make.sh
Executable file
102
package/c/postgreact/make.sh
Executable file
@@ -0,0 +1,102 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Usage: make.sh [build|watch] [--debug] [--color]
|
||||||
|
# Options:
|
||||||
|
# build Build the postgres extension (default if no mode is provided).
|
||||||
|
# watch Build the extension and watch for changes.
|
||||||
|
# --debug Build with -O0 (debug mode).
|
||||||
|
# --color Pass -fdiagnostics-color=always to compiler.
|
||||||
|
# help, --help Show this help message.
|
||||||
|
|
||||||
|
check_dependencies() {
|
||||||
|
for dep in gcc pg_config; do
|
||||||
|
if ! command -v "$dep" >/dev/null 2>&1; then
|
||||||
|
echo "Error: Required dependency '$dep' not found." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check for either fswatch or inotifywait for watch mode
|
||||||
|
if [ "$MODE" = "watch" ] && ! command -v fswatch >/dev/null 2>&1 && ! command -v inotifywait >/dev/null 2>&1; then
|
||||||
|
echo "Error: Neither fswatch nor inotifywait found. Please install one of them." >&2
|
||||||
|
echo " On macOS: brew install fswatch" >&2
|
||||||
|
echo " On Linux: sudo apt install inotify-tools" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
print_help() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $0 [build|watch] [--debug] [--color]
|
||||||
|
build Build the postgres extension (default).
|
||||||
|
watch Build the extension and watch for changes.
|
||||||
|
--debug Build with debug flags (-O0).
|
||||||
|
--color Force colored compiler diagnostics.
|
||||||
|
help, --help Display this help message.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show help if requested
|
||||||
|
case "$1" in
|
||||||
|
help|--help)
|
||||||
|
print_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Default flags
|
||||||
|
OPTFLAGS="-O2"
|
||||||
|
CFLAGS="-Wall -Wextra -pedantic -fPIC"
|
||||||
|
COLOR_FLAG=""
|
||||||
|
DEBUG=0
|
||||||
|
|
||||||
|
# Process options
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--debug)
|
||||||
|
OPTFLAGS="-O0 -gdwarf-2 -g3 -Wno-error"
|
||||||
|
DEBUG=1
|
||||||
|
;;
|
||||||
|
--color)
|
||||||
|
COLOR_FLAG="-fdiagnostics-color=always"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
MODE="${1:-build}"
|
||||||
|
shift 2> /dev/null
|
||||||
|
|
||||||
|
if [ -n "$COLOR_FLAG" ]; then
|
||||||
|
CFLAGS="$CFLAGS $COLOR_FLAG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_dependencies
|
||||||
|
|
||||||
|
# Get PostgreSQL include directory
|
||||||
|
PG_INCLUDE=$(pg_config --includedir-server)
|
||||||
|
PG_LIBDIR=$(pg_config --libdir)
|
||||||
|
|
||||||
|
case "$MODE" in
|
||||||
|
watch)
|
||||||
|
find . -type d | nix run .#watch -- 'sh ./make.sh build' -p '*.c' -p '*.h' 2>&1
|
||||||
|
;;
|
||||||
|
build)
|
||||||
|
mkdir -p target
|
||||||
|
echo "# Building PostgreSQL extension"
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
gcc $CFLAGS $OPTFLAGS -I$PG_INCLUDE -shared -o target/postgreact.so postgreact.c
|
||||||
|
|
||||||
|
# Copy extension files to target directory
|
||||||
|
cp postgreact.control target/
|
||||||
|
cp postgreact--0.1.sql target/
|
||||||
|
|
||||||
|
echo "Build complete. Files available in target/ directory."
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
print_help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
8
package/c/postgreact/postgreact--0.1.sql
Executable file
8
package/c/postgreact/postgreact--0.1.sql
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
|
||||||
|
\echo Use "CREATE EXTENSION postgreact" to load this file. \quit
|
||||||
|
|
||||||
|
-- Define the hello function that uses our C implementation
|
||||||
|
CREATE FUNCTION hello()
|
||||||
|
RETURNS text
|
||||||
|
AS 'postgreact', 'hello'
|
||||||
|
LANGUAGE C STRICT;
|
||||||
16
package/c/postgreact/postgreact.c
Executable file
16
package/c/postgreact/postgreact.c
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#include "postgres.h"
|
||||||
|
#include "fmgr.h"
|
||||||
|
#include "utils/builtins.h" /* for text_to_cstring and cstring_to_text */
|
||||||
|
|
||||||
|
#ifdef PG_MODULE_MAGIC
|
||||||
|
PG_MODULE_MAGIC;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Define the function hello */
|
||||||
|
PG_FUNCTION_INFO_V1(hello);
|
||||||
|
|
||||||
|
/* Implement the function */
|
||||||
|
Datum hello(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
PG_RETURN_TEXT_P(cstring_to_text("Hello, world!"));
|
||||||
|
}
|
||||||
3
package/c/postgreact/postgreact.control
Executable file
3
package/c/postgreact/postgreact.control
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
comment = 'My first extension'
|
||||||
|
default_version = '0.1'
|
||||||
|
module_pathname = '$libdir/postgreact'
|
||||||
Reference in New Issue
Block a user