cssgrep 1.1.0 → 1.3.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/man/cssgrep.1 CHANGED
@@ -1,6 +1,6 @@
1
1
  .\" Man page for cssgrep. Keep the OPTIONS section in sync with the USAGE
2
- .\" string in index.js.
3
- .TH CSSGREP 1 "2026-07-02" "cssgrep 1.1.0" "User Commands"
2
+ .\" string in cli.js.
3
+ .TH CSSGREP 1 "2026-07-07" "cssgrep 1.3.0" "User Commands"
4
4
  .SH NAME
5
5
  cssgrep \- search HTML by CSS selector, grep-style
6
6
  .SH SYNOPSIS
@@ -13,6 +13,13 @@ cssgrep \- search HTML by CSS selector, grep-style
13
13
  .B \-r
14
14
  .IR dir ...
15
15
  .br
16
+ .B cssgrep
17
+ .B \-e
18
+ .RI \(aq[ label =] sel \(aq
19
+ .RB [ \-e
20
+ \&...]
21
+ .RI [ file ...]
22
+ .br
16
23
  .B cat
17
24
  .I file.html
18
25
  | \fBcssgrep\fR \fIselector\fR
@@ -33,9 +40,9 @@ Input is read from the
33
40
  .I file
34
41
  arguments, recursively from directories with
35
42
  .BR \-r ,
36
- or from standard input when no path is given. Binary files (a NUL byte or a
37
- high ratio of control bytes in the first 8 KB) are skipped with a note on
38
- standard error, suppressible with
43
+ or from standard input when no path is given. Binary input, including standard
44
+ input (a NUL byte or a high ratio of control bytes in the first 8 KB), is
45
+ skipped with a note on standard error, suppressible with
39
46
  .B \-s
40
47
  or
41
48
  .BR \-q .
@@ -50,6 +57,31 @@ locator appears only with
50
57
  .BR \-n .
51
58
  .SH OPTIONS
52
59
  .TP
60
+ .BR \-e ", " \-\-selector " [\fIlabel\fR=]\fIsel\fR"
61
+ Add a selector (repeatable). Matches from all
62
+ .B \-e
63
+ selectors merge into one document-order stream, each tagged
64
+ .RI [ label ]
65
+ in the output (and as a
66
+ .I label
67
+ field with
68
+ .BR \-\-json ).
69
+ An unlabeled selector is tagged with its own text. A label is an identifier
70
+ before a
71
+ .BR = ;
72
+ a
73
+ .B =
74
+ outside brackets is never valid CSS, so attribute selectors like
75
+ .I [href=x]
76
+ are never mistaken for labels.
77
+ .B \-m
78
+ and
79
+ .B \-M
80
+ cap the merged stream, and print modes apply to every selector alike. With
81
+ .BR \-e ,
82
+ every positional argument is a file path, as with
83
+ .BR "grep \-e" .
84
+ .TP
53
85
  .BR \-r ", " \-\-recursive
54
86
  Recurse into directory arguments (defaults to the current directory if none
55
87
  are given).
@@ -91,10 +123,21 @@ one per line
91
123
  comments and blank lines ignored), like a
92
124
  .IR .gitignore .
93
125
  .TP
126
+ .BR \-S ", " \-\-follow
127
+ Follow symbolic links while recursing with
128
+ .B \-r
129
+ (they are skipped by default). Each physical directory is visited once, so
130
+ symlink cycles are safe.
131
+ .TP
94
132
  .BR \-n ", " \-\-line\-number
95
133
  Prefix each match with its
96
134
  .IR line : col
97
- locator. Excludes
135
+ locator. The column counts bytes (what vim's
136
+ .B grepformat
137
+ .I %c
138
+ expects), pointing at the opening
139
+ .B <
140
+ of the matched tag. Excludes
98
141
  .B \-c
99
142
  and
100
143
  .BR \-p .
@@ -107,14 +150,19 @@ locator is shown.
107
150
  .BI \-\-attr " name"
108
151
  Print the value of attribute
109
152
  .I name
110
- for each match (nodes without it are skipped).
153
+ for each match (nodes without it are skipped; if every match is skipped the
154
+ exit status is 1). The name is matched case-insensitively.
111
155
  .TP
112
156
  .B \-\-text
113
157
  Print the matched node's text content, whitespace collapsed.
114
158
  .TP
115
159
  .B \-\-json
116
160
  Print one JSON object per match (NDJSON) with
117
- .IR file ", " line ", " col ", " html ", " text .
161
+ .IR file ", " line ", " col ", " html ", " text
162
+ (plus
163
+ .I label
164
+ with
165
+ .BR \-e ).
118
166
  .TP
119
167
  .BI \-\-parent " n"
120
168
  Report the
@@ -149,10 +197,17 @@ matches per file.
149
197
  .BR \-M ", " \-\-max\-total " \fIn\fR"
150
198
  Stop after
151
199
  .I n
152
- matches in total across all files.
200
+ matches in total across all files. The budget counts matches, not files;
201
+ combined with
202
+ .B \-l
203
+ or
204
+ .B \-L
205
+ scanning stops once the budget is spent, which can cut the file list short.
153
206
  .TP
154
207
  .BR \-c ", " \-\-count
155
- Print only a count of matches (per file when relevant).
208
+ Print only a count of matches (per file when relevant). Files with no match
209
+ report a zero count, as in
210
+ .BR grep .
156
211
  .TP
157
212
  .BR \-l ", " \-\-files\-with\-matches
158
213
  Print only the names of files that have a match.
@@ -184,7 +239,12 @@ Colorize output:
184
239
  A bare
185
240
  .B \-\-color
186
241
  means
187
- .IR always .
242
+ .IR auto ,
243
+ as in
244
+ .BR grep ;
245
+ use
246
+ .B \-\-color=always
247
+ to force color into a pipe.
188
248
  .TP
189
249
  .BR \-h ", " \-\-help
190
250
  Show help and exit.
@@ -208,10 +268,94 @@ Globs for
208
268
  support
209
269
  .BR * " (within a path segment), " ** " (across " / ),
