Compare commits

...

10 Commits

Author SHA1 Message Date
PedroEdiaz
7124aeb94e Add partial use of comments 2025-06-19 22:01:03 -06:00
PedroEdiaz
0f76ad666a Pass section 2025-06-19 21:57:18 -06:00
PedroEdiaz
774d1fd8eb Clean up 2025-06-19 01:42:08 -06:00
PedroEdiaz
65c6a901d2 preprocess -> section 2025-06-19 00:35:42 -06:00
PedroEdiaz
f9f4f5d34a Add decoding of map 2025-06-19 00:35:24 -06:00
PedroEdiaz
03292a15c8 Add testing for spec 2025-06-19 00:34:16 -06:00
PedroEdiaz
b707360c85 Merge remote-tracking branch 'refs/remotes/origin/mustacheless' into mustacheless 2025-06-18 16:34:17 -06:00
PedroEdiaz
6388d52499 Add mustache spec 2025-06-18 15:59:50 -06:00
PedroEdiaz
31f2b1c5c9 Add Sections as mustache for arrays 2025-06-18 01:35:19 -06:00
PedroEdiaz
e4e797fe15 Add: Recursivity in decode 2025-06-17 19:43:31 -06:00
5 changed files with 294 additions and 222 deletions

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "spec"]
path = spec
url = https://github.com/mustache/spec

View File

