Dirty initial commit

This commit is contained in:
He4eT 2021-02-23 12:31:02 +05:00
commit eab87d32a9
13 changed files with 7039 additions and 0 deletions

106
src/cheapGlkOte.js Normal file
View file

@ -0,0 +1,106 @@
const GlkOte = require('./glkOte/glkote-term')
class CheapGlkOte extends GlkOte {
constructor(handlers) {
super()
this.window = null
this.current_input_type = null
this.handlers = handlers
}
sendFn(message) {
this.send_response(
this.current_input_type,
this.window,
message)
this.current_input_type = null
}
init(iface) {
/* Only one window can be opened */
const glk_window_open = iface.Glk.glk_window_open
iface.Glk.glk_window_open = (splitwin, ...args) =>
splitwin
? null
: glk_window_open(splitwin, ...args)
this.handlers.onInit()
super.init(iface)
}
update_inputs(data) {
if (!data.length) return null
const {type} = data[0]
if (['char', 'line'].includes(type)) {
this.current_input_type = type
this.handlers.onUpdateInputs(type)
}
}
accept_specialinput(data) {
if (data.type === 'fileref_prompt') {
const callback = ref =>
this.send_response(
'specialresponse', null, 'fileref_prompt', ref)
this.interface.Dialog.open(
data.filemode !== 'read',
data.filetype,
data.gameid,
callback)
} else {
this.error(
'Request for unknown special input type: ' + data.type)
}
}
update_content(messages) {
const filtered =
messages.filter(content =>
content.id === this.window.id)[0]
this.handlers.onUpdateContent(filtered)
}
exit() {
this.handlers.onExit()
super.exit()
}
cancel_inputs(data) {
if (data.length === 0) {
this.current_input_type = null
this.handlers.onUpdateInputs(null)
}
}
disable(disable) {
this.disabled = disable
this.handlers.onDisable(disable)
}
update_windows(data) {
data.forEach(win => {
if (win.type === 'buffer') {
this.window = win
}
})
}
log(msg) {
console.log(`[log]: ${msg}`)
}
warning(msg) {
console.warn(`[warning]: ${msg}`)
}
error(message) {
console.error(`[error]: ${message}`)
}
}
module.exports = CheapGlkOte

36
src/fakeDialog.js Normal file
View file

@ -0,0 +1,36 @@
class FakeDialog {
constructor(handlers) {
this.streaming = false
this.handlers = handlers
this.path = 'fake/path'
}
file_ref_exists = ref => false
file_construct_ref(filename, usage, gameid) {
return {
filename: [this.path, filename].join('/'),
usage: usage || ''
}
}
file_read(dirent, israw) {
console.log('fake_file_read', dirent, israw)
return 'content'
}
file_write(dirent, content, israw) {
if (content.length === 0) return (void null)
console.log('fake_file_write', dirent, israw, content.length)
}
open(tosave, usage, gameid, callback) {
this.handlers.onFileNameRequest(tosave, usage, callback)
}
log(message) {
console.log(message)
}
}
module.exports = FakeDialog

6396
src/glkOte/glkapi.js Normal file

File diff suppressed because it is too large Load diff

145
src/glkOte/glkote-term.js Normal file
View file

@ -0,0 +1,145 @@
class GlkOte {
constructor() {
this.current_metrics = null
this.disabled = false
this.generation = 0
this.interface = null
this.version = require('../../package.json').version
}
measure_window() {
return {
buffercharheight: 1,
buffercharwidth: 1,
buffermarginx: 0,
buffermarginy: 0,
graphicsmarginx: 0,
graphicsmarginy: 0,
gridcharheight: 1,
gridcharwidth: 1,
gridmarginx: 0,
gridmarginy: 0,
height: 0,
inspacingx: 0,
inspacingy: 0,
outspacingx: 0,
outspacingy: 0,
width: 0,
}
}
getinterface() {
return this.interface
}
init(iface) {
if (!iface) {
this.error('No game interface object has been provided.')
}
if (!iface.accept) {
this.error('The game interface object must have an accept() function.')
}
this.interface = iface
this.current_metrics = this.measure_window()
this.send_response('init', null, this.current_metrics)
}
update(data) {
if (data.type === 'error') {
this.error(data.message)
}
if (data.type === 'pass') {
return
}
if (data.type !== 'update' && data.type !== 'exit') {
this.log(`Ignoring unknown message type: ${data.type}`)
return
}
if (data.gen === this.generation) {
this.log(`Ignoring repeated generation number: ${data.gen}`)
return
}
if (data.gen < this.generation) {
this.log(
`Ignoring out-of-order generation number: got ${data.gen}, currently at ${this.generation}`
)
return
}
this.generation = data.gen
if (this.disabled) {
this.disable(false)
}
/* Handle the update */
if (data.input != null) {
this.cancel_inputs(data.input)
}
if (data.windows != null) {
this.update_windows(data.windows)
}
if (data.content != null && data.content.length) {
this.update_content(data.content)
}
if (data.input != null) {
this.update_inputs(data.input)
}
/* Disable everything if requested */
this.disabled = false
if (data.disabled || data.specialinput) {
this.disable(true)
}
if (data.specialinput != null) {
this.accept_specialinput(data.specialinput)
}
/* Detach all handlers and exit */
if (data.type === 'exit') {
this.exit()
}
}
send_response(type, win, val, val2) {
const res = {
type: type,
gen: this.generation,
}
if (win) {
res.window = win.id
}
if (type === 'init' || type === 'arrange') {
res.metrics = val
}
if (type === 'init') {
res.support = this.support()
}
if (type === 'char') {
res.value = val
}
if (type === 'line') {
res.value = val
}
if (type === 'specialresponse') {
res.response = val
res.value = val2
}
this.interface.accept(res)
}
support() {
return []
}
}
module.exports = GlkOte

