210 lines
6.0 KiB
JavaScript
210 lines
6.0 KiB
JavaScript
/**
|
|
* Default aliases, alias/unalias builtins, and ~/.barerc loader.
|
|
*/
|
|
import { stripAliasQuotes } from './shell-syntax.js'
|
|
import { tokenize } from './shell-token.js'
|
|
import { expandWord } from './shell-expand.js'
|
|
import {
|
|
applyBareOsThemeFromEnv,
|
|
bareOsGetThemePreset
|
|
} from '../cli/bare-os-theme-presets.js'
|
|
|
|
/** Max alias indirections (prevents cycles). */
|
|
const MAX_ALIAS_DEPTH = 16
|
|
|
|
/**
|
|
* Baseline aliases; `~/.barerc` and `unalias -a` merge/reset from this table.
|
|
* @returns {Record<string, string>}
|
|
*/
|
|
export function defaultShellAliases() {
|
|
return {
|
|
nano: 'edit',
|
|
top: 'baretop',
|
|
btop: 'baretop',
|
|
ll: 'ls -la',
|
|
la: 'ls -A',
|
|
l: 'ls',
|
|
'..': 'cd ..',
|
|
'...': 'cd ../..'
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Expand first argv[0] through alias chain; append original argv.slice(1).
|
|
* @param {string[]} argv
|
|
* @param {Record<string, string> | null | undefined} aliases
|
|
* @returns {string[]}
|
|
*/
|
|
export function expandArgvAliases(argv, aliases) {
|
|
if (!argv.length) return argv
|
|
const map = aliases && typeof aliases === 'object' ? aliases : {}
|
|
const out = [...argv]
|
|
let depth = 0
|
|
while (depth < MAX_ALIAS_DEPTH) {
|
|
const first = out[0]
|
|
const repl = map[first]
|
|
if (repl == null || repl === '') break
|
|
const words = tokenize(repl)
|
|
.filter((t) => t.type === 'word')
|
|
.map((t) => t.value)
|
|
if (!words.length) break
|
|
out.splice(0, 1, ...words)
|
|
depth++
|
|
}
|
|
if (depth >= MAX_ALIAS_DEPTH && map[out[0]]) {
|
|
throw new Error('alias: expansion nested too deeply')
|
|
}
|
|
return out
|
|
}
|
|
|
|
/**
|
|
* @param {Record<string, unknown>} ctx
|
|
* @param {string} rest content after `alias ` (name=value...)
|
|
*/
|
|
export function applyAliasDefinition(ctx, rest) {
|
|
const eq = rest.indexOf('=')
|
|
if (eq <= 0) return false
|
|
const aname = rest.slice(0, eq).trim()
|
|
if (!aname) return false
|
|
let val = rest.slice(eq + 1).trim()
|
|
val = stripAliasQuotes(val)
|
|
if (!ctx.shellAliases) ctx.shellAliases = {}
|
|
ctx.shellAliases[aname] = val
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* @param {Record<string, unknown>} ctx
|
|
* @param {string[]} argv argv for unalias builtin (includes 'unalias')
|
|
*/
|
|
export function runUnaliasBuiltin(ctx, argv, logError) {
|
|
if (!ctx.shellAliases) ctx.shellAliases = { ...defaultShellAliases() }
|
|
const args = argv.slice(1)
|
|
if (args.length === 0) {
|
|
logError('unalias: missing name')
|
|
return
|
|
}
|
|
if (args.includes('-a')) {
|
|
ctx.shellAliases = { ...defaultShellAliases() }
|
|
return
|
|
}
|
|
for (const name of args) {
|
|
if (name === '-a') continue
|
|
delete ctx.shellAliases[name]
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse one line from ~/.barerc (without leading `unalias`).
|
|
*/
|
|
export function applyBarercUnalias(ctx, rest) {
|
|
if (!ctx.shellAliases) ctx.shellAliases = { ...defaultShellAliases() }
|
|
const parts = rest.split(/\s+/).filter(Boolean)
|
|
if (parts.length === 1 && parts[0] === '-a') {
|
|
ctx.shellAliases = { ...defaultShellAliases() }
|
|
return
|
|
}
|
|
for (const name of parts) {
|
|
if (name === '-a') ctx.shellAliases = { ...defaultShellAliases() }
|
|
else delete ctx.shellAliases[name]
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Comment-only template written on first login when `~/.barerc` is absent
|
|
* (`loadBarerc(ctx, { createSkeletonIfMissing: true })`).
|
|
*/
|
|
export const BARERC_SKELETON = `# Bare OS — ~/.barerc (not full sh; only export, alias, unalias, theme, # comments).
|
|
#
|
|
# export MY_VAR=value
|
|
# theme default
|
|
# export BARE_OS_COLOR_DEPTH=truecolor
|
|
# export BARE_OS_DIRCOLORS=~/.dir_colors
|
|
# export BARE_OS_LS_COLORS_LOCKED=1
|
|
# alias gst='git status'
|
|
# unalias ll
|
|
`
|
|
|
|
/**
|
|
* Load `~/.barerc`: only `export`, `alias`, `unalias`, `theme`, comments, blank lines.
|
|
* Resets aliases to defaults first, then applies file.
|
|
* @param {Record<string, unknown>} ctx
|
|
* @param {{ createSkeletonIfMissing?: boolean }} [opts] If true and the file is missing, write {@link BARERC_SKELETON} (login / unlock only).
|
|
*/
|
|
export async function loadBarerc(ctx, opts = {}) {
|
|
const { createSkeletonIfMissing = false } = opts
|
|
const strict = globalThis.process?.env?.BARE_OS_STRICT_BARC === '1'
|
|
ctx.shellAliases = { ...defaultShellAliases() }
|
|
const vfs = ctx.vfs
|
|
const env = vfs.env
|
|
let buf = null
|
|
try {
|
|
buf = await vfs.readFile('~/.barerc')
|
|
} catch {
|
|
buf = null
|
|
}
|
|
|
|
let text = null
|
|
if (!buf && createSkeletonIfMissing) {
|
|
try {
|
|
await vfs.writeFile('~/.barerc', ctx.b4a.from(BARERC_SKELETON))
|
|
text = BARERC_SKELETON
|
|
} catch (e) {
|
|
ctx.console?.error?.(
|
|
'[bare-os] could not create ~/.barerc: ' + ((e && e.message) || e)
|
|
)
|
|
}
|
|
}
|
|
|
|
if (!text) {
|
|
if (!buf) {
|
|
await applyBareOsThemeFromEnv(ctx)
|
|
return
|
|
}
|
|
text = ctx.b4a.toString(buf)
|
|
}
|
|
for (const line of text.split(/\r?\n/)) {
|
|
const t = line.trim()
|
|
if (!t || t.startsWith('#')) continue
|
|
if (t.startsWith('export ')) {
|
|
const rest = t.slice(7).trim()
|
|
const eq = rest.indexOf('=')
|
|
if (eq > 0 && /^[A-Za-z_][A-Za-z0-9_]*$/.test(rest.slice(0, eq))) {
|
|
env[rest.slice(0, eq)] = expandWord(rest.slice(eq + 1), env)
|
|
} else if (strict) {
|
|
ctx.console?.error?.('barerc: ignored: ' + t)
|
|
}
|
|
continue
|
|
}
|
|
if (t.startsWith('theme ') || t === 'theme') {
|
|
const name = t === 'theme' ? '' : t.slice(6).trim()
|
|
if (!name) {
|
|
if (strict) ctx.console?.error?.('barerc: theme requires a name')
|
|
continue
|
|
}
|
|
if (!/^[a-zA-Z0-9_.-]+$/.test(name)) {
|
|
if (strict) ctx.console?.error?.('barerc: invalid theme name: ' + name)
|
|
continue
|
|
}
|
|
const norm = name.toLowerCase().replace(/\s+/g, '_')
|
|
if (!bareOsGetThemePreset(norm)) {
|
|
ctx.console?.error?.('barerc: unknown theme: ' + name)
|
|
continue
|
|
}
|
|
env.BARE_OS_THEME = norm
|
|
continue
|
|
}
|
|
if (t.startsWith('alias ')) {
|
|
const ok = applyAliasDefinition(ctx, t.slice(6).trim())
|
|
if (!ok && strict) ctx.console?.error?.('barerc: ignored: ' + t)
|
|
continue
|
|
}
|
|
if (t.startsWith('unalias ')) {
|
|
applyBarercUnalias(ctx, t.slice(8).trim())
|
|
continue
|
|
}
|
|
if (strict) ctx.console?.error?.('barerc: ignored: ' + t)
|
|
}
|
|
await applyBareOsThemeFromEnv(ctx)
|
|
}
|