mirror of
https://github.com/flynx/pWiki.git
synced 2026-07-14 04:20:58 +00:00
@include(..)'s body is now resolved in the context of the loaded page (non-isolated)... now I'm not sure what's the difference between @include(..) and @macro(..) other than the name... Am I designing a language with a single operator with the logic pushed out into paths??? =)
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
79ffe58b46
commit
782d2e7acd
@ -260,6 +260,7 @@ If `isolated` is given, prevent slots from included page from affecting
|
|||||||
the including page.
|
the including page.
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
|
|
||||||
`/some/page`:
|
`/some/page`:
|
||||||
```
|
```
|
||||||
<slot X value/>
|
<slot X value/>
|
||||||
|
|||||||
@ -442,7 +442,6 @@ module.BaseParser = {
|
|||||||
// .body -> .args.body -> .args.text
|
// .body -> .args.body -> .args.text
|
||||||
//
|
//
|
||||||
// NOTE: this internaly uses .macros to check for propper nesting
|
// NOTE: this internaly uses .macros to check for propper nesting
|
||||||
//group: function*(page, lex, to=false){
|
|
||||||
group: function*(lex, to=false, context){
|
group: function*(lex, to=false, context){
|
||||||
lex = typeof(lex) != 'object' ?
|
lex = typeof(lex) != 'object' ?
|
||||||
this.lex(lex)
|
this.lex(lex)
|
||||||
@ -526,6 +525,9 @@ module.BaseParser = {
|
|||||||
// normal value...
|
// normal value...
|
||||||
yield value } },
|
yield value } },
|
||||||
|
|
||||||
|
// NOTE: the output of this can be safely cached, it does not depend
|
||||||
|
// on anything external and as long as the code stays the same
|
||||||
|
// this will not change.
|
||||||
// XXX do we need a pre-parse stage???
|
// XXX do we need a pre-parse stage???
|
||||||
// - expand local macros
|
// - expand local macros
|
||||||
// - collect links
|
// - collect links
|
||||||
@ -558,11 +560,20 @@ module.BaseParser = {
|
|||||||
// waitAll: <promise> | null,
|
// waitAll: <promise> | null,
|
||||||
//
|
//
|
||||||
// // wait for all...
|
// // wait for all...
|
||||||
|
// // NOTE: this is set just before .expand(..) returns and is
|
||||||
|
// // not available during the expansion process.
|
||||||
// wait: <promise> | null,
|
// wait: <promise> | null,
|
||||||
//
|
//
|
||||||
// ...
|
// ...
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
|
// NOTE: .waitNested and .waitAll are "live", i.e. while expanding,
|
||||||
|
// at any given time they contain the promise of the last async
|
||||||
|
// element upto the point of read. .wait however is set at the
|
||||||
|
// end of .expand(..), i.e. it has no meaning until .expand(..)
|
||||||
|
// finishes, and represents the last .waitAll but will return
|
||||||
|
// the whole expanded ast.
|
||||||
|
//
|
||||||
//
|
//
|
||||||
// XXX the parser is always sync
|
// XXX the parser is always sync
|
||||||
// - macros always return sync but can be resolved or not resolved
|
// - macros always return sync but can be resolved or not resolved
|
||||||
@ -819,236 +830,6 @@ module.BaseParser = {
|
|||||||
// ...should filters be client-side only??
|
// ...should filters be client-side only??
|
||||||
parseNested: function(page, ast, state={}, nested_handlers={}){
|
parseNested: function(page, ast, state={}, nested_handlers={}){
|
||||||
return this.parse(page, ast, state, nested_handlers, 'waitNestedi') },
|
return this.parse(page, ast, state, nested_handlers, 'waitNestedi') },
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Expand macros...
|
|
||||||
//
|
|
||||||
// <ast> ::= [ <item>, .. ]
|
|
||||||
// <item> ::=
|
|
||||||
// <func>
|
|
||||||
// | <promise>
|
|
||||||
// | <string>
|
|
||||||
// | { skip: true, ... }
|
|
||||||
// | { data: <ast> }
|
|
||||||
// | <ast>
|
|
||||||
//
|
|
||||||
// XXX macros: we are mixing up ast state and parse state...
|
|
||||||
// one should only be used for parsing and be forgotten after
|
|
||||||
// the ast is constructed the other should be part of the ast...
|
|
||||||
// XXX DOC inconsistent await behavior...
|
|
||||||
// in var macro, as an example, there are two paths: the assign and
|
|
||||||
// the get, the assign calls .parse(..) and awaits for the result
|
|
||||||
// while the get path is "sync", this results in the first exec path
|
|
||||||
// getting pushed to the next execution frame while the second is run
|
|
||||||
// in sync with the caller, here is a simplified demo:
|
|
||||||
// console.log(1)
|
|
||||||
// // note that we are NOTE await'ing for the function here...
|
|
||||||
// (async function f(){
|
|
||||||
// console.log(2)})()
|
|
||||||
// console.log(3)
|
|
||||||
// -> prints 1, 2, 3
|
|
||||||
// and:
|
|
||||||
// console.log(1)
|
|
||||||
// (async function f(){
|
|
||||||
// // note the await -- this is the only difference...
|
|
||||||
// console.log(await 2)})()
|
|
||||||
// console.log(3)
|
|
||||||
// -> prints 1, 3, 2
|
|
||||||
// this could both be a bug or a feature depending on how you look
|
|
||||||
// at it, but it makes promise sequencing very unpredictable...
|
|
||||||
// ...another problem here is that in the var macro, simply adding
|
|
||||||
// an await of something does not fix the issue, we need to await
|
|
||||||
// for something significant -- await this.parse('') works, while
|
|
||||||
// await vars[name] does not -- to the contrary of the above example...
|
|
||||||
/* XXX
|
|
||||||
expand: function(page, ast, state={}){
|
|
||||||
var that = this
|
|
||||||
ast = ast == null ?
|
|
||||||
Promise.awaitOrRun(
|
|
||||||
page.raw,
|
|
||||||
function(raw){
|
|
||||||
return that.group(page, raw ?? '') })
|
|
||||||
: typeof(ast) != 'object' ?
|
|
||||||
this.group(page, ast)
|
|
||||||
: ast instanceof types.Generator ?
|
|
||||||
ast
|
|
||||||
: ast.iter()
|
|
||||||
|
|
||||||
// NOTE this must execute sequentially for things that depend on
|
|
||||||
// lexical scope not to get lost in the mess...
|
|
||||||
return Promise.seqiter(ast,
|
|
||||||
function(value){
|
|
||||||
// text block...
|
|
||||||
if(typeof(value) == 'string'){
|
|
||||||
return value }
|
|
||||||
// macro...
|
|
||||||
var {name, args, body} = value
|
|
||||||
// nested macro -- skip...
|
|
||||||
if(typeof(that.macros[name]) != 'function'){
|
|
||||||
return {...value, skip: true} }
|
|
||||||
// macro call...
|
|
||||||
return Promise.awaitOrRun(
|
|
||||||
that.callMacro(page, name, args, body, state),
|
|
||||||
function(res){
|
|
||||||
res = res ?? ''
|
|
||||||
// result...
|
|
||||||
if(res instanceof Array
|
|
||||||
|| that.macros[name] instanceof types.Generator){
|
|
||||||
return res
|
|
||||||
} else {
|
|
||||||
return [res] } }) },
|
|
||||||
function(err){
|
|
||||||
console.error(err)
|
|
||||||
return [page.parse(
|
|
||||||
// XXX add line number and page path...
|
|
||||||
'@include("./ParseError'
|
|
||||||
+':path='
|
|
||||||
// XXX use pwpath.encodeElem(..) ???
|
|
||||||
+ page.path
|
|
||||||
+':msg='
|
|
||||||
+ err.message
|
|
||||||
// quote html stuff...
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
// quote argument syntax...
|
|
||||||
.replace(/["']/g, function(c){
|
|
||||||
return '%'+ c.charCodeAt().toString(16) })
|
|
||||||
.replace(/:/g, ':')
|
|
||||||
.replace(/=/g, '=')
|
|
||||||
+'")')] })
|
|
||||||
.sync() },
|
|
||||||
//*/
|
|
||||||
|
|
||||||
|
|
||||||
// recursively resolve and enumerate the ast...
|
|
||||||
//
|
|
||||||
// <ast> ::= [ <item>, .. ]
|
|
||||||
// <item> ::=
|
|
||||||
// <string>
|
|
||||||
// | { data: <ast> }
|
|
||||||
// | <func>
|
|
||||||
//
|
|
||||||
// <func>(state)
|
|
||||||
// -> <ast>
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// NOTE: <func>(..) is called in the context of page...
|
|
||||||
//
|
|
||||||
// XXX should this also resolve e.data???
|
|
||||||
/* XXX
|
|
||||||
resolve: function(page, ast, state={}){
|
|
||||||
var that = this
|
|
||||||
ast = ast
|
|
||||||
?? this.expand(page, null, state)
|
|
||||||
ast = typeof(ast) != 'object' ?
|
|
||||||
this.expand(page, ast, state)
|
|
||||||
: ast
|
|
||||||
// XXX .awaitOrRun(..) will check inside the input array for promises, do
|
|
||||||
// we need to do this???
|
|
||||||
ast = Promise.awaitOrRun(
|
|
||||||
ast,
|
|
||||||
function(ast){
|
|
||||||
var async_content = false
|
|
||||||
return ast
|
|
||||||
.map(function(e){
|
|
||||||
// expand delayed sections...
|
|
||||||
e = typeof(e) == 'function' ?
|
|
||||||
e.call(page, state)
|
|
||||||
: e
|
|
||||||
// promise...
|
|
||||||
if(e instanceof Promise){
|
|
||||||
async_content = true
|
|
||||||
return e
|
|
||||||
// expand arrays...
|
|
||||||
} else if(e instanceof Array
|
|
||||||
|| e instanceof types.Generator){
|
|
||||||
return that.resolve(page, e, state)
|
|
||||||
// data -- unwrap content...
|
|
||||||
} else if(e instanceof Object && 'data' in e){
|
|
||||||
var res = Promise.awaitOrRun(
|
|
||||||
that.resolve(page, e.data, state),
|
|
||||||
function(e){
|
|
||||||
return { data: e } })
|
|
||||||
res instanceof Promise
|
|
||||||
&& (async_content = true)
|
|
||||||
return res
|
|
||||||
// skipped items...
|
|
||||||
} else if(e instanceof Object && e.skip){
|
|
||||||
return []
|
|
||||||
} else {
|
|
||||||
return [e] } })
|
|
||||||
// NOTE: if we still have promises in the ast, wrap the
|
|
||||||
// whole thing in a promise...
|
|
||||||
.run(function(){
|
|
||||||
return async_content ?
|
|
||||||
Promise.iter(this)
|
|
||||||
: this })
|
|
||||||
.flat() })
|
|
||||||
return ast instanceof Promise ?
|
|
||||||
// keep the API consistently array-like...
|
|
||||||
ast.iter()
|
|
||||||
: ast },
|
|
||||||
//*/
|
|
||||||
|
|
||||||
// Fully parse a page...
|
|
||||||
//
|
|
||||||
// This runs in two stages:
|
|
||||||
// - resolve the page
|
|
||||||
// - lex the page -- .lex(..)
|
|
||||||
// - group block elements -- .group(..)
|
|
||||||
// - expand macros -- .expand(..)
|
|
||||||
// - resolve ast -- .resolve(..)
|
|
||||||
// - apply filters
|
|
||||||
//
|
|
||||||
// NOTE: this has to synchronize everything between stage 1 (up to
|
|
||||||
// and including expand) and stage 2 (post-handlers, filters, ...)
|
|
||||||
// because the former need a fully loaded and expanded page if
|
|
||||||
// we want to do this in 2 stages and not 3...
|
|
||||||
// XXX might be fun to try a load-and-tweak approach the first
|
|
||||||
// version used -- i.e. setting placeholders and replacing
|
|
||||||
// them on demand rather than on encounter (as is now), e.g.
|
|
||||||
// a slot when loaded will replace the prior occurrences...
|
|
||||||
//
|
|
||||||
// XXX add a special filter to clear pending filters... (???)
|
|
||||||
/* XXX
|
|
||||||
parse: function(page, ast, state={}){
|
|
||||||
var that = this
|
|
||||||
return this.resolve(page, ast, state)
|
|
||||||
// filters...
|
|
||||||
.map(function(section){
|
|
||||||
// normalize types...
|
|
||||||
section =
|
|
||||||
typeof(section) == 'number' ?
|
|
||||||
section + ''
|
|
||||||
: section == null ?
|
|
||||||
''
|
|
||||||
: section
|
|
||||||
return (
|
|
||||||
// expand section...
|
|
||||||
typeof(section) != 'string' ?
|
|
||||||
section.data
|
|
||||||
// global filters...
|
|
||||||
: state.filters ?
|
|
||||||
that.normalizeFilters(state.filters)
|
|
||||||
.reduce(function(res, filter){
|
|
||||||
// unknown filter...
|
|
||||||
// NOTE: we try not to break on user errors
|
|
||||||
// if we can help it...
|
|
||||||
if(page.filters[filter] == null){
|
|
||||||
console.warn(
|
|
||||||
'.parse(..): unsupported filter: '+ filter)
|
|
||||||
return res }
|
|
||||||
// NOTE: if a filter returns falsy then it
|
|
||||||
// will have no effect on the result...
|
|
||||||
return page.filters[filter].call(page, res)
|
|
||||||
?? res }, section)
|
|
||||||
// no global filters...
|
|
||||||
: section ) })
|
|
||||||
.flat()
|
|
||||||
.join('') },
|
|
||||||
//*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1088,9 +869,6 @@ function(macro){
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// XXX RENAME...
|
// XXX RENAME...
|
||||||
// ...this is more of an expander/executer...
|
// ...this is more of an expander/executer...
|
||||||
// ...might be a good idea to also do a check without executing...
|
// ...might be a good idea to also do a check without executing...
|
||||||
@ -1511,6 +1289,8 @@ module.parser = {
|
|||||||
state.recursive =
|
state.recursive =
|
||||||
args.recursive
|
args.recursive
|
||||||
?? state.recursive
|
?? state.recursive
|
||||||
|
// XXX get default...
|
||||||
|
recursive ??= ''
|
||||||
|
|
||||||
var base = page.basepath
|
var base = page.basepath
|
||||||
// XXX we do this before or after we parse???
|
// XXX we do this before or after we parse???
|
||||||
@ -1523,10 +1303,7 @@ module.parser = {
|
|||||||
// XXX will this work for @source(..)???
|
// XXX will this work for @source(..)???
|
||||||
var stack = state.include_stack ??= []
|
var stack = state.include_stack ??= []
|
||||||
if(stack.includes(src)){
|
if(stack.includes(src)){
|
||||||
if(recursive){
|
return that.expand(page, recursive, state) }
|
||||||
return that.expand(page, recursive, state) }
|
|
||||||
throw new Error('Recursion:\n\t'+
|
|
||||||
[...stack, src].join('\n\t\t-> ')) }
|
|
||||||
stack.push(src)
|
stack.push(src)
|
||||||
|
|
||||||
var cache = state.cache ??= {}
|
var cache = state.cache ??= {}
|
||||||
@ -1559,12 +1336,12 @@ module.parser = {
|
|||||||
: this.expand(page, text, state) }
|
: this.expand(page, text, state) }
|
||||||
|
|
||||||
var pageHandler =
|
var pageHandler =
|
||||||
function(text){
|
function([path, text]){
|
||||||
var content_handled = false
|
var content_handled = false
|
||||||
return body ?
|
return body ?
|
||||||
// handle body / <content/>...
|
// handle body / <content/>...
|
||||||
Promise.awaitOrRun(
|
Promise.awaitOrRun(
|
||||||
that.expand(page, body, state,
|
that.expand(page.get(path), body, state,
|
||||||
{ content: function(){
|
{ content: function(){
|
||||||
content_handled = true
|
content_handled = true
|
||||||
return text = handler.call(that, page, src, text, state) } }),
|
return text = handler.call(that, page, src, text, state) } }),
|
||||||
@ -1590,8 +1367,12 @@ module.parser = {
|
|||||||
|
|
||||||
// get and run things...
|
// get and run things...
|
||||||
return Promise.awaitOrRun(
|
return Promise.awaitOrRun(
|
||||||
|
page.get(src).matched,
|
||||||
page.get(src).raw,
|
page.get(src).raw,
|
||||||
function(pages){
|
function(paths, pages){
|
||||||
|
paths = paths instanceof Array ?
|
||||||
|
paths
|
||||||
|
: [paths]
|
||||||
pages = pages instanceof Array ?
|
pages = pages instanceof Array ?
|
||||||
pages
|
pages
|
||||||
: [pages]
|
: [pages]
|
||||||
@ -1601,7 +1382,9 @@ module.parser = {
|
|||||||
.iter(
|
.iter(
|
||||||
that.joinBlocks(
|
that.joinBlocks(
|
||||||
page,
|
page,
|
||||||
pages.map(pageHandler),
|
Array
|
||||||
|
.zip(paths, pages)
|
||||||
|
.map(pageHandler),
|
||||||
args.join,
|
args.join,
|
||||||
state))
|
state))
|
||||||
.flat()
|
.flat()
|
||||||
@ -1718,6 +1501,7 @@ module.parser = {
|
|||||||
filters.splice(filters.length, 0, ...Object.keys(args)) },
|
filters.splice(filters.length, 0, ...Object.keys(args)) },
|
||||||
//*/
|
//*/
|
||||||
|
|
||||||
|
// expand the body in the context of a page...
|
||||||
//
|
//
|
||||||
// <macro src=<url>> .. </macro>
|
// <macro src=<url>> .. </macro>
|
||||||
//
|
//
|
||||||
@ -1759,6 +1543,12 @@ module.parser = {
|
|||||||
// XXX SORT sorting not implemented yet...
|
// XXX SORT sorting not implemented yet...
|
||||||
// XXX UPDATE...
|
// XXX UPDATE...
|
||||||
macro: Macro(
|
macro: Macro(
|
||||||
|
['name', 'src', 'sort', 'text', 'join', 'else',
|
||||||
|
['strict', 'isolated', 'inheritmacros', 'inheritvars']],
|
||||||
|
lazy(
|
||||||
|
function(page, args, body, state){
|
||||||
|
})),
|
||||||
|
_macro: Macro(
|
||||||
['name', 'src', 'sort', 'text', 'join', 'else',
|
['name', 'src', 'sort', 'text', 'join', 'else',
|
||||||
['strict', 'isolated', 'inheritmacros', 'inheritvars']],
|
['strict', 'isolated', 'inheritmacros', 'inheritvars']],
|
||||||
async function*(args, body, state){
|
async function*(args, body, state){
|
||||||
|
|||||||
@ -53,8 +53,43 @@ module.exports.P = {
|
|||||||
get basepath(){
|
get basepath(){
|
||||||
return path.dirname(this.path ?? '') },
|
return path.dirname(this.path ?? '') },
|
||||||
|
|
||||||
|
get matched(){
|
||||||
|
var path_action_pattern = /\/path\/?/
|
||||||
|
var path = this.path.replace(path_action_pattern, '')
|
||||||
|
var path_action = path != this.path
|
||||||
|
|
||||||
|
if(path.includes('*')){
|
||||||
|
var that = this
|
||||||
|
var re = new RegExp(path
|
||||||
|
.replace(/\//, '\\/')
|
||||||
|
.replace(/\*/g, '[^\\/]+'))
|
||||||
|
return Object.keys(this.__pages__)
|
||||||
|
.map(function(p){
|
||||||
|
return re.test(p) ?
|
||||||
|
[path_action ?
|
||||||
|
p +'/path'
|
||||||
|
:p]
|
||||||
|
: [] })
|
||||||
|
.flat() }
|
||||||
|
return [this.path] },
|
||||||
|
|
||||||
|
// * -> a promise or a list of promises???
|
||||||
get raw(){
|
get raw(){
|
||||||
return this.__pages__[this.path] ?? '' },
|
var res = []
|
||||||
|
var path_action_pattern = /\/path\/?/
|
||||||
|
var path_action = this.path.match(path_action_pattern)
|
||||||
|
|
||||||
|
for(var path of this.matched){
|
||||||
|
res.push(
|
||||||
|
path_action ?
|
||||||
|
path.replace(path_action_pattern, '')
|
||||||
|
: this.__pages__[path]) }
|
||||||
|
|
||||||
|
return res.length == 1 ?
|
||||||
|
res[0]
|
||||||
|
: res },
|
||||||
|
|
||||||
|
// XXX should this return an arrya for a multi-match???
|
||||||
get text(){
|
get text(){
|
||||||
return this.__parser__.parse(this, this.raw, {}) },
|
return this.__parser__.parse(this, this.raw, {}) },
|
||||||
|
|
||||||
@ -194,12 +229,6 @@ test.Setups({
|
|||||||
return i +' | @slot(slot shown)' }),
|
return i +' | @slot(slot shown)' }),
|
||||||
' | value' ]} },
|
' | value' ]} },
|
||||||
|
|
||||||
// XXX var...
|
|
||||||
// XXX
|
|
||||||
|
|
||||||
// XXX arg...
|
|
||||||
// XXX
|
|
||||||
|
|
||||||
// include...
|
// include...
|
||||||
// XXX see inside...
|
// XXX see inside...
|
||||||
include: function(assert, path='/blank', expected){
|
include: function(assert, path='/blank', expected){
|
||||||
@ -239,13 +268,23 @@ test.Setups({
|
|||||||
'original overloaded',
|
'original overloaded',
|
||||||
],
|
],
|
||||||
} },
|
} },
|
||||||
|
/* XXX
|
||||||
|
include_partial_isolated: function(assert){
|
||||||
|
return {
|
||||||
|
page: P,
|
||||||
|
code:[
|
||||||
|
'@include(isolated="partial" /isolated) @slot(slot overloaded)',
|
||||||
|
'original overloaded',
|
||||||
|
],
|
||||||
|
} },
|
||||||
|
//*/
|
||||||
// content...
|
// content...
|
||||||
include_content: function(assert){
|
include_content: function(assert, path='/page'){
|
||||||
return {
|
return {
|
||||||
page: P,
|
page: P,
|
||||||
code: [
|
code: [
|
||||||
'<include "/page">[[ <content/> ]]</include>',
|
'<include "'+ path +'">[[ <content/> ]]</include>',
|
||||||
'[[ '+ P.get('/page').raw +' ]]', ], } },
|
'[[ '+ P.get(path).raw +' ]]', ], } },
|
||||||
// XXX
|
// XXX
|
||||||
|
|
||||||
// recursion...
|
// recursion...
|
||||||
@ -268,6 +307,22 @@ test.Setups({
|
|||||||
// quote...
|
// quote...
|
||||||
// for inline quoting see: test.Modifiers.quote
|
// for inline quoting see: test.Modifiers.quote
|
||||||
// XXX <quote src=.. />
|
// XXX <quote src=.. />
|
||||||
|
quote_content: function(assert, path='/isolated'){
|
||||||
|
return {
|
||||||
|
page: P,
|
||||||
|
code: [
|
||||||
|
'<quote "'+ path +'">[[ <content/> ]]</quote>',
|
||||||
|
'[[ '+ P.get(path).raw +' ]]', ], } },
|
||||||
|
|
||||||
|
// XXX macro...
|
||||||
|
// XXX
|
||||||
|
|
||||||
|
// XXX var...
|
||||||
|
// XXX
|
||||||
|
|
||||||
|
// XXX arg...
|
||||||
|
// XXX
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user