updates
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
"section": 1,
|
||||
"title": "pattern matching utility",
|
||||
"synopsis": [
|
||||
"grep [-E|-F] [-i] [-v] [-n] [-c] [-l] [-q] [-s] [-H|-h] [-e pat] ... [-f file] ... [pattern] [file...]"
|
||||
"grep [-E|-F] [-i] [-v] [-w] [-n] [-c] [-l] [-q] [-s] [-H|-h] [-e pat] ... [-f file] ... [pattern] [file...]"
|
||||
],
|
||||
"description": "Searches input or files for lines matching a pattern. Uses JavaScript RegExp unless -F (fixed string). Not bit-identical to GNU grep.",
|
||||
"options": [
|
||||
@@ -23,6 +23,10 @@
|
||||
"flag": "-v",
|
||||
"meaning": "Invert match"
|
||||
},
|
||||
{
|
||||
"flag": "-w",
|
||||
"meaning": "Match whole words (regex: \\b…\\b; fixed: non-alphanumeric boundaries)"
|
||||
},
|
||||
{
|
||||
"flag": "-n",
|
||||
"meaning": "Prefix lines with line number"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"section": 1,
|
||||
"title": "display on-line manual pages",
|
||||
"synopsis": [
|
||||
"man [-k keyword] [-f name] [-l] [[section] name]",
|
||||
"man [-k keyword] [-f name] [-l] [-w] [[section] name]",
|
||||
"man reads /share/man/man.json on the system drive."
|
||||
],
|
||||
"description": "Displays manual pages from the merged JSON database. Section 1: /bin and git/shell pages. Section 7: handbook (man handbook) and developer guide (man devguide), merged at build from handbook/*.md and developer-guide/*.md.",
|
||||
@@ -19,6 +19,10 @@
|
||||
{
|
||||
"flag": "-l, --list",
|
||||
"meaning": "List pages grouped by category (/bin, git/shell, handbook, developer guide), then alphabetically"
|
||||
},
|
||||
{
|
||||
"flag": "-w, --where, --path",
|
||||
"meaning": "Print logical path to the manual database (/share/man/man.json)"
|
||||
}
|
||||
],
|
||||
"keywords": [
|
||||
@@ -33,7 +37,8 @@
|
||||
],
|
||||
"environment": [
|
||||
"MANWIDTH — wrap width (default 72, min 40)",
|
||||
"NO_COLOR — disable bold headings on TTY"
|
||||
"NO_COLOR — disable bold headings on TTY",
|
||||
"PAGER=bare-slice — insert section breaks in long pages (optional MAN_SLICE lines per chunk, default 24)"
|
||||
],
|
||||
"seeAlso": [
|
||||
{
|
||||
|
||||
@@ -16,13 +16,14 @@ async function run(ctx, argv) {
|
||||
let suppressErrors = false
|
||||
let forceFilename = false
|
||||
let noFilename = false
|
||||
let word = false
|
||||
|
||||
const args = argv.slice(1)
|
||||
let i = 0
|
||||
|
||||
function usage() {
|
||||
ctx.console.error(
|
||||
'usage: grep [-E|-F] [-i] [-v] [-n] [-c] [-l] [-q] [-s] [-H|-h] [-e pat] ... [-f file] ... [pattern] [file...]'
|
||||
'usage: grep [-E|-F] [-i] [-v] [-w] [-n] [-c] [-l] [-q] [-s] [-H|-h] [-e pat] ... [-f file] ... [pattern] [file...]'
|
||||
)
|
||||
ctx.exitCode = 2
|
||||
}
|
||||
@@ -107,6 +108,9 @@ async function run(ctx, argv) {
|
||||
case 'h':
|
||||
noFilename = true
|
||||
break
|
||||
case 'w':
|
||||
word = true
|
||||
break
|
||||
default:
|
||||
ctx.console.error('grep: invalid option -- ' + c)
|
||||
ctx.exitCode = 2
|
||||
@@ -155,7 +159,7 @@ async function run(ctx, argv) {
|
||||
|
||||
let matchers
|
||||
try {
|
||||
matchers = buildMatchers(patterns, { fixed, icase })
|
||||
matchers = buildMatchers(patterns, { fixed, icase, word })
|
||||
} catch (e) {
|
||||
ctx.console.error('grep: ' + (e.message || e))
|
||||
ctx.exitCode = 2
|
||||
@@ -242,7 +246,7 @@ async function run(ctx, argv) {
|
||||
|
||||
/**
|
||||
* @param {string[]} patterns
|
||||
* @param {{ fixed: boolean, icase: boolean }} o
|
||||
* @param {{ fixed: boolean, icase: boolean, word?: boolean }} o
|
||||
*/
|
||||
function buildMatchers(patterns, o) {
|
||||
if (patterns.length === 0) throw new Error('no pattern')
|
||||
@@ -250,15 +254,22 @@ function buildMatchers(patterns, o) {
|
||||
const pats = o.icase
|
||||
? patterns.map((p) => p.toLowerCase())
|
||||
: patterns.slice()
|
||||
return pats.map(
|
||||
(p) => (line) =>
|
||||
return pats.map((p) => {
|
||||
if (o.word) {
|
||||
const esc = p.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&')
|
||||
const flags = o.icase ? 'i' : ''
|
||||
const re = new RegExp('(?:^|[^0-9A-Za-z_])' + esc + '(?:$|[^0-9A-Za-z_])', flags)
|
||||
return (line) => re.test(line)
|
||||
}
|
||||
return (line) =>
|
||||
o.icase ? line.toLowerCase().includes(p) : line.includes(p)
|
||||
)
|
||||
})
|
||||
}
|
||||
const flags = o.icase ? 'i' : ''
|
||||
return patterns.map((p) => {
|
||||
try {
|
||||
const re = new RegExp(p, flags)
|
||||
const body = o.word ? '\\b(?:' + p + ')\\b' : p
|
||||
const re = new RegExp(body, flags)
|
||||
return (line) => re.test(line)
|
||||
} catch (e) {
|
||||
throw new Error('invalid regex: ' + (e.message || e))
|
||||
|
||||
@@ -5,9 +5,10 @@ async function run(ctx, argv) {
|
||||
|
||||
function usage() {
|
||||
ctx.console.error(
|
||||
'usage: man [-k keyword] [-f name] [-l] [[section] name]\n' +
|
||||
'usage: man [-k keyword] [-f name] [-l] [-w] [[section] name]\n' +
|
||||
' Section 1: /bin; section 7: handbook + developer guide (man handbook, man devguide).\n' +
|
||||
' Data: /share/man/man.json on the system drive.'
|
||||
' Data: /share/man/man.json on the system drive.\n' +
|
||||
' Long pages: set PAGER=bare-slice and optional MAN_SLICE=N (default 24) for section breaks.'
|
||||
)
|
||||
ctx.exitCode = 2
|
||||
}
|
||||
@@ -49,6 +50,10 @@ async function run(ctx, argv) {
|
||||
mode = 'list'
|
||||
continue
|
||||
}
|
||||
if (a === '-w' || a === '--where' || a === '--path') {
|
||||
ctx.console.log('/share/man/man.json')
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-')) {
|
||||
ctx.console.error('man: unknown option: ' + a)
|
||||
ctx.exitCode = 2
|
||||
@@ -205,5 +210,35 @@ async function run(ctx, argv) {
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
ctx.console.log(bareManRenderPage(page, ctx, width).replace(/\n$/, ''))
|
||||
const rendered = bareManRenderPage(page, ctx, width).replace(/\n$/, '')
|
||||
if (bareManSlicePage(rendered, env, ctx.console)) return
|
||||
ctx.console.log(rendered)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {Record<string, string>} env
|
||||
* @param {{ log: (s: string) => void }} cons
|
||||
*/
|
||||
function bareManSlicePage(text, env, cons) {
|
||||
const pager = env.PAGER || ''
|
||||
if (pager !== 'bare-slice' && pager !== 'bare_slice') return false
|
||||
const raw = env.MAN_SLICE || '24'
|
||||
const n = Number.parseInt(String(raw), 10)
|
||||
const sliceLines = Number.isFinite(n) && n > 0 ? n : 24
|
||||
const lines = text.split('\n')
|
||||
const total = lines.length
|
||||
for (let i = 0; i < total; i += sliceLines) {
|
||||
const chunk = lines.slice(i, i + sliceLines).join('\n')
|
||||
cons.log(chunk)
|
||||
if (i + sliceLines < total) {
|
||||
const hi = Math.min(i + sliceLines, total)
|
||||
cons.log('')
|
||||
cons.log(
|
||||
`--- man: lines ${i + 1}-${hi} of ${total} (PAGER=bare-slice) ---`
|
||||
)
|
||||
cons.log('')
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user