@shockpkg/dir-projector
Version:
Package for creating Shockwave Director projectors
550 lines (451 loc) • 10.7 kB
JavaScript
import { join as pathJoin } from 'path';
import { ArchiveDir, ArchiveHdi, createArchiveByFileExtension } from "@shockpkg/archive-files/module.mjs";
import fse from 'fs-extra';
import { defaultFalse, defaultNull, pathRelativeBase, trimExtension } from "./util.mjs";
/**
* Projector constructor.
*
* @param options Options object.
*/
export class Projector extends Object {
/**
* Skeleton file or directory.
*
* @default null
*/
/**
* Movie file.
*
* @default null
*/
/**
* Movie data.
*
* @default null
*/
/**
* Movie name.
*
* @default null
*/
/**
* Movie data.
*
* @default false
*/
/**
* Config file.
*
* @default null
*/
/**
* Config data.
*
* @default null
*/
/**
* Lingo file.
*
* @default null
*/
/**
* Lingo data.
*
* @default null
*/
/**
* Splash image file.
*
* @default null
*/
/**
* Splash image data.
*
* @default null
*/
/**
* Xtras include map.
*
* @default null
*/
/**
* Nest xtras in a Configuration directory.
*
* @default false
*/
/**
* Path to hdiutil binary.
*
* @default null
*/
constructor(options = {}) {
super();
this.skeleton = void 0;
this.movieFile = void 0;
this.movieData = void 0;
this.movieName = void 0;
this.shockwave = void 0;
this.configFile = void 0;
this.configData = void 0;
this.lingoFile = void 0;
this.lingoData = void 0;
this.splashImageFile = void 0;
this.splashImageData = void 0;
this.includeXtras = void 0;
this.nestXtrasConfiguration = void 0;
this.pathToHdiutil = void 0;
this.skeleton = defaultNull(options.skeleton);
this.movieFile = defaultNull(options.movieFile);
this.movieData = defaultNull(options.movieData);
this.movieName = defaultNull(options.movieName);
this.shockwave = defaultFalse(options.shockwave);
this.configFile = defaultNull(options.configFile);
this.configData = defaultNull(options.configData);
this.lingoFile = defaultNull(options.lingoFile);
this.lingoData = defaultNull(options.lingoData);
this.splashImageFile = defaultNull(options.splashImageFile);
this.splashImageData = defaultNull(options.splashImageData);
this.includeXtras = defaultNull(options.includeXtras);
this.nestXtrasConfiguration = defaultFalse(options.nestXtrasConfiguration);
this.pathToHdiutil = defaultNull(options.pathToHdiutil);
}
/**
* Config file extension.
*
* @returns File extension.
*/
get configExtension() {
return '.INI';
}
/**
* Config file encoding.
*
* @returns File encoding.
*/
get configEncoding() {
return 'ascii';
}
/**
* Lingo file name.
*
* @returns File name.
*/
get lingoName() {
return 'LINGO.INI';
}
/**
* Lingo file encoding.
*
* @returns File encoding.
*/
get lingoEncoding() {
return 'ascii';
}
/**
* Xtras directory name.
*
* @returns Directory encoding.
*/
get xtrasDirectoryName() {
return 'xtras';
}
/**
* Configuration directory name.
*
* @returns Directory encoding.
*/
get configurationDirectoryName() {
return 'Configuration';
}
/**
* Get movie data if any specified, from data or file.
*
* @returns Movie data or null.
*/
async getMovieData() {
return this._dataFromBufferOrFile(this.movieData, this.movieFile);
}
/**
* Get config data if any specified, from data or file.
*
* @returns Config data or null.
*/
async getConfigData() {
return this._dataFromValueOrFile(this.configData, this.configFile, this.configNewline, this.configEncoding);
}
/**
* Get lingo data if any specified, from data or file.
*
* @returns Lingo data or null.
*/
async getLingoData() {
return this._dataFromValueOrFile(this.lingoData, this.lingoFile, this.lingoNewline, this.lingoEncoding);
}
/**
* Get splash image data if any specified, from data or file.
*
* @returns Splash image data or null.
*/
async getSplashImageData() {
return this._dataFromBufferOrFile(this.splashImageData, this.splashImageFile);
}
/**
* Get the name of a projector trimming the extension, case insensitive.
*
* @param name Projector name.
* @returns Projector name without extension.
*/
getProjectorNameNoExtension(name) {
return trimExtension(name, this.projectorExtension, true);
}
/**
* Get the Xtras path.
*
* @param name Save name.
* @returns Xtras path.
*/
getXtrasPath(name) {
const cdn = this.configurationDirectoryName;
return this.nestXtrasConfiguration && cdn ? `${cdn}/${this.xtrasDirectoryName}` : this.xtrasDirectoryName;
}
/**
* Get the config path.
*
* @param name Save name.
* @returns Config path.
*/
getConfigPath(name) {
const n = this.getProjectorNameNoExtension(name);
return `${n}${this.configExtension}`;
}
/**
* Get the splash image path.
*
* @param name Save name.
* @returns Config path.
*/
getSplashImagePath(name) {
const n = this.getProjectorNameNoExtension(name);
return `${n}${this.splashImageExtension}`;
}
/**
* Get the skeleton file or directory as an Archive instance.
*
* @returns Archive instance.
*/
async getSkeletonArchive() {
const {
skeleton
} = this;
if (!skeleton) {
throw new Error('Projector skeleton not specified');
}
const stat = await fse.stat(skeleton);
if (stat.isDirectory()) {
return new ArchiveDir(skeleton);
}
if (!stat.isFile()) {
throw new Error('Projector skeleton not file or directory');
}
const r = createArchiveByFileExtension(skeleton);
if (!r) {
throw new Error('Projector skeleton archive file type unknown');
}
if (r instanceof ArchiveHdi) {
const {
pathToHdiutil
} = this;
if (pathToHdiutil) {
r.mounterMac.hdiutil = pathToHdiutil;
}
r.nobrowse = true;
}
return r;
}
/**
* Get include Xtras as a list of mappings.
*
* @returns Mappings list.
*/
getIncludeXtrasMappings() {
const {
includeXtras
} = this;
const r = [];
if (includeXtras) {
for (const src of Object.keys(includeXtras)) {
const dest = includeXtras[src];
r.push({
src,
dest
});
}
}
return r;
}
/**
* Find the best match for a path in a list of Xtras mappings.
* Path search is case-insensitive.
*
* @param mappings Mappings list.
* @param path Path to search for.
* @returns Best match or null.
*/
findIncludeXtrasMappingsBestMatch(mappings, path) {
let best = null;
let bestScore = -1;
for (const map of mappings) {
const {
src
} = map;
const relative = src === '' ? path : pathRelativeBase(path, src, true);
if (relative === null || bestScore >= src.length) {
continue;
}
best = {
map,
relative
};
bestScore = src.length;
}
return best;
}
/**
* Find output path for an Xtra.
*
* @param mappings Mappings list.
* @param path Path to search for.
* @returns Output path or null.
*/
includeXtrasMappingsDest(mappings, path) {
const best = this.findIncludeXtrasMappingsBestMatch(mappings, path);
if (!best) {
return null;
}
const {
map,
relative
} = best;
const base = map.dest || map.src; // eslint-disable-next-line no-nested-ternary
return base ? relative ? `${base}/${relative}` : base : relative;
}
/**
* Write out the projector.
*
* @param path Save path.
* @param name Save name.
*/
async write(path, name) {
await this._writeSkeleton(path, name);
await this._writeMovie(path, name);
await this._writeConfig(path, name);
await this._writeSplashImage(path, name);
await this._writeLingo(path, name);
}
/**
* Write out the projector movie file.
*
* @param path Save path.
* @param name Save name.
*/
async _writeMovie(path, name) {
const data = await this.getMovieData();
if (!data) {
return;
}
const {
movieName
} = this;
if (!movieName) {
throw new Error('Cannot write movie data without a movie name');
}
await fse.writeFile(pathJoin(path, movieName), data);
}
/**
* Write out the projector config file.
*
* @param path Save path.
* @param name Save name.
*/
async _writeConfig(path, name) {
await this._maybeWriteFile((await this.getConfigData()), pathJoin(path, this.getConfigPath(name)));
}
/**
* Write out the projector splash image file.
*
* @param path Save path.
* @param name Save name.
*/
async _writeSplashImage(path, name) {
await this._maybeWriteFile((await this.getSplashImageData()), pathJoin(path, this.getSplashImagePath(name)));
}
/**
* Write out the projector lingo file.
*
* @param path Save path.
* @param name Save name.
*/
async _writeLingo(path, name) {
await this._maybeWriteFile((await this.getLingoData()), pathJoin(path, this.lingoName));
}
/**
* Get data from buffer or file.
*
* @param data Data buffer.
* @param file File path.
* @returns Data buffer.
*/
async _dataFromBufferOrFile(data, file) {
if (data) {
return data;
}
if (file) {
return fse.readFile(file);
}
return null;
}
/**
* Get data from value or file.
*
* @param data Data value.
* @param file File path.
* @param newline Newline string.
* @param encoding String encoding.
* @returns Data buffer.
*/
async _dataFromValueOrFile(data, file, newline, encoding) {
let str = null;
if (typeof data === 'string') {
str = data;
} else if (Array.isArray(data)) {
if (newline === null) {
throw new Error('New line delimiter required');
}
str = data.join(newline);
} else {
return this._dataFromBufferOrFile(data, file);
}
if (!encoding) {
throw new Error('String data encoding required');
}
return Buffer.from(str, encoding);
}
/**
* Maybe write file if data is not null.
*
* @param data Data to maybe write.
* @param path Output path.
*/
async _maybeWriteFile(data, path) {
if (!data) {
return;
}
await fse.writeFile(path, data);
}
/**
* Splash image file extension.
*
* @returns File extension.
*/
}
//# sourceMappingURL=projector.mjs.map