import type { NumOrString } from "@thi.ng/api" ; import { SEMAPHORE } from "@thi.ng/api/api" ; import { isArray } from "@thi.ng/checks/is-array" ; import { isFunction } from "@thi.ng/checks/is-function" ; import { isPlainObject } from "@thi.ng/checks/is-plain-object" ; import { isString } from "@thi.ng/checks/is-string" ; import { illegalArgs } from "@thi.ng/errors/illegal-arguments" ; import { getInUnsafe } from "@thi.ng/paths/get-in" ; import { mutInUnsafe } from "@thi.ng/paths/mut-in" ; import { exists } from "@thi.ng/paths/path" ; const RE_ARGS = /^(function\s+\w+)?\s*\(\{([\w\s,:]+)\}/ ; export type ResolveFn = ( path : string ) => any ; export type LookupPath = NumOrString [ ] ; example param root param prefix export const resolve = ( root , prefix = "@" ) => { if ( isPlainObject ( root ) ) { return resolveMap ( root , prefix ) ; } else if ( isArray ( root ) ) { return resolveArray ( root , prefix ) ; } return root ; } ; const resolveMap = ( obj , prefix , root ? , path = [ ] , resolved = { } , stack = [ ] ) => { root = root || obj ; for ( let k in obj ) { _resolve ( root , [ ... path , k ] , resolved , stack , prefix ) ; } return obj ; } ; const resolveArray = ( arr , prefix , root ? , path = [ ] , resolved = { } , stack = [ ] ) => { root = root || arr ; for ( let k = 0 , n = arr . length ; k < n ; k ++ ) { _resolve ( root , [ ... path , k ] , resolved , stack , prefix ) ; } return arr ; } ; param root param path param resolved param stack const _resolve = ( root , path , resolved , stack , prefix ) => { const pathID = path . join ( "/" ) ; if ( stack . indexOf ( pathID ) >= 0 ) { illegalArgs ( `cyclic references not allowed: ${ pathID }` ) ; } let v = getInUnsafe ( root , path ) ; if ( ! resolved [ pathID ] ) { let res = SEMAPHORE ; stack . push ( pathID ) ; if ( isPlainObject ( v ) ) { resolveMap ( v , prefix , root , path , resolved , stack ) ; } else if ( isArray ( v ) ) { resolveArray ( v , prefix , root , path , resolved , stack ) ; } else if ( isString ( v ) && v . startsWith ( prefix ) ) { res = _resolve ( root , absPath ( path , v , prefix . length ) , resolved , stack , prefix ) ; } else if ( isFunction ( v ) ) { res = resolveFunction ( v , ( p ) => _resolve ( root , absPath ( path , p , 0 ) , resolved , stack , prefix ) , pathID , resolved ) ; } else if ( ! exists ( root , path ) ) { v = resolvePath ( root , path , resolved , stack , prefix ) ; } if ( res !== SEMAPHORE ) { mutInUnsafe ( root , path , res ) ; v = res ; } resolved [ pathID ] = true ; stack . pop ( ) ; } return v ; } ; param root param path param resolved const resolvePath = ( root , path , resolved , stack , prefix ) => { let pathID = stack . pop ( ) ; let v ; for ( let i = 1 , n = path . length ; i <= n ; i ++ ) { v = _resolve ( root , path . slice ( 0 , i ) , resolved , stack , prefix ) ; } stack . push ( pathID ! ) ; return v ; } ; param fn param resolve param pathID param resolved const resolveFunction = ( fn , resolve , pathID , resolved ) => { const match = RE_ARGS . exec ( fn . toString ( ) ) ; let res ; if ( match ) { const args = match [ 2 ] . replace ( /\s|(,\s*$)/g , "" ) . split ( /,/g ) . map ( ( k ) => k . split ( ":" ) [ 0 ] ) . reduce ( ( acc , k ) => ( ( acc [ k ] = resolve ( k ) ) , acc ) , { } ) ; res = fn ( args , resolve ) ; } else { res = fn ( resolve ) ; } markResolved ( res , pathID , resolved ) ; return res ; } ; const markResolved = ( v , path , resolved ) => { resolved [ path ] = true ; if ( isPlainObject ( v ) ) { markObjResolved ( v , path , resolved ) ; } else if ( isArray ( v ) ) { markArrayResolved ( v , path , resolved ) ; } } ; const markObjResolved = ( obj , path , resolved ) => { let v , p ; for ( let k in obj ) { v = obj [ k ] ; p = path + "/" + k ; markResolved ( v , p , resolved ) ; } } ; const markArrayResolved = ( arr , path , resolved ) => { let v , p ; for ( let i = 0 , n = arr . length ; i < n ; i ++ ) { v = arr [ i ] ; p = path + "/" + i ; markResolved ( v , p , resolved ) ; } } ; param curr param path param idx export const absPath = ( curr , path , idx = 1 ) : NumOrString [ ] => { if ( path . charAt ( idx ) === "/" ) { return path . substr ( idx + 1 ) . split ( "/" ) ; } curr = curr . slice ( 0 , curr . length - 1 ) ; const sub = path . substr ( idx ) . split ( "/" ) ; for ( let i = 0 , n = sub . length ; i < n ; i ++ ) { if ( sub [ i ] === ".." ) { ! curr . length && illegalArgs ( `invalid lookup path: ${ path }` ) ; curr . pop ( ) ; } else { return curr . concat ( sub . slice ( i ) ) ; } } ! curr . length && illegalArgs ( `invalid lookup path: ${ path }` ) ; return curr ; } ;