diff --git a/pwiki/page.js b/pwiki/page.js index 1831dcf..9dc1484 100755 --- a/pwiki/page.js +++ b/pwiki/page.js @@ -496,6 +496,8 @@ module.Page = object.Constructor('Page', BasePage, { __parser__: parser.parser, + NESTING_RECURSION_THRESHOLD: 10, + // Filter that will isolate the page/include/.. from parent filters... ISOLATED_FILTERS: 'isolated', @@ -641,8 +643,20 @@ object.Constructor('Page', BasePage, { // // // - // XXX 'text' argument is changed to 'recursive'... - // XXX revise recursion checks.... + // NOTE: there can be two ways of recursion in pWiki: + // - flat recursion + // /A -> /A -> /A -> .. + // - nested recursion + // /A -> /A/A -> /A/A/A -> .. + // Both can be either direct (type I) or indirect (type II). + // The former is trivial to check for while the later is + // not quite so, as we can have different contexts at + // different paths that would lead to different resulting + // renders. + // At the moment nested recursion is checked in a fast but + // not 100% correct manner focusing on path depth and ignoring + // the context, this potentially can lead to false positives. + // // XXX should this be lazy??? include: Macro( ['src', 'recursive', 'join', ['isolated']], @@ -665,7 +679,8 @@ object.Constructor('Page', BasePage, { ?? async function(src){ return isolated ? {data: await this.get(src) - .parse({seen: (state.seen ?? []).slice()})} + //.parse({seen: new Set(state.seen ?? [])})} + .parse({seen: state.seen})} : this.get(src) .parse(state) } @@ -677,20 +692,30 @@ object.Constructor('Page', BasePage, { // handle recursion... var parent_seen = 'seen' in state var seen = state.seen = - (state.seen - ?? []).slice() + new Set(state.seen ?? []) // recursion detected... - //if(seen.includes(full) || full == base){ - if(seen.includes(full)){ + if(seen.has(full) + // nesting path recursion... + // XXX a more general way to check would be to see if the + // path resolves to the same source (done below) and + // check if the context has changed -- i.e. if the paths + // actually contain anything... + || (seen.size % (this.NESTING_RECURSION_THRESHOLD || 10) == 0 + && new Set([...seen] + .map(function(p){ + return page.get(p).match()[0] })) + .size < seen.size)){ if(!recursive){ - return base.parse(page.get('./'+page.RECURSION_ERROR).raw) } + return page.get(page.RECURSION_ERROR).parse(state) } // have the 'recursive' arg... return base.parse(recursive, state) } - seen.push(full) + seen.add(full) // load the included page... var res = await handler.call(page, full) + // NOTE: we only track recursion down and not sideways... + seen.delete(full) if(!parent_seen){ delete state.seen } @@ -710,7 +735,7 @@ object.Constructor('Page', BasePage, { 'source', args, body, state, 'sources', async function(src){ - return this.parse(await this.get(src).raw, state) }) }), + return this.parse(this.get(src).raw, state) }) }), // // @quote() // @@ -950,8 +975,7 @@ object.Constructor('Page', BasePage, { // sort pages... // XXX SORT if(sort.length > 0){ - console.log('XXX: macro sort: not implemented') - } + console.log('XXX: macro sort: not implemented') } var join_block = _getBlock('join') @@ -990,6 +1014,7 @@ object.Constructor('Page', BasePage, { // parse: async function(text, state){ var that = this + text = await text // .parser() if(arguments.length == 1 && text instanceof Object @@ -1023,7 +1048,7 @@ object.Constructor('Page', BasePage, { // went really wrong... if(data == null){ if(this.NOT_FOUND_ERROR){ - var msg = this.get('./'+ this.NOT_FOUND_ERROR) + var msg = this.get(this.NOT_FOUND_ERROR) if(await msg.match()){ return msg.raw } } // last resort... @@ -1068,7 +1093,7 @@ object.Constructor('Page', BasePage, { // render template in context of page... var data = { render_root: this } return this.get(path, data) - .parse(await this.get(tpl, data).raw) }).call(this) }, + .parse(this.get(tpl, data).raw) }).call(this) }, set text(value){ this.__update__({text: value}) }, //this.onTextUpdate(value) }, @@ -1252,8 +1277,6 @@ module.System = { // XXX tests... // - _test_macro: { - text: '@source(./name) @source(./name)'}, test_page: function(){ console.log('--- RENDERER:', this.render_root) console.log('--- PATH: ', this.path) @@ -1273,9 +1296,9 @@ module.System = { // // NOTE: these are last resort pages, preferably overloaded in /Templates. RecursionError: { - text: 'RECURSION ERROR: @quote(./path)' }, + text: 'RECURSION ERROR: @quote(../path)' }, NotFoundError: { - text: 'NOT FOUND ERROR: @quote(./path)' }, + text: 'NOT FOUND ERROR: @quote(../path)' }, DeletingPage: { text: 'Deleting: @source(../path)' }, @@ -1338,7 +1361,7 @@ module.System = { this.render_root && (this.render_root.location = this.referrer) // show info about the delete operation... - return target.get('./DeletingPage').text }, + return target.get('DeletingPage').text }, // XXX System/back // XXX System/forward diff --git a/pwiki2-test.js b/pwiki2-test.js index 0828980..fcd3218 100755 --- a/pwiki2-test.js +++ b/pwiki2-test.js @@ -60,22 +60,24 @@ pwiki.store.update('@pouch', { // XXX TEST... // XXX add filter tests... pwiki.pwiki - // XXX BUG: the second @source(..) includes the current page again - // for some reason.... - .update({ - location: '/test/macros', - text: object.doc` - some text with inline @source(./path) macros... - -
- now @source(./path) inside a div... -
` }) .update({ location: '/test/markdown', text: object.doc` some text with inline @source(./path) macros... @filter(markdown)` }) + .update({ + location: '/test/recursion', + text: object.doc` @include(.) ` }) + .update({ + location: '/test/recursionA', + text: object.doc` @include(../recursionB) ` }) + .update({ + location: '/test/recursionB', + text: object.doc` @include(../recursionA) ` }) + .update({ + location: '/test/recursionC', + text: object.doc` @include(recursionC) ` }) .update({ location: '/test/sort/*', order: ['a', 'c', 'b'], }) diff --git a/pwiki2.js b/pwiki2.js index b6a376f..6bb498f 100755 --- a/pwiki2.js +++ b/pwiki2.js @@ -1,11 +1,7 @@ /********************************************************************** * * -* XXX BUG: source/include problem... -* to reproduce: -* .get('/System/_text_macro/_text').text -* this does not exhibit the issue: -* .get('/System/_text_macro').text +* XXX BUG: /** /paths -- does not work... * XXX BUG?: markdown: when parsing chunks each chunk gets an open/closed *

inserted at start/end -- this breaks stuff returned by macros... * ...there are two ways to dance around this: @@ -15,7 +11,11 @@ * ...this is expected but not intuitive... * might be a good idea to add a special path like /Stores to list * sub-stores... -* XXX OPTIMIZE: /_tree is really slow... +* XXX OPTIMIZE: /tree is really slow... +* ...is the problem purely in the async/await playing ping-pong??? +* XXX might be a good idea to make things optionally sync via a .sync() +* method, or request a specific set of data (in the parser, after +* collecting all the stuff needed and fetching it in one go)... * XXX might be a good idea to add page caching (state.page_cache) relative * to a path on parsing, to avoid re-matching the same page over and * over again from the same context @@ -159,6 +159,8 @@ * NOTE: implementing things in a traditional manner would * introduce lots of edge-cases and subtle ways to make * mistakes, bugs and inconsistencies. +* - types of recursion +* (see: pwiki/page.js: Page.macros.include(..) notes) * - * *