153 lines
4.7 KiB
TypeScript
153 lines
4.7 KiB
TypeScript
import LifecycleUnit from '../lifecycle/Unit.ts'
|
|
import {fs, path} from '../external/std.ts'
|
|
import {Canon} from './Canon.ts'
|
|
import {Logging} from '../service/logging/Logging.ts'
|
|
|
|
/**
|
|
* Base type for a canonical definition.
|
|
*/
|
|
export interface CanonicalDefinition {
|
|
canonical_name: string,
|
|
original_name: string,
|
|
imported: any,
|
|
}
|
|
|
|
/**
|
|
* Base type for a canonical name reference.
|
|
*/
|
|
export interface CanonicalReference {
|
|
resource?: string,
|
|
item: string,
|
|
particular?: string,
|
|
}
|
|
|
|
/**
|
|
* Base class for all canonical units. Provides helpers for reading and standardizing
|
|
* the names of classes defined in the filesystem structure.
|
|
* @extends LifecycleUnit
|
|
*/
|
|
export class Canonical<T> extends LifecycleUnit {
|
|
/**
|
|
* The base path directory where the canonical definitions reside.
|
|
* @type string
|
|
*/
|
|
protected base_path: string = '.'
|
|
|
|
/**
|
|
* The file suffix of files in the base path that should be loaded.
|
|
* @type string
|
|
*/
|
|
protected suffix: string = '.ts'
|
|
|
|
/**
|
|
* The singular, programmatic name of one of these canonical items.
|
|
* @example middleware
|
|
* @type string
|
|
*/
|
|
protected canonical_item: string = ''
|
|
|
|
/**
|
|
* Object mapping canonical names to loaded file references.
|
|
* @type object
|
|
*/
|
|
protected _items: { [key: string]: T } = {}
|
|
|
|
/**
|
|
* Resolve a canonical reference from its string form to a CanonicalReference.
|
|
* @param {string} reference
|
|
* @return CanonicalReference
|
|
*/
|
|
public static resolve(reference: string): CanonicalReference {
|
|
const rsc_parts = reference.split('::')
|
|
const resource = rsc_parts.length > 1 ? rsc_parts[0] + 's' : undefined
|
|
const rsc_less = rsc_parts.length > 1 ? rsc_parts[1] : rsc_parts[0]
|
|
const prt_parts = rsc_less.split('.')
|
|
const item = prt_parts[0]
|
|
const particular = prt_parts.length > 1 ? prt_parts.slice(1).join('.') : undefined
|
|
|
|
return {
|
|
resource, item, particular
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get an array of all canonical reference names.
|
|
* @return Array<string>
|
|
*/
|
|
public all(): string[] {
|
|
return Object.keys(this._items)
|
|
}
|
|
|
|
/**
|
|
* Get the fully-qualified path to the base directory for this unit.
|
|
* @type string
|
|
*/
|
|
public get path(): string {
|
|
return path.resolve(this.base_path)
|
|
}
|
|
|
|
/**
|
|
* Get the plural, programmatic name of the canonical items provide by this unit.
|
|
* @type string
|
|
*/
|
|
public get canonical_items() {
|
|
return `${this.canonical_item}s`
|
|
}
|
|
|
|
public async up() {
|
|
const logger = this.make(Logging)
|
|
for await ( const entry of fs.walk(this.path) ) {
|
|
if ( !entry.isFile || !entry.path.endsWith(this.suffix) ) {
|
|
if ( entry.isFile ) logger.debug(`Skipping file in canonical path with invalid suffix: ${entry.path}`)
|
|
continue
|
|
}
|
|
const def = await this._get_canonical_definition(entry.path)
|
|
logger.verbose(`Registering canonical ${this.canonical_item} "${def.canonical_name}" from ${entry.path}.`)
|
|
this._items[def.canonical_name] = await this.init_canonical_item(def)
|
|
}
|
|
this.make(Canon).register_canonical(this)
|
|
}
|
|
|
|
/**
|
|
* Given a single canonical definition loaded from a file in the base path,
|
|
* initialize the item and return the result that should be mapped to the reference name.
|
|
* @param {CanonicalDefinition} definition
|
|
* @return Promise<any>
|
|
*/
|
|
public async init_canonical_item(definition: CanonicalDefinition): Promise<T> {
|
|
return definition.imported.default
|
|
}
|
|
|
|
/**
|
|
* Given a file path, build the canonical definition represented by that path.
|
|
* @param {string} file_path
|
|
* @private
|
|
* @return Promise<CanonicalDefinition>
|
|
*/
|
|
private async _get_canonical_definition(file_path: string): Promise<CanonicalDefinition> {
|
|
const original_name = file_path.replace(this.path, '').substr(1)
|
|
const path_regex = new RegExp(path.SEP, 'g')
|
|
const canonical_name = original_name.replace(path_regex, ':')
|
|
.split('').reverse().join('')
|
|
.substr(this.suffix.length)
|
|
.split('').reverse().join('')
|
|
|
|
if ( file_path.startsWith('/') ) {
|
|
file_path = `file://${file_path}`
|
|
}
|
|
|
|
this.make(Logging).debug(`Importing from: ${file_path}`)
|
|
const imported = await import(file_path)
|
|
return { canonical_name, original_name, imported }
|
|
}
|
|
|
|
/**
|
|
* Given a canonical reference string, get the corresponding item, if it exists.
|
|
* @param {string} key
|
|
* @return any | undefined
|
|
*/
|
|
public get(key: string): T | undefined {
|
|
return this._items[key]
|
|
}
|
|
}
|