feat: add template based data generator

This commit is contained in:
Joachim Van Herwegen
2020-11-27 13:44:41 +01:00
parent 9653deec7f
commit f387b36dc2
14 changed files with 285 additions and 6 deletions

View File

@@ -0,0 +1,12 @@
import { compile } from 'handlebars';
import type { TemplateEngine } from './TemplateEngine';
/**
* Fills in Handlebars templates.
*/
export class HandlebarsTemplateEngine implements TemplateEngine {
public apply(template: string, options: NodeJS.Dict<string>): string {
const compiled = compile(template);
return compiled(options);
}
}

View File

@@ -0,0 +1,8 @@
import Dict = NodeJS.Dict;
/**
* A template engine takes as input a template and applies the given options to it.
*/
export interface TemplateEngine {
apply: (template: string, options: Dict<string>) => string;
}

View File

@@ -0,0 +1,92 @@
import { promises as fsPromises } from 'fs';
import { posix } from 'path';
import { RepresentationMetadata } from '../../ldp/representation/RepresentationMetadata';
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
import type { FileIdentifierMapper, FileIdentifierMapperFactory } from '../../storage/mapping/FileIdentifierMapper';
import { guardedStreamFrom } from '../../util/StreamUtil';
import type { Resource, ResourcesGenerator } from './ResourcesGenerator';
import type { TemplateEngine } from './TemplateEngine';
import Dict = NodeJS.Dict;
const { join: joinPath } = posix;
/**
* Generates resources by making use of a template engine.
* The template folder structure will be kept.
* Folders will be interpreted as containers and files as documents.
* A FileIdentifierMapper will be used to generate identifiers that correspond to the relative structure.
*/
export class TemplatedResourcesGenerator implements ResourcesGenerator {
private readonly templateFolder: string;
private readonly factory: FileIdentifierMapperFactory;
private readonly engine: TemplateEngine;
/**
* A mapper is needed to convert the template file paths to identifiers relative to the given base identifier.
*
* @param templateFolder - Folder where the templates are located.
* @param factory - Factory used to generate mapper relative to the base identifier.
* @param engine - Template engine for generating the resources.
*/
public constructor(templateFolder: string, factory: FileIdentifierMapperFactory, engine: TemplateEngine) {
this.templateFolder = templateFolder;
this.factory = factory;
this.engine = engine;
}
public async* generate(location: ResourceIdentifier, options: Dict<string>): AsyncIterable<Resource> {
const mapper = await this.factory.create(location.path, this.templateFolder);
yield* this.parseFolder(this.templateFolder, mapper, options);
}
/**
* Generates results for all entries in the given folder, including the folder itself.
*/
private async* parseFolder(filePath: string, mapper: FileIdentifierMapper, options: Dict<string>):
AsyncIterable<Resource> {
// Generate representation for the container
const link = await mapper.mapFilePathToUrl(filePath, true);
yield {
identifier: link.identifier,
representation: {
binary: true,
data: guardedStreamFrom([]),
metadata: new RepresentationMetadata(link.identifier.path),
},
};
// Generate representations for all resources in this container
const files = await fsPromises.readdir(filePath);
for (const childName of files) {
const childPath = joinPath(filePath, childName);
const childStats = await fsPromises.lstat(childPath);
if (childStats.isDirectory()) {
yield* this.parseFolder(childPath, mapper, options);
} else if (childStats.isFile()) {
yield this.generateDocument(childPath, mapper, options);
}
}
}
/**
* Generates a new Representation corresponding to the template file at the given location.
*/
private async generateDocument(filePath: string, mapper: FileIdentifierMapper, options: Dict<string>):
Promise<Resource> {
const link = await mapper.mapFilePathToUrl(filePath, false);
const metadata = new RepresentationMetadata(link.identifier.path);
metadata.contentType = link.contentType;
const raw = await fsPromises.readFile(filePath, 'utf8');
const compiled = this.engine.apply(raw, options);
return {
identifier: link.identifier,
representation: {
binary: true,
data: guardedStreamFrom([ compiled ]),
metadata,
},
};
}
}

View File

@@ -19,7 +19,7 @@ import { parseQuads, pushQuad, serializeQuads } from '../../util/QuadUtil';
import { generateContainmentQuads, generateResourceQuads } from '../../util/ResourceUtil';
import { CONTENT_TYPE, DCTERMS, POSIX, RDF, XSD } from '../../util/UriConstants';
import { toNamedNode, toTypedLiteral } from '../../util/UriUtil';
import type { FileIdentifierMapper, ResourceLink } from '../FileIdentifierMapper';
import type { FileIdentifierMapper, ResourceLink } from '../mapping/FileIdentifierMapper';
import type { DataAccessor } from './DataAccessor';
const { join: joinPath } = posix;

View File

@@ -12,7 +12,7 @@ import {
isContainerIdentifier,
trimTrailingSlashes,
} from '../../util/PathUtil';
import type { FileIdentifierMapper, ResourceLink } from '../FileIdentifierMapper';
import type { FileIdentifierMapper, FileIdentifierMapperFactory, ResourceLink } from './FileIdentifierMapper';
import { getAbsolutePath, getRelativePath, validateRelativePath } from './MapperUtil';
const { join: joinPath, normalize: normalizePath } = posix;
@@ -197,3 +197,10 @@ export class ExtensionBasedMapper implements FileIdentifierMapper {
return extension && extension[1];
}
}
export class ExtensionBasedMapperFactory implements FileIdentifierMapperFactory<ExtensionBasedMapper> {
public async create(base: string, rootFilePath: string): Promise<ExtensionBasedMapper> {
return new ExtensionBasedMapper(base, rootFilePath);
}
}

View File

@@ -1,4 +1,4 @@
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
export interface ResourceLink {
/**
@@ -38,3 +38,11 @@ export interface FileIdentifierMapper {
*/
mapUrlToFilePath: (identifier: ResourceIdentifier, contentType?: string) => Promise<ResourceLink>;
}
/**
* Factory that can create FileIdentifierMappers so the base and rootFilePath can be set dynamically.
* Specifically used when identifiers need to be generated for a new pod (since pod identifiers are generated).
*/
export interface FileIdentifierMapperFactory<T extends FileIdentifierMapper = FileIdentifierMapper> {
create: (base: string, rootFilePath: string) => Promise<T>;
}

View File

@@ -7,7 +7,7 @@ import {
ensureTrailingSlash, isContainerIdentifier,
trimTrailingSlashes,
} from '../../util/PathUtil';
import type { FileIdentifierMapper, ResourceLink } from '../FileIdentifierMapper';
import type { FileIdentifierMapper, ResourceLink } from './FileIdentifierMapper';
import { getAbsolutePath, getRelativePath, validateRelativePath } from './MapperUtil';
const { normalize: normalizePath } = posix;