scroll/src/common/runtime.ts
Timothy J. Warren 4be7be09a7
All checks were successful
timw4mail/scroll/pipeline/head This commit looks good
Fix some issues with line splitting/merging
2024-07-10 16:12:39 -04:00

205 lines
4.9 KiB
JavaScript

/**
* Functions/Methods that depend on the current runtime to function
*/
import process from 'node:process';
import Ansi from './ansi.ts';
import { IRuntime, ITerminalSize, ITestBase } from './types.ts';
import { noop } from './fns.ts';
import {
defaultTerminalSize,
SCROLL_LOG_FILE_PREFIX,
SCROLL_LOG_FILE_SUFFIX,
} from './config.ts';
export type { IFileIO, IRuntime, ITerminal } from './types.ts';
/**
* Which Typescript runtime is currently being used
*/
export enum RunTimeType {
Bun = 'bun',
Deno = 'deno',
Tsx = 'tsx',
Unknown = 'common',
}
/**
* The label for type/severity of the log entry
*/
export enum LogLevel {
Debug = 'Debug',
Info = 'Info',
Notice = 'Notice',
Warning = 'Warning',
Error = 'Error',
}
let scrollRuntime: IRuntime | null = null;
// ----------------------------------------------------------------------------
// Misc runtime functions
// ----------------------------------------------------------------------------
/**
* Get the size of the terminal window via ANSI codes
* @see https://viewsourcecode.org/snaptoken/kilo/03.rawInputAndOutput.html#window-size-the-hard-way
*/
async function _getTerminalSizeFromAnsi(): Promise<ITerminalSize> {
const { term } = await getRuntime();
// Tell the cursor to move to Row 999 and Column 999
// Since this command specifically doesn't go off the screen
// When we ask where the cursor is, we should get the size of the screen
await term.writeStdout(
Ansi.moveCursorForward(999) + Ansi.moveCursorDown(999),
);
// Ask where the cursor is
await term.writeStdout(Ansi.GetCursorLocation);
// Get the first chunk from stdin
// The response is \x1b[(rows);(cols)R..
const chunk = await term.readStdinRaw();
if (chunk === null) {
return defaultTerminalSize;
}
const rawCode = (new TextDecoder()).decode(chunk);
const res = rawCode.trim().replace(/^.\[([0-9]+;[0-9]+)R$/, '$1');
const [srows, scols] = res.split(';');
const rows = parseInt(srows, 10) ?? 24;
const cols = parseInt(scols, 10) ?? 80;
// Clear the screen
await term.writeStdout(Ansi.ClearScreen + Ansi.ResetCursor);
return {
rows,
cols,
};
}
/**
* Basic logging -
*/
export function log(
s: unknown,
level: LogLevel = LogLevel.Notice,
data?: any,
): void {
getRuntime().then(({ file }) => {
const rawS = JSON.stringify(s, null, 2);
const rawData = JSON.stringify(data, null, 2);
const output = (typeof data !== 'undefined')
? `${rawS}\n${rawData}\n\n`
: `${rawS}\n`;
const outputFile =
`${SCROLL_LOG_FILE_PREFIX}-${level.toLowerCase()}${SCROLL_LOG_FILE_SUFFIX}`;
file.appendFile(outputFile, output).then(noop);
});
}
export const logDebug = (s: unknown, data?: any) =>
log(s, LogLevel.Debug, data);
export const logInfo = (s: unknown, data?: any) => log(s, LogLevel.Info, data);
export const logNotice = (s: unknown, data?: any) =>
log(s, LogLevel.Notice, data);
export const logWarning = (s: unknown, data?: any) =>
log(s, LogLevel.Warning, data);
export const logError = (s: unknown, data?: any) =>
log(s, LogLevel.Warning, data);
/**
* Kill program, displaying an error message
* @param s
*/
export function die(s: string | Error): void {
logError(s);
process.stdin.setRawMode(false);
console.error(s);
getRuntime().then((r) => r.exit());
}
/**
* Determine which Typescript runtime we are operating under
*/
export function runtimeType(): RunTimeType {
let runtime = RunTimeType.Tsx;
if ('Deno' in globalThis) {
runtime = RunTimeType.Deno;
}
if ('Bun' in globalThis) {
runtime = RunTimeType.Bun;
}
return runtime;
}
/**
* Get the adapter object for the current Runtime
*/
export async function getRuntime(): Promise<IRuntime> {
if (scrollRuntime === null) {
const runtime = runtimeType();
const path = `../${runtime}/mod.ts`;
const pkg = await import(path);
if ('default' in pkg) {
scrollRuntime = pkg.default;
}
if (scrollRuntime !== null) {
return Promise.resolve(scrollRuntime);
}
return Promise.reject('Missing default import');
}
return Promise.resolve(scrollRuntime);
}
/**
* Get the common test interface object
*/
export async function getTestRunner(): Promise<ITestBase> {
const runtime = runtimeType();
const path = `../${runtime}/test_base.ts`;
const pkg = await import(path);
if ('default' in pkg) {
return pkg.default;
}
return pkg;
}
/**
* Import a runtime-specific module
*
* e.g. to load "src/bun/mod.ts", if the runtime is bun,
* you can use like so `await importForRuntime('index')`;
*
* @param path - the path within the runtime module
*/
export const importForRuntime = async (path: string) => {
const runtime = runtimeType();
const suffix = '.ts';
const base = `../${runtime}/`;
const pathParts = path
.split('/')
.filter((part) => part !== '' && part !== '.' && part !== suffix)
.map((part) => part.replace(suffix, ''));
const cleanedPath = pathParts.join('/');
const importPath = base + cleanedPath + suffix;
const pkg = await import(importPath);
if ('default' in pkg) {
return pkg.default;
}
return pkg;
};