210
270
  .BR ? " (one non-slash char), and brace alternation like " *.{html,htm} .
271
+ .SH WATCH MODE
272
+ .TP
273
+ .B \-\-watch
274
+ Keep running and repeat the search whenever a watched file changes. Requires
275
+ file or directory arguments (standard input cannot be watched); cannot be
276
+ combined with
277
+ .B \-q
278
+ or the rewrite operations. Runs until interrupted; Ctrl-C exits with
279
+ status 0, like
280
+ .BR watch (1).
281
+ .TP
282
+ .B \-\-no\-clear
283
+ With
284
+ .B \-\-watch
285
+ on a terminal: append run results instead of clearing the screen.
286
+ .PP
287
+ Output adapts to its destination, like
288
+ .BR \-\-color=auto :
289
+ on a terminal each run clears the screen and reprints; piped output contains
290
+ no escape codes and appends each run after a
291
+ .RI \(dq== " HH:MM:SS changed-file " ==\(dq
292
+ separator; with
293
+ .B \-\-json
294
+ each rerun emits an NDJSON
295
+ .I {"event":"run","changed":...,"matches":n}
296
+ record followed by the match records.
297
+ .PP
298
+ Every rerun repeats the full directory walk, so new files are discovered (and
299
+ deleted ones dropped) under the same
300
+ .BR \-\-include / \-\-ignore / \-\-ext
301
+ rules as a fresh invocation; change bursts are debounced. Watching uses the
302
+ operating system's native file events (no polling), which may be unreliable
303
+ on network or virtual filesystems.
304
+ .SH REWRITING
305
+ Five repeatable, freely combinable operations edit the matched elements
306
+ instead of reporting them:
307
+ .TP
308
+ .BI \-\-add\-class " c"
309
+ Add a class to each matched element (deduplicated; attribute created if
310
+ missing).
311
+ .TP
312
+ .BI \-\-remove\-class " c"
313
+ Remove a class; the attribute is dropped when emptied.
314
+ .TP
315
+ .BI \-\-set\-attr " k=v"
316
+ Set attribute
317
+ .I k
318
+ to
319
+ .IR v ,
320
+ adding it if missing. The value is escaped.
321
+ .TP
322
+ .BI \-\-remove\-attr " k"
323
+ Remove attribute
324
+ .I k
325
+ (all source occurrences).
326
+ .TP
327
+ .BI \-\-rename\-tag " t"
328
+ Rename the element \(em its closing tag too, when one explicitly exists
329
+ (voids like
330
+ .IR <img> ,
331
+ self-closing and implied closes are left without one).
332
+ .TP
333
+ .B \-\-diff
334
+ Emit a unified diff instead of the rewritten document. Required for multiple
335
+ files; apply with
336
+ .BR git-apply (1)
337
+ or
338
+ .BR patch (1).
339
+ .PP
340
+ Only the matched tags' bytes change: matching runs once against the original
341
+ document and edits are byte-splices, so quoting, entities and formatting
342
+ everywhere else pass through untouched. Operations compose in a fixed order
343
+ \(em rename \(-> remove-attr \(-> set-attr \(-> remove-class \(-> add-class
344
+ \(em regardless of argument order.
345
+ .B \-\-parent
346
+ composes with rewriting. A single input prints the rewritten document to
347
+ standard output;
348
+ .B cssgrep
349
+ never writes a file. Input that is not valid UTF-8 is refused (exit 2).
350
+ Rewriting cannot be combined with
351
+ .BR \-n ", " \-p ", " \-\-attr ", " \-\-text ", " \-\-json ", " \-c ", "
352
+ .BR \-l ", " \-L ", " \-q ", " \-A / \-B / \-C ", " \-m / \-M ", " \-w ", "
353
+ .BR \-0 " or " \-e .
354
+ Exit status: 0 if anything was edited, 1 if nothing matched, 2 on error.
211
355
  .SH EXIT STATUS
212
356
  .TP
213
357
  .B 0
214
- One or more matches were found.
358
+ One or more matches were found (or, when rewriting, edits were made).
215
359
  .TP
216
360
  .B 1
217
361
  No matches were found.
@@ -231,6 +375,31 @@ Recurse a tree, skipping vendored files:
231
375
  .TP
232
376
  Extract attribute values for scripting:
233
377
  .B cssgrep \-\-attr href "a.external" page.html
378
+ .TP
379
+ Scrape several labeled fields in one pass:
380
+ .B cssgrep \-e "title=h1" \-e "price=.card .price" \-\-json page.html
381
+ .TP
382
+ Rename every <b> to <strong> across a tree, review, then apply:
383
+ .B cssgrep b \-\-rename\-tag strong \-r src/ \-\-diff | git apply
384
+ .SH INVERTING MATCHES
385
+ There is no
386
+ .B \-v
387
+ flag: CSS expresses inversion in the selector itself via
388
+ .BR :not() " (complex selectors and selector lists included) and " :has() ,
389
+ scoped to the elements of interest \(em a flag would have to invent its own
390
+ answer to \(lqwhich non-matching elements?\(rq.
391
+ .TP
392
+ Images missing alt text:
393
+ .B cssgrep \(dqimg:not([alt])\(dq \-rn .
394
+ .TP
395
+ Cards containing no link:
396
+ .B cssgrep \(dqdiv.card:not(:has(a))\(dq page.html
397
+ .TP
398
+ Links outside nav and footer:
399
+ .B cssgrep \(dqa:not(nav a, footer a)\(dq page.html
400
+ .PP
401
+ For whole files without any match, use
402
+ .BR \-L .
234
403
  .SH VIM INTEGRATION
235
404
  Wire
236
405
  .B cssgrep
package/package.json CHANGED
@@ -1,14 +1,17 @@
1
1
  {
2
2
  "name": "cssgrep",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Search HTML by CSS selector and print matches grep-style (file:line:col with -n).",
5
- "main": "index.js",
5
+ "main": "lib.js",
6
+ "types": "index.d.ts",
6
7
  "bin": {
7
- "cssgrep": "index.js"
8
+ "cssgrep": "cli.js"
8
9
  },
9
10
  "man": "./man/cssgrep.1",
10
11
  "files": [
11
- "index.js",
12
+ "lib.js",
13
+ "cli.js",
14
+ "index.d.ts",
12
15
  "README.md",
13
16
  "CHANGELOG.md",
14
17
  "LICENSE",
@@ -28,11 +31,11 @@
28
31
  "homepage": "https://github.com/msbatarce/cssgrep#readme",
29
32
  "scripts": {
30
33
  "test": "node test.js",
31
- "build:linux-x64": "bun build ./index.js --compile --target=bun-linux-x64 --outfile dist/cssgrep-linux-x64",
32
- "build:linux-arm64": "bun build ./index.js --compile --target=bun-linux-arm64 --outfile dist/cssgrep-linux-arm64",
33
- "build:darwin-x64": "bun build ./index.js --compile --target=bun-darwin-x64 --outfile dist/cssgrep-darwin-x64",
34
- "build:darwin-arm64": "bun build ./index.js --compile --target=bun-darwin-arm64 --outfile dist/cssgrep-darwin-arm64",
35
- "build:windows-x64": "bun build ./index.js --compile --target=bun-windows-x64 --outfile dist/cssgrep-windows-x64.exe",
34
+ "build:linux-x64": "bun build ./cli.js --compile --target=bun-linux-x64 --outfile dist/cssgrep-linux-x64",
35
+ "build:linux-arm64": "bun build ./cli.js --compile --target=bun-linux-arm64 --outfile dist/cssgrep-linux-arm64",
36
+ "build:darwin-x64": "bun build ./cli.js --compile --target=bun-darwin-x64 --outfile dist/cssgrep-darwin-x64",
37
+ "build:darwin-arm64": "bun build ./cli.js --compile --target=bun-darwin-arm64 --outfile dist/cssgrep-darwin-arm64",
38
+ "build:windows-x64": "bun build ./cli.js --compile --target=bun-windows-x64 --outfile dist/cssgrep-windows-x64.exe",
36
39
  "build:binaries": "npm run build:linux-x64 && npm run build:linux-arm64 && npm run build:darwin-x64 && npm run build:darwin-arm64 && npm run build:windows-x64",
37
40
  "build:sea": "node scripts/build-sea.js"
38
41
  },