Compare commits

..

No commits in common. "85cf87813bad07f41f02857384d9b99623ed75f8" and "0acb5a223aae54a01533c883b408a3d1693eccbc" have entirely different histories.

3 changed files with 103 additions and 312 deletions

View File

@ -7,8 +7,6 @@ Any macro can be used in any of the two forms, either _inline_ or _HTML-like_.
Inline: Inline:
``` ```
@macro-name(value) @macro-name(value)
@macro-name(value text=" ...text... ")
``` ```
HTML-style: HTML-style:
@ -29,54 +27,6 @@ The two forms exist to fill two distinct functions:
- html-like: element-like, simpler when dealing with html - html-like: element-like, simpler when dealing with html
### Positional and keyword attributes
Attributes (arguments) can be set sequentially by value, as they are
defined or by name in any order.
The following are equivalent:
```
<slot abc "some text"/>
<slot name=abc text="some text"/>
<slot "some text" name=abc/>
```
Keyword attributes are special attributes that are given by name rather
than by value by default and if no value is set explicitly it defaults
to `true` if passed, and to false if omitted.
The following are equivalent:
```
<slot some-name hidden/>
<slot some-name hidden=true/>
```
### Special attributes
Two special attributes are handled differently: `text` and `body`, these
are equivalent, the two names exist to make the semantics of macros simpler.
If both attributes are defined `body` takes precedence over `text` and
both are overriden by a non-empty body, e.g. the following:
```
<slot slot text=text-attr body=body-attr>body</slot>
```
will resolve to `body`.
The main difference from normal attributes is that the value of these is
parsed in the same way as the element's body is. Thus, the following are
identical:
```
<macro-name text="body text"/>
<macro-name body="body text"/>
<macro-name>body text</macro-name>
```
The values are parsed lazily, i.e. only the final value is parsed, the
overridden values are ignored.
### Escaping macros ### Escaping macros
@ -132,15 +82,10 @@ This will enable writing documents (mainly in _markdown_) that are usable
bot from within pWiki as well as outside. bot from within pWiki as well as outside.
## Macros ## Macros
### now () ### now ()
```
\@now()
```
Get current date in seconds since epoch, this is equivalet Javascript's Get current date in seconds since epoch, this is equivalet Javascript's
`Date.now()`. `Date.now()`.
@ -151,87 +96,17 @@ the date once per page load, i.e. the date changes on page load, while
`$NOW` is set every time the path is used, i.e. on every click or script `$NOW` is set every time the path is used, i.e. on every click or script
use. use.
**Example:**
```
\@now()
```
<pwiki-comment>Will produce: `1471389217848`</pwiki-comment> <pwiki-comment>Will produce: `1471389217848`</pwiki-comment>
<!--[pWiki[ Will produce: `@now()` ]]--> <!--[pWiki[ Will produce: `@now()` ]]-->
### slot (<name> <text> shown|hidden) / content
```
@slot(<name>)
@slot(<name> <text>)
@slot(<name> <text> hidden)
@slot(<name> <text> shown)
<slot <name> ...>
...
<content/>
...
</slot>
```
Define or fill a slot.
First occurrence of a slot `name` will _define_ a slot and set its value
(fill it) with `text` if given.
Each new occurrence of a slot with the same name will override slot content.
Only the first occurance of the `name` slot macro is displayed by default.
Slot display can be explicitly controlled via the `hidden` and `shown`
keywords, if both are given `hidden` has precedence. All _shown_ slots of
the same name will display the same value.
Nested slots are processed in order of occurrence, i.e. a nested slot can
override it's parent's value.
**Example:**
```
<slot X>
some text
<slot X "new text"/>
</slot>
```
Will resolve to: `new text`
`<content/>` / `@content()` if encountered will be replaced with previous
slot value.
**Example:**
```
This is: <slot X text="some text"/>
<slot X>[[ <content/> ]]</slot>
```
Will resolve to `This is: [[ some text ]]`
**Example:**
<pwiki-comment>
- [bootstrap view](/bootstrap/Templates/_view.html)
- [bootstrap edit](/bootstrap/Templates/_edit.html)
</pwiki-comment>
<!--[pWiki[
[Templates/\_view] / [bootstrap view](bootstrap/Templates/_view.html):
```
@source(Templates/_view)
```
[Templates/\_edit] / [bootstrap edit](bootstrap/Templates/_edit.html):
```
@source(Templates/_edit)
```
]]-->
---
### filter (name) ### filter (name)
Enable or disable a page filter. Enable or disable a page filter.
@ -306,6 +181,33 @@ Arguments:
]]--> ]]-->
### slot (name text)
Define or fill a slot.
First occurrence of a `name` will _define_ a slot and fill it with `text`.
Each new occurrence of a name will change slot content.
**Example:**
<pwiki-comment>
- [bootstrap view](/bootstrap/Templates/_view.html)
- [bootstrap edit](/bootstrap/Templates/_edit.html)
</pwiki-comment>
<!--[pWiki[
[Templates/\_view] / [bootstrap view](bootstrap/Templates/_view.html):
```
@source(Templates/_view)
```
[Templates/\_edit] / [bootstrap edit](bootstrap/Templates/_edit.html):
```
@source(Templates/_edit)
```
]]-->
### macro (name src sort) / else () ### macro (name src sort) / else ()
Apply macro to source page and include the result. Apply macro to source page and include the result.