33
src/index.js Normal file
View file

@ -0,0 +1,33 @@
const FakeDialog = require('./fakeDialog')
const CheapGlkOte = require('./cheapGlkOte')
const noop = () => void null
const noopHandlers = [
'onInit',
'onUpdateContent',
'onDisable',
'onUpdateInputs',
'onFileNameRequest',
'onExit',
'setSend'
].reduce((acc, x) => (acc[x] = noop, acc), {})
module.exports = handlers_ => {
const handlers =
Object.assign({}, noopHandlers, handlers_)
const Dialog = new FakeDialog(handlers)
const GlkOte = new CheapGlkOte(handlers)
const sendFn = GlkOte.sendFn.bind(GlkOte)
return {
sendFn,
glkInterface: {
Dialog,
GlkOte,
Glk: {}
}
}
}

133
src/stdio.js Normal file
View file

@ -0,0 +1,133 @@
const readline = require('readline')
const MuteStream = require('mute-stream')
const ansiEscapes = require('ansi-escapes')
const stdin = process.stdin
const stdout = new MuteStream()
stdout.pipe(process.stdout)
const rl = readline.createInterface({
input: stdin,
output: stdout,
prompt: ''
})
let send = _ => _
const setSend = fn => {
send = fn
}
const onInit = () => {
if (stdin.isTTY) {
stdin.setRawMode(true)
}
readline.emitKeypressEvents(stdin)
rl.resume()
}
const onUpdateContent = messages =>
messages.text.forEach(({append, content}) => {
if (!append) {
stdout.write('\n')
}
if (content) {
content.forEach(x => {
if (x.text === '>') return null
if (x.style === 'input') {
if (stdout.isTTY) {
stdout.write(ansiEscapes.eraseLines(2))
stdout.write('> ')
}
}
stdout.write(
typeof x === 'string'
? x
: x.text)
})
}
})
const onUpdateInputs = type => {
type
? attach_handlers(type)
: detach_handlers()
}
const onExit = () => {
detach_handlers()
rl.close()
stdout.write('\n')
}
const onDisable = disable =>
disable
? detach_handlers()
: attach_handlers()
const onFileNameRequest = (tosave, usage, callback) => {
stdout.write('\n')
rl.question(
'Please enter a file name (without an extension): ',
filename => callback(filename
? {filename, usage}
: null))
}
const handle_char_input = (str, key) => {
const key_replacements = {
'\x7F': 'delete',
'\t': 'tab',
}
detach_handlers()
// Make sure this char isn't being remembered for the next line input
rl._line_buffer = null
rl.line = ''
// Process special keys
const res =
key_replacements[str] ||
str ||
key.name.replace(/f(\d+)/, 'func$1')
send(res)
}
const attach_handlers = type => {
if (type === 'char') {
stdout.mute()
stdin.on('keypress', handle_char_input)
}
if (type === 'line') {
rl.on('line', handle_line_input)
}
}
const detach_handlers = () => {
stdin.removeListener('keypress', handle_char_input)
rl.removeListener('line', handle_line_input)
stdout.unmute()
}
const handle_line_input = line => {
if (stdout.isTTY) {
stdout.write(ansiEscapes.eraseLines(1))
}
detach_handlers()
send(line)
}
module.exports.handlers = {
onInit,
onUpdateContent,
onDisable,
onUpdateInputs,
onFileNameRequest,
onExit,
setSend
}