This commit is contained in:
Raven Scott
2026-04-03 23:42:47 -04:00
parent d8e580d1af
commit 2ef56ac314
43 changed files with 1941 additions and 553 deletions
+82 -6
View File
@@ -26,6 +26,12 @@ async function run(ctx, argv) {
/** @type {'never' | 'always' | 'auto'} */
let colorMode = 'never'
let recursive = false
/** @type {string[]} */
const includeGlobs = []
/** @type {string[]} */
const excludeGlobs = []
/** @type {string[]} */
const excludeDirGlobs = []
const args = argv.slice(1)
let i = 0
@@ -93,6 +99,36 @@ async function run(ctx, argv) {
i++
continue
}
if (a === '--include' && args[i + 1]) {
includeGlobs.push(args[++i])
i++
continue
}
if (a.startsWith('--include=')) {
includeGlobs.push(a.slice('--include='.length))
i++
continue
}
if (a === '--exclude' && args[i + 1]) {
excludeGlobs.push(args[++i])
i++
continue
}
if (a.startsWith('--exclude=')) {
excludeGlobs.push(a.slice('--exclude='.length))
i++
continue
}
if (a === '--exclude-dir' && args[i + 1]) {
excludeDirGlobs.push(args[++i])
i++
continue
}
if (a.startsWith('--exclude-dir=')) {
excludeDirGlobs.push(a.slice('--exclude-dir='.length))
i++
continue
}
ctx.console.error('grep: unknown option ' + a)
ctx.exitCode = 2
return
@@ -304,7 +340,15 @@ async function run(ctx, argv) {
continue
}
if (st.type === 'directory') {
await grepWalkFiles(ctx, p, acc, suppressErrors)
await grepWalkFiles(ctx, p, acc, suppressErrors, {
includeGlobs,
excludeGlobs,
excludeDirGlobs,
filterCap: Number.parseInt(
String(ctx.vfs?.env?.BARE_OS_GREP_FILTER_MAX || '32'),
10
) || 32
})
} else {
acc.push(p)
}
@@ -500,8 +544,31 @@ const GREP_RECURSE_MAX_DEPTH = 64
* @param {string[]} acc
* @param {boolean} suppressErrors
*/
async function grepWalkFiles(ctx, dir, acc, suppressErrors) {
/**
* @param {string} name
* @param {string} pat
*/
function grepSimpleGlobMatch(name, pat) {
if (!pat || pat === '*') return true
if (pat.includes('/')) return name === pat
if (pat.startsWith('*') && pat.length > 1 && pat.endsWith('*')) {
const mid = pat.slice(1, -1)
return mid !== '' && name.includes(mid)
}
if (pat.startsWith('*')) return name.endsWith(pat.slice(1))
if (pat.endsWith('*')) return name.startsWith(pat.slice(0, -1))
return name === pat
}
/**
* @param {{ includeGlobs?: string[], excludeGlobs?: string[], excludeDirGlobs?: string[], filterCap?: number }} [opts]
*/
async function grepWalkFiles(ctx, dir, acc, suppressErrors, opts = {}) {
const vfs = ctx.vfs
const cap = opts.filterCap && opts.filterCap > 0 ? opts.filterCap : 32
const inc = (opts.includeGlobs || []).slice(0, cap)
const exc = (opts.excludeGlobs || []).slice(0, cap)
const excd = (opts.excludeDirGlobs || []).slice(0, cap)
/** @param {string} d @param {number} depth */
const walk = async (d, depth) => {
if (depth > GREP_RECURSE_MAX_DEPTH) return
@@ -523,12 +590,21 @@ async function grepWalkFiles(ctx, dir, acc, suppressErrors) {
continue
}
if (!st) continue
if (st.type === 'directory') await walk(sub, depth + 1)
else if (st.type === 'file') acc.push(sub)
else if (st.type === 'symlink') {
if (st.type === 'directory') {
if (excd.some((p) => grepSimpleGlobMatch(n, p))) continue
await walk(sub, depth + 1)
} else if (st.type === 'file') {
if (inc.length && !inc.some((p) => grepSimpleGlobMatch(n, p))) continue
if (exc.some((p) => grepSimpleGlobMatch(n, p))) continue
acc.push(sub)
} else if (st.type === 'symlink') {
try {
const ft = await vfs.stat(sub)
if (ft && ft.type === 'file') acc.push(sub)
if (ft && ft.type === 'file') {
if (inc.length && !inc.some((p) => grepSimpleGlobMatch(n, p))) continue
if (exc.some((p) => grepSimpleGlobMatch(n, p))) continue
acc.push(sub)
}
} catch {
/* skip */
}
+25 -6
View File
@@ -1,6 +1,7 @@
async function run(ctx, argv) {
let silent = false
let extended = false
let nullData = false
/** @type {string[]} */
const scripts = []
/** @type {string[]} */
@@ -11,6 +12,10 @@ async function run(ctx, argv) {
silent = true
continue
}
if (a === '-z' || a === '--null-data') {
nullData = true
continue
}
if (a === '-E' || a === '-r') {
extended = true
continue
@@ -89,6 +94,12 @@ async function run(ctx, argv) {
readCache[rp] = buf ? ctx.b4a.toString(buf) : ''
}
const maxNull =
Number.parseInt(
String(ctx.vfs?.env?.BARE_OS_SED_NULL_MAX_RECORDS || '100000'),
10
) || 100000
/** @type {string[]} */
const lines = []
async function pushFile(path) {
@@ -99,16 +110,22 @@ async function run(ctx, argv) {
return false
}
const t = ctx.b4a.toString(buf)
const ls = t.split(/\r?\n/)
if (ls.length && ls[ls.length - 1] === '') ls.pop()
lines.push(...ls)
if (nullData) {
const rec = t.split('\0')
const room = maxNull - lines.length
lines.push(...rec.slice(0, Math.max(0, room)))
} else {
const ls = t.split(/\r?\n/)
if (ls.length && ls[ls.length - 1] === '') ls.pop()
lines.push(...ls)
}
return true
}
if (!files.length) {
const s = bareStdin(ctx)
const ls = s.split(/\r?\n/)
if (ls.length && ls[ls.length - 1] === '') ls.pop()
const ls = nullData ? s.split('\0').slice(0, maxNull) : s.split(/\r?\n/)
if (!nullData && ls.length && ls[ls.length - 1] === '') ls.pop()
lines.push(...ls)
} else {
for (const f of files) {
@@ -121,6 +138,7 @@ async function run(ctx, argv) {
const out = bareSedRun(lines, scripts, {
silent,
extended,
nullData,
readFile: (p) => readCache[p] ?? null,
writeFile: (p, chunk) => {
wAccum[p] = (wAccum[p] || '') + chunk
@@ -141,6 +159,7 @@ async function run(ctx, argv) {
}
}
const t = out.replace(/\n$/, '')
const trail = nullData ? /\0$/ : /\n$/
const t = out.replace(trail, '')
ctx.console.log(t)
}