From b9b11ecbda5687b2d0d82735ff343c867654d051 Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Sun, 28 Jun 2026 15:28:42 +0300 Subject: [PATCH] added tests + cleaning up @slot(..) Signed-off-by: Alex A. Naanou --- v3/package.json | 4 +- v3/pwiki/parser.js | 79 +++++++++++++---- v3/pwiki/test/parser.js | 189 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 256 insertions(+), 16 deletions(-) create mode 100755 v3/pwiki/test/parser.js diff --git a/v3/package.json b/v3/package.json index 692f043..f5a2862 100755 --- a/v3/package.json +++ b/v3/package.json @@ -11,7 +11,9 @@ "ig-actions": "*", "ig-features": "*", "ig-object": "*", - "ig-types": "*", + "ig-types": "*" + }, + "devDependencies": { "ig-test": "*" }, "disabled-dependencies": { diff --git a/v3/pwiki/parser.js b/v3/pwiki/parser.js index a13a8b9..5ae6bd8 100644 --- a/v3/pwiki/parser.js +++ b/v3/pwiki/parser.js @@ -229,7 +229,9 @@ module.BaseParser = { macro.arg_spec ?? [], args), - body, + // XXX + body + ?? args.body, state, ...rest) }, @@ -256,7 +258,12 @@ module.BaseParser = { // args: { // : , // : , + // // ... + // + // // special case: .body argument's value is treated in + // // the same way as block body -- it is parsed. + // body: , // } // match: , // } @@ -335,6 +342,10 @@ module.BaseParser = { ?? groups.unnamedArg) .replace(/\\(["'])/g, '$1') } + // special case: .body arg -> lex... + if(args.body){ + args.body = [...this.lex(page, args.body)] } + // macro-spec... yield { name: (cur.nameInline @@ -384,7 +395,7 @@ module.BaseParser = { // // NOTE: this internaly uses .macros to check for propper nesting //group: function*(page, lex, to=false){ - group: function*(page, lex, to=false, parent){ + group: function*(page, lex, to=false, parent, context){ // XXX we can't get .raw from the page without going async... //lex = lex // ?? this.lex(page) @@ -421,14 +432,28 @@ module.BaseParser = { : value.match ) } continue } + // special case: .body argument -> group... + if((value.args ?? {}).body){ + value.args.body = + [...this.group( + page, + value.args.body.iter(), + false, + parent, + value.name)] } + // assert nesting rules... // NOTE: we only check for direct nesting... // XXX might be a good idea to link nested block to the parent... if(this.macros[value.name] instanceof Array + // stray nesting... + && (context + && !this.macros[value.name].includes(context)) + // stray nesting/closing... && !this.macros[value.name].includes(to) // do not complain about closing nestable tags... - && !(value.name == to - && value.type == 'closing')){ + && !(value.name == to + && value.type == 'closing') ){ throw new Error( 'Unexpected <'+ value.name +'> macro' +(to ? @@ -556,6 +581,12 @@ module.BaseParser = { && typeof(that.macros[name]) != 'string'){ continue } + // expand down... + body + && (body = this.expand(page, body, state)) + ;(args ?? {}).body + && (args.body = this.expand(page, args.body, state)) + // cleanup... elem = {...elem} delete elem.error @@ -646,11 +677,13 @@ module.BaseParser = { for(var elem of ast){ // nesting... - while(elem.value){ + while(elem && elem.value){ elem = elem.value // exec stage II macros... if(typeof(elem) == 'function'){ - elem = elem(state) } } + elem = elem(state) } } + if(elem == null){ + continue } // atomic values... if(typeof(elem) != 'object'){ merge(elem) @@ -662,8 +695,12 @@ module.BaseParser = { continue } // expand ast... if(elem instanceof Array){ + //merge(...this.resolve(page, elem, state)) merge(...this.resolve(page, elem, state)) continue } + // expand .body attribute... + if((elem.attrs ?? {}).body instanceof Array){ + elem.attrs.body = this.resolve(page, elem.attrs.body, state) } // nested macro with no value set -- skip... if(this.macros[elem.name] instanceof Array){ continue } @@ -697,7 +734,8 @@ module.BaseParser = { if(ast.length > 1){ throw new Error('!!!!') } - return ast[0] }, + return ast[0] + ?? '' }, // XXX this can't be used from within macros -- will deadlock the results... // XXX how should this play with filters??? @@ -714,7 +752,8 @@ module.BaseParser = { if(ast.length > 1){ throw new Error('!!!!') } - return ast[0] }, + return ast[0] + ?? '' }, @@ -1247,6 +1286,10 @@ module.parser = { // This also works for cases where slots override slots they // are contained in, this will not lead to recursion. // + // XXX do we actually need ?? + // + // + // // XXX do we show a slot with unfilled content??? // ...what's the point in having , can't is just // be replaced by a slot? @@ -1260,10 +1303,7 @@ module.parser = { function(parser, args, body, state){ var name = args.name var text = args.text - ?? body - // NOTE: this can't be undefined for .expand(..) to work - // correctly... - ?? [] + return Promise.awaitOrRun( parser.parseNested(this, name, state), function(name){ @@ -1281,19 +1321,28 @@ module.parser = { : name in slots) // set slot value... + // XXX simplify this... var stack = [] slots[name] && stack.push(slots[name]) delete slots[name] - var slot = parser.expand(this, text, state) + text = text ? + parser.expand(this, text ?? [], state) + : text + if(body && text){ + stack.push(body) + var slot = text + } else { + var slot = body ?? text } var original = slot slots[name] && stack.unshift(slot) slot = slots[name] ??= slot // handle ... - for(prev of stack){ + for(prev of stack.reverse()){ // get the first - // XXX this is a flat search, should be deep... + // NOTE: this is a flat search because we can't + // have indirect nesting (see: .group(..)) for(var i in prev){ if(typeof(prev[i]) != 'string' && prev[i].name == 'content'){ diff --git a/v3/pwiki/test/parser.js b/v3/pwiki/test/parser.js new file mode 100755 index 0000000..e016a86 --- /dev/null +++ b/v3/pwiki/test/parser.js @@ -0,0 +1,189 @@ +#!/usr/bin/node +//--------------------------------------------------------------------- + +var test = require('ig-test') + +var parser = require('../parser').parser + + +//--------------------------------------------------------------------- + +test.Setups({ + empty: function(assert){ + return [ '', '' ] }, + + slot_empty: function(assert){ + return [ + '@slot(slot)', + '@slot("slot")', + '@slot(\'slot\')', + '@slot(name=slot)', + '@slot(name="slot")', + '@slot(name=\'slot\')', + '', + '', + '', + '', + '', + '', + '' ] }, + slot_value: function(assert){ + return [ + '', + '', + '@slot(slot value)', + '@slot(slot text=value)', + 'value' ] }, + slot_fill: function(assert){ + var ins = this.slot_value(assert).slice(0, -1) + return [ + ...ins.map(function(e){ + return e + '@slot(slot other)' }), + ...ins.map(function(e){ + return e + '' }), + ...ins.map(function(e){ + return e + 'other' }), + 'other' ] }, + slot_fill_fill: function(assert){ + var ins = this.slot_fill(assert).slice(0, -1) + return [ + ...ins.map(function(e){ + return e + '@slot(slot third)' }), + ...ins.map(function(e){ + return e + '' }), + ...ins.map(function(e){ + return e + 'third' }), + 'third' ] }, + slot_content_empty: function(assert){ + return [ + '@slot(slot body="[[ ]]")', + '[[ ]]', + '[[ ]]' ] }, + slot_content_default: function(assert){ + return [ + '@slot(slot default body="[[ ]]")', + '[[ ]]', + '[[ default ]]' ] }, + slot_content_fill: function(assert){ + var ins = this.slot_content_default(assert) + var expect = ins.pop() + return [ + ...ins.map(function(i){ + return i +'@slot(slot value)' }), + expect.replace('default', 'value') ] }, + + slot_nested: function(assert){ + return [ + '[[ ]]@slot(slot.content value)', + '[[ value ]]' ] }, + slot_nested_overwrite: function(assert){ + return [ + '[[ ]]@slot(slot value)', + 'value' ] }, + + // XXX the question with the next tow is: + // should body override or nest? + // ...both are logical but in either case the result should be + // consistent. + // + // XXX should the new body/content override or expand the original??? + slot_content_content: function(assert){ + var ins = this.slot_content_default(assert) + var expect = ins.pop() + return [ + ...ins.map(function(i){ + return i +'@slot(slot body="(( @content() ))")' }), + // XXX should this override the default above??? + //expect.replace('default', '(( default ))') ] }, + expect.replace('default', '(( ))') ] }, + slot_content_content_content: function(assert){ + var ins = this.slot_content_content(assert) + var expect = ins.pop() + return [ + ...ins.map(function(i){ + return i +'@slot(slot body="<< @content() >>")' }), + // XXX should this override the default above??? + //expect.replace('default', '(( default ))') ] }, + expect.replace('(( ))', '(( << >> ))') ] }, + // XXX if we are expanding (see above) why are we overriding here??? + slot_content_content_fill: function(assert){ + var ins = this.slot_content_content(assert) + var expect = ins.pop() + return [ + ...ins.map(function(i){ + return i +'@slot(slot value)' }), + expect.replace('(( ))', '(( value ))') ] }, + //expect.replace('(( ))', 'value') ] }, + // + + + // XXX these need to be revised... + // ...do we actually need hidden/shown??? + // + slot_shown: function(assert){ + var ins = this.slot_value(assert) + var expect = ins.pop() + return [ + ...ins.map(function(i){ + return i +' @slot(slot that shown)' }), + 'that that' ] }, + slot_hidden: function(assert){ + return [ + '