/**********************************************************************
*
*
*
**********************************************************************/
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
(function(require){ var module={} // make module AMD/node compatible...
/*********************************************************************/
var object = require('lib/object')
var actions = require('lib/actions')
var features = require('lib/features')
var macro = require('macro')
/*********************************************************************/
// Split path into a list and handle special path elements...
//
// The path is split via '/' or '\', no difference is made between the
// two styles, e.g. 'a/b' is the same as 'a\b'.
//
// Consecutive '/' or '\' are treated as one, e.g. 'a///b' and 'a/b'
// are the same.
//
// Special path items supported:
// "." - Current position indicator
// this is simply removed from positions other than 0
// a/./b/c -> a/b/c
// ./b/c -> ./b/c
// ".." - Consumes path item above (pop) / up one level
// a/../b/c -> b/c
// ../b/c -> ../b/c
// ">>" - Consumes path item below (shift)
// a/>>/b/c -> a/c
// a/b/c/>> -> a/b/c (XXX ???)
//
// NOTE: the path is handled out of context, this leading '.' and '..'
// are left as-is.
// NOTE: '>>' has no effect when at last position (XXX ???)
var path2list =
module.path2list = function(path){
return (path instanceof Array ? path : path.split(/[\\\/]+/g))
// handle '..' (lookahead) and trim path elements...
// NOTE: this will not touch the leading '.' or '..'
.map(function(p, i, l){
// remove '..' and '.' out at positions > 0...
return (i > 0 && (p.trim() == '..' || p.trim() == '.')
// remove items followed by '..'...
|| (l[i+1] || '').trim() == '..'
// remove items preceded by '>>'...
|| (l[i-1] || '').trim() == '>>') ?
null
: p.trim() })
// cleanup and clear '>>'...
.filter(function(p){
return p != null
&& p != ''
&& p != '>>' })}
// Normalize path...
//
// This is the same as path2list(..) but also joins the path with '/'
var normalizePath =
module.normalizePath = function(path){ return path2list(path).join('/') }
var path2re =
module.path2re = function(path){
return RegExp('^'
+normalizePath(path)
// quote regexp chars...
.replace(/([\.\\\/\(\)\[\]\$\+\-\{\}\@\^\&\?\<\>])/g, '\\$1')
// convert '*' and '**' to regexp...
.replace(/\*\*/g, '.*')
.replace(/^\*|([^.])\*/g, '$1[^\\/]*')
+'$')}
/*********************************************************************/
// pWiki featureset...
var pWikiFeatures =
module.pWikiFeatures = new features.FeatureSet()
/*
// base pWiki object...
var pWiki =
module.pWiki = object.makeConstructor('pWiki', actions.MetaActions)
// base instance constructor...
pWikiFeatures.__actions__ =
function(){ return actions.Actions(pWiki()) }
//*/
/*********************************************************************/
var BaseData = {
// Macro acces to standard page attributes (paths)...
'System/title': function(){ return { text: this.get('..').title() } },
'System/path': function(){ return { text: this.base() } },
'System/dir': function(){ return { text: this.get('..').base() } },
'System/location': function(){ return { text: this.base() } },
'System/resolved': function(){ return { text: this.get('..').acquire() } },
// page data...
//
// NOTE: special case: ./raw is treated a differently when getting .text
// i.e:
// .get('./raw').text
// is the same as:
// .get('.').raw
'System/raw': function(){ return { text: this.get('..').raw() } },
'System/text': function(){ return { text: this.get('..').text() } },
// XXX update these to the new format -- must return an object...
/*
// XXX move this to Wiki.children + rename...
'System/list': function(){
var p = this.dir
return Object.keys(this.__wiki_data)
.map(function(k){
if(k.indexOf(p) == 0){
return path2lst(k.slice(p.length)).shift()
}
return null
})
.filter(function(e){ return e != null })
.sort()
.map(function(e){ return '['+ e +']' })
.join('
')
},
// list links to this page...
'System/links': function(){
var that = this
var p = this.dir
var res = []
var wiki = this.__wiki_data
Object.keys(wiki).forEach(function(k){
(wiki[k].links || []).forEach(function(l){
(l == p || that.get(path2lst(l).slice(0, -1)).acquire('./'+path2lst(l).pop()) == p)
&& res.push([l, k])
})
})
return res
//.map(function(e){ return '['+ e[0] +'] from page: ['+ e[1] +']' })
.map(function(e){ return '['+ e[1] +'] -> ['+ e[0] +']' })
.sort()
.join('
')
},
//*/
// Page modifiers/actions...
// XXX these needs redirecting...
//'System/sort': function(){ return this.get('..').sort() },
//'System/reverse': function(){ return this.get('..').reverse() },
/*
'System/delete': function(){
var p = this.dir
delete this.__wiki_data[p]
return this.get('..')
},
//*/
}
/*********************************************************************/
// XXX should this be art of the main API or a separate entity???
// XXX should we combine page and wiki api???
// - pWikiData is wiki api
// - pWiki is page api
var pWikiData =
module.pWikiData = {
__data: null,
// XXX
search: function(query){
},
// get a list of matching paths...
// XXX sort API???
// ...results shoulde be sorted via the saved order if available...
// .....or should this be done at a later stage as in gen1???
match: function(path){
var data = this.__data || {}
if(path == null){
return []
}
// strict path...
if(path.indexOf('*') < 0){
return path in data ? [ path ] : []
}
var pattern = path2re(path)
return Object.keys(data)
// XXX is this correct???
.concat(Object.keys(data.__proto__)
// do not repeat overloaded stuff...
.filter(function(e){ return !data.hasOwnProperty(e) }))
// XXX sort???
// XXX
.filter(function(p){ return pattern.test(p) })
},
// get/set data at path...
// XXX should this overwrite or expand???
// XXX should from be pattern compatible???
data: function(path, value){
// get the data...
if(value == null){
if(this.__data == null){
return null
}
var data = this.__data[path]
return data == null ? null
: data instanceof Function ? data
: JSON.parse(JSON.stringify(data))
// set the data...
} else {
this.__data = this.__data || {}
this.__data[path] = JSON.parse(JSON.stringify(value))
return this
}
},
// move data from path to path...
// XXX should from be pattern compatible???
move: function(from, to){
if(this.__data == null){
return
}
var d = this.__data[from]
this.clear(from)
this.__data[to] = d
return this
},
// clear data at path...
clear: function(path){
if(this.__data == null){
return this
}
var that = this
this.match(path).forEach(function(p){
delete that.__data[p]
})
return this
},
// XXX
json: function(data){
if(arguments.length == 0){
return JSON.parse(JSON.stringify(this.__data))
} else {
this.__data = data
}
},
}
/*********************************************************************/
// Base pWiki page API...
//
var pWikiBase =
module.pWikiBase = actions.Actions({
config: {
'home-page': 'WikiHome',
'default-page': 'EmptyPage',
'no-match-page': 'NoMatch',
'system-path': 'System',
'acquesition-order': [
'Templates',
],
'post-acquesition-order': [],
'order-unsorted-first': false,
// sorting...
'default-sort-methods': [
'path',
],
},
// pWikiData...
wiki: null,
// XXX should this be local/dump???
json: ['', function(){ }],
get length(){
// special case -- non-pattern path that does not exist...
if(this.location().path.indexOf('*') < 0
&& !this.exists()){
return 1
}
return this.wiki.match(this.location().path)
// skip special paths containing '*'...
.filter(function(p){ return p.indexOf('*') < 0 })
.length },
resolve: ['Path/Resolve relative path and expand path variables',
function(path){
path = path || this.path()
// path variables...
// XXX make this more modular...
path = path
// NOTE: these are equivalent to '..' and '.' but not
// identical -- the variables are useful for things
// like moving a page to:
// "Trash/$PATH"
// ...to move the above page out of trash move it to:
// ">>/$PATH"
.replace(/\$PATH|\$\{PATH\}/g, this.path())
.replace(/\$BASE|\$\{BASE\}/g, this.base())
.replace(/\$TITLE|\$\{TITLE\}/g, this.title())
.replace(/\$INDEX|\$\{INDEX\}/g, this.at())
.replace(/\$NOW|\$\{NOW\}/g, Date.now())
path = normalizePath(path)
// relative paths -- "." and ".."
if(path.indexOf('.') >= 0){
path =
// '.' or './*'
path == '.' || /^\.\//.test(path) ?
normalizePath(path.replace(/^\./, this.path()))
// '..' or '../*'
: path == '..' || /^\.\.\//.test(path) ?
normalizePath(path.replace(/^\.\./, this.base()))
: path
}
return path
}],
// XXX should this get a page???
acquire: ['Path/Acquire the page path that the given path resolves to',
function(path, no_default){
var that = this
// handle paths and relative paths...
var p = this.get(path || this.path())
var title = p.title()
path = path2list(p.base())
var acquire_from = this.config['acquesition-order'] || []
var post_acquire_from = this.config['post-acquesition-order'] || []
var _get = function(path, title, lst){
lst = (lst == null || lst.length == 0) ? [''] : lst
for(var i=0; i < lst.length; i++){
var p = normalizePath(path.concat([lst[i], title]))
if(that.exists(p)){
return that.wiki.data(p) && p
}
}
}
while(true){
// get title from path...
var p = _get(path, title)
// get title from special paths in path...
|| _get(path, title, acquire_from)
if(p != null){
return p
}
if(path.length == 0){
break
}
path.pop()
}
// default paths...
var p = _get(path, title, post_acquire_from)
// system path...
|| this.config['system-path']
&& _get([this.config['system-path']], title)
// NOTE: this may be null...
return p
|| ((!no_default && title != this.config['default-page']) ?
this.acquire('./'+this.config['default-page'])
: null)
}],
location: ['Page/Get or set location',
function(value){
if(value === null){
return
}
// XXX should we set/return a default empty value here???
this.__location = this.__location || {}
// get location...
if(arguments.length == 0){
return this.__location || this.config['home-page']
}
// set location index...
if(typeof(value) == typeof(123)){
this.__location.at = value
// set location path...
} else if(typeof(value) == typeof('str')){
this.__location.path = this.resolve(value)
this.__location.at = 0
// object...
} else {
this.__location = value
}
}],
// XXX pattern does not match anything needs to be handled correctly...
path: ['Page/Get or set path',
function(value){
// get explcit path from location (acounting for 'at')...
if(arguments.length == 0){
return this.order(true)[this.at()]
// nothing matched the pattern...
|| this.config['no-match-page']
|| ''
// move page to path...
} else if(value != null) {
this.wiki.move(this.path(), this.resolve(value))
// XXX
this.location(value)
}
}],
title: ['Page/Get or set title',
function(value){
if(arguments.length == 0){
return path2list(this.path()).pop() || ''
} else if(value != null){
this.path(this.base() +'/'+ value)
}
}],
base: ['Page/Get or set directory',
function(base){
if(arguments.length == 0){
return path2list(this.path()).slice(0, -1).join('/')
} else if(base != null){
this.path(base +'/'+ this.title())
}
}],
attr: ['Page/Get or set attribute',
function(name, value){
var d = this.data()
// get...
if(arguments.length == 1){
return d[name] === undefined ?
// force returning undefined...
actions.UNDEFINED
: d[name]
// clear...
} else if(value === undefined){
delete d[name]
// set...
} else {
d[name] = value
}
// write the data...
// XXX is it good to write the whole thing???
this.data(d)
}],
// content shorthands...
raw: ['Page/',
function(value){
return arguments.length == 0 ?
(this.attr('text') || '')
: this.attr('text', value) }],
checked: ['Page/',
function(value){
return arguments.length == 0 ?
!!this.attr('checked')
: this.attr('checked', value || undefined) }],
exists: ['Page/Check if path explicitly exists.',
function(path){
path = path || this.path()
return this.wiki.match(this.get(path).location().path)[this.at()] !== undefined
}],
// Format:
// {
// 'order': [