@shockpkg/dir-projector
Version:
Package for creating Shockwave Director projectors
447 lines (364 loc) • 8.44 kB
JavaScript
import { join as pathJoin, dirname } from 'path';
import { ArchiveDir, ArchiveHdi, createArchiveByFileExtension } from "@shockpkg/archive-files/module.mjs";
import fse from 'fs-extra';
import { pathRelativeBase, trimExtension } from "./util.mjs";
/**
* Projector constructor.
*
* @param path Output path.
*/
export class Projector extends Object {
/**
* Make a Shockwave projector.
*
* @default false
*/
/**
* Splash image file.
*
* @default null
*/
/**
* Splash image data.
*
* @default null
*/
/**
* Lingo file.
*
* @default null
*/
/**
* Lingo data.
*
* @default null
*/
/**
* Xtras include map.
*
* @default null
*/
/**
* Nest xtras in a Configuration directory.
*
* @default false
*/
/**
* Path to hdiutil binary.
*
* @default null
*/
/**
* Output path.
*/
constructor(path) {
super();
this.shockwave = false;
this.splashImageFile = null;
this.splashImageData = null;
this.lingoFile = null;
this.lingoData = null;
this.includeXtras = null;
this.nestXtrasConfiguration = false;
this.pathToHdiutil = null;
this.path = void 0;
this.path = path;
}
/**
* 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 xtrasName() {
return 'xtras';
}
/**
* Configuration directory name.
*
* @returns Directory encoding.
*/
get configurationName() {
return 'Configuration';
}
/**
* Name of a projector trimming the extension, case insensitive.
*
* @returns Projector name without extension.
*/
get name() {
return trimExtension(dirname(this.path), this.extension, true);
}
/**
* Config file path.
*
* @returns Config path.
*/
get configPath() {
const base = trimExtension(this.path, this.extension, true);
return `${base}${this.configExtension}`;
}
/**
* Splash image file path.
*
* @returns Splash image path.
*/
get splashImagePath() {
const base = trimExtension(this.path, this.extension, true);
return `${base}${this.splashImageExtension}`;
}
/**
* Lingo file path.
*
* @returns Lingo file path.
*/
get lingoPath() {
return pathJoin(dirname(this.path), this.lingoName);
}
/**
* Get outout Xtras path.
*
* @returns Output path.
*/
get xtrasPath() {
const cn = this.configurationName;
return this.nestXtrasConfiguration && cn ? pathJoin(dirname(this.path), cn, this.xtrasName) : pathJoin(dirname(this.path), this.xtrasName);
}
/**
* Get splash image data if any specified, from data or file.
*
* @returns Splash image data or null.
*/
async getSplashImageData() {
const {
splashImageData,
splashImageFile
} = this;
return splashImageData || (splashImageFile ? fse.readFile(splashImageFile) : null);
}
/**
* Get lingo data if any specified, from data or file.
*
* @returns Lingo data or null.
*/
async getLingoData() {
const {
lingoData,
lingoFile
} = this;
if (typeof lingoData === 'string') {
return Buffer.from(lingoData, this.lingoEncoding);
}
if (Array.isArray(lingoData)) {
return Buffer.from(lingoData.join(this.lingoNewline), this.lingoEncoding);
}
if (lingoData) {
return lingoData;
}
return lingoFile ? fse.readFile(lingoFile) : null;
}
/**
* Get the skeleton file or directory as an Archive instance.
*
* @param skeleton Skeleton path.
* @returns Archive instance.
*/
async getSkeletonArchive(skeleton) {
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 projector with skeleton and config file.
*
* @param skeleton Skeleton path.
* @param configFile Config file.
*/
async withFile(skeleton, configFile) {
const configData = configFile ? await fse.readFile(configFile) : null;
await this.withData(skeleton, configData);
}
/**
* Write out projector with skeleton and config data.
*
* @param skeleton Skeleton path.
* @param configData Config data.
*/
async withData(skeleton, configData) {
await this._checkOutput();
await this._writeSkeleton(skeleton);
await this._modifySkeleton();
await this._writeConfig(configData);
await this._writeSplashImage();
await this._writeLingo();
}
/**
* Check that output path is valid, else throws.
*/
async _checkOutput() {
for (const p of [this.path, this.configPath, this.splashImagePath, this.lingoPath]) {
// eslint-disable-next-line no-await-in-loop
if (await fse.pathExists(p)) {
throw new Error(`Output path already exists: ${p}`);
}
}
}
/**
* Write out the projector config file.
*
* @param configData Config data.
*/
async _writeConfig(configData) {
let data = null;
if (typeof configData === 'string') {
data = Buffer.from(configData, this.configEncoding);
} else if (Array.isArray(data)) {
data = Buffer.from(configData.join(this.configNewline), this.configEncoding);
} else if (configData) {
data = configData;
}
if (data) {
await fse.outputFile(this.configPath, data);
}
}
/**
* Write out the projector splash image file.
*/
async _writeSplashImage() {
const data = await this.getSplashImageData();
if (data) {
await fse.outputFile(this.splashImagePath, data);
}
}
/**
* Write out the projector lingo file.
*/
async _writeLingo() {
const data = await this.getLingoData();
if (data) {
await fse.outputFile(this.lingoPath, data);
}
}
/**
* Projector file extension.
*
* @returns File extension.
*/
}
//# sourceMappingURL=projector.mjs.map