cssgrep 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -1
- package/README.md +6 -4
- package/cli.js +82 -16
- package/lib.js +1 -0
- package/man/cssgrep.1 +4 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.7.0] - 2026-07-12
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `-p` output is syntax-highlighted when color is on: tag names, attribute
|
|
14
|
+
names, attribute values and comments each get a color, and with
|
|
15
|
+
`--parent -p` the matched node's region stays wrapped in unbroken match
|
|
16
|
+
red on top of it. `--color=never` (or piping) keeps the plain output.
|
|
17
|
+
|
|
10
18
|
## [1.6.0] - 2026-07-12
|
|
11
19
|
|
|
12
20
|
### Added
|
|
@@ -144,7 +152,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
144
152
|
- `-0`/`--null`, `--color`, `-w`/`--max-width`, `-V`/`--version`.
|
|
145
153
|
- Standalone binaries (Bun, Node SEA), shell completions, and a man page.
|
|
146
154
|
|
|
147
|
-
[Unreleased]: https://github.com/msbatarce/cssgrep/compare/v1.
|
|
155
|
+
[Unreleased]: https://github.com/msbatarce/cssgrep/compare/v1.7.0...HEAD
|
|
156
|
+
[1.7.0]: https://github.com/msbatarce/cssgrep/compare/v1.6.0...v1.7.0
|
|
148
157
|
[1.6.0]: https://github.com/msbatarce/cssgrep/compare/v1.5.0...v1.6.0
|
|
149
158
|
[1.5.0]: https://github.com/msbatarce/cssgrep/compare/v1.4.0...v1.5.0
|
|
150
159
|
[1.4.0]: https://github.com/msbatarce/cssgrep/compare/v1.3.0...v1.4.0
|
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ own `line:col` locator (that per-match precision is the point of the tool). A
|
|
|
87
87
|
| `--ignore-file <path>` | Load ignore globs from a file, one per line (`#` comments and blank lines ignored) — like a `.gitignore`. |
|
|
88
88
|
| `-S`, `--follow` | Follow symbolic links while recursing with `-r` (they are skipped by default). Each physical directory is visited once, so symlink cycles are safe. |
|
|
89
89
|
| `-n`, `--line-number` | Prefix each match with its `line:col` locator. Mutually exclusive with `-c` and `-p`. |
|
|
90
|
-
| `-p`, `--print` | Pretty-print the matched node's HTML, re-indented from scratch (works on minified input). No `line:col` locator is shown. |
|
|
90
|
+
| `-p`, `--print` | Pretty-print the matched node's HTML, re-indented from scratch (works on minified input) and syntax-highlighted when color is on. No `line:col` locator is shown. |
|
|
91
91
|
| `--attr <name>` | Print the value of attribute `<name>` for each match (nodes without it are skipped; if every match is skipped the exit status is 1). The name is matched case-insensitively. Honors `-n` and `-w`. |
|
|
92
92
|
| `--text` | Print the matched node's text content, whitespace collapsed. Honors `-n` and `-w`. |
|
|
93
93
|
| `--json` | Print one JSON object per match (NDJSON), with `file`, `line`, `col`, `attribs` (the element's attributes, names lowercased), `html`, `text` — plus `label` when `-e` is used. |
|
|
@@ -132,9 +132,11 @@ useful. Suppress the note with `-s` (or `-q`).
|
|
|
132
132
|
|
|
133
133
|
When coloring is on, the matched node is highlighted within its line (grep's
|
|
134
134
|
bold-red); the `file:` prefix and `line:col` locator get their own colors
|
|
135
|
-
(magenta and green, like grep).
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
(magenta and green, like grep). `-p` blocks are syntax-highlighted — tag
|
|
136
|
+
names, attribute names, attribute values and comments each get a color — and
|
|
137
|
+
with `--parent <n>` the original matched node is additionally wrapped in the
|
|
138
|
+
match color inside the printed container (match red wins over syntax within
|
|
139
|
+
its region), so you can see what matched within its surroundings.
|
|
138
140
|
|
|
139
141
|
Exit status: `0` if any match was found, `1` if none, `2` on error — same
|
|
140
142
|
convention as `grep`.
|
package/cli.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const {
|
|
7
|
-
parse, extractHtmlFragments, lineIndex, offsetToPosition,
|
|
8
|
-
textOf, collapseWs, ancestor,
|
|
7
|
+
parse, extractHtmlFragments, lexOpenTag, lineIndex, offsetToPosition,
|
|
8
|
+
lineTextAt, textOf, collapseWs, ancestor,
|
|
9
9
|
} = require('./lib.js');
|
|
10
10
|
|
|
11
11
|
// Files with these extensions are scanned for HTML inside JS/TS template
|
|
@@ -29,7 +29,7 @@ function loadPrettyPrinter() {
|
|
|
29
29
|
// package.json, so it survives compilation into a standalone binary (Bun
|
|
30
30
|
// --compile / Node SEA), where package.json won't sit next to the executable.
|
|
31
31
|
// Keep in sync with package.json on release.
|
|
32
|
-
const VERSION = '1.
|
|
32
|
+
const VERSION = '1.7.0';
|
|
33
33
|
|
|
34
34
|
const USAGE = `cssgrep - search HTML by CSS selector, grep-style.
|
|
35
35
|
|
|
@@ -60,7 +60,8 @@ Options:
|
|
|
60
60
|
-S, --follow Follow symbolic links when recursing with -r
|
|
61
61
|
(default: skip them; loops are detected).
|
|
62
62
|
-n, --line-number Prefix each match with its line:col (excludes -c, -p).
|
|
63
|
-
-p, --print Pretty-print the matched node's HTML
|
|
63
|
+
-p, --print Pretty-print the matched node's HTML, syntax-
|
|
64
|
+
highlighted when color is on.
|
|
64
65
|
--attr <name> Print the value of attribute <name> (skips nodes without it).
|
|
65
66
|
--text Print the matched node's text content (whitespace collapsed).
|
|
66
67
|
--json Print one JSON record per match (NDJSON: file,line,col,
|
|
@@ -137,12 +138,19 @@ function failInvert(flag) {
|
|
|
137
138
|
// ANSI SGR codes matching grep's default scheme: bold-red match, magenta
|
|
138
139
|
// filename, green line/col numbers, cyan separators. The [label] tag from -e
|
|
139
140
|
// has no grep counterpart; yellow keeps it distinct from all of the above.
|
|
141
|
+
// The last four paint -p's syntax highlighting (tag names, attribute names,
|
|
142
|
+
// attribute values, comments/doctypes) — distinct from match red, which
|
|
143
|
+
// always wins inside a --parent -p match region.
|
|
140
144
|
const COLORS = {
|
|
141
145
|
match: '1;31',
|
|
142
146
|
file: '35',
|
|
143
147
|
line: '32',
|
|
144
148
|
sep: '36',
|
|
145
149
|
label: '33',
|
|
150
|
+
tag: '1;34',
|
|
151
|
+
attr: '36',
|
|
152
|
+
value: '32',
|
|
153
|
+
comment: '90',
|
|
146
154
|
};
|
|
147
155
|
|
|
148
156
|
function paint(code, str) {
|
|
@@ -386,9 +394,8 @@ function parseArgs(argv) {
|
|
|
386
394
|
fail(`invalid --color value: ${opts.color} (expected auto, always or never)`);
|
|
387
395
|
}
|
|
388
396
|
// Resolve the tri-state into a single boolean: color only when forced on, or
|
|
389
|
-
// 'auto' and stdout is an interactive terminal.
|
|
390
|
-
//
|
|
391
|
-
// here, so --parent -p can highlight the matched node inside the container.
|
|
397
|
+
// 'auto' and stdout is an interactive terminal. -p uses it for syntax
|
|
398
|
+
// highlighting (and --parent -p for the match region on top of it).
|
|
392
399
|
opts.colorOn = opts.color === 'always' || (opts.color === 'auto' && Boolean(process.stdout.isTTY));
|
|
393
400
|
return opts;
|
|
394
401
|
}
|
|
@@ -467,14 +474,63 @@ function renderText(pos, off, nodeEnd, opts) {
|
|
|
467
474
|
const HL_START = '';
|
|
468
475
|
const HL_END = '';
|
|
469
476
|
|
|
477
|
+
// Syntax-highlight a pretty-printed HTML block: tag names, attribute names,
|
|
478
|
+
// attribute values (quotes included) and comments/doctypes get their own
|
|
479
|
+
// colors; text content and punctuation stay unpainted. Spans come from the
|
|
480
|
+
// same opening-tag lexer the rewrite mode uses, so a quoted '>' never
|
|
481
|
+
// confuses the scan.
|
|
482
|
+
function highlightHtml(s) {
|
|
483
|
+
let out = '';
|
|
484
|
+
let i = 0;
|
|
485
|
+
while (i < s.length) {
|
|
486
|
+
const ch = s[i];
|
|
487
|
+
if (ch === '<' && (s.startsWith('<!--', i) || s[i + 1] === '!')) {
|
|
488
|
+
const close = s.startsWith('<!--', i) ? '-->' : '>';
|
|
489
|
+
let end = s.indexOf(close, i + 2);
|
|
490
|
+
end = end === -1 ? s.length : end + close.length;
|
|
491
|
+
out += paint(COLORS.comment, s.slice(i, end));
|
|
492
|
+
i = end;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
if (ch === '<' && s[i + 1] === '/') {
|
|
496
|
+
const m = /^<\/([^\s>]*)([^>]*>?)/.exec(s.slice(i));
|
|
497
|
+
out += '</' + paint(COLORS.tag, m[1]) + m[2];
|
|
498
|
+
i += m[0].length;
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
if (ch === '<' && /[a-zA-Z]/.test(s[i + 1] || '')) {
|
|
502
|
+
const lx = lexOpenTag(s, i);
|
|
503
|
+
out += '<' + paint(COLORS.tag, s.slice(lx.nameStart, lx.nameEnd));
|
|
504
|
+
let j = lx.nameEnd;
|
|
505
|
+
for (const a of lx.attrs) {
|
|
506
|
+
out += s.slice(j, a.start) + paint(COLORS.attr, s.slice(a.start, a.nameEnd));
|
|
507
|
+
if (a.vStart >= 0) {
|
|
508
|
+
const quote = s[a.vStart - 1];
|
|
509
|
+
const valFrom = quote === '"' || quote === "'" ? a.vStart - 1 : a.vStart;
|
|
510
|
+
out += s.slice(a.nameEnd, valFrom) + paint(COLORS.value, s.slice(valFrom, a.end));
|
|
511
|
+
}
|
|
512
|
+
j = a.end;
|
|
513
|
+
}
|
|
514
|
+
out += s.slice(j, lx.end);
|
|
515
|
+
i = lx.end;
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
out += ch;
|
|
519
|
+
i++;
|
|
520
|
+
}
|
|
521
|
+
return out;
|
|
522
|
+
}
|
|
523
|
+
|
|
470
524
|
// Re-indent a matched node's HTML from scratch (so minified input still comes
|
|
471
525
|
// out readable). dom-serializer turns the parsed node back into a string;
|
|
472
|
-
// js-beautify does the formatting.
|
|
473
|
-
//
|
|
474
|
-
//
|
|
526
|
+
// js-beautify does the formatting. With color on the block is syntax-
|
|
527
|
+
// highlighted; when `origins` (descendant nodes to highlight) is also given,
|
|
528
|
+
// those nodes are wrapped in the match color instead — match red wins over
|
|
529
|
+
// syntax inside its region.
|
|
475
530
|
function prettyPrint(el, origins, opts) {
|
|
476
531
|
loadPrettyPrinter();
|
|
477
|
-
const
|
|
532
|
+
const colorOn = Boolean(opts && opts.colorOn);
|
|
533
|
+
const highlight = origins && origins.length && colorOn;
|
|
478
534
|
const inserted = [];
|
|
479
535
|
if (highlight) {
|
|
480
536
|
// Bracket each origin with sentinel comment nodes among its siblings.
|
|
@@ -504,13 +560,23 @@ function prettyPrint(el, origins, opts) {
|
|
|
504
560
|
k = kids.indexOf(end); if (k >= 0) kids.splice(k, 1);
|
|
505
561
|
}
|
|
506
562
|
}
|
|
507
|
-
if (!
|
|
563
|
+
if (!colorOn) return html;
|
|
564
|
+
if (!highlight) return highlightHtml(html);
|
|
565
|
+
// Split on the sentinel comments: regions outside the match get syntax
|
|
566
|
+
// highlighting, the match region itself gets the match color, unbroken —
|
|
567
|
+
// syntax codes inside would end the red at every token.
|
|
508
568
|
const startCode = `\x1b[${COLORS.match}m`;
|
|
509
569
|
const resetCode = '\x1b[0m';
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
570
|
+
const isStart = new RegExp(`^<!--\\s*${HL_START}\\s*-->$`);
|
|
571
|
+
const isEnd = new RegExp(`^<!--\\s*${HL_END}\\s*-->$`);
|
|
572
|
+
let out = '';
|
|
573
|
+
let inside = false;
|
|
574
|
+
for (const part of html.split(new RegExp(`(<!--\\s*[${HL_START}${HL_END}]\\s*-->)`, 'g'))) {
|
|
575
|
+
if (isStart.test(part)) { out += startCode; inside = true; continue; }
|
|
576
|
+
if (isEnd.test(part)) { out += resetCode; inside = false; continue; }
|
|
577
|
+
out += inside ? part : highlightHtml(part);
|
|
578
|
+
}
|
|
579
|
+
return foldStandaloneCodes(out, startCode, resetCode);
|
|
514
580
|
}
|
|
515
581
|
|
|
516
582
|
// beautify sometimes parks a marker comment on its own line (e.g. when the
|
package/lib.js
CHANGED
package/man/cssgrep.1
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
.\" Man page for cssgrep. Keep the OPTIONS section in sync with the USAGE
|
|
2
2
|
.\" string in cli.js.
|
|
3
|
-
.TH CSSGREP 1 "2026-07-12" "cssgrep 1.
|
|
3
|
+
.TH CSSGREP 1 "2026-07-12" "cssgrep 1.7.0" "User Commands"
|
|
4
4
|
.SH NAME
|
|
5
5
|
cssgrep \- search HTML by CSS selector, grep-style
|
|
6
6
|
.SH SYNOPSIS
|
|
@@ -144,7 +144,9 @@ and
|
|
|
144
144
|
.BR \-p .
|
|
145
145
|
.TP
|
|
146
146
|
.BR \-p ", " \-\-print
|
|
147
|
-
Pretty-print the matched node's HTML, re-indented from scratch
|
|
147
|
+
Pretty-print the matched node's HTML, re-indented from scratch and
|
|
148
|
+
syntax-highlighted when color is on (tag names, attribute names and values,
|
|
149
|
+
comments). No
|
|
148
150
|
.IR line : col
|
|
149
151
|
locator is shown.
|
|
150
152
|
.TP
|