@absolunet/ioc2.1.0

View on GitHub

view/services/Resolver.js

//--------------------------------------------------------
//-- Node IoC - View - Services - Resolver
//--------------------------------------------------------

import __ from '@absolunet/private-registry';


/**
 * View file resolver.
 * It allows to resolve path of a given view name.
 *
 * @memberof view.services
 * @hideconstructor
 */
class Resolver {

	/**
	 * Class dependencies: <code>['app', 'config', 'file']</code>.
	 *
	 * @type {Array<string>}
	 */
	static get dependencies() {
		return ['app', 'config', 'file'];
	}

	/**
	 * @inheritdoc
	 * @private
	 */
	init() {
		__(this).set('paths',      []);
		__(this).set('namespaces', {});
	}

	/**
	 * Find view path by name.
	 *
	 * @param {string} view - The view name.
	 * @returns {string} The view content.
	 * @throws {TypeError} Indicates that the given view was not found.
	 */
	find(view) {
		const viewPath = this.getViewPath(view);

		if (!viewPath) {
			throw new TypeError(`The view "${view}" does not exists.`);
		}

		return this.file.driver('text').load(viewPath);
	}

	/**
	 * Check if view file exists by name.
	 *
	 * @param {string} view - The view name.
	 * @returns {boolean} Indicates that the view file exists and may be rendered.
	 */
	exists(view) {
		return Boolean(this.getViewPath(view));
	}

	/**
	 * Get view path by name.
	 *
	 * @param {string} view - The view name.
	 * @returns {string|null} The found view path, or null if not found.
	 */
	getViewPath(view) {
		return this.file.findFirst(this.getViewPaths(view)) || null;
	}

	/**
	 * Get view possible paths by name.
	 *
	 * @param {string} view - The view name.
	 * @returns {Array<string>} The possible view file paths.
	 */
	getViewPaths(view) {
		const namespaceRegex = new RegExp(`^.+${this.namespaceDelimiter}`, 'u');
		const viewFileName   = `${view.replace(namespaceRegex, '').replace(/\./gu, '/')}`;

		return this.getViewBasePaths(view).flatMap((basePath) => {
			return this.extensions.flatMap((extension) => {
				return this.app.formatPath(basePath, `${viewFileName}.${extension}`);
			});
		});
	}

	/**
	 * Get base path of the given view based on the namespace if present.
	 *
	 * @param {string} view - The view name.
	 * @returns {Array<string>} The view folder paths.
	 * @throws {TypeError} Indicates that the given view namespace was not found.
	 */
	getViewBasePaths(view) {
		if (!view.includes(this.namespaceDelimiter)) {
			return this.getBasePaths();
		}

		const namespace      = view.slice(0, view.indexOf(this.namespaceDelimiter));
		const namespacePaths = __(this).get('namespaces')[namespace];

		if (!namespacePaths || namespacePaths.length === 0) {
			throw new TypeError(`View namespace "${namespace}" (${view}) does not exist`);
		}

		return [...namespacePaths];
	}

	/**
	 * Register views namespace.
	 *
	 * @param {string} namespace - The namespace.
	 * @param {string} folder - The folder.
	 * @returns {view.services.Resolver} The current resolver instance.
	 */
	namespace(namespace, folder) {
		const namespaces = __(this).get('namespaces');
		namespaces[namespace] = namespaces[namespace] || [];
		namespaces[namespace].push(folder);

		return this;
	}

	/**
	 * Add possible views path.
	 *
	 * @param {string} path - The new views path.
	 * @returns {view.services.Resolver} The current resolver instance.
	 */
	addPath(path) {
		__(this).get('paths').push(path);

		return this;
	}

	/**
	 * Get all views base paths.
	 *
	 * @returns {Array<string>} The view base paths.
	 */
	getBasePaths() {
		return [
			this.app.viewPath(),
			...__(this).get('paths')
		];
	}

	/**
	 * The supported file extensions.
	 *
	 * @type {Array<string>}
	 */
	get extensions() {
		return this.config.get('view.extensions', [
			'html',
			'jshtml'
		]);
	}

	/**
	 * The namespace delimiter.
	 *
	 * @type {string}
	 */
	get namespaceDelimiter() {
		return '::';
	}

}


export default Resolver;