mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-16 12:50:38 -07:00
91 lines
2.8 KiB
TypeScript
91 lines
2.8 KiB
TypeScript
import { FS } from './fs'
|
|
import { assert, LiquidAsync, toLiquidAsync } from '../util'
|
|
|
|
export interface LoaderOptions {
|
|
fs: FS;
|
|
extname: string;
|
|
root: string[];
|
|
partials: string[];
|
|
layouts: string[];
|
|
relativeReference: boolean;
|
|
}
|
|
export enum LookupType {
|
|
Partials = 'partials',
|
|
Layouts = 'layouts',
|
|
Root = 'root'
|
|
}
|
|
export class Loader {
|
|
public shouldLoadRelative: (referencedFile: string) => boolean
|
|
private options: LoaderOptions
|
|
private contains: LiquidAsync<NonNullable<FS['containsSync']>>
|
|
private exists: LiquidAsync<FS['existsSync']>
|
|
|
|
constructor (options: LoaderOptions) {
|
|
this.options = options
|
|
if (options.relativeReference) {
|
|
const sep = options.fs.sep
|
|
assert(sep, '`fs.sep` is required for relative reference')
|
|
const prefixes = ['.' + sep, '..' + sep, './', '../']
|
|
this.shouldLoadRelative = (referencedFile: string) => prefixes.some(prefix => referencedFile.startsWith(prefix))
|
|
} else {
|
|
this.shouldLoadRelative = (_referencedFile: string) => false
|
|
}
|
|
const fs = options.fs
|
|
this.contains = toLiquidAsync(
|
|
fs.containsSync?.bind(fs) || (() => true),
|
|
fs.contains?.bind(fs)
|
|
)
|
|
this.exists = toLiquidAsync(
|
|
fs.existsSync.bind(fs),
|
|
fs.exists.bind(fs)
|
|
)
|
|
}
|
|
|
|
public * lookup (file: string, type: LookupType, sync?: boolean, currentFile?: string): Generator<unknown, string, string> {
|
|
const dirs = this.options[type]
|
|
const enforceRoot = type !== LookupType.Root
|
|
for (const filepath of this.candidates(file, dirs, currentFile)) {
|
|
if (enforceRoot) {
|
|
let allowed = false
|
|
for (const dir of dirs) {
|
|
if (yield this.contains(!!sync, dir, filepath)) { allowed = true; break }
|
|
}
|
|
if (!allowed) continue
|
|
}
|
|
if (yield this.exists(!!sync, filepath)) return filepath
|
|
}
|
|
throw this.lookupError(file, dirs)
|
|
}
|
|
|
|
public * candidates (file: string, dirs: string[], currentFile?: string) {
|
|
const { fs, extname } = this.options
|
|
|
|
if (this.shouldLoadRelative(file) && currentFile) {
|
|
const referenced = fs.resolve(this.dirname(currentFile), file, extname)
|
|
yield referenced
|
|
}
|
|
for (const dir of dirs) {
|
|
const referenced = fs.resolve(dir, file, extname)
|
|
yield referenced
|
|
}
|
|
|
|
if (fs.fallback !== undefined) {
|
|
const filepath = fs.fallback(file)
|
|
if (filepath !== undefined) yield filepath
|
|
}
|
|
}
|
|
|
|
private dirname (path: string) {
|
|
const fs = this.options.fs
|
|
assert(fs.dirname, '`fs.dirname` is required for relative reference')
|
|
return fs.dirname!(path)
|
|
}
|
|
|
|
private lookupError (file: string, roots: string[]) {
|
|
const err = new Error('ENOENT') as any
|
|
err.message = `ENOENT: Failed to lookup "${file}" in "${roots}"`
|
|
err.code = 'ENOENT'
|
|
return err
|
|
}
|
|
}
|