@gent-js/gent
Version:
template-based data generator.
314 lines (313 loc) • 9.95 kB
JavaScript
import * as fs from "node:fs";
import * as fsPromises from "node:fs/promises";
import * as nodePath from "node:path";
import { normalizeWeight } from "./common/weightedItemFeeder.js";
import { DEFAULT_TEMPLATE_WEIGHT, DefaultEps, DefaultTcpFramingMethod, DefaultTrailerReplacer, NetworkOutputTypes, OutputTypes, TcpFramingMethods, TemplateModes, } from "./consts.js";
// region type guards
export function isString(value) {
return typeof value === "string";
}
export function isNonNullObject(value) {
if (typeof value !== "object" || value === null) {
return false;
}
return true;
}
export function isTemplateMode(value) {
if (typeof value !== "string") {
return false;
}
const candidates = TemplateModes;
return candidates.includes(value);
}
export function isOutputType(value) {
if (typeof value !== "string") {
return false;
}
const candidates = OutputTypes;
return candidates.includes(value);
}
export function isNetworkOutputType(value) {
if (typeof value !== "string") {
return false;
}
const candidates = NetworkOutputTypes;
return candidates.includes(value);
}
export function isTcpFramingType(value) {
if (typeof value !== "string") {
return false;
}
const candidates = TcpFramingMethods;
return candidates.includes(value);
}
// endregion
// region parsers
export function parseString(value) {
if (!isString(value)) {
return undefined;
}
return value;
}
export function parseNonNaNInteger(value) {
if (Number.isInteger(value)) {
return value;
}
if (typeof value !== "string") {
return undefined;
}
const numValue = Number.parseInt(value);
if (Number.isNaN(numValue)) {
return undefined;
}
return numValue;
}
export function parseNonNaNFloat(value) {
if (typeof value === "number") {
if (Number.isNaN(value)) {
return undefined;
}
return value;
}
if (typeof value !== "string") {
return undefined;
}
const numValue = Number.parseFloat(value);
if (Number.isNaN(numValue)) {
return undefined;
}
return numValue;
}
export function parseDate(value) {
if (!isString(value)) {
return undefined;
}
const dateNumber = Date.parse(value);
if (Number.isNaN(dateNumber)) {
return undefined;
}
return new Date(dateNumber);
}
export function parseAndResolveFilePath(value, basePath) {
if (!isString(value)) {
return undefined;
}
if (nodePath.isAbsolute(value)) {
return value;
}
else {
return nodePath.resolve(basePath, value);
}
}
export async function tryReadFile(path) {
try {
await fsPromises.access(path, fs.constants.R_OK);
}
catch (error) {
console.log(error);
throw new Error(`cannot access the file. ${path}`);
}
let contentString;
try {
contentString = await fsPromises.readFile(path, {
encoding: "utf8",
});
}
catch (error) {
console.log(error);
throw new Error(`failed to read the file. ${path}`);
}
return contentString;
}
export function normalizeProgramOptions(possibleProgramOptions, basePath) {
if (!isNonNullObject(possibleProgramOptions)) {
return undefined;
}
let from = parseDate(possibleProgramOptions["from"]);
let to = parseDate(possibleProgramOptions["to"]);
if (from === undefined || to === undefined) {
const now = Date.now();
from = new Date(now - 1000 * 60 * 60 * 24);
to = new Date(now);
}
let count = parseNonNaNInteger(possibleProgramOptions["count"]);
if (count === undefined) {
count = 0;
}
let out = normalizeOutputOptions(possibleProgramOptions["out"], basePath);
if (out === undefined) {
console.error(`Invalid out option. (${out})`);
return undefined;
}
const debug = possibleProgramOptions["debug"] === true;
let templateOptionsArray;
const possibleTemplateOptionsArray = possibleProgramOptions["templates"];
if (Array.isArray(possibleTemplateOptionsArray)) {
templateOptionsArray =
possibleTemplateOptionsArray.flatMap((possibleTemplateOptions) => {
if (!isNonNullObject(possibleTemplateOptions)) {
return [];
}
const possiblePath = possibleTemplateOptions["path"];
const path = parseString(possiblePath);
if (path === undefined) {
console.error(`invalid file path.(${possiblePath})`);
return [];
}
const resolvedFilePath = parseAndResolveFilePath(path, basePath);
if (resolvedFilePath === undefined) {
console.error(`failed to resolve the file path.(${path})`);
return [];
}
const possibleMode = possibleTemplateOptions["mode"];
let mode;
if (isTemplateMode(possibleMode)) {
mode = possibleMode;
}
else {
mode = determineTemplateModeByFile(resolvedFilePath);
}
const weight = parseNonNaNFloat(possibleTemplateOptions["weight"]) ??
DEFAULT_TEMPLATE_WEIGHT;
const normalizedWeight = normalizeWeight(weight);
return {
mode: mode,
path: resolvedFilePath,
weight: normalizedWeight,
};
});
}
if (templateOptionsArray === undefined || templateOptionsArray.length === 0) {
console.error("no effective template specified.");
return undefined;
}
return {
debug,
from,
to,
count,
out,
templates: templateOptionsArray,
};
}
function normalizeOutputOptions(possibleOutputOptions, basePath) {
if (typeof possibleOutputOptions === "string") {
// shorthand
return {
type: "file",
path: possibleOutputOptions,
};
}
if (!isNonNullObject(possibleOutputOptions)) {
return undefined;
}
const possibleType = parseString(possibleOutputOptions["type"]);
if (!isOutputType(possibleType)) {
console.error(`invalid output type.(${possibleType})`);
return undefined;
}
const possiblePath = parseAndResolveFilePath(possibleOutputOptions["path"], basePath);
if (possibleType == "file") {
if (possiblePath === undefined) {
console.error(`output path must be specified.(${possiblePath})`);
return undefined;
}
const possibleSize = parseString(possibleOutputOptions["size"]);
return {
type: possibleType,
path: possiblePath,
size: possibleSize,
};
}
else if (isNetworkOutputType(possibleType)) {
const possibleAddress = parseString(possibleOutputOptions["address"]);
const possiblePort = parseNonNaNInteger(possibleOutputOptions["port"]);
const possibleEps = parseNonNaNInteger(possibleOutputOptions["eps"]) ?? DefaultEps;
if (possibleAddress === undefined || possiblePort === undefined) {
console.error("invalid udp output options");
return undefined;
}
if (possibleType === "udp") {
return {
type: possibleType,
path: possiblePath,
address: possibleAddress,
port: possiblePort,
eps: possibleEps,
};
}
else if (possibleType === "tcp" || possibleType === "tls") {
const possibleFraming = parseString(possibleOutputOptions["framing"]) ??
DefaultTcpFramingMethod;
if (!isTcpFramingType(possibleFraming)) {
console.error(`invalid framing type.(${possibleFraming})`);
return undefined;
}
if (possibleFraming === "octet-counting") {
return {
type: possibleType,
path: possiblePath,
address: possibleAddress,
port: possiblePort,
eps: possibleEps,
framing: possibleFraming,
};
}
else if (possibleFraming === "lf") {
const possibleTrailerReplacer = parseString(possibleOutputOptions["trailerReplacer"]) ??
DefaultTrailerReplacer;
return {
type: possibleType,
path: possiblePath,
address: possibleAddress,
port: possiblePort,
eps: possibleEps,
framing: possibleFraming,
trailerReplacer: possibleTrailerReplacer,
};
}
else {
assertNever(possibleFraming);
}
}
else {
assertNever(possibleType);
}
}
else {
return assertNever(possibleType);
}
}
export function determineTemplateModeByFile(filePath) {
return nodePath.extname(filePath) === ".json" ? "json" : "text";
}
// endregion
// region misc
export function pickFirst(input) {
if (input === undefined) {
return undefined;
}
else if (Array.isArray(input)) {
return input[0];
}
else {
return input;
}
}
export function pickMany(input) {
if (input === undefined) {
return [];
}
else if (Array.isArray(input)) {
return input;
}
else {
return [input];
}
}
// endregion
// region misc
export function assertNever(x) {
throw new Error(`Unexpected object: ${x}`);
}
// endregion