195 lines
5.1 KiB
JavaScript
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')
|
|
}
|