759 lines
20 KiB
Plaintext
759 lines
20 KiB
Plaintext
/* 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
|
|
}
|
|
|
|
/**
|
|
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
|
|
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
|
|
* @param {Record<string, unknown>} ctx
|
|
* @param {string | Uint8Array} chunk
|
|
* @returns {boolean}
|
|
*/
|
|
function bareOsEmitRaw(ctx, chunk) {
|
|
if (typeof ctx.bareOsBinWrite === 'function') {
|
|
const b4 = ctx.b4a
|
|
const u8 =
|
|
typeof chunk === 'string'
|
|
? b4 && typeof b4.from === 'function'
|
|
? b4.from(chunk)
|
|
: new TextEncoder().encode(chunk)
|
|
: chunk
|
|
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
|
|
return true
|
|
}
|
|
const w = globalThis.process?.stdout?.write
|
|
if (typeof w === 'function') {
|
|
w.call(globalThis.process.stdout, chunk)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
}
|
|
|
|
/**
|
|
* @param {unknown} vfs
|
|
* @param {string[]} names
|
|
* @param {string} t
|
|
* @param {string | null} singleEntryPath
|
|
* @param {'time' | 'size' | null} sortBy
|
|
* @param {boolean} reverse
|
|
*/
|
|
async function bareLsSortNames(
|
|
vfs,
|
|
names,
|
|
t,
|
|
singleEntryPath,
|
|
sortBy,
|
|
reverse
|
|
) {
|
|
if (singleEntryPath != null) return names
|
|
if (!sortBy && !reverse) return names
|
|
const pairs = []
|
|
for (const n of names) {
|
|
const sub = t === '.' || t === './' ? n : t.replace(/\/$/, '') + '/' + n
|
|
let st = null
|
|
try {
|
|
st = await vfs.lstat(sub)
|
|
} catch {
|
|
st = null
|
|
}
|
|
pairs.push({ n, st })
|
|
}
|
|
pairs.sort((a, b) => {
|
|
let cmp = 0
|
|
if (sortBy === 'time') cmp = (b.st?.mtimeMs ?? 0) - (a.st?.mtimeMs ?? 0)
|
|
else if (sortBy === 'size') cmp = (b.st?.size ?? 0) - (a.st?.size ?? 0)
|
|
else cmp = a.n.localeCompare(b.n)
|
|
return reverse ? -cmp : cmp
|
|
})
|
|
return pairs.map((p) => p.n)
|
|
}
|
|
|
|
async function run(ctx, argv) {
|
|
const vfs = ctx.vfs
|
|
let showAll = false
|
|
let longFmt = false
|
|
/** One name per line (GNU **`-1`** / **`--format=single-column`**, or piped/captured stdout). */
|
|
let singleColumn = false
|
|
/** @type {'never' | 'auto' | 'always'} */
|
|
let colorMode = 'auto'
|
|
/** @type {'time' | 'size' | null} */
|
|
let sortBy = null
|
|
let reverseSort = false
|
|
const paths = []
|
|
for (let i = 1; i < argv.length; i++) {
|
|
const a = argv[i]
|
|
if (a === '--') {
|
|
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
|
|
}
|
|
if (a === '--format=single-column' || a === '--format=vertical') {
|
|
singleColumn = true
|
|
continue
|
|
}
|
|
if (a === '--sort=time' || a === '--sort=none') {
|
|
sortBy = a.endsWith('time') ? 'time' : null
|
|
continue
|
|
}
|
|
if (a === '--sort=size') {
|
|
sortBy = 'size'
|
|
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]
|
|
if (c === 'a') showAll = true
|
|
else if (c === 'l') longFmt = true
|
|
else if (c === '1') singleColumn = true
|
|
else if (c === 't') sortBy = 'time'
|
|
else if (c === 'S') sortBy = 'size'
|
|
else if (c === 'r') reverseSort = true
|
|
}
|
|
continue
|
|
}
|
|
paths.push(a)
|
|
}
|
|
const targets = paths.length ? paths : ['.']
|
|
const useColor = bareLsUseColor(ctx, colorMode)
|
|
const onePerLine = singleColumn || ctx.bareOsStdoutCaptured === true
|
|
|
|
for (const t of targets) {
|
|
if (targets.length > 1) ctx.console.log(t + ':')
|
|
let names
|
|
/** @type {string | null} */
|
|
let singleEntryPath = null
|
|
try {
|
|
const stT = await vfs.lstat(t)
|
|
if (stT && stT.type !== 'directory') {
|
|
const leaf = t.replace(/\/+$/, '').split('/').filter(Boolean).pop()
|
|
names = [leaf || t]
|
|
singleEntryPath = t
|
|
} else {
|
|
names = await vfs.readdir(t)
|
|
}
|
|
} catch (e) {
|
|
ctx.console.error('ls: cannot access ' + t + ': ' + (e.message || e))
|
|
continue
|
|
}
|
|
if (!showAll) names = names.filter((n) => !n.startsWith('.'))
|
|
names = await bareLsSortNames(
|
|
vfs,
|
|
names,
|
|
t,
|
|
singleEntryPath,
|
|
sortBy,
|
|
reverseSort
|
|
)
|
|
if (!longFmt) {
|
|
if (onePerLine) {
|
|
for (const n of names) {
|
|
if (!useColor) {
|
|
ctx.console.log(n)
|
|
} else {
|
|
const sub =
|
|
singleEntryPath != null
|
|
? singleEntryPath
|
|
: t === '.' || t === './'
|
|
? n
|
|
: t.replace(/\/$/, '') + '/' + n
|
|
let st = null
|
|
try {
|
|
st = await vfs.lstat(sub)
|
|
} catch {
|
|
st = null
|
|
}
|
|
ctx.console.log(bareLsColorWrap(n, st, true, ctx))
|
|
}
|
|
}
|
|
} else 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 = []
|
|
for (const n of names) {
|
|
const sub =
|
|
singleEntryPath != null
|
|
? singleEntryPath
|
|
: t === '.' || t === './'
|
|
? n
|
|
: t.replace(/\/$/, '') + '/' + n
|
|
const st = await vfs.lstat(sub)
|
|
if (!st) {
|
|
rows.push({
|
|
modeStr: '?---------',
|
|
nlink: '?',
|
|
user: '?',
|
|
group: '?',
|
|
size: 0,
|
|
mtimeStr: '?',
|
|
name: n,
|
|
arrow: '',
|
|
st: null
|
|
})
|
|
continue
|
|
}
|
|
const blocks = barePosixBlocks(st.size || 0)
|
|
totalBlocks += blocks
|
|
const modeStr = bareFormatModeString(st.mode, st.type)
|
|
const nlink = st.nlink != null ? String(st.nlink) : '1'
|
|
const user = st.user != null ? st.user : String(st.uid ?? 0)
|
|
const group = st.group != null ? st.group : String(st.gid ?? 0)
|
|
const size = st.size != null ? String(st.size) : '0'
|
|
const mtimeStr = bareFormatLsMtime(
|
|
typeof st.mtimeMs === 'number' ? st.mtimeMs : Date.now()
|
|
)
|
|
let arrow = ''
|
|
if (st.type === 'symlink' && st.linkname != null) {
|
|
arrow = ' -> ' + st.linkname
|
|
}
|
|
rows.push({
|
|
modeStr,
|
|
nlink,
|
|
user,
|
|
group,
|
|
size,
|
|
mtimeStr,
|
|
name: n,
|
|
arrow,
|
|
st
|
|
})
|
|
}
|
|
if ((sortBy || reverseSort) && singleEntryPath == null) {
|
|
rows.sort((a, b) => {
|
|
let cmp = 0
|
|
const sta = a.st
|
|
const stb = b.st
|
|
if (sortBy === 'time') cmp = (stb?.mtimeMs ?? 0) - (sta?.mtimeMs ?? 0)
|
|
else if (sortBy === 'size') cmp = (stb?.size ?? 0) - (sta?.size ?? 0)
|
|
else cmp = a.name.localeCompare(b.name)
|
|
return reverseSort ? -cmp : cmp
|
|
})
|
|
}
|
|
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 +
|
|
' ' +
|
|
r.nlink +
|
|
' ' +
|
|
r.user +
|
|
' ' +
|
|
r.group +
|
|
' ' +
|
|
r.size +
|
|
' ' +
|
|
r.mtimeStr +
|
|
' ' +
|
|
tail
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|