reworking store API to be sync/async...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2023-02-14 16:50:35 +03:00
parent 8acb914237
commit d4a1e3f055
2 changed files with 310 additions and 58 deletions

View File

@ -541,6 +541,40 @@ object.Constructor('BasePage', {
}) },
// XXX should this be an iterator???
// XXX EXPERIMENTAL...
// to be sync this needs:
// .energetic
// .store.isEnergetic(..)
// .resolve(..) -> .store.resolve(..)
each: function(path){
var that = this
// NOTE: we are trying to avoid resolving non-pattern paths unless
// we really have to...
path = path ?
pwpath.relative(this.path, path)
: this.location
var paths = path.includes('*') ?
Promise.awaitOrRun(
this.energetic,
this.store.isEnergetic(path),
function(a, b){
return !(a || b) ?
that.resolve(path)
: path })
: path
paths = Promise.awaitOrRun(
paths,
function(paths){
return (paths instanceof Array
|| paths instanceof Promise) ?
paths
: [paths] })
return Promise.iter(
paths,
function(path){
return that.get('/'+ path) })
.sync() },
/*/ // XXX ASYNC...
each: async function*(path){
// NOTE: we are trying to avoid resolving non-pattern paths unless
// we really have to...
@ -561,14 +595,15 @@ object.Constructor('BasePage', {
: [paths]
for(var path of paths){
yield this.get('/'+ path) } },
//*/
[Symbol.asyncIterator]: async function*(){
yield* this.each() },
map: async function(func){
map: function(func){
return this.each().map(func) },
filter: async function(func){
filter: function(func){
return this.each().filter(func) },
reduce: async function(func, dfl){
reduce: function(func, dfl){
return this.each().reduce(func, dfl) },
// sorting...
@ -1842,6 +1877,52 @@ object.Constructor('Page', BasePage, {
// actions...
//
// XXX revise name...
/*/ XXX EXPERIMENTAL
asPages: function(path='.:$ARGS', strict=false){
// options...
var args = [...arguments]
var opts = typeof(args.at(-1)) == 'object' ?
args.pop()
: {}
var {path, strict} = {
...opts,
path: typeof(args[0]) == 'string' ?
args.shift()
: '.:$ARGS',
strict: args.shift()
?? false,
}
var page = this.get(path, strict)
// each...
if(page.isPattern){
return page.each()
// handle lists in pages (actions, ... etc.)...
} else {
return Promise.awaitOrRun(
page.data,
function(data){
data =
data instanceof types.Generator ?
// XXX
//await data()
data()
: typeof(data) == 'function' ?
data
: data && 'text' in data ?
data.text
: null
if(data instanceof Array
|| data instanceof types.Generator){
return data
.map(function(p){
return page.virtual({text: p}) }) }
// do not iterate pages/actions that are undefined...
if(data == null){
return }
return page }) } },
/*/ // XXX ASYNC...
asPages: async function*(path='.:$ARGS', strict=false){
// options...
var args = [...arguments]
@ -1883,6 +1964,7 @@ object.Constructor('Page', BasePage, {
return }
yield page } },
//*/
// expanded page text...
//

View File

@ -886,6 +886,27 @@ module.BaseStore = {
//
// NOTE: handlers are run in order of definition.
//
// XXX EXPERIMENTAL
tags: function(tags){
var that = this
tags = typeof(tags) == 'string' ?
this.parseTags(tags)
: false
return Promise.awaitOrRun(
tags
&& this.tags,
function(tags){
return tags
&& function(path){
// tags -> skip untagged pages...
var t = this.tags.paths[path]
if(!t){
return false }
for(var tag of tags){
if(!t || !t.has(tag)){
return false } }
return true } }) },
/*/ // XXX ASYNC...
tags: async function(tags){
tags = typeof(tags) == 'string' ?
this.parseTags(tags)
@ -901,6 +922,7 @@ module.BaseStore = {
if(!t || !t.has(tag)){
return false } }
return true } },
//*/
search: async function(search){
search = search
&& new Set(await this.search(search))
@ -926,6 +948,27 @@ module.BaseStore = {
count--
return !!(count >= 0) } },
},
// XXX EXPERIMENTAL...
__match_args: function(args){
var that = this
var predicates = []
for(var [key, gen] of Object.entries(this.__match_args__ ?? {})){
var p = gen.call(this, args[key], args)
p && predicates.push(p) }
return Promise.awaitOrRun(
...predicates,
function(...predicates){
predicates = predicates
.filter(function(p){ return p })
return predicates.length > 0 ?
function(path){
for(var p of predicates){
if(!p.call(that, path)){
return false } }
return true }
: undefined },
null) },
/*/ // XXX ASYNC...
__match_args: async function(args){
var that = this
var predicates = []
@ -939,7 +982,12 @@ module.BaseStore = {
return false } }
return true }
: undefined },
match: async function(path, strict=false){
//*/
// XXX EXPERIMENTAL...
// to be sync this needs:
// .__match_args(..) -- DONE
// .metadata(..)
match: function(path, strict=false){
var that = this
// pattern match * / **
if(path.includes('*')
@ -954,10 +1002,14 @@ module.BaseStore = {
?? !(args.sortnewfirst
// default is sortnewlast...
?? false)
var test = await this.__match_args(args)
var test = this.__match_args(args)
args = pwpath.joinArgs('', args)
var order = (await this.metadata(path) ?? {}).order || []
var order = Promise.awaitOrRun(
this.metadata(path),
function(metadata){
return (metadata ?? {}).order || [] })
// NOTE: we are matching full paths only here so leading and
// trainling '/' are optional...
@ -970,58 +1022,149 @@ module.BaseStore = {
// dir for hidden tests...
.replace(/(^|\\\/+)(\\\.|)([^\/]*)\\\*/g, '$1$2($3[^\\/]*)')
+'(?=[\\/]|$)', 'g')
return [...(await this.paths)
// NOTE: we are not using .filter(..) here as wee
// need to keep parts of the path only and not
// return the whole thing...
.reduce(function(res, p){
// skip metadata paths...
if(p.includes('*')){
return res }
// check path: stage 1
var m = [...p.matchAll(pattern)]
var visible = m.length > 0
&& (!all ?
// test if we need to hide things....
m.reduce(function(res, m){
return res === false ?
res
: !/(^\.|[\\\/]\.)/.test(m[1])
}, true)
: true)
// args...
// NOTE: this needs to be between path checking
// stages as we need to skip paths depending
// on the all argument...
if(visible
&& test
&& !test(p)){
return res }
// check path: stage 2
visible
&& (m = m[0])
&& (!strict
|| m[0] == p)
&& res.add(
// normalize the path elements...
m[0][0] == '/' ?
m[0].slice(1)
: m[0])
return res }, new Set())]
// handle live sort...
.run(function(){
return (sort && sort !== true) ?
that
.sort(this, ...sort.split(/\s*[,\s]+/g))
:this
.sortAs(order,
newlast ?
'head'
: 'tail') })
.map(function(p){
return p+args })}
// direct search...
return this.find(path, strict) },
// XXX ASYNC...
return Promise.awaitOrRun(
this.paths,
test,
order,
function(paths, test, order){
return [...paths
// NOTE: we are not using .filter(..) here as wee
// need to keep parts of the path only and not
// return the whole thing...
.reduce(function(res, p){
// skip metadata paths...
if(p.includes('*')){
return res }
// check path: stage 1
var m = [...p.matchAll(pattern)]
var visible = m.length > 0
&& (!all ?
// test if we need to hide things....
m.reduce(function(res, m){
return res === false ?
res
: !/(^\.|[\\\/]\.)/.test(m[1])
}, true)
: true)
// args...
// NOTE: this needs to be between path checking
// stages as we need to skip paths depending
// on the all argument...
if(visible
&& test
&& !test(p)){
return res }
// check path: stage 2
visible
&& (m = m[0])
&& (!strict
|| m[0] == p)
&& res.add(
// normalize the path elements...
m[0][0] == '/' ?
m[0].slice(1)
: m[0])
return res }, new Set())]
// handle live sort...
.run(function(){
return (sort && sort !== true) ?
that
.sort(this, ...sort.split(/\s*[,\s]+/g))
: this
.sortAs(order,
newlast ?
'head'
: 'tail') })
.map(function(p){
return p+args }) }) }
// direct search...
return this.find(path, strict) },
// // XXX ASYNC...
// match: async function(path, strict=false){
// var that = this
// // pattern match * / **
// if(path.includes('*')
// || path.includes('**')){
// var {path, args} = pwpath.splitArgs(path)
// path = pwpath.sanitize(path)
//
// var all = args.all
// var sort = args.sort
// var newlast =
// args.sortnewlast
// ?? !(args.sortnewfirst
// // default is sortnewlast...
// ?? false)
// var test = await this.__match_args(args)
// args = pwpath.joinArgs('', args)
//
// var order = (await this.metadata(path) ?? {}).order || []
//
// // NOTE: we are matching full paths only here so leading and
// // trainling '/' are optional...
// var pattern = new RegExp(`^\\/?`
// +RegExp.quoteRegExp(path)
// // pattern: **
// .replace(/\\\*\\\*/g, '(.*)')
// // pattern: *
// // NOTE: we are prepping the leading '.' of a pattern
// // dir for hidden tests...
// .replace(/(^|\\\/+)(\\\.|)([^\/]*)\\\*/g, '$1$2($3[^\\/]*)')
// +'(?=[\\/]|$)', 'g')
// return [...(await this.paths)
// // NOTE: we are not using .filter(..) here as wee
// // need to keep parts of the path only and not
// // return the whole thing...
// .reduce(function(res, p){
// // skip metadata paths...
// if(p.includes('*')){
// return res }
// // check path: stage 1
// var m = [...p.matchAll(pattern)]
// var visible = m.length > 0
// && (!all ?
// // test if we need to hide things....
// m.reduce(function(res, m){
// return res === false ?
// res
// : !/(^\.|[\\\/]\.)/.test(m[1])
// }, true)
// : true)
// // args...
// // NOTE: this needs to be between path checking
// // stages as we need to skip paths depending
// // on the all argument...
// if(visible
// && test
// && !test(p)){
// return res }
// // check path: stage 2
// visible
// && (m = m[0])
// && (!strict
// || m[0] == p)
// && res.add(
// // normalize the path elements...
// m[0][0] == '/' ?
// m[0].slice(1)
// : m[0])
// return res }, new Set())]
// // handle live sort...
// .run(function(){
// return (sort && sort !== true) ?
// that
// .sort(this, ...sort.split(/\s*[,\s]+/g))
// :this
// .sortAs(order,
// newlast ?
// 'head'
// : 'tail') })
// .map(function(p){
// return p+args })}
// // direct search...
// return this.find(path, strict) },
//*/
//
// .resolve(<path>)
// -> <path>
@ -1039,6 +1182,32 @@ module.BaseStore = {
// -> ['System/tree', 'Dir/tree', ...]
//
// XXX should this be used by .get(..) instead of .match(..)???
// XXX EXPERIMENTAL
// to be sync requires:
// .match(..)
resolve: function(path, strict){
// pattern match * / **
if(path.includes('*')
|| path.includes('**')){
var p = pwpath.splitArgs(path)
var args = pwpath.joinArgs('', p.args)
p = pwpath.split(p.path)
var tail = []
while(!p.at(-1).includes('*')){
tail.unshift(p.pop()) }
tail = tail.join('/')
if(tail.length > 0){
return Promise
.iter(this.match(
p.join('/') + args,
strict))
.map(function(p){
var {path, args} = pwpath.splitArgs(p)
return pwpath.joinArgs(pwpath.join(path, tail), args) })
.sync() } }
// direct...
return this.match(path, strict) },
/*/ // XXX ASYNC...
resolve: async function(path, strict){
// pattern match * / **
if(path.includes('*')
@ -1059,6 +1228,7 @@ module.BaseStore = {
return pwpath.joinArgs(pwpath.join(path, tail), args) }) } }
// direct...
return this.match(path, strict) },
//*/
//
// Resolve page
// .get(<path>)