themes
This commit is contained in:
@@ -0,0 +1,428 @@
|
||||
/* BARE_OS_BIN_API 1.0.0 — bump when staged /bin script semantics change (see developer guide). */
|
||||
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
|
||||
function bareStdin(ctx) {
|
||||
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
|
||||
}
|
||||
|
||||
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
|
||||
function bareFormatModeString(mode, type) {
|
||||
const typeChar = type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
|
||||
const perm = mode & 0o777
|
||||
const r = (bit) => (perm & bit ? 'r' : '-')
|
||||
const w = (bit) => (perm & bit ? 'w' : '-')
|
||||
const x = (bit) => (perm & bit ? 'x' : '-')
|
||||
return (
|
||||
typeChar +
|
||||
r(0o400) +
|
||||
w(0o200) +
|
||||
x(0o100) +
|
||||
r(0o040) +
|
||||
w(0o020) +
|
||||
x(0o010) +
|
||||
r(0o004) +
|
||||
w(0o002) +
|
||||
x(0o001)
|
||||
)
|
||||
}
|
||||
|
||||
/** @param {number} mtimeMs @param {number} [nowMs] */
|
||||
function bareFormatLsMtime(mtimeMs, nowMs) {
|
||||
const now = nowMs != null ? nowMs : Date.now()
|
||||
const d = new Date(mtimeMs)
|
||||
const months = [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'May',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Aug',
|
||||
'Sep',
|
||||
'Oct',
|
||||
'Nov',
|
||||
'Dec'
|
||||
]
|
||||
const mon = months[d.getMonth()]
|
||||
const day = String(d.getDate()).padStart(2, ' ')
|
||||
const sixMo = 180 * 24 * 3600 * 1000
|
||||
if (Math.abs(now - mtimeMs) > sixMo) {
|
||||
const yr = String(d.getFullYear()).padStart(4, ' ')
|
||||
return mon + ' ' + day + ' ' + yr
|
||||
}
|
||||
const hh = String(d.getHours()).padStart(2, '0')
|
||||
const mm = String(d.getMinutes()).padStart(2, '0')
|
||||
return mon + ' ' + day + ' ' + hh + ':' + mm
|
||||
}
|
||||
|
||||
/** @param {number} size */
|
||||
function barePosixBlocks(size) {
|
||||
return Math.ceil(Number(size) / 512) || 0
|
||||
}
|
||||
|
||||
/**
|
||||
* GNU-style LS_COLORS string + dircolors(5) database parsing and file classification.
|
||||
* Consumed by bare-os-booter (ESM import). Prepended into /bin/ls and /bin/dircolors by bare-os-coreutils build (no import there).
|
||||
*/
|
||||
|
||||
/** Default LS_COLORS (subset aligned with GNU dircolors -p essentials + common globs). */
|
||||
function bareDefaultLsColorsString() {
|
||||
return [
|
||||
'no=00',
|
||||
'fi=00',
|
||||
'di=01;34',
|
||||
'ln=01;36',
|
||||
'pi=40;33',
|
||||
'so=01;35',
|
||||
'do=01;35',
|
||||
'bd=40;33;01',
|
||||
'cd=40;33;01',
|
||||
'or=40;31;01',
|
||||
'mi=00',
|
||||
'su=37;41',
|
||||
'sg=30;43',
|
||||
'ca=30;41',
|
||||
'tw=30;42',
|
||||
'ow=34;43',
|
||||
'st=37;44',
|
||||
'ex=01;32',
|
||||
'mh=00',
|
||||
'*.tar=01;31',
|
||||
'*.tgz=01;31',
|
||||
'*.arc=01;31',
|
||||
'*.arj=01;31',
|
||||
'*.taz=01;31',
|
||||
'*.lha=01;31',
|
||||
'*.lz4=01;31',
|
||||
'*.lzh=01;31',
|
||||
'*.tlz=01;31',
|
||||
'*.txz=01;31',
|
||||
'*.tzo=01;31',
|
||||
'*.tzst=01;31',
|
||||
'*.bz2=01;31',
|
||||
'*.bz=01;31',
|
||||
'*.tbz=01;31',
|
||||
'*.tbz2=01;31',
|
||||
'*.tz=01;31',
|
||||
'*.deb=01;31',
|
||||
'*.rpm=01;31',
|
||||
'*.jar=01;31',
|
||||
'*.war=01;31',
|
||||
'*.ear=01;31',
|
||||
'*.sar=01;31',
|
||||
'*.rar=01;31',
|
||||
'*.alz=01;31',
|
||||
'*.ace=01;31',
|
||||
'*.zoo=01;31',
|
||||
'*.cpio=01;31',
|
||||
'*.7z=01;31',
|
||||
'*.rz=01;31',
|
||||
'*.cab=01;31',
|
||||
'*.wim=01;31',
|
||||
'*.swm=01;31',
|
||||
'*.dwm=01;31',
|
||||
'*.esd=01;31',
|
||||
'*.jpg=01;35',
|
||||
'*.jpeg=01;35',
|
||||
'*.mjpg=01;35',
|
||||
'*.mjpeg=01;35',
|
||||
'*.gif=01;35',
|
||||
'*.bmp=01;35',
|
||||
'*.pbm=01;35',
|
||||
'*.pgm=01;35',
|
||||
'*.ppm=01;35',
|
||||
'*.tga=01;35',
|
||||
'*.xbm=01;35',
|
||||
'*.xpm=01;35',
|
||||
'*.tif=01;35',
|
||||
'*.tiff=01;35',
|
||||
'*.png=01;35',
|
||||
'*.svg=01;35',
|
||||
'*.svgz=01;35',
|
||||
'*.mng=01;35',
|
||||
'*.pcx=01;35',
|
||||
'*.mov=01;35',
|
||||
'*.mpg=01;35',
|
||||
'*.mpeg=01;35',
|
||||
'*.m2v=01;35',
|
||||
'*.mkv=01;35',
|
||||
'*.webm=01;35',
|
||||
'*.ogm=01;35',
|
||||
'*.mp4=01;35',
|
||||
'*.m4v=01;35',
|
||||
'*.mp4v=01;35',
|
||||
'*.vob=01;35',
|
||||
'*.qt=01;35',
|
||||
'*.nuv=01;35',
|
||||
'*.wmv=01;35',
|
||||
'*.asf=01;35',
|
||||
'*.rm=01;35',
|
||||
'*.rmvb=01;35',
|
||||
'*.flc=01;35',
|
||||
'*.avi=01;35',
|
||||
'*.fli=01;35',
|
||||
'*.flv=01;35',
|
||||
'*.gl=01;35',
|
||||
'*.dl=01;35',
|
||||
'*.xcf=01;35',
|
||||
'*.xwd=01;35',
|
||||
'*.yuv=01;35',
|
||||
'*.cgm=01;35',
|
||||
'*.emf=01;35',
|
||||
'*.ogv=01;35',
|
||||
'*.ogx=01;35',
|
||||
'*.aac=00;36',
|
||||
'*.au=00;36',
|
||||
'*.flac=00;36',
|
||||
'*.m4a=00;36',
|
||||
'*.mid=00;36',
|
||||
'*.midi=00;36',
|
||||
'*.mka=00;36',
|
||||
'*.mp3=00;36',
|
||||
'*.mpc=00;36',
|
||||
'*.ogg=00;36',
|
||||
'*.ra=00;36',
|
||||
'*.wav=00;36',
|
||||
'*.oga=00;36',
|
||||
'*.opus=00;36',
|
||||
'*.spx=00;36',
|
||||
'*.xspf=00;36',
|
||||
'*.pdf=00;32',
|
||||
'*.ps=00;32',
|
||||
'*.txt=00;32',
|
||||
'*.patch=00;32',
|
||||
'*.diff=00;32',
|
||||
'*.log=00;32',
|
||||
'*.tex=00;32',
|
||||
'*.doc=00;32',
|
||||
'*.docx=00;32',
|
||||
'*.rtf=00;32',
|
||||
'*.odt=00;32',
|
||||
'*.md=00;32',
|
||||
'*.markdown=00;32',
|
||||
'*.css=00;32',
|
||||
'*.htm=00;32',
|
||||
'*.html=00;32',
|
||||
'*.xml=00;32',
|
||||
'*.json=00;32',
|
||||
'*.yaml=00;32',
|
||||
'*.yml=00;32',
|
||||
'*.c=00;32',
|
||||
'*.h=00;32',
|
||||
'*.js=00;32',
|
||||
'*.mjs=00;32',
|
||||
'*.java=00;32',
|
||||
'*.py=00;32',
|
||||
'*.go=00;32',
|
||||
'*.rs=00;32',
|
||||
'*.cpp=00;32',
|
||||
'*.cc=00;32',
|
||||
'*.cxx=00;32',
|
||||
'*.hpp=00;32',
|
||||
'*.shlib=01;32',
|
||||
'*.so=01;32',
|
||||
'*.dylib=01;32',
|
||||
'*.dll=01;32',
|
||||
'*.a=01;32',
|
||||
'*.lib=01;32',
|
||||
'*.ko=01;32',
|
||||
'*.sh=01;32',
|
||||
'*.bash=01;32',
|
||||
'*.zsh=01;32',
|
||||
'*.bat=01;32',
|
||||
'*.cmd=01;32',
|
||||
'*.exe=01;32',
|
||||
'*.com=01;32',
|
||||
'*.btm=01;32',
|
||||
'*.msi=01;32'
|
||||
].join(':')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} s
|
||||
* @returns {Record<string, string>}
|
||||
*/
|
||||
function bareParseLsColors(s) {
|
||||
const out = /** @type {Record<string, string>} */ ({})
|
||||
if (s == null || !String(s).trim()) return out
|
||||
for (const part of String(s).split(':')) {
|
||||
const p = part.trim()
|
||||
if (!p) continue
|
||||
const eq = p.indexOf('=')
|
||||
if (eq <= 0) continue
|
||||
const k = p.slice(0, eq)
|
||||
const v = p.slice(eq + 1)
|
||||
if (k) out[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, string>} map
|
||||
*/
|
||||
function bareSerializeLsColors(map) {
|
||||
const keys = Object.keys(map).sort()
|
||||
return keys.map((k) => k + '=' + map[k]).join(':')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} code dircolors value (e.g. 01;34)
|
||||
* @returns {string} opening SGR including ESC
|
||||
*/
|
||||
function bareLscolorsToOpenSgr(code) {
|
||||
if (code == null || code === '' || code === '0' || code === '00') return ''
|
||||
return '\x1b[' + code + 'm'
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse dircolors database (GNU subset): # comments, blank lines, TERM blocks, KEY VALUE pairs.
|
||||
* @param {string} text
|
||||
* @param {string} termEnv value of TERM
|
||||
* @returns {Record<string, string>}
|
||||
*/
|
||||
function bareParseDircolorsDatabase(text, termEnv) {
|
||||
const term = termEnv != null ? String(termEnv) : ''
|
||||
/** @type {Record<string, string>} */
|
||||
const out = {}
|
||||
let blockActive = true
|
||||
const lines = String(text).split(/\r?\n/)
|
||||
for (let raw of lines) {
|
||||
const line = raw.replace(/#.*$/, '').trim()
|
||||
if (!line) continue
|
||||
const upper = line.toUpperCase()
|
||||
if (upper.startsWith('TERM ')) {
|
||||
const rest = line.slice(5).trim()
|
||||
if (rest === 'none') blockActive = false
|
||||
else
|
||||
blockActive =
|
||||
rest === '*' || rest === '' || rest === term || term.indexOf(rest) === 0
|
||||
continue
|
||||
}
|
||||
if (upper.startsWith('COLOR ') || upper.startsWith('OPTIONS ')) continue
|
||||
if (!blockActive) continue
|
||||
const ws = line.search(/\s/)
|
||||
if (ws <= 0) continue
|
||||
const key = line.slice(0, ws).trim()
|
||||
const val = line.slice(ws + 1).trim()
|
||||
if (!key || !val) continue
|
||||
if (key === 'TERM' || key === 'COLOR' || key === 'OPTIONS') continue
|
||||
out[key] = val
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} filename basename only
|
||||
* @param {Record<string, string>} map
|
||||
*/
|
||||
function bareLsColorsGlobMatch(filename, map) {
|
||||
const keys = Object.keys(map).filter((k) => k.startsWith('*.'))
|
||||
keys.sort((a, b) => b.length - a.length)
|
||||
const lower = filename.toLowerCase()
|
||||
for (const k of keys) {
|
||||
const suf = k.slice(1).toLowerCase()
|
||||
if (lower.endsWith(suf)) return map[k]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ type?: string, mode?: number, linkname?: string, targetMissing?: boolean, nlink?: number, capabilities?: boolean } | null} st
|
||||
* @param {string} filename basename
|
||||
* @param {Record<string, string>} map from LS_COLORS
|
||||
* @returns {string} SGR open sequence or ''
|
||||
*/
|
||||
function bareLsColorOpenSgrFromMap(st, filename, map) {
|
||||
if (!st) {
|
||||
const no = map.no
|
||||
return no ? bareLscolorsToOpenSgr(no) : ''
|
||||
}
|
||||
const mode = Number(st.mode) || 0
|
||||
const perm = mode & 0o777
|
||||
const t = st.type
|
||||
let key = 'fi'
|
||||
|
||||
if (t === 'symlink') {
|
||||
key = st.targetMissing ? 'or' : 'ln'
|
||||
} else if (t === 'directory') {
|
||||
const ow = (perm & 0o002) !== 0
|
||||
const sticky = (mode & 0o1000) !== 0
|
||||
if (ow && sticky) key = 'tw'
|
||||
else if (ow) key = 'ow'
|
||||
else if (sticky) key = 'st'
|
||||
else key = 'di'
|
||||
} else if (t === 'file') {
|
||||
if (st.capabilities) key = 'ca'
|
||||
else if (mode & 0o4000) key = 'su'
|
||||
else if (mode & 0o2000) key = 'sg'
|
||||
else if (perm & 0o111) key = 'ex'
|
||||
else {
|
||||
const nlink = Number(st.nlink)
|
||||
if (nlink > 1 && map.mh != null && String(map.mh).trim() !== '') {
|
||||
const mhOpen = bareLscolorsToOpenSgr(map.mh)
|
||||
if (mhOpen) return mhOpen
|
||||
}
|
||||
const g = bareLsColorsGlobMatch(filename, map)
|
||||
if (g != null) return bareLscolorsToOpenSgr(g)
|
||||
key = 'fi'
|
||||
}
|
||||
}
|
||||
|
||||
const code = map[key] != null ? map[key] : map.fi
|
||||
return code ? bareLscolorsToOpenSgr(code) : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} multiline dircolors database (default colors)
|
||||
*/
|
||||
function bareDefaultDircolorsDatabase() {
|
||||
const pairs = bareParseLsColors(bareDefaultLsColorsString())
|
||||
const lines = ['# Bare OS default dircolors (GNU-like)', 'TERM *', '']
|
||||
for (const k of Object.keys(pairs).sort()) {
|
||||
lines.push(k + ' ' + pairs[k])
|
||||
}
|
||||
return lines.join('\n') + '\n'
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const env = ctx.env || {}
|
||||
let bourne = false
|
||||
let printDefault = false
|
||||
/** @type {string | null} */
|
||||
let file = null
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '--') continue
|
||||
if (a === '-b' || a === '--sh' || a === '--bourne-shell') bourne = true
|
||||
else if (a === '-p' || a === '--print-database') printDefault = true
|
||||
else if (a.startsWith('-')) {
|
||||
ctx.console.error('dircolors: unrecognized option: ' + a)
|
||||
ctx.exitCode = 2
|
||||
return
|
||||
} else file = a
|
||||
}
|
||||
if (printDefault) {
|
||||
ctx.console.log(bareDefaultDircolorsDatabase())
|
||||
return
|
||||
}
|
||||
let text = bareDefaultDircolorsDatabase()
|
||||
if (file) {
|
||||
const buf = await ctx.vfs.readFile(file)
|
||||
if (!buf) {
|
||||
ctx.console.error('dircolors: cannot read ' + file)
|
||||
ctx.exitCode = 2
|
||||
return
|
||||
}
|
||||
text = ctx.b4a.toString(buf)
|
||||
}
|
||||
const term = env.TERM || ''
|
||||
const db = bareParseDircolorsDatabase(text, term)
|
||||
const ls = bareSerializeLsColors(db)
|
||||
if (bourne) {
|
||||
const q = "'" + ls.replace(/'/g, "'\\''") + "'"
|
||||
ctx.console.log('LS_COLORS=' + q)
|
||||
ctx.console.log('export LS_COLORS')
|
||||
} else {
|
||||
ctx.console.log(ls)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -62,7 +62,7 @@ function barePosixBlocks(size) {
|
||||
|
||||
async function run(ctx, argv) {
|
||||
ctx.console.log(
|
||||
'Bare OS — default user: guest | shell builtins: alias, cd, command, export, exit, login, logout, readonly, type, umask, unalias, unset, : | /bin: awk basename cat chgrp chmod chown cksum clear cp crontab curl cut date dirname du echo env exit false find getconf grep head hdms help hostname id journalctl jq ln login logout logname ls man mkdir mkfifo mv nl od pathchk printenv printf pwd readlink rm rmdir savevault sed seq sleep sort stat systemctl tail tee test time touch tr true tty uname wc wget which whoami xargs'
|
||||
'Bare OS — default user: guest | shell builtins: alias, barerc, cd, command, export, exit, login, logout, readonly, type, umask, unalias, unset, : | /bin: awk basename cat chgrp chmod chown cksum clear cp crontab curl cut date dirname dircolors du echo env exit false find getconf grep head hdms help hostname id journalctl jq ln login logout logname ls man mkdir mkfifo mv nl od pathchk printenv printf pwd readlink rm rmdir savevault sed seq sleep sort stat systemctl tail tee test theme time touch tr true tty uname wc wget which whoami xargs'
|
||||
)
|
||||
ctx.console.log(
|
||||
'Docs: man <command> | man handbook (full handbook, section 7) | man bare-os-shell | man -k <word> | man -l'
|
||||
|
||||
+458
-5
@@ -60,10 +60,424 @@ function barePosixBlocks(size) {
|
||||
return Math.ceil(Number(size) / 512) || 0
|
||||
}
|
||||
|
||||
/**
|
||||
* GNU-style LS_COLORS string + dircolors(5) database parsing and file classification.
|
||||
* Consumed by bare-os-booter (ESM import). Prepended into /bin/ls and /bin/dircolors by bare-os-coreutils build (no import there).
|
||||
*/
|
||||
|
||||
/** Default LS_COLORS (subset aligned with GNU dircolors -p essentials + common globs). */
|
||||
function bareDefaultLsColorsString() {
|
||||
return [
|
||||
'no=00',
|
||||
'fi=00',
|
||||
'di=01;34',
|
||||
'ln=01;36',
|
||||
'pi=40;33',
|
||||
'so=01;35',
|
||||
'do=01;35',
|
||||
'bd=40;33;01',
|
||||
'cd=40;33;01',
|
||||
'or=40;31;01',
|
||||
'mi=00',
|
||||
'su=37;41',
|
||||
'sg=30;43',
|
||||
'ca=30;41',
|
||||
'tw=30;42',
|
||||
'ow=34;43',
|
||||
'st=37;44',
|
||||
'ex=01;32',
|
||||
'mh=00',
|
||||
'*.tar=01;31',
|
||||
'*.tgz=01;31',
|
||||
'*.arc=01;31',
|
||||
'*.arj=01;31',
|
||||
'*.taz=01;31',
|
||||
'*.lha=01;31',
|
||||
'*.lz4=01;31',
|
||||
'*.lzh=01;31',
|
||||
'*.tlz=01;31',
|
||||
'*.txz=01;31',
|
||||
'*.tzo=01;31',
|
||||
'*.tzst=01;31',
|
||||
'*.bz2=01;31',
|
||||
'*.bz=01;31',
|
||||
'*.tbz=01;31',
|
||||
'*.tbz2=01;31',
|
||||
'*.tz=01;31',
|
||||
'*.deb=01;31',
|
||||
'*.rpm=01;31',
|
||||
'*.jar=01;31',
|
||||
'*.war=01;31',
|
||||
'*.ear=01;31',
|
||||
'*.sar=01;31',
|
||||
'*.rar=01;31',
|
||||
'*.alz=01;31',
|
||||
'*.ace=01;31',
|
||||
'*.zoo=01;31',
|
||||
'*.cpio=01;31',
|
||||
'*.7z=01;31',
|
||||
'*.rz=01;31',
|
||||
'*.cab=01;31',
|
||||
'*.wim=01;31',
|
||||
'*.swm=01;31',
|
||||
'*.dwm=01;31',
|
||||
'*.esd=01;31',
|
||||
'*.jpg=01;35',
|
||||
'*.jpeg=01;35',
|
||||
'*.mjpg=01;35',
|
||||
'*.mjpeg=01;35',
|
||||
'*.gif=01;35',
|
||||
'*.bmp=01;35',
|
||||
'*.pbm=01;35',
|
||||
'*.pgm=01;35',
|
||||
'*.ppm=01;35',
|
||||
'*.tga=01;35',
|
||||
'*.xbm=01;35',
|
||||
'*.xpm=01;35',
|
||||
'*.tif=01;35',
|
||||
'*.tiff=01;35',
|
||||
'*.png=01;35',
|
||||
'*.svg=01;35',
|
||||
'*.svgz=01;35',
|
||||
'*.mng=01;35',
|
||||
'*.pcx=01;35',
|
||||
'*.mov=01;35',
|
||||
'*.mpg=01;35',
|
||||
'*.mpeg=01;35',
|
||||
'*.m2v=01;35',
|
||||
'*.mkv=01;35',
|
||||
'*.webm=01;35',
|
||||
'*.ogm=01;35',
|
||||
'*.mp4=01;35',
|
||||
'*.m4v=01;35',
|
||||
'*.mp4v=01;35',
|
||||
'*.vob=01;35',
|
||||
'*.qt=01;35',
|
||||
'*.nuv=01;35',
|
||||
'*.wmv=01;35',
|
||||
'*.asf=01;35',
|
||||
'*.rm=01;35',
|
||||
'*.rmvb=01;35',
|
||||
'*.flc=01;35',
|
||||
'*.avi=01;35',
|
||||
'*.fli=01;35',
|
||||
'*.flv=01;35',
|
||||
'*.gl=01;35',
|
||||
'*.dl=01;35',
|
||||
'*.xcf=01;35',
|
||||
'*.xwd=01;35',
|
||||
'*.yuv=01;35',
|
||||
'*.cgm=01;35',
|
||||
'*.emf=01;35',
|
||||
'*.ogv=01;35',
|
||||
'*.ogx=01;35',
|
||||
'*.aac=00;36',
|
||||
'*.au=00;36',
|
||||
'*.flac=00;36',
|
||||
'*.m4a=00;36',
|
||||
'*.mid=00;36',
|
||||
'*.midi=00;36',
|
||||
'*.mka=00;36',
|
||||
'*.mp3=00;36',
|
||||
'*.mpc=00;36',
|
||||
'*.ogg=00;36',
|
||||
'*.ra=00;36',
|
||||
'*.wav=00;36',
|
||||
'*.oga=00;36',
|
||||
'*.opus=00;36',
|
||||
'*.spx=00;36',
|
||||
'*.xspf=00;36',
|
||||
'*.pdf=00;32',
|
||||
'*.ps=00;32',
|
||||
'*.txt=00;32',
|
||||
'*.patch=00;32',
|
||||
'*.diff=00;32',
|
||||
'*.log=00;32',
|
||||
'*.tex=00;32',
|
||||
'*.doc=00;32',
|
||||
'*.docx=00;32',
|
||||
'*.rtf=00;32',
|
||||
'*.odt=00;32',
|
||||
'*.md=00;32',
|
||||
'*.markdown=00;32',
|
||||
'*.css=00;32',
|
||||
'*.htm=00;32',
|
||||
'*.html=00;32',
|
||||
'*.xml=00;32',
|
||||
'*.json=00;32',
|
||||
'*.yaml=00;32',
|
||||
'*.yml=00;32',
|
||||
'*.c=00;32',
|
||||
'*.h=00;32',
|
||||
'*.js=00;32',
|
||||
'*.mjs=00;32',
|
||||
'*.java=00;32',
|
||||
'*.py=00;32',
|
||||
'*.go=00;32',
|
||||
'*.rs=00;32',
|
||||
'*.cpp=00;32',
|
||||
'*.cc=00;32',
|
||||
'*.cxx=00;32',
|
||||
'*.hpp=00;32',
|
||||
'*.shlib=01;32',
|
||||
'*.so=01;32',
|
||||
'*.dylib=01;32',
|
||||
'*.dll=01;32',
|
||||
'*.a=01;32',
|
||||
'*.lib=01;32',
|
||||
'*.ko=01;32',
|
||||
'*.sh=01;32',
|
||||
'*.bash=01;32',
|
||||
'*.zsh=01;32',
|
||||
'*.bat=01;32',
|
||||
'*.cmd=01;32',
|
||||
'*.exe=01;32',
|
||||
'*.com=01;32',
|
||||
'*.btm=01;32',
|
||||
'*.msi=01;32'
|
||||
].join(':')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} s
|
||||
* @returns {Record<string, string>}
|
||||
*/
|
||||
function bareParseLsColors(s) {
|
||||
const out = /** @type {Record<string, string>} */ ({})
|
||||
if (s == null || !String(s).trim()) return out
|
||||
for (const part of String(s).split(':')) {
|
||||
const p = part.trim()
|
||||
if (!p) continue
|
||||
const eq = p.indexOf('=')
|
||||
if (eq <= 0) continue
|
||||
const k = p.slice(0, eq)
|
||||
const v = p.slice(eq + 1)
|
||||
if (k) out[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, string>} map
|
||||
*/
|
||||
function bareSerializeLsColors(map) {
|
||||
const keys = Object.keys(map).sort()
|
||||
return keys.map((k) => k + '=' + map[k]).join(':')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} code dircolors value (e.g. 01;34)
|
||||
* @returns {string} opening SGR including ESC
|
||||
*/
|
||||
function bareLscolorsToOpenSgr(code) {
|
||||
if (code == null || code === '' || code === '0' || code === '00') return ''
|
||||
return '\x1b[' + code + 'm'
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse dircolors database (GNU subset): # comments, blank lines, TERM blocks, KEY VALUE pairs.
|
||||
* @param {string} text
|
||||
* @param {string} termEnv value of TERM
|
||||
* @returns {Record<string, string>}
|
||||
*/
|
||||
function bareParseDircolorsDatabase(text, termEnv) {
|
||||
const term = termEnv != null ? String(termEnv) : ''
|
||||
/** @type {Record<string, string>} */
|
||||
const out = {}
|
||||
let blockActive = true
|
||||
const lines = String(text).split(/\r?\n/)
|
||||
for (let raw of lines) {
|
||||
const line = raw.replace(/#.*$/, '').trim()
|
||||
if (!line) continue
|
||||
const upper = line.toUpperCase()
|
||||
if (upper.startsWith('TERM ')) {
|
||||
const rest = line.slice(5).trim()
|
||||
if (rest === 'none') blockActive = false
|
||||
else
|
||||
blockActive =
|
||||
rest === '*' || rest === '' || rest === term || term.indexOf(rest) === 0
|
||||
continue
|
||||
}
|
||||
if (upper.startsWith('COLOR ') || upper.startsWith('OPTIONS ')) continue
|
||||
if (!blockActive) continue
|
||||
const ws = line.search(/\s/)
|
||||
if (ws <= 0) continue
|
||||
const key = line.slice(0, ws).trim()
|
||||
const val = line.slice(ws + 1).trim()
|
||||
if (!key || !val) continue
|
||||
if (key === 'TERM' || key === 'COLOR' || key === 'OPTIONS') continue
|
||||
out[key] = val
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} filename basename only
|
||||
* @param {Record<string, string>} map
|
||||
*/
|
||||
function bareLsColorsGlobMatch(filename, map) {
|
||||
const keys = Object.keys(map).filter((k) => k.startsWith('*.'))
|
||||
keys.sort((a, b) => b.length - a.length)
|
||||
const lower = filename.toLowerCase()
|
||||
for (const k of keys) {
|
||||
const suf = k.slice(1).toLowerCase()
|
||||
if (lower.endsWith(suf)) return map[k]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ type?: string, mode?: number, linkname?: string, targetMissing?: boolean, nlink?: number, capabilities?: boolean } | null} st
|
||||
* @param {string} filename basename
|
||||
* @param {Record<string, string>} map from LS_COLORS
|
||||
* @returns {string} SGR open sequence or ''
|
||||
*/
|
||||
function bareLsColorOpenSgrFromMap(st, filename, map) {
|
||||
if (!st) {
|
||||
const no = map.no
|
||||
return no ? bareLscolorsToOpenSgr(no) : ''
|
||||
}
|
||||
const mode = Number(st.mode) || 0
|
||||
const perm = mode & 0o777
|
||||
const t = st.type
|
||||
let key = 'fi'
|
||||
|
||||
if (t === 'symlink') {
|
||||
key = st.targetMissing ? 'or' : 'ln'
|
||||
} else if (t === 'directory') {
|
||||
const ow = (perm & 0o002) !== 0
|
||||
const sticky = (mode & 0o1000) !== 0
|
||||
if (ow && sticky) key = 'tw'
|
||||
else if (ow) key = 'ow'
|
||||
else if (sticky) key = 'st'
|
||||
else key = 'di'
|
||||
} else if (t === 'file') {
|
||||
if (st.capabilities) key = 'ca'
|
||||
else if (mode & 0o4000) key = 'su'
|
||||
else if (mode & 0o2000) key = 'sg'
|
||||
else if (perm & 0o111) key = 'ex'
|
||||
else {
|
||||
const nlink = Number(st.nlink)
|
||||
if (nlink > 1 && map.mh != null && String(map.mh).trim() !== '') {
|
||||
const mhOpen = bareLscolorsToOpenSgr(map.mh)
|
||||
if (mhOpen) return mhOpen
|
||||
}
|
||||
const g = bareLsColorsGlobMatch(filename, map)
|
||||
if (g != null) return bareLscolorsToOpenSgr(g)
|
||||
key = 'fi'
|
||||
}
|
||||
}
|
||||
|
||||
const code = map[key] != null ? map[key] : map.fi
|
||||
return code ? bareLscolorsToOpenSgr(code) : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} multiline dircolors database (default colors)
|
||||
*/
|
||||
function bareDefaultDircolorsDatabase() {
|
||||
const pairs = bareParseLsColors(bareDefaultLsColorsString())
|
||||
const lines = ['# Bare OS default dircolors (GNU-like)', 'TERM *', '']
|
||||
for (const k of Object.keys(pairs).sort()) {
|
||||
lines.push(k + ' ' + pairs[k])
|
||||
}
|
||||
return lines.join('\n') + '\n'
|
||||
}
|
||||
|
||||
/** ANSI coloring for /bin/ls (after bare-os-lscolors.js preamble). */
|
||||
|
||||
const BARE_LS_RESET = '\x1b[0m'
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} [ctx]
|
||||
* @returns {import('stream').Writable | undefined}
|
||||
*/
|
||||
function bareLsResolveStdout(ctx) {
|
||||
if (!ctx || typeof ctx !== 'object') return globalThis.process?.stdout
|
||||
const c = /** @type {{ replStdout?: unknown, stdout?: unknown }} */ (ctx)
|
||||
const out = c.replStdout || c.stdout || globalThis.process?.stdout
|
||||
return /** @type {import('stream').Writable | undefined} */ (out)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} [ctx]
|
||||
* @param {'never' | 'auto' | 'always'} colorMode
|
||||
*/
|
||||
function bareLsUseColor(ctx, colorMode) {
|
||||
const env =
|
||||
ctx && typeof ctx === 'object' && ctx.env && typeof ctx.env === 'object'
|
||||
? /** @type {Record<string, string>} */ (ctx.env)
|
||||
: {}
|
||||
if (env.NO_COLOR != null && String(env.NO_COLOR) !== '') return false
|
||||
if (colorMode === 'never') return false
|
||||
if (colorMode === 'always') return true
|
||||
const out = bareLsResolveStdout(ctx)
|
||||
return Boolean(out && /** @type {{ isTTY?: boolean }} */ (out).isTTY)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} [ctx]
|
||||
* @returns {Record<string, string>}
|
||||
*/
|
||||
function bareLsGetLsColorsMap(ctx) {
|
||||
const env =
|
||||
ctx && typeof ctx === 'object' && ctx.env && typeof ctx.env === 'object'
|
||||
? /** @type {Record<string, string>} */ (ctx.env)
|
||||
: {}
|
||||
const raw = env.LS_COLORS
|
||||
if (raw == null || !String(raw).trim()) {
|
||||
return bareParseLsColors(bareDefaultLsColorsString())
|
||||
}
|
||||
return bareParseLsColors(String(raw))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ mode?: number, type?: string, linkname?: string, targetMissing?: boolean, nlink?: number, capabilities?: boolean } | null | undefined} st
|
||||
* @param {string} filename basename
|
||||
* @param {Record<string, unknown>} [ctx]
|
||||
*/
|
||||
function bareLsOpenSgr(st, filename, ctx) {
|
||||
const map = bareLsGetLsColorsMap(ctx)
|
||||
return bareLsColorOpenSgrFromMap(st, filename, map)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {{ mode?: number, type?: string } | null | undefined} st
|
||||
* @param {boolean} on
|
||||
* @param {Record<string, unknown>} [ctx]
|
||||
*/
|
||||
function bareLsColorWrap(name, st, on, ctx) {
|
||||
if (!on) return name
|
||||
const open = bareLsOpenSgr(st, name, ctx)
|
||||
if (!open) return name
|
||||
return open + name + BARE_LS_RESET
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} arrow
|
||||
* @param {{ mode?: number, type?: string, linkname?: string, targetMissing?: boolean, nlink?: number, capabilities?: boolean } | null | undefined} st
|
||||
* @param {boolean} on
|
||||
* @param {Record<string, unknown>} [ctx]
|
||||
*/
|
||||
function bareLsColorLongTail(name, arrow, st, on, ctx) {
|
||||
if (!on) return name + arrow
|
||||
if (st && st.type === 'symlink') {
|
||||
const map = bareLsGetLsColorsMap(ctx)
|
||||
const ln = map.ln
|
||||
const open = ln ? bareLscolorsToOpenSgr(ln) : '\x1b[36m'
|
||||
return open + name + arrow + BARE_LS_RESET
|
||||
}
|
||||
return bareLsColorWrap(name, st, true, ctx) + arrow
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const vfs = ctx.vfs
|
||||
let showAll = false
|
||||
let longFmt = false
|
||||
/** @type {'never' | 'auto' | 'always'} */
|
||||
let colorMode = 'auto'
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
@@ -71,6 +485,22 @@ async function run(ctx, argv) {
|
||||
paths.push(...argv.slice(i + 1))
|
||||
break
|
||||
}
|
||||
if (a.startsWith('--')) {
|
||||
if (a === '--color') {
|
||||
colorMode = 'auto'
|
||||
continue
|
||||
}
|
||||
if (a.startsWith('--color=')) {
|
||||
const v = a.slice(8).toLowerCase()
|
||||
if (v === 'never' || v === 'none' || v === 'no') colorMode = 'never'
|
||||
else if (v === 'always' || v === 'yes' || v === 'force') colorMode = 'always'
|
||||
else colorMode = 'auto'
|
||||
continue
|
||||
}
|
||||
ctx.console.error('ls: unrecognized option ' + a)
|
||||
ctx.exitCode = 2
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a.length > 1) {
|
||||
for (let j = 1; j < a.length; j++) {
|
||||
const c = a[j]
|
||||
@@ -83,6 +513,7 @@ async function run(ctx, argv) {
|
||||
paths.push(a)
|
||||
}
|
||||
const targets = paths.length ? paths : ['.']
|
||||
const useColor = bareLsUseColor(ctx, colorMode)
|
||||
|
||||
for (const t of targets) {
|
||||
if (targets.length > 1) ctx.console.log(t + ':')
|
||||
@@ -104,7 +535,27 @@ async function run(ctx, argv) {
|
||||
}
|
||||
if (!showAll) names = names.filter((n) => !n.startsWith('.'))
|
||||
if (!longFmt) {
|
||||
ctx.console.log(names.join(' '))
|
||||
if (!useColor) {
|
||||
ctx.console.log(names.join(' '))
|
||||
} else {
|
||||
const parts = []
|
||||
for (const n of names) {
|
||||
const sub =
|
||||
singleEntryPath != null
|
||||
? singleEntryPath
|
||||
: t === '.' || t === './'
|
||||
? n
|
||||
: t.replace(/\/$/, '') + '/' + n
|
||||
let st = null
|
||||
try {
|
||||
st = await vfs.lstat(sub)
|
||||
} catch {
|
||||
st = null
|
||||
}
|
||||
parts.push(bareLsColorWrap(n, st, true, ctx))
|
||||
}
|
||||
ctx.console.log(parts.join(' '))
|
||||
}
|
||||
} else {
|
||||
let totalBlocks = 0
|
||||
const rows = []
|
||||
@@ -125,7 +576,8 @@ async function run(ctx, argv) {
|
||||
size: 0,
|
||||
mtimeStr: '?',
|
||||
name: n,
|
||||
arrow: ''
|
||||
arrow: '',
|
||||
st: null
|
||||
})
|
||||
continue
|
||||
}
|
||||
@@ -151,11 +603,13 @@ async function run(ctx, argv) {
|
||||
size,
|
||||
mtimeStr,
|
||||
name: n,
|
||||
arrow
|
||||
arrow,
|
||||
st
|
||||
})
|
||||
}
|
||||
if (singleEntryPath == null) ctx.console.log('total ' + totalBlocks)
|
||||
for (const r of rows) {
|
||||
const tail = bareLsColorLongTail(r.name, r.arrow, r.st, useColor, ctx)
|
||||
ctx.console.log(
|
||||
r.modeStr +
|
||||
' ' +
|
||||
@@ -169,8 +623,7 @@ async function run(ctx, argv) {
|
||||
' ' +
|
||||
r.mtimeStr +
|
||||
' ' +
|
||||
r.name +
|
||||
r.arrow
|
||||
tail
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
/* BARE_OS_BIN_API 1.0.0 — bump when staged /bin script semantics change (see developer guide). */
|
||||
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
|
||||
function bareStdin(ctx) {
|
||||
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
|
||||
}
|
||||
|
||||
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
|
||||
function bareFormatModeString(mode, type) {
|
||||
const typeChar = type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
|
||||
const perm = mode & 0o777
|
||||
const r = (bit) => (perm & bit ? 'r' : '-')
|
||||
const w = (bit) => (perm & bit ? 'w' : '-')
|
||||
const x = (bit) => (perm & bit ? 'x' : '-')
|
||||
return (
|
||||
typeChar +
|
||||
r(0o400) +
|
||||
w(0o200) +
|
||||
x(0o100) +
|
||||
r(0o040) +
|
||||
w(0o020) +
|
||||
x(0o010) +
|
||||
r(0o004) +
|
||||
w(0o002) +
|
||||
x(0o001)
|
||||
)
|
||||
}
|
||||
|
||||
/** @param {number} mtimeMs @param {number} [nowMs] */
|
||||
function bareFormatLsMtime(mtimeMs, nowMs) {
|
||||
const now = nowMs != null ? nowMs : Date.now()
|
||||
const d = new Date(mtimeMs)
|
||||
const months = [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'May',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Aug',
|
||||
'Sep',
|
||||
'Oct',
|
||||
'Nov',
|
||||
'Dec'
|
||||
]
|
||||
const mon = months[d.getMonth()]
|
||||
const day = String(d.getDate()).padStart(2, ' ')
|
||||
const sixMo = 180 * 24 * 3600 * 1000
|
||||
if (Math.abs(now - mtimeMs) > sixMo) {
|
||||
const yr = String(d.getFullYear()).padStart(4, ' ')
|
||||
return mon + ' ' + day + ' ' + yr
|
||||
}
|
||||
const hh = String(d.getHours()).padStart(2, '0')
|
||||
const mm = String(d.getMinutes()).padStart(2, '0')
|
||||
return mon + ' ' + day + ' ' + hh + ':' + mm
|
||||
}
|
||||
|
||||
/** @param {number} size */
|
||||
function barePosixBlocks(size) {
|
||||
return Math.ceil(Number(size) / 512) || 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme switcher: list presets, show current, set preset (updates ~/.barerc), re-apply.
|
||||
* Uses ctx.bareOsListThemes / ctx.bareOsApplyTheme when provided by the booter.
|
||||
*/
|
||||
|
||||
const FALLBACK_THEMES = [
|
||||
'catppuccin_mocha',
|
||||
'default',
|
||||
'dracula',
|
||||
'github_dark',
|
||||
'gruvbox_dark',
|
||||
'nord',
|
||||
'solarized_dark',
|
||||
'tokyo_night'
|
||||
]
|
||||
|
||||
function themeListFromCtx(ctx) {
|
||||
if (typeof ctx.bareOsListThemes === 'function') {
|
||||
try {
|
||||
const names = ctx.bareOsListThemes()
|
||||
if (Array.isArray(names) && names.length) return names
|
||||
} catch {
|
||||
/* fall through */
|
||||
}
|
||||
}
|
||||
return FALLBACK_THEMES.slice().sort()
|
||||
}
|
||||
|
||||
async function readBarerc(ctx) {
|
||||
try {
|
||||
const buf = await ctx.vfs.readFile('~/.barerc')
|
||||
return buf ? ctx.b4a.toString(buf) : ''
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
async function writeBarerc(ctx, text) {
|
||||
await ctx.vfs.writeFile('~/.barerc', ctx.b4a.from(text))
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure ~/.barerc contains `theme <name>` (replace first theme line or append).
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string} name
|
||||
*/
|
||||
async function persistThemeLine(ctx, name) {
|
||||
let text = await readBarerc(ctx)
|
||||
const lines = text.split(/\r?\n/)
|
||||
const themeRe = /^\s*theme\s+/
|
||||
let replaced = false
|
||||
const out = []
|
||||
for (const line of lines) {
|
||||
if (themeRe.test(line)) {
|
||||
if (!replaced) {
|
||||
out.push('theme ' + name)
|
||||
replaced = true
|
||||
}
|
||||
} else out.push(line)
|
||||
}
|
||||
if (!replaced) {
|
||||
if (out.length && out[out.length - 1].trim() !== '') out.push('')
|
||||
out.push('theme ' + name)
|
||||
}
|
||||
const newText = out.join('\n')
|
||||
if (!/\n$/.test(newText) && newText.length) await writeBarerc(ctx, newText + '\n')
|
||||
else await writeBarerc(ctx, newText || 'theme ' + name + '\n')
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const env = ctx.vfs?.env || ctx.env || {}
|
||||
const sub = argv[1] || 'list'
|
||||
|
||||
if (sub === 'list') {
|
||||
for (const n of themeListFromCtx(ctx)) ctx.console.log(n)
|
||||
return
|
||||
}
|
||||
|
||||
if (sub === 'current') {
|
||||
const t = env.BARE_OS_THEME != null ? String(env.BARE_OS_THEME) : 'default'
|
||||
ctx.console.log(t)
|
||||
const ls = env.LS_COLORS
|
||||
if (ls != null && String(ls).length) {
|
||||
ctx.console.log('LS_COLORS_len=' + String(ls).length)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (sub === 'apply') {
|
||||
if (typeof ctx.bareOsApplyTheme === 'function') {
|
||||
await ctx.bareOsApplyTheme()
|
||||
} else {
|
||||
ctx.console.error('theme: bareOsApplyTheme not available')
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (sub === 'set') {
|
||||
const name = argv[2]
|
||||
if (!name) {
|
||||
ctx.console.error('theme: usage: theme set <name>')
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
const norm = String(name)
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/\s+/g, '_')
|
||||
const valid = new Set(themeListFromCtx(ctx).map((s) => s.toLowerCase()))
|
||||
if (!valid.has(norm)) {
|
||||
ctx.console.error('theme: unknown preset: ' + name)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
try {
|
||||
await persistThemeLine(ctx, norm)
|
||||
} catch (e) {
|
||||
ctx.console.error('theme: ' + ((e && e.message) || e))
|
||||
ctx.exitCode = 2
|
||||
return
|
||||
}
|
||||
env.BARE_OS_THEME = norm
|
||||
if (typeof ctx.bareOsApplyTheme === 'function') {
|
||||
await ctx.bareOsApplyTheme()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.console.error(
|
||||
'theme: usage: theme [list|current|set <name>|apply]'
|
||||
)
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
Reference in New Issue
Block a user