/** * 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 './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} */ 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 | 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} 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} 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} 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) }