mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: add template based data generator
This commit is contained in:
12
src/pods/generate/HandlebarsTemplateEngine.ts
Normal file
12
src/pods/generate/HandlebarsTemplateEngine.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
8
src/pods/generate/TemplateEngine.ts
Normal file
8
src/pods/generate/TemplateEngine.ts
Normal 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;
|
||||
}
|
||||
92
src/pods/generate/TemplatedResourcesGenerator.ts
Normal file
92
src/pods/generate/TemplatedResourcesGenerator.ts
Normal 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,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user