View File

@ -9,7 +9,6 @@
var object = require('ig-object') var object = require('ig-object')
var types = require('ig-types') var types = require('ig-types')
var serialize = require('ig-serialize')
var pwpath = require('./path') var pwpath = require('./path')
@ -220,34 +219,19 @@ module.BaseParser = {
(res[e] = true) (res[e] = true)
: (res[order.shift()] = e) }) : (res[order.shift()] = e) })
return res }, return res },
// NOTE: this unifies the body, body argument and text argument (in
// order of priority) and passes the value in the body macro
// handler argument.
callMacro: function(page, macro, args, body, state, ...rest){ callMacro: function(page, macro, args, body, state, ...rest){
do { do {
macro = this.macros[macro] macro = this.macros[macro]
} while(typeof(macro) == 'string') } while(typeof(macro) == 'string')
var args =
this.parseArgs(
macro.arg_spec
?? [],
args)
body = body == '' ?
undefined
: body
if(args.body
|| args.text
|| body){
body =
args.body =
args.text =
body
?? args.body
?? args.text }
return macro.call(page, return macro.call(page,
this, this,
args, this.parseArgs(
body, macro.arg_spec
?? [],
args),
// XXX
body
?? args.body,
state, state,
...rest) }, ...rest) },
@ -358,6 +342,10 @@ module.BaseParser = {
?? groups.unnamedArg) ?? groups.unnamedArg)
.replace(/\\(["'])/g, '$1') } .replace(/\\(["'])/g, '$1') }
// special case: .body arg -> lex...
if(args.body){
args.body = [...this.lex(page, args.body)] }
// macro-spec... // macro-spec...
yield { yield {
name: (cur.nameInline name: (cur.nameInline
@ -405,18 +393,12 @@ module.BaseParser = {
// ... // ...
// } // }
// //
// Special arguments:
// .args.body | .args.text
// - if .body is given both arges are ignored and dropped
// - if .body is empty and one of the args is present it's
// content will be set as .body and grouped while the rest
// is dropped
// - priority order:
// .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*(page, lex, to=false){
group: function*(page, lex, to=false, parent, context){ 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)
lex = typeof(lex) != 'object' ? lex = typeof(lex) != 'object' ?
this.lex(page, lex) this.lex(page, lex)
: lex : lex
@ -450,6 +432,16 @@ module.BaseParser = {
: value.match ) } : value.match ) }
continue } 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... // assert nesting rules...
// NOTE: we only check for direct nesting... // NOTE: we only check for direct nesting...
// XXX might be a good idea to link nested block to the parent... // XXX might be a good idea to link nested block to the parent...
@ -467,37 +459,17 @@ module.BaseParser = {
+(to ? +(to ?
' in <'+to+'>' ' in <'+to+'>'
: '')) } : '')) }
// open block... // open block...
if(value.type == 'opening'){ if(value.type == 'opening'){
//value.body = [...this.group(page, lex, value.name)] //value.body = [...this.group(page, lex, value.name)]
value.body = [...this.group(page, lex, value.name, value)] value.body = [...this.group(page, lex, value.name, value)]
value.type = 'block' value.type = 'block'
// unify .body, .args.body and .args.text into .body...
// (first non-empty takes precedance, the rest are removed)
if(value.body.length == 0
&& (value.args.body
?? value.args.text)){
value.body =
[...this.group(
page,
value.args.body
?? value.args.text,
false,
parent,
value.name)] }
delete value.args.body
delete value.args.text
// close block... // close block...
} else if(value.type == 'closing'){ } else if(value.type == 'closing'){
if(value.name != to){ if(value.name != to){
throw new Error('Unexpected </'+ value.name +'>') } throw new Error('Unexpected </'+ value.name +'>') }
// NOTE: we are intentionally not yielding the value here... // NOTE: we are intentionally not yielding the value here...
// ...this supports the above scan use-case.
return } return }
// normal value... // normal value...
yield value } }, yield value } },
@ -595,21 +567,14 @@ module.BaseParser = {
// do not re-expand expanded elements... // do not re-expand expanded elements...
if('value' in elem if('value' in elem
&& !state.forceReExpand){ && !state.forceReExpand){
elems.push(serialize.partialDeepCopy(elem)) elems.push({...elem})
continue } continue }
// cleanup...
//elem = {...elem}
elem = serialize.partialDeepCopy(elem)
delete elem.error
delete elem.value
//delete elem.resolving
var {name, args, body} = elem var {name, args, body} = elem
// nested macro -- skip... // nested macro -- skip...
if(that.macros[name] instanceof Array){ if(that.macros[name] instanceof Array){
elems.push(elem) elems.push({...elem})
continue } continue }
// drop non-macros/aliases... // drop non-macros/aliases...
if(typeof(that.macros[name]) != 'function' if(typeof(that.macros[name]) != 'function'
@ -618,8 +583,15 @@ module.BaseParser = {
// expand down... // expand down...
body body
&& !that.macros[name].lazy
&& (body = this.expand(page, body, state)) && (body = this.expand(page, body, state))
;(args ?? {}).body
&& (args.body = this.expand(page, args.body, state))
// cleanup...
elem = {...elem}
delete elem.error
delete elem.value
//delete elem.resolving
// call macro... // call macro...
var res = that.callMacro(page, name, args, body, state) var res = that.callMacro(page, name, args, body, state)
@ -1018,7 +990,6 @@ module.BaseParser = {
// XXX do we need anything else like .doc, attrs??? // XXX do we need anything else like .doc, attrs???
// XXX might be a good idea to offload arg value parsing to here... // XXX might be a good idea to offload arg value parsing to here...
// XXX should macros be lazy by default???
var Macro = var Macro =
module.Macro = module.Macro =
function(spec, func){ function(spec, func){
@ -1034,15 +1005,9 @@ function(spec, func){
var isolated = var isolated =
module.isolated = module.isolated =
function(macro){ function(func){
macro.isolated = true func.isolated = true
return macro } return func }
var lazy =
module.lazy =
function(macro){
macro.lazy = true
return macro }
// XXX RENAME... // XXX RENAME...
@ -1309,41 +1274,32 @@ module.parser = {
// ... // ...
// </slot> // </slot>
// //
// Wrap previous value of slot
// <slot name=<name>>
// ...
// <content/>
// ...
// </slot>
//
// Force show a slot... // Force show a slot...
// <slot shown ... /> // <slot shown ... />
// //
// Force hide a slot... // Force hide a slot...
// <slot hidden ... /> // <slot hidden ... />
// //
// NOTE: slots are expanded in order of occurance not in order // Insert previous slot content...
// of topology, thus nested can override slots they are // <content/>
// nested in, e.g.: //
// '<slot moo>[[ <slot moo "new value"> ]]</slot>' //
// will resolve to:
// 'new value'
// NOTE: by default only the first slot with <name> is visible, // NOTE: by default only the first slot with <name> is visible,
// all other slots with <name> will replace its content, unless // all other slots with <name> will replace its content, unless
// explicit shown/hidden arguments are given. // explicit shown/hidden arguments are given.
// NOTE: hidden has precedence over shown if both are given. // NOTE: hidden has precedence over shown if both are given.
// XXX revise...
// //
// XXX can there be a situation where not all <content/> elements
// are cleared???
// XXX revise the use of hidden/shown use mechanic and if it's // XXX revise the use of hidden/shown use mechanic and if it's
// needed... // needed...
// XXX do we need to guard from recursion???
// ...do not think it's possible -- everything is resolved
// once and just inseerted as-is (revise)
slot: Macro( slot: Macro(
['name', 'text', ['shown', 'hidden']], ['name', 'text', ['shown', 'hidden']],
// NOTE: this is needed to be able to control the sequence of
// overrides in nesteed slots...
lazy(
function(parser, args, body, state){ function(parser, args, body, state){
var name = args.name var name = args.name
var text = args.text
return Promise.awaitOrRun( return Promise.awaitOrRun(
parser.parseNested(this, name, state), parser.parseNested(this, name, state),
@ -1351,6 +1307,7 @@ module.parser = {
var slots = state.slots ??= {} var slots = state.slots ??= {}
//var hidden = name in slots //var hidden = name in slots
// XXX EXPERIMENTAL
var hidden = var hidden =
// 'hidden' has priority... // 'hidden' has priority...
args.hidden args.hidden
@ -1361,44 +1318,22 @@ module.parser = {
: name in slots) : name in slots)
// set slot value... // set slot value...
// text = text ?
var slot = slots[name] ??= [] parser.expand(this, text ?? [], state)
// NOTE: the placeholder is a stand-in for our : text
// current value that is still to be generated... if(body && text){
var placeholder = [...slot] var slot = text
slot.splice(0, slot.length, placeholder) } else {
// expand body... var slot = body ?? text }
body = body ? slots[name] = slot
parser.expand(this, body ?? [], state)
: body
// if slot not overriden, write our value...
if(slot[0] === placeholder){
slot.splice(0, slot.length,
...(body != null ?
[body]
: placeholder))
// placeholder -> <content/>
body = placeholder }
// <content/> -- handle slot's original value...
slot[0] =
body.flat().length > 0
&& slot[0] instanceof Array ?
slot[0]
.map(function(e){
if(e && e.name == 'content'){
return body }
return e })
: slot[0]
return hidden ? return hidden ?
'' ''
: Object.assign( : Object.assign(
// stage II: place the latest slot value...
function(st){ function(st){
return ((st ?? state).slots ?? {})[name] }, return ((st ?? state).slots ?? {})[name]
{slot: name}) }) })), ?? slot },
// XXX do not like this name... {slot: name}) }) }),
content: ['slot'],
// //
@ -1497,14 +1432,9 @@ module.parser = {
handler.call(that, handler.call(that,
parser, parser,
text, text,
// isolated up -- will see all args.isolated ?
// the state but can have no // XXX clean (now), partial or derp-copy?
// side-effects... {}
args.isolated == 'partial' ?
serialize.partialDeepCopy(state)
// fully isolated...
: args.isolated ?
{}
: state), : state),
// join... // join...
(args.join (args.join

View File

@ -56,7 +56,14 @@ test.Setups({
...ins.map(function(e){ ...ins.map(function(e){
return e + '<slot slot>third</slot>' }), return e + '<slot slot>third</slot>' }),
'third' ]} }, 'third' ]} },
// nested... /* XXX not sure how...
slot_recursion: function(assert){
return {code: [
'<slot slot> <slot slot.inner/> </slot> <slot slot.inner "@slot"/>',
'', ]} },
//*/
// XXX these are an alternative to <content/>...
slot_nested: function(assert){ slot_nested: function(assert){
return {code: [ return {code: [
'<slot slot>[[ <slot slot.content/> ]]</slot>@slot(slot.content value)', '<slot slot>[[ <slot slot.content/> ]]</slot>@slot(slot.content value)',
@ -65,42 +72,12 @@ test.Setups({
return {code: [ return {code: [
'<slot slot>[[ <slot slot.content/> ]]</slot>@slot(slot value)', '<slot slot>[[ <slot slot.content/> ]]</slot>@slot(slot value)',
'value' ]} }, 'value' ]} },
// nested-self...
// XXX should this work???
slot_nested_nested: function(assert){ slot_nested_nested: function(assert){
return {code: [ return {code: [
'<slot slot>[[ <slot slot value/> ]]</slot>', '<slot slot>[[ <slot slot value/> ]]</slot>',
'<slot slot shown>[[ <slot slot value/> ]]</slot>',
'value' ]} }, 'value' ]} },
slot_nested_shown_nested: function(assert){
return {code: [
'<slot slot>[[ <slot slot/> ]]</slot>',
'[[ ]]' ]} },
// slot content...
slot_content: function(assert){
return {code: [
'<slot slot>[[ <content/> ]]</slot>',
'[[ ]]' ]} },
slot_content_multi: function(assert){
return {code: [
'<slot slot "moo"/><slot slot>[[ <content/> <content/> <content/> ]]</slot>',
'[[ moo moo moo ]]' ]} },
slot_content_filled: function(assert){
var ins = this.slot_content(assert).code.slice(0, -1)
return {code: [
...ins.map(function(e){
return '<slot slot "content"/>' + e }),
'[[ content ]]' ]} },
slot_content_content_filled: function(assert){
var ins = this.slot_content_filled(assert).code
var res = ins.pop()
return {code: [
...ins.map(function(e){
return e +'<slot slot>(( <content/> ))</slot>' }),
'(( '+ res +' ))' ]} },
slot_content_nested: function(assert){
return {code: [
'<slot slot>moo<slot slot>[[ <content/> ]]</slot></slot>',
'[[ moo ]]' ]} },
/* XXX SHOWN_HIDDEN /* XXX SHOWN_HIDDEN
// XXX these need to be revised... // XXX these need to be revised...
@ -150,24 +127,6 @@ test.Modifiers({
string: 'string', string: 'string',
} }
return state }, return state },
slot: function(assert, state){
state.code = [
...state.code
.slice(0, -1)
.map(function(e){
return `[[ <slot parent>${e}</slot> ]]` }),
`[[ ${state.code.at(-1)} ]]`,
]
return state },
slot_expand: function(assert, state){
state.code = [
...state.code
.slice(0, -1)
.map(function(e){
return `[[ <slot parent/> ]]<slot parent>${e}</slot>` }),
`[[ ${state.code.at(-1)} ]]`,
]
return state },
}) })