@@ -2,80 +2,154 @@ package mustache
import "base:runtime" import "base:runtime"
import "core:reflect" import "core:reflect"
import "core:strconv" import "core:strings"
import "core:mem"
import "core:log" import "core:log"
decode :: proc( v: any, key: string ) -> string { @private
_clean_html :: proc(s: string) -> string {
ret: strings.Builder
if v == nil do return "" for c in s do switch c {
case '<':
strings.write_string(&ret, "&lt;")
case '>':
strings.write_string(&ret, "&gt;")
case '&':
strings.write_string(&ret, "&amp;")
case '"':
strings.write_string(&ret, "&quot;")
case '\'':
strings.write_string(&ret, "&apos;")
case:
strings.write_rune(&ret, c)
}
ti := runtime.type_info_base(type_info_of(v.id)) return strings.to_string(ret)
a := any{v.data, ti.id} }
#partial switch info in ti.variant { decode :: proc( v: any, key: string ) -> any {
ti := type_info_of(v.id)
variant := runtime.type_info_base(ti).variant
#partial switch i in variant {
case runtime.Type_Info_Enumerated_Array:
return decode(any{v.data, ti.id}, key)
case runtime.Type_Info_Union:
return decode(any{v.data, reflect.union_variant_typeid(v) }, key)
}
if key == "." {
return v
}
#partial switch i in variant {
case runtime.Type_Info_Struct: case runtime.Type_Info_Struct:
return decode( reflect.struct_field_value_by_name(v, key), "." ) newkey, err := strings.split_after_n(key, ".", 2)
case runtime.Type_Info_String: if err != nil {
if key != "." do return "" return nil
switch t in a {
case string:
return t
case cstring:
return string(t)
} }
case runtime.Type_Info_Integer: defer delete(newkey)
if key != "." do return ""
buf: [40]byte newkey_0 := newkey[0]
u := cast_any_int_to_u128(a) newkey_1 := "."
return strconv.append_bits_128(buf[:], u, 10, info.signed, 8*ti.size, "0123456789", nil) if len(newkey) != 2 {
case runtime.Type_Info_Enum: newkey_0 = newkey[0][:len(newkey[0])-1]
return decode(any{v.data, info.base.id}, key) newkey_1 = newkey[1]
} }
return decode( reflect.struct_field_value_by_name(v, newkey_0), newkey_1 )
case runtime.Type_Info_Map:
newkey, err := strings.split_after_n(key, ".", 2)
if err!= nil {
return nil
}
defer delete(newkey)
newkey_0 := newkey[0]
newkey_1 := "."
if len(newkey) == 2 {
newkey_0 = newkey[0][:len(newkey[0])-1]
newkey_1 = newkey[1]
}
m := (^mem.Raw_Map)(v.data)
ks, vs, _, _, _ := runtime.map_kvh_data_dynamic(m^, i.map_info)
for j in 0..<runtime.map_cap(m^) {
key_data := runtime.map_cell_index_dynamic(ks, i.map_info.ks, uintptr(j))
ky, ok := reflect.as_string(any{rawptr(key_data), i.key.id})
if !ok {
return nil
}
if ky == newkey_0 {
t := any{rawptr(runtime.map_cell_index_dynamic(vs, i.map_info.vs, uintptr(j))), i.value.id}
return decode(t, newkey_1 )
}
}
return nil
}
return nil
}
@private
mustache_section :: proc( r: ^strings.Reader, v, p: any, section_key: string, inv: bool = false ) -> string {
tmp := mustache(r, v, section_key)
defer delete(tmp)
tmp2 := mustache(tmp, p, section_key)
if inv {
return tmp2
}
delete(tmp2)
return "" return ""
} }
section :: proc( r: ^strings.Reader, v: any, section_key: string, inv: bool = false ) -> string {
save := r.i
@(private) t := reflect.any_base(decode(v, section_key))
cast_any_int_to_u128 :: proc(any_int_value: any) -> u128 { ti := type_info_of(t.id)
u: u128 = 0
switch i in any_int_value { #partial switch i in runtime.type_info_base(ti).variant {
case i8: u = u128(i) case runtime.Type_Info_Slice:
case i16: u = u128(i) ret : string= ""
case i32: u = u128(i) for i in 0..<reflect.length(t) {
case i64: u = u128(i) elem :=reflect.index(t, i);
case i128: u = u128(i) strings.reader_seek(r, save, .Start)
case int: u = u128(i)
case u8: u = u128(i)
case u16: u = u128(i)
case u32: u = u128(i)
case u64: u = u128(i)
case u128: u = u128(i)
case uint: u = u128(i)
case uintptr: u = u128(i)
case i16le: u = u128(i) tmp := mustache_section(r, elem, v, section_key, !inv)
case i32le: u = u128(i) defer delete(tmp)
case i64le: u = u128(i)
case u16le: u = u128(i) ret = strings.concatenate({ret, tmp})
case u32le: u = u128(i) }
case u64le: u = u128(i) return ret
case u128le: u = u128(i) case runtime.Type_Info_Boolean:
case i16be: u = u128(i) b, _ := reflect.as_bool(t)
case i32be: u = u128(i)
case i64be: u = u128(i) return mustache_section(r, t, v, section_key, b~inv)
case u16be: u = u128(i)
case u32be: u = u128(i)
case u64be: u = u128(i)
case u128be: u = u128(i)
} }
return u; if t == nil {
return mustache_section(r, t, v, section_key, inv)
}
return mustache_section(r, t, v, section_key, !inv)
} }

View File

