diff --git a/lib/src/support/UniversalPath.ts b/lib/src/support/UniversalPath.ts new file mode 100644 index 0000000..dfc7e88 --- /dev/null +++ b/lib/src/support/UniversalPath.ts @@ -0,0 +1,90 @@ +import { path as deno_path } from '../external/std.ts' + +export enum UniversalPathPrefix { + HTTP = 'http://', + HTTPS = 'https://', + Local = 'file://', +} + +export type PathLike = string | UniversalPath + +export function universal_path(...parts: PathLike[]): UniversalPath { + let [main, ...concats] = parts + if ( !(main instanceof UniversalPath) ) main = new UniversalPath(main) + return main.concat(...concats) +} + +export class UniversalPath { + protected _prefix!: UniversalPathPrefix + protected _local!: string + + constructor( + protected readonly initial: string, + ) { + this.set_prefix() + this.set_local() + } + + protected set_prefix() { + if ( this.initial.toLowerCase().startsWith('http://') ) { + this._prefix = UniversalPathPrefix.HTTP + } else if ( this.initial.toLowerCase().startsWith('https://') ) { + this._prefix = UniversalPathPrefix.HTTPS + } else { + this._prefix = UniversalPathPrefix.Local + } + } + + protected set_local() { + this._local = this.initial + if ( this.initial.toLowerCase().startsWith(this._prefix) ) { + this._local = this._local.slice(this._prefix.length) + } + + if ( this._prefix === UniversalPathPrefix.Local && !this._local.startsWith('/') ) { + this._local = deno_path.resolve(this._local) + } + } + + get prefix() { + return this._prefix + } + + get is_local() { + return this._prefix === UniversalPathPrefix.Local + } + + get is_remote() { + return this._prefix !== UniversalPathPrefix.Local + } + + get unqualified() { + return this._local + } + + get to_local() { + if ( this.is_local ) { + return this._local + } else { + return `${this.prefix}${this._local}` + } + } + + get to_remote() { + return `${this.prefix}${this._local}` + } + + public concat(...paths: PathLike[]): UniversalPath { + const resolved = deno_path.join(this.unqualified, ...(paths.map(p => typeof p === 'string' ? p : p.unqualified))) + return new UniversalPath(`${this.prefix}${resolved}`) + } + + public append(path: PathLike): this { + this._local += String(path) + return this + } + + toString() { + return `${this.prefix}${this._local}` + } +} diff --git a/lib/src/unit/ViewEngine.ts b/lib/src/unit/ViewEngine.ts index 495f624..404849b 100644 --- a/lib/src/unit/ViewEngine.ts +++ b/lib/src/unit/ViewEngine.ts @@ -5,7 +5,8 @@ import {views, handlebars} from '../external/http.ts' import {Unit} from '../lifecycle/decorators.ts' import {Logging} from '../service/logging/Logging.ts' import {ConfigError} from '../error/ConfigError.ts' -import {path, fs} from '../external/std.ts' +import {fs} from '../external/std.ts' +import {PathLike, universal_path, UniversalPath} from '../support/UniversalPath.ts' /** * Error thrown when an action requiring a view engine is attempted, but no view @@ -35,7 +36,7 @@ export class MissingTemplateDirectoryError extends Error { @Unit() export default class ViewEngine extends LifecycleUnit { protected engine?: Types - protected template_dir?: string + protected template_dir?: UniversalPath constructor( protected readonly config: Config, @@ -56,7 +57,7 @@ export default class ViewEngine extends LifecycleUnit { throw new ConfigError('app.views.base_dir', base_dir) } - this.template_dir = this.app.app_path(base_dir) + this.template_dir = universal_path(this.app.app_path(base_dir)) this.engine = config this.logger.info(`Determined view engine from config: ${config}`) this.logger.info(`Determined base directory for templates: ${this.template_dir}`) @@ -71,16 +72,14 @@ export default class ViewEngine extends LifecycleUnit { * @return Promise */ public async template(template_path: string, data: { [key: string]: any } = {}) { - let file_path = `${this.template_path(template_path.replace(/:/g, '/'))}${this.get_file_extension()}` - if ( file_path.startsWith('file://') ) { - file_path = file_path.slice(7) - } + const template_name = template_path.replace(/:/g, '/') + const file_path = this.template_path(template_name).append(this.get_file_extension()) this.logger.debug(`Rendering template "${template_path}" from file: ${file_path}`) // TODO cache this // TODO replace with fs.readFileStr - const content = await Deno.readTextFile(file_path) + const content = await Deno.readTextFile(file_path.to_local) const engine: views.Engine = this.get_engine() return engine( @@ -96,17 +95,9 @@ export default class ViewEngine extends LifecycleUnit { * @param {...string} parts * @return string */ - public template_path(...parts: string[]): string { + public template_path(...parts: PathLike[]): UniversalPath { if ( !this.template_dir ) throw new MissingTemplateDirectoryError() - let template_dir = this.template_dir - if ( template_dir.startsWith('file://') ) template_dir = template_dir.slice(7) - - const resolved_path = path.resolve(template_dir, ...parts) - if ( resolved_path.startsWith('/') ) { - return `file://${resolved_path}` - } else { - return resolved_path - } + return this.template_dir.concat(...parts) } /** @@ -156,13 +147,11 @@ export default class ViewEngine extends LifecycleUnit { */ public get_render_context() { if ( !this.template_dir ) throw new MissingTemplateDirectoryError() - let template_dir = this.template_dir - if ( template_dir.startsWith('file://') ) template_dir = template_dir.slice(7) return { viewExt: this.get_file_extension(), viewEngine: this.get_engine(), - viewRoot: template_dir, + viewRoot: this.template_dir.to_local, useCache: false, // TODO better value here cache: undefined, } @@ -184,14 +173,10 @@ export default class ViewEngine extends LifecycleUnit { protected async init_engine_handlebars() { const partials_dir: unknown = this.config.get('app.views.partials_dir') if ( String(partials_dir) ) { - let partials_path = this.template_path(String(partials_dir)) + const partials_path = this.template_path(String(partials_dir)) this.logger.info(`Registering Handlebars partials from: ${partials_path}`) - if ( partials_path.startsWith('file://') ) { - partials_path = partials_path.slice(7) - } - - for await ( const entry of fs.walk(partials_path) ) { + for await ( const entry of fs.walk(partials_path.to_local) ) { if ( !entry.isFile || !entry.path.endsWith(this.get_file_extension()) ) { if ( entry.isFile ) this.logger.debug(`Skipping file in Handlebars partials path with invalid suffix: ${entry.path}`) continue