From 8dcff32823d6d752cb360026139bcd89d1857811 Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Sun, 24 Sep 2023 14:30:32 +0300 Subject: [PATCH] moved macros to parser (mostly working as before)... Signed-off-by: Alex A. Naanou --- v2/pwiki/page.js | 883 +-------------------------------------------- v2/pwiki/parser.js | 858 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 850 insertions(+), 891 deletions(-) diff --git a/v2/pwiki/page.js b/v2/pwiki/page.js index 3e187c9..cd0bc07 100755 --- a/v2/pwiki/page.js +++ b/v2/pwiki/page.js @@ -911,884 +911,6 @@ object.Constructor('Page', BasePage, { return `
${source}
` }, }, - // - // (, , ){ .. } - // -> undefined - // -> - // -> - // -> - // -> () - // -> ... - // - // XXX do we need to make .macro.__proto__ module level object??? - // XXX ASYNC make these support async page getters... - macros: { __proto__: { - // - // @([ ][ local]) - // @(name=[ else=][ local]) - // - // @arg([ ][ local]) - // @arg(name=[ else=][ local]) - // - // [ ][ local]/> - // [ else=][ local]/> - // - // Resolution order: - // - local - // - .renderer - // - .root - // - // NOTE: else (default) value is parsed when accessed... - arg: Macro( - ['name', 'else', ['local']], - function(args){ - var v = this.args[args.name] - || (!args.local - && (this.renderer - && this.renderer.args[args.name]) - || (this.root - && this.root.args[args.name])) - v = v === true ? - args.name - : v - return v - || (args['else'] - && this.parse(args['else'])) }), - '': Macro( - ['name', 'else', ['local']], - function(args){ - return this.macros.arg.call(this, args) }), - args: function(){ - return pwpath.obj2args(this.args) }, - // - // @filter() - // /> - // - // > - // ... - // - // - // ::= - // - // | - - // - // XXX BUG: this does not show any results: - // pwiki.parse('moo test') - // -> '' - // while these do: - // pwiki.parse('moo test') - // -> 'moo TEST' - // await pwiki.parse('moo test@var()') - // -> 'moo TEST' - // for more info see: - // file:///L:/work/pWiki/pwiki2.html#/Editors/Results - // XXX do we fix this or revise how/when filters work??? - // ...including accounting for variables/expansions and the like... - // XXX REVISE... - filter: function(args, body, state, expand=true){ - var that = this - - var outer = state.filters = - state.filters ?? [] - var local = Object.keys(args) - - // trigger quote-filter... - var quote = local - .map(function(filter){ - return (that.filters[filter] ?? {})['quote'] ?? [] }) - .flat() - quote.length > 0 - && this.macros['quote-filter'] - .call(this, Object.fromEntries(Object.entries(quote)), null, state) - - // local filters... - if(body != null){ - // expand the body... - var ast = expand ? - this.__parser__.expand(this, body, state) - : body instanceof Array ? - body - // NOTE: wrapping the body in an array effectively - // escapes it from parsing... - : [body] - - return function(state){ - // XXX can we loose stuff from state this way??? - // ...at this stage it should more or less be static -- check! - return Promise.awaitOrRun( - this.__parser__.parse(this, ast, { - ...state, - filters: local.includes(this.ISOLATED_FILTERS) ? - local - : [...outer, ...local], - }), - function(res){ - return {data: res} }) } - /*/ // XXX ASYNC... - return async function(state){ - // XXX can we loose stuff from state this way??? - // ...at this stage it should more or less be static -- check! - var res = - await this.__parser__.parse(this, ast, { - ...state, - filters: local.includes(this.ISOLATED_FILTERS) ? - local - : [...outer, ...local], - }) - return {data: res} } - //*/ - - // global filters... - } else { - state.filters = [...outer, ...local] } }, - // - // @include() - // - // @include( isolated recursive=) - // @include(src= isolated recursive=) - // - // .. > - // - // - // - // 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 need a way to make encode option transparent... - // XXX store a page cache in state... - include: Macro( - ['src', 'recursive', 'join', - ['s', 'strict', 'isolated']], - async function*(args, body, state, key='included', handler){ - var macro = 'include' - if(typeof(args) == 'string'){ - var [macro, args, body, state, key, handler] = arguments - key = key ?? 'included' } - var base = this.get(this.path.split(/\*/).shift()) - var src = args.src - && this.resolvePathVars( - await base.parse(args.src, state)) - if(!src){ - return } - // XXX INHERIT_ARGS special-case: inherit args by default... - // XXX should this be done when isolated??? - if(this.actions_inherit_args - && this.actions_inherit_args.has(pwpath.basename(src)) - && this.get(pwpath.dirname(src)).path == this.path){ - src += ':$ARGS' } - var recursive = args.recursive ?? body - var isolated = args.isolated - var strict = args.strict - var strquotes = args.s - var join = args.join - && await base.parse(args.join, state) - - var depends = state.depends = - state.depends - ?? new Set() - // XXX DEPENDS_PATTERN - depends.add(src) - - handler = handler - ?? async function(src, state){ - return isolated ? - //{data: await this.get(src) - {data: await this - .parse({ - seen: state.seen, - depends, - renderer: state.renderer, - })} - //: this.get(src) - : this - .parse(state) } - - var first = true - for await (var page of this.get(src).asPages(strict)){ - if(join && !first){ - yield join } - first = false - - //var full = page.path - var full = page.location - - // handle recursion... - var parent_seen = 'seen' in state - var seen = state.seen = - new Set(state.seen ?? []) - if(seen.has(full) - // nesting path recursion... - || (full.length % (this.NESTING_RECURSION_TEST_THRESHOLD || 50) == 0 - && (pwpath.split(full).length > 3 - && new Set([ - await page.find(), - await page.get('..').find(), - await page.get('../..').find(), - ]).size == 1 - // XXX HACK??? - || pwpath.split(full).length > (this.NESTING_DEPTH_LIMIT || 20)))){ - if(recursive == null){ - console.warn( - `@${key}(..): ${ - seen.has(full) ? - 'direct' - : 'depth-limit' - } recursion detected:`, full, seen) - yield page.get(page.RECURSION_ERROR).parse() - continue } - // have the 'recursive' arg... - yield base.parse(recursive, state) - continue } - seen.add(full) - - // load the included page... - var res = await handler.call(page, full, state) - depends.add(full) - res = strquotes ? - res - .replace(/["']/g, function(c){ - return '%'+ c.charCodeAt().toString(16) }) - : res - - // NOTE: we only track recursion down and not sideways... - seen.delete(full) - if(!parent_seen){ - delete state.seen } - - yield res } }), - // NOTE: the main difference between this and @include is that - // this renders the src in the context of current page while - // include is rendered in the context of its page but with - // the same state... - // i.e. for @include(PATH) the paths within the included page - // are resolved relative to PATH while for @source(PATH) - // relative to the page containing the @source(..) statement... - source: Macro( - // XXX should this have the same args as include??? - ['src', 'recursive', 'join', - ['s', 'strict']], - //['src'], - async function*(args, body, state){ - var that = this - yield* this.macros.include.call(this, - 'source', - args, body, state, 'sources', - async function(src, state){ - //return that.parse(that.get(src).raw, state) }) }), - return that.parse(this.raw, state) }) }), - - // Load macro and slot definitions but ignore the page text... - // - // NOTE: this is essentially the same as @source(..) but returns ''. - // XXX revise name... - load: Macro( - ['src', ['strict']], - async function*(args, body, state){ - var that = this - yield* this.macros.include.call(this, - 'load', - args, body, state, 'sources', - async function(src, state){ - await that.parse(this.raw, state) - return '' }) }), - // - // @quote() - // - // [ filter=" ..."]/> - // - // - // - // - // .. - // - // - // - // NOTE: src ant text arguments are mutually exclusive, src takes - // priority. - // NOTE: the filter argument has the same semantics as the filter - // macro with one exception, when used in quote, the body is - // not expanded... - // NOTE: the filter argument uses the same filters as @filter(..) - // NOTE: else argument implies strict mode... - // XXX need a way to escape macros -- i.e. include in a quoted text... - // XXX should join/else be sub-tags??? - quote: Macro( - ['src', 'filter', 'text', 'join', 'else', - ['s', 'expandactions', 'strict']], - async function*(args, body, state){ - var src = args.src //|| args[0] - var base = this.get(this.path.split(/\*/).shift()) - var text = args.text - ?? body - ?? [] - var strict = !!(args.strict - ?? args['else'] - ?? false) - // parse arg values... - src = src ? - await base.parse(src, state) - : src - // XXX INHERIT_ARGS special-case: inherit args by default... - if(this.actions_inherit_args - && this.actions_inherit_args.has(pwpath.basename(src)) - && this.get(pwpath.dirname(src)).path == this.path){ - src += ':$ARGS' } - var expandactions = - args.expandactions - ?? true - // XXX EXPERIMENTAL - var strquotes = args.s - - var depends = state.depends = - state.depends - ?? new Set() - // XXX DEPENDS_PATTERN - depends.add(src) - - var pages = src ? - (!expandactions - && await this.get(src).type == 'action' ? - base.get(this.QUOTE_ACTION_PAGE) - : await this.get(src).asPages(strict)) - : text instanceof Array ? - [text.join('')] - : typeof(text) == 'string' ? - [text] - : text - // else... - pages = ((!pages - || pages.length == 0) - && args['else']) ? - [await base.parse(args['else'], state)] - : pages - // empty... - if(!pages || pages.length == 0){ - return } - - var join = args.join - && await base.parse(args.join, state) - var first = true - for await (var page of pages){ - if(join && !first){ - yield join } - first = false - - text = typeof(page) == 'string' ? - page - : (!expandactions - && await page.type == 'action') ? - base.get(this.QUOTE_ACTION_PAGE).raw - : await page.raw - text = strquotes ? - text - .replace(/["']/g, function(c){ - return '%'+ c.charCodeAt().toString(16) }) - : text - - page.path - && depends.add(page.path) - - var filters = - args.filter - && args.filter - .trim() - .split(/\s+/g) - - // NOTE: we are delaying .quote_filters handling here to - // make their semantics the same as general filters... - // ...and since we are internally calling .filter(..) - // macro we need to dance around it's architecture too... - // NOTE: since the body of quote(..) only has filters applied - // to it doing the first stage of .filter(..) as late - // as the second stage here will have no ill effect... - // NOTE: this uses the same filters as @filter(..) - // NOTE: the function wrapper here isolates text in - // a closure per function... - yield (function(text){ - return async function(state){ - // add global quote-filters... - filters = - (state.quote_filters - && !(filters ?? []).includes(this.ISOLATED_FILTERS)) ? - [...state.quote_filters, ...(filters ?? [])] - : filters - return filters ? - await this.__parser__.callMacro( - this, 'filter', filters, text, state, false) - .call(this, state) - : text } })(text) } }), - // very similar to @filter(..) but will affect @quote(..) filters... - 'quote-filter': function(args, body, state){ - var filters = state.quote_filters = - state.quote_filters ?? [] - filters.splice(filters.length, 0, ...Object.keys(args)) }, - // - // /> - // - // text=/> - // - // > - // ... - // - // - // Force show a slot... - // - // - // Force hide a slot... - //