@@ -1,8 +1,8 @@
package mustache package mustache
import "core:fmt"
import "core:strings" import "core:strings"
@private
state :: enum { state :: enum {
writing, writing,
reading_key, reading_key,
@@ -10,36 +10,40 @@ state :: enum {
close_bracket, close_bracket,
} }
/* mustache :: proc{mustache_reader, mustache_string}
Input:
- fmt: A string with placeholders in the form `{{key}}`.
- dict: A map from string to string, where each key corresponds to a placeholder in the format string.
Returns: mustache_string :: proc(fmt: string, data: any , section_key: string = "") -> string {
A new string with all placeholders replaced by their corresponding values from r : strings.Reader
the dictionary. If a key is missing in the dictionary, the placeholder is strings.reader_init(&r, fmt)
replaces with an empty string. return mustache(&r, data, section_key)
*/ }
mustache :: proc(fmt: string, v: any ) -> string {
mustache_reader :: proc(r: ^strings.Reader, data: any, section_key: string = "" ) -> string {
/* /*
template works as a state machine, it manipulates `b` (returned string) This is the main parser for mustache templates, it's a recursive decent parser, that works as a state machine,
and `key` (placeholder string), according to the states. No error it manipulates the processed template with data, `ret`, and the key to element on data `key`, according to the states.
returns are needed because if `key` is not close it writes it as This approach let us process the data as fast as posible.
it's not a placeholder
*/ */
b, key: strings.Builder ret, key: strings.Builder
defer strings.builder_destroy(&key) defer strings.builder_destroy(&key)
s:= state.writing s:= state.writing
for c in fmt do switch c { for {
c, _, err := strings.reader_read_rune(r);
if err != nil {
break
}
switch c {
case '{': case '{':
switch s { switch s {
case .open_bracket: case .open_bracket:
s=.reading_key s=.reading_key
case .close_bracket: case .close_bracket:
strings.write_string(&b, "}{" ) strings.write_string(&ret, "}{" )
s=.writing s=.writing
case .writing: case .writing:
s=.open_bracket s=.open_bracket
@@ -49,14 +53,48 @@ mustache :: proc(fmt: string, v: any ) -> string {
case '}': case '}':
switch s { switch s {
case .open_bracket: case .open_bracket:
strings.write_string(&b, "{}" ) strings.write_string(&ret, "{}" )
s=.writing s=.writing
case .close_bracket: case .close_bracket:
strings.write_string(&b, decode(v, strings.to_string(key) )) // Work with key
skey := strings.to_string(key)
strings.builder_reset(&key) strings.builder_reset(&key)
s=.writing s=.writing
if len(skey) == 0 {
break
}
switch skey[0] {
case '/':
if skey[1:] == section_key {
return strings.to_string(ret)
}
case '#':
strings.write_string(&ret, section(r, data, skey[1:]) )
case '^':
strings.write_string(&ret, section(r, data, skey[1:], true) )
case '&':
strings.write_string(&ret, fmt.tprintf("%v",decode(data, skey[1:])) )
case '!':
case:
dec := decode(data, skey)
if dec == nil {
// If not decoded write as key
strings.write_string(&ret, "{{" )
strings.write_string(&ret, skey )
strings.write_string(&ret, "}}" )
break
}
clean := _clean_html(fmt.tprintf("%v", dec))
defer delete(clean)
strings.write_string(&ret, clean)
}
case .writing: case .writing:
strings.write_rune(&b, '}' ) strings.write_rune(&ret, '}' )
s=.writing s=.writing
case .reading_key: case .reading_key:
s=.close_bracket s=.close_bracket
@@ -64,35 +102,36 @@ mustache :: proc(fmt: string, v: any ) -> string {
case: case:
switch s { switch s {
case .open_bracket: case .open_bracket:
strings.write_rune(&b, '{' ) strings.write_rune(&ret, '{' )
strings.write_rune(&b, c ) strings.write_rune(&ret, c )
s=.writing s=.writing
case .close_bracket: case .close_bracket:
strings.write_rune(&key, '}' ) strings.write_rune(&key, '}' )
strings.write_rune(&key, c ) strings.write_rune(&key, c )
s=.reading_key s=.reading_key
case .writing: case .writing:
strings.write_rune(&b, c ) strings.write_rune(&ret, c )
s=.writing s=.writing
case .reading_key: case .reading_key:
strings.write_rune(&key, c ) strings.write_rune(&key, c )
} }
} }
}
switch s { switch s {
case .open_bracket: case .open_bracket:
strings.write_rune(&b, '{' ) strings.write_rune(&ret, '{' )
case .reading_key: case .reading_key:
strings.write_string(&b,"{{") strings.write_string(&ret,"{{")
strings.write_string(&b,strings.to_string(key)) strings.write_string(&ret,strings.to_string(key))
case .close_bracket: case .close_bracket:
strings.write_string(&b,"{{") strings.write_string(&ret,"{{")
strings.write_string(&b,strings.to_string(key)) strings.write_string(&ret,strings.to_string(key))
strings.write_rune(&b, '}' ) strings.write_rune(&ret, '}' )
case .writing: case .writing:
} }
return strings.to_string(b) return strings.to_string(ret)
} }
/* /*

1
spec Submodule

Submodule spec added at 97c05b0652

View File

@@ -2,106 +2,61 @@
#+private #+private
package mustache package mustache
import "core:os"
import "core:log"
import "core:testing" import "core:testing"
import "core:encoding/json"
data_struct :: union {
map[string]data_struct,
[]data_struct,
string,
bool,
}
test_struct :: struct {
overview: string,
tests: []struct{
name, desc, template, expected: string,
data: data_struct
}
}
@(test) @(test)
test1 :: proc(t: ^testing.T){ spec_test :: proc(t: ^testing.T){
fmt := "{" test_files := []string {
tmp := mustache(fmt,{}) "./spec/specs/sections.json",
defer delete(tmp) "./spec/specs/interpolation.json",
testing.expect(t, tmp==fmt, tmp) "./spec/specs/inverted.json",
"./spec/specs/comments.json",
/*
"./spec/specs/partials.json",
"./spec/specs/delimiters.json",
*/
} }
@(test)
test2 :: proc(t: ^testing.T){
fmt := "}"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp==fmt, tmp)
}
@(test)
test3 :: proc(t: ^testing.T){
fmt := "{{"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp==fmt, tmp)
}
@(test)
test4 :: proc(t: ^testing.T){
fmt := "{{}"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp==fmt, tmp)
}
@(test)
test5 :: proc(t: ^testing.T){
fmt := "{{}}"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp=="", tmp)
}
@(test)
test6 :: proc(t: ^testing.T){
fmt := "{{foo}}"
dict : struct { foo: string } = {"var"} for i in test_files {
data, err:=os.read_entire_file_from_filename_or_err(i)
defer delete(data)
tmp := mustache(fmt, dict) if err != nil {
defer delete(tmp) testing.expectf(t, false, "%v", err)
testing.expect(t, tmp=="var", tmp)
} }
@(test)
test7 :: proc(t: ^testing.T){ test: test_struct
fmt := "{{{}}"
tmp := mustache(fmt,{}) json.unmarshal(data, &test, allocator=context.temp_allocator)
defer delete(tmp)
testing.expect(t, tmp=="", tmp) failed := 0
for j in test.tests {
ret := mustache(j.template, j.data)
defer delete(ret)
if ret!=j.expected {
log.warnf( "[%s:%s]: %s", i, j.name, j.desc )
failed += 1
} }
@(test)
test8 :: proc(t: ^testing.T){
fmt := "{{}}}"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp=="}", tmp)
} }
@(test) log.infof( "[%s] Passed: (%d/%d)", i, len(test.tests)-failed, len(test.tests) )
test9 :: proc(t: ^testing.T){
fmt := "{{{}}}"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp=="}", tmp)
} }
@(test)
test10 :: proc(t: ^testing.T){
fmt := "{{} }}"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp=="", tmp)
}
@(test)
test11 :: proc(t: ^testing.T){
fmt := "{{{} }}"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp=="", tmp)
}
@(test)
test12 :: proc(t: ^testing.T){
fmt := " {{{} }}"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp==" ", tmp)
}
@(test)
test13 :: proc(t: ^testing.T){
fmt := "{{{} }} "
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp==" ", tmp)
}
@(test)
test14 :: proc(t: ^testing.T){
fmt := "{{{}}}"
tmp := mustache(fmt,{})
defer delete(tmp)
testing.expect(t, tmp=="}", tmp)
} }