From 928d602b45f0f321878ce429767b6273224a7471 Mon Sep 17 00:00:00 2001 From: Dannii Willis Date: Fri, 22 Mar 2024 13:35:28 +1000 Subject: [PATCH] Export system_cwd, hopefully the final form of get_dirs() Fix read() for non-existent files Prepare for Windows-POSIX path conversions, though I didn't do the actual conversions yet Update to Typescript 5.4, and embed the few ansi-escapes we were using, because of https://github.com/sindresorhus/ansi-escapes/issues/29 --- package.json | 11 +++--- src/dialog/common/common.ts | 63 ++++++++++++++++++++++++++++++++++ src/dialog/common/interface.ts | 48 +++++--------------------- src/dialog/node/async.ts | 42 ++++++++++------------- src/dialog/node/cheap.ts | 3 +- src/glkote/cheap/cheap.ts | 14 ++++++-- src/glkote/common/glkote.ts | 3 +- src/index-common.ts | 2 +- 8 files changed, 112 insertions(+), 74 deletions(-) create mode 100644 src/dialog/common/common.ts diff --git a/package.json b/package.json index 0dfa747..e12f108 100644 --- a/package.json +++ b/package.json @@ -13,23 +13,22 @@ ], "type": "module", "dependencies": { - "ansi-escapes": "^6.0.0", "lodash-es": "^4.17.21", "mute-stream": "1.0.0" }, "devDependencies": { - "@types/css-font-loading-module": "^0.0.9", + "@types/css-font-loading-module": "^0.0.13", "@types/jquery": "^3.5.11", "@types/lodash-es": "^4.17.5", - "@types/mute-stream": "^0.0.1", + "@types/mute-stream": "^0.0.4", "@types/node": "^20.0.0", "@types/resize-observer-browser": "^0.1.6", - "@typescript-eslint/eslint-plugin": "~6.7.0", - "@typescript-eslint/parser": "~6.7.0", + "@typescript-eslint/eslint-plugin": "~7.3.0", + "@typescript-eslint/parser": "~7.3.0", "eslint": "^8.31.0", "ifvms": "^1.1.6", "minimist": "^1.2.7", - "typescript": "~5.2.0" + "typescript": "~5.4.0" }, "scripts": { "build": "tsc", diff --git a/src/dialog/common/common.ts b/src/dialog/common/common.ts new file mode 100644 index 0000000..b131203 --- /dev/null +++ b/src/dialog/common/common.ts @@ -0,0 +1,63 @@ +/* + +Common Dialog functions +======================= + +Copyright (c) 2024 Dannii Willis +MIT licenced +https://github.com/curiousdannii/asyncglk + +*/ + +import {FileType} from '../../common/protocol.js' + +/** File extensions for Glk file types */ +export function filetype_to_extension(filetype: FileType): string { + switch (filetype) { + case 'command': + case 'transcript': + return 'txt' + case 'data': + return 'glkdata' + case 'save': + return 'glksave' + default: + return 'glkdata' + } +} + +/** Construct a file-filter list for a given usage type. */ +export function filters_for_usage(usage: string | null) { + switch (usage) { + case 'data': + return [ { name: 'Glk Data File', extensions: ['glkdata'] } ] + case 'save': + return [ { name: 'Glk Save File', extensions: ['glksave'] } ] + case 'transcript': + return [ { name: 'Transcript File', extensions: ['txt'] } ] + case 'command': + return [ { name: 'Command File', extensions: ['txt'] } ] + default: + return [] + } +} + +/** Convert a native path to a POSIX path */ +export function path_native_to_posix(path: string): string { + if (process.platform === 'win32') { + throw new Error('not implemented') + } + else { + return path + } +} + +/** Convert a POSIX path to a native path */ +export function path_posix_to_native(path: string): string { + if (process.platform === 'win32') { + throw new Error('not implemented') + } + else { + return path + } +} \ No newline at end of file diff --git a/src/dialog/common/interface.ts b/src/dialog/common/interface.ts index 8d89a4f..cd0dcc9 100644 --- a/src/dialog/common/interface.ts +++ b/src/dialog/common/interface.ts @@ -8,7 +8,7 @@ MIT licenced https://github.com/curiousdannii/asyncglk */ -import {FileRef, FileType} from '../../common/protocol.js' +import {FileRef} from '../../common/protocol.js' import {GlkOte} from '../../glkote/common/glkote.js' @@ -18,12 +18,6 @@ export type AutosaveData = { ram?: Array | Uint8Array, } -export interface DialogDirectories { - storyfile: string - temp: string - working: string -} - export interface DialogOptions { dom_prefix?: string, GlkOte: GlkOte, @@ -44,11 +38,18 @@ export interface AsyncDialog { /** Read a file */ read(path: string): Promise /** Set storyfile directory and return directories */ - set_storyfile_dir(path: string): DialogDirectories + set_storyfile_dir(path: string): Partial /** Write a file */ write(path: string, data: Uint8Array): void } +export interface DialogDirectories { + storyfile: string + system_cwd: string + temp: string + working: string +} + interface ClassicDialogBase { async: false /** Clear an autosave */ @@ -123,35 +124,4 @@ export interface ClassicFileStream { * given, that many bytes are written; the buffer must be at least len * bytes long. Return the number of bytes written. */ fwrite(buf: Uint8Array, len?: number): number, -} - -/** File extensions for Glk file types */ -export function filetype_to_extension(filetype: FileType): string { - switch (filetype) { - case 'command': - case 'transcript': - return 'txt' - case 'data': - return 'glkdata' - case 'save': - return 'glksave' - default: - return 'glkdata' - } -} - -/** Construct a file-filter list for a given usage type. */ -export function filters_for_usage(usage: string | null) { - switch (usage) { - case 'data': - return [ { name: 'Glk Data File', extensions: ['glkdata'] } ] - case 'save': - return [ { name: 'Glk Save File', extensions: ['glksave'] } ] - case 'transcript': - return [ { name: 'Transcript File', extensions: ['txt'] } ] - case 'command': - return [ { name: 'Command File', extensions: ['txt'] } ] - default: - return [] - } } \ No newline at end of file diff --git a/src/dialog/node/async.ts b/src/dialog/node/async.ts index 43a6a96..f73d947 100644 --- a/src/dialog/node/async.ts +++ b/src/dialog/node/async.ts @@ -14,23 +14,16 @@ import fs_async from 'fs/promises' import MuteStream from 'mute-stream' import os from 'os' -import {AsyncDialog, DialogDirectories, DialogOptions} from '../common/interface.js' +import {path_native_to_posix, path_posix_to_native} from '../common/common.js' +import {AsyncDialog, DialogOptions} from '../common/interface.js' import {get_stdio, HackableReadline} from '../../glkote/cheap/stdio.js' export class CheapAsyncDialog implements AsyncDialog { 'async' = true as const - private dirs: DialogDirectories private rl: HackableReadline private stdout: MuteStream constructor() { - const cwd = process.cwd() - this.dirs = { - storyfile: cwd, - temp: os.tmpdir(), - working: cwd, - } - const cheap_stdio = get_stdio() this.rl = cheap_stdio.rl this.stdout = cheap_stdio.stdout @@ -42,14 +35,14 @@ export class CheapAsyncDialog implements AsyncDialog { delete(path: string) { try { - fs.unlinkSync(path) + fs.unlinkSync(path_posix_to_native(path)) } catch (_) {} } async exists(path: string) { try { - await fs_async.access(path, fs.constants.F_OK) + await fs_async.access(path_posix_to_native(path), fs.constants.F_OK) return true } catch (ex) { @@ -58,7 +51,13 @@ export class CheapAsyncDialog implements AsyncDialog { } get_dirs() { - return this.dirs + const cwd = path_native_to_posix(process.cwd()) + return { + storyfile: cwd, + system_cwd: cwd, + temp: path_native_to_posix(os.tmpdir()), + working: cwd, + } } prompt(extension: string, _save: boolean): Promise { @@ -70,22 +69,19 @@ export class CheapAsyncDialog implements AsyncDialog { }) } - async read(path: string): Promise { - try { - return fs_async.readFile(path) - } - catch (ex) { - return null - } + read(path: string): Promise { + return fs_async.readFile(path_posix_to_native(path)) + .catch(() => null) } set_storyfile_dir(path: string) { - this.dirs.storyfile = path - this.dirs.working = path - return this.dirs + return { + storyfile: path, + working: path, + } } write(path: string, data: Uint8Array) { - fs.writeFileSync(path, data, {flush: true}) + fs.writeFileSync(path_posix_to_native(path), data, {flush: true}) } } \ No newline at end of file diff --git a/src/dialog/node/cheap.ts b/src/dialog/node/cheap.ts index 89f922b..f540179 100644 --- a/src/dialog/node/cheap.ts +++ b/src/dialog/node/cheap.ts @@ -14,7 +14,8 @@ import os from 'os' import {FileRef} from '../../common/protocol.js' -import {ClassicStreamingDialog, filters_for_usage} from '../common/interface.js' +import {filters_for_usage} from '../common/common.js' +import {ClassicStreamingDialog} from '../common/interface.js' import NodeStreamingDialog from './node-streaming.js' import {get_stdio, HackableReadline} from '../../glkote/cheap/stdio.js' diff --git a/src/glkote/cheap/cheap.ts b/src/glkote/cheap/cheap.ts index 26d55d1..b0775eb 100644 --- a/src/glkote/cheap/cheap.ts +++ b/src/glkote/cheap/cheap.ts @@ -9,7 +9,6 @@ https://github.com/curiousdannii/asyncglk */ -import ansiEscapes from 'ansi-escapes' import MuteStream from 'mute-stream' import * as readline from 'readline' import * as TTY from 'tty' @@ -18,6 +17,15 @@ import {get_stdio, HackableReadline} from './stdio.js' import * as GlkOte from '../common/glkote.js' import * as protocol from '../../common/protocol.js' +// Ansi escapes +// From https://github.com/sindresorhus/ansi-escapes/blob/main/index.js +const isTerminalApp = process.env.TERM_PROGRAM === 'Apple_Terminal' +const ESC = '\u001B[' +const cursorRestorePosition = isTerminalApp ? '\u001B8' : ESC + 'u' +const cursorSavePosition = isTerminalApp ? '\u001B7' : ESC + 's' +const eraseEndLine = ESC + 'K' +const scrollDown = ESC + 'T' + const KEY_REPLACEMENTS: Record = { '\x7F': 'delete', '\t': 'tab', @@ -131,7 +139,7 @@ export default class CheapGlkOte extends GlkOte.GlkOteBase implements GlkOte.Glk private handle_line_input(line: string) { if (this.current_input_type === 'line') { if (this.stdout.isTTY) { - this.stdout.write(ansiEscapes.scrollDown + ansiEscapes.cursorRestorePosition + ansiEscapes.eraseEndLine) + this.stdout.write(scrollDown + cursorRestorePosition + eraseEndLine) } this.current_input_type = null this.detach_handlers() @@ -177,7 +185,7 @@ export default class CheapGlkOte extends GlkOte.GlkOteBase implements GlkOte.Glk if (windows[0].type === 'line') { if (this.stdout.isTTY) { - this.stdout.write(ansiEscapes.cursorSavePosition) + this.stdout.write(cursorSavePosition) } this.current_input_type = 'line' } diff --git a/src/glkote/common/glkote.ts b/src/glkote/common/glkote.ts index 6dc5cab..be99c5b 100644 --- a/src/glkote/common/glkote.ts +++ b/src/glkote/common/glkote.ts @@ -12,7 +12,8 @@ https://github.com/curiousdannii/asyncglk import {Blorb} from '../../blorb/blorb.js' import * as Constants from '../../common/constants.js' import * as protocol from '../../common/protocol.js' -import {Dialog, filetype_to_extension} from '../../dialog/common/interface.js' +import {filetype_to_extension} from '../../dialog/common/common.js' +import {Dialog} from '../../dialog/common/interface.js' import {GlkApi} from '../../glkapi/interface.js' export interface GlkOte { diff --git a/src/index-common.ts b/src/index-common.ts index 4650081..7bc6e71 100644 --- a/src/index-common.ts +++ b/src/index-common.ts @@ -17,8 +17,8 @@ export * as constants from './common/constants.js' export {FileView} from './common/misc.js' export * as protocol from './common/protocol.js' +export {filetype_to_extension, filters_for_usage, path_native_to_posix, path_posix_to_native} from './dialog/common/common.js' export type {AsyncDialog, AutosaveData, ClassicFileStream, ClassicStreamingDialog, ClassicSyncDialog, Dialog, DialogDirectories, DialogOptions} from './dialog/common/interface.js' -export {filetype_to_extension, filters_for_usage} from './dialog/common/interface.js' export type {GiDispa, GlkApi, GlkApiAsync, GlkApiOptions, GlkClassName, GlkFref, GlkObject, GlkSchannel, GlkStream, GlkVM, GlkWindow} from './glkapi/interface.js' export {AsyncGlk, RefBox, RefStruct} from './glkapi/glkapi.js'