Files
bare-operating-system/packages/bare-os-coreutils/lib/baresay-engine.js
T

195 lines
5.1 KiB
JavaScript

/**
* Bubble + cow rendering for baresay (no imports; prepended before src/baresay.js).
*/
/** @param {string} text @param {number} width @param {boolean} noWrap */
function bareBaresayWrap(text, width, noWrap) {
const t = String(text || '').replace(/\r\n/g, '\n')
if (noWrap) {
const line = t.replace(/\n/g, ' ')
return line.length ? [line] : ['']
}
const out = []
const lim = Math.max(8, Math.min(200, width | 0)) || 40
for (const para of t.split('\n')) {
if (!para.trim()) {
out.push('')
continue
}
let rest = para.trim()
while (rest.length) {
if (rest.length <= lim) {
out.push(rest)
break
}
let cut = rest.lastIndexOf(' ', lim)
if (cut <= 0) cut = lim
const piece = rest.slice(0, cut).trimEnd()
if (!piece.length) {
out.push(rest.slice(0, lim))
rest = rest.slice(lim).trimStart()
} else {
out.push(piece)
rest = rest.slice(cut).trimStart()
}
}
}
return out.length ? out : ['']
}
/**
* @param {string[]} lines
* @param {{ think?: boolean, rainbow?: boolean }} opts
*/
function bareBaresayBalloon(lines, opts) {
const think = !!(opts && opts.think)
const rainbow = !!(opts && opts.rainbow)
const rows = lines.map((l) => String(l))
const n = rows.length
const longest = Math.max(...rows.map((r) => r.length), 2)
const padded = rows.map((r) => r.padEnd(longest))
/** @type {string[]} */
const mid = []
if (n === 1) {
const inner = think
? '( ' + padded[0] + ' )'
: '< ' + padded[0] + ' >'
mid.push(inner)
} else if (think) {
for (let i = 0; i < n; i++) {
mid.push('( ' + padded[i] + ' )')
}
} else {
for (let i = 0; i < n; i++) {
let L = '|'
let R = '|'
if (i === 0) {
L = '/'
R = '\\'
} else if (i === n - 1) {
L = '\\'
R = '/'
}
mid.push(L + ' ' + padded[i] + ' ' + R)
}
}
const topUnders = Math.max(0, mid[0].length - 2)
const botDash = Math.max(0, mid[mid.length - 1].length - 2)
/** @type {string[]} */
const out = []
out.push(' ' + '_'.repeat(topUnders))
if (rainbow) {
const ansi = bareBaresayRainbowCodes()
let k = 0
for (const line of mid) {
const c = ansi[k % ansi.length]
k++
out.push(c + line + '\x1b[0m')
}
} else {
for (const line of mid) out.push(line)
}
out.push(' ' + '-'.repeat(botDash))
return out
}
function bareBaresayRainbowCodes() {
return [
'\x1b[31m',
'\x1b[91m',
'\x1b[33m',
'\x1b[32m',
'\x1b[36m',
'\x1b[34m',
'\x1b[35m'
]
}
/** Extract figure body from .bare template text (Perl heredoc or raw). */
function bareBaresayExtractCow(raw) {
const s = String(raw || '').replace(/^\ufeff/, '')
// cowsay uses: $the_cow = <<"EOC"; ← semicolon before newline
const m = s.match(
/\$the_cow\s*=\s*<<"EOC"\s*;?\s*\r?\n([\s\S]*?)\r?\n\s*EOC\s*(?:\r?\n|$)/m
)
if (m)
return m[1].replace(/\r\n/g, '\n').replace(/\n+$/, '') + '\n'
return s.replace(/\r\n/g, '\n').trimEnd() + '\n'
}
/**
* @param {string} template
* @param {string} eyes two chars
* @param {string} tongue one char
* @param {string} thoughts
*/
function bareBaresayFillCow(template, eyes, tongue, thoughts) {
const e = (eyes || 'oo').slice(0, 2).padEnd(2, 'o')
const L = e.charAt(0) || 'o'
const R = e.charAt(1) || 'o'
const tg = (tongue || ' ').slice(0, 1)
const th = thoughts || '\\'
return template
.replace(/\$thoughts/g, th)
.replace(/\$eyes/g, e)
.replace(/\$L/g, L)
.replace(/\$R/g, R)
.replace(/\$tongue/g, tg)
}
/** @param {string} flag */
function bareBaresayPreset(flag) {
switch (flag) {
case 'b':
return { eyes: '==', tongue: ' ' }
case 'd':
return { eyes: 'XX', tongue: 'U' }
case 'g':
return { eyes: '$$', tongue: ' ' }
case 'p':
return { eyes: '@@', tongue: ' ' }
case 's':
return { eyes: '**', tongue: ' ' }
case 't':
return { eyes: '--', tongue: ' ' }
case 'w':
return { eyes: '@@', tongue: ' ' }
case 'y':
return { eyes: '..', tongue: ' ' }
default:
return { eyes: 'oo', tongue: ' ' }
}
}
/**
* @param {string} message
* @param {string} cowBody filled lines (no vars)
* @param {{ width: number, noWrap: boolean, think?: boolean, rainbow?: boolean }} opts
*/
function bareBaresayCompose(message, cowBody, opts) {
const width = opts.width | 0
const wrapped = bareBaresayWrap(message, width, opts.noWrap)
const balloon = bareBaresayBalloon(wrapped, {
think: opts.think,
rainbow: opts.rainbow
})
const cowLines = cowBody.replace(/\s+$/, '').split('\n')
return balloon.concat(cowLines).join('\n') + '\n'
}
/** Minimal bear if no cowfile on disk (ASCII). */
function bareBaresayFallbackBearTemplate() {
return [
' $thoughts',
' $thoughts __ __',
' $thoughts / \\.-"""-./ \\',
' $thoughts \\ - - /',
' $thoughts | $L $R |',
' $thoughts \\ .-\'\'\'-. /',
" $thoughts '-\\__Y__/-'",
' $thoughts \u0060---\u0060'
].join('\n')
}