marked-conductor 1.0.19 → 1.0.21
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/README.md +8 -1
- data/lib/conductor/config.rb +2 -0
- data/lib/conductor/filter.rb +169 -6
- data/lib/conductor/version.rb +1 -1
- data/src/_README.md +8 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5e859f85b430fafdb74633e795caefd6cfc14ce26585970026d77c20ddce06c2
|
4
|
+
data.tar.gz: 292fbc78bdeaeb3480092872afa558c25fa156c1cb80784eee98c87dca55f253
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ea468effe4d2be8b534d7bc9ea27dbb09a57f756393d7aebe4a33cda998326fb0eaa8b31194bc8c17e21487d3dd28aeaf3cedf21b707e61bda65a2253a609b8
|
7
|
+
data.tar.gz: 893e362f929e2a26a4815bc3782d758d6f4d25270ca2137867c56de846b5755adbb4dc1bf3983b996b88a61e904731220d2c76123f523825d88be39dc5bb4ebd
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,23 @@
|
|
1
|
+
### 1.0.21
|
2
|
+
|
3
|
+
2024-07-10 12:18
|
4
|
+
|
5
|
+
#### NEW
|
6
|
+
|
7
|
+
- New filter `fixHeaders` will adapt all headlines in the document to be in semantic order
|
8
|
+
|
9
|
+
### 1.0.20
|
10
|
+
|
11
|
+
2024-07-04 12:18
|
12
|
+
|
13
|
+
#### NEW
|
14
|
+
|
15
|
+
- The `insertTitle` filter can now take an argument of `true` or a number and will shift the remaining headlines in the document by 1 (or the number given in the argument), allowing for title insertion while only having 1 H1 in the document.
|
16
|
+
|
17
|
+
#### IMPROVED
|
18
|
+
|
19
|
+
- Ignore self-linking urls in single quotes, just in case they're used in a script line
|
20
|
+
|
1
21
|
### 1.0.19
|
2
22
|
|
3
23
|
2024-07-02 11:25
|
data/README.md
CHANGED
@@ -163,6 +163,7 @@ The action can be `script`, `command`, or `filter`.
|
|
163
163
|
| `prepend/appendCode(path)` | insert a file as a code block at beginning or end of content |
|
164
164
|
| `insertCSS(path)` | insert custom CSS into document |
|
165
165
|
| `autoLink()` | Turn bare URLs into \<self-linked\> urls |
|
166
|
+
| `fixHeaders()` | Reorganize headline levels to semantic order |
|
166
167
|
|
167
168
|
For `replace` and `replaceAll`: If *search* is surrounded with forward slashes followed by optional flags (*i* for case-insensitive, *m* to make dot match newlines), e.g. `/contribut(ing)?/i`, it will be interpreted as a regular expression. The *replace* value can include numeric capture groups, e.g. `Follow$2`.
|
168
169
|
|
@@ -170,11 +171,17 @@ For `insertScript`, if path is just a filename it will look for a match in `~/.c
|
|
170
171
|
|
171
172
|
For `insertCSS`, if path is just a filename (with or without .css extension), the file will be searched for in `~/.config/conductor/css` or `~/.config/conductor/files` and injected. CSS will be compressed using the YUI algorithm and inserted at the top of the document, but after any existing metadata.
|
172
173
|
|
174
|
+
For `insertTitle`, if an argument of `true` or a number is given (e.g. `insertTitle(true)`, the headers in the document will be shifted by 1 (or by the number given) so that there's only one H1 in the document.
|
175
|
+
|
173
176
|
If the path for `insertScript` or `insertCSS` is a URL instead of a filename, the URL will be properly inserted instead of a file path. Inserted scripts will be surrounded with `<div>` tags, which fixes a quirk with javascript in Marked.
|
174
177
|
|
175
178
|
For all of the prepend/append file filters, you can store files in `~/.config/conductor/files` and reference them with just a filename. Otherwise a full path will be assumed.
|
176
179
|
|
177
|
-
For `autoLink`, any URL that's not contained in parenthesis or following a `[]: url` pattern will be autolinked (surrounded by angle brackets). URLs must contain `//` to be recognized, but any protocol will work, e.g. `x-marked://refresh`.
|
180
|
+
For `autoLink`, any URL that's not contained in parenthesis or following a `[]: url` pattern will be autolinked (surrounded by angle brackets). URLs must contain `//` to be recognized, but any protocol will work, e.g. `x-marked://refresh`. Must be run on Markdown, not HTML.
|
181
|
+
|
182
|
+
For `fixHeaders`, it will be ensured that the document has an h1, and all header levels will be adapted to never jump more than one header level when increasing. If no H1 exists in the document, the first header of the lowest existing level will be turned into an H1 and all other headers will be decremented to fit the hierarchy. It's not perfect, but it does a pretty good job. When saving the document as Markdown from Marked, the new headers will be applied.
|
183
|
+
|
184
|
+
**Note:** successive filters in a sequence that insert or prepend will always insert content before/above the result of the previous insert filter. So if you have an `insertTitle` filter followed by an `insertCSS` filter, the CSS will appear above the inserted title. If you want elements inserted in reverse order, reverse the order of the inserts in the sequence.
|
178
185
|
|
179
186
|
> Example:
|
180
187
|
>
|
data/lib/conductor/config.rb
CHANGED
data/lib/conductor/filter.rb
CHANGED
@@ -69,9 +69,40 @@ class ::String
|
|
69
69
|
first
|
70
70
|
end
|
71
71
|
|
72
|
+
##
|
73
|
+
## Count the characters in a string
|
74
|
+
##
|
75
|
+
## @return [Integer] number of characters
|
76
|
+
##
|
77
|
+
def chars
|
78
|
+
split(//).count
|
79
|
+
end
|
80
|
+
|
81
|
+
def decrease_headers(amt = 1)
|
82
|
+
gsub(/^(\#{1,6})(?!=#)/) do
|
83
|
+
m = Regexp.last_match
|
84
|
+
level = m[1].chars
|
85
|
+
level -= amt
|
86
|
+
level = 1 if level < 1
|
87
|
+
"#{"#" * level}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def decrease_headers!(amt = 1)
|
92
|
+
replace decrease_headers(amt)
|
93
|
+
end
|
94
|
+
|
95
|
+
def increase_headers(amt = 1)
|
96
|
+
gsub(/^#/, "#{"#" * amt}#").gsub(/^\#{7,}/, '######')
|
97
|
+
end
|
98
|
+
|
99
|
+
def increase_headers!(amt = 1)
|
100
|
+
replace increase_headers(amt)
|
101
|
+
end
|
102
|
+
|
72
103
|
def insert_toc(max = nil, after = :h1)
|
73
104
|
lines = split(/\n/)
|
74
|
-
max = max
|
105
|
+
max = max.to_i&.positive? ? " max#{max}" : ""
|
75
106
|
line = case after.to_sym
|
76
107
|
when :h2
|
77
108
|
first_h2.positive? ? first_h2 + 1 : 0
|
@@ -234,10 +265,12 @@ class ::String
|
|
234
265
|
title
|
235
266
|
end
|
236
267
|
|
237
|
-
def insert_title
|
268
|
+
def insert_title(shift: 0)
|
269
|
+
content = dup
|
238
270
|
title = get_title
|
239
|
-
|
240
|
-
|
271
|
+
content.increase_headers!(shift) if shift.positive?
|
272
|
+
lines = content.split(/\n/)
|
273
|
+
insert_point = content.meta_insert_point
|
241
274
|
insert_at = insert_point.positive? ? insert_point + 1 : 0
|
242
275
|
lines.insert(insert_at, "# #{title}\n")
|
243
276
|
lines.join("\n")
|
@@ -328,10 +361,125 @@ class ::String
|
|
328
361
|
end
|
329
362
|
|
330
363
|
def autolink
|
331
|
-
gsub(%r{(?mi)(?<!\(|\]: |")\b((?:[\w-]+?://)[-a-zA-Z0-9@:%._+~#=]{2,256}\b(?:[-a-zA-Z0-9@:%_+.~#?&/=]*))},
|
364
|
+
gsub(%r{(?mi)(?<!\(|\]: |"|')\b((?:[\w-]+?://)[-a-zA-Z0-9@:%._+~#=]{2,256}\b(?:[-a-zA-Z0-9@:%_+.~#?&/=]*))},
|
332
365
|
'<\1>')
|
333
366
|
end
|
334
367
|
|
368
|
+
##
|
369
|
+
## Count the number of h1 headers in the document
|
370
|
+
##
|
371
|
+
## @return Number of h1s.
|
372
|
+
##
|
373
|
+
def count_h1s
|
374
|
+
scan(/^#[^#]/).count
|
375
|
+
end
|
376
|
+
|
377
|
+
##
|
378
|
+
## Normalize Setext headers to ATX
|
379
|
+
##
|
380
|
+
## @return [String] content with headers updated
|
381
|
+
##
|
382
|
+
def normalize_headers
|
383
|
+
gsub(/^(\S.*)\n([=-]+)\n/) do
|
384
|
+
m = Regexp.last_match
|
385
|
+
case m[2]
|
386
|
+
when /\=/
|
387
|
+
"# #{m[1]}\n\n"
|
388
|
+
else
|
389
|
+
"## #{m[1]}\n\n"
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
def normalize_headers!
|
395
|
+
replace normalize_headers
|
396
|
+
end
|
397
|
+
|
398
|
+
##
|
399
|
+
## Ensure there's at least 1 h1 in the document
|
400
|
+
##
|
401
|
+
## If no h1 is found, converts the lowest level header (first one) into an h1
|
402
|
+
##
|
403
|
+
## @return [String] content with at least 1 h1
|
404
|
+
##
|
405
|
+
def ensure_h1
|
406
|
+
headers = to_enum(:scan, /(\#{1,6})([^#].*?)$/m).map { Regexp.last_match }
|
407
|
+
return self if headers.select { |h| h[1].chars == 1 }.count.positive?
|
408
|
+
|
409
|
+
lowest_header = headers.min_by { |h| h[1].chars }
|
410
|
+
level = lowest_header[1].chars
|
411
|
+
|
412
|
+
sub(/#{Regexp.escape(lowest_header[0])}/, "# #{lowest_header[2].strip}").decrease_headers(level)
|
413
|
+
end
|
414
|
+
|
415
|
+
def ensure_h1!
|
416
|
+
replace ensure_h1
|
417
|
+
end
|
418
|
+
|
419
|
+
##
|
420
|
+
## Bump all headers except for first H1
|
421
|
+
##
|
422
|
+
## @return Content with adjusted headers
|
423
|
+
##
|
424
|
+
def fix_headers
|
425
|
+
return self if count_h1s == 1
|
426
|
+
|
427
|
+
first_h1 = true
|
428
|
+
|
429
|
+
gsub(%r/^(\#{1,6})([^#].*?)$/m) do
|
430
|
+
m = Regexp.last_match
|
431
|
+
level = m[1].chars
|
432
|
+
content = m[2].strip
|
433
|
+
if level == 1 && first_h1
|
434
|
+
first_h1 = false
|
435
|
+
m[0]
|
436
|
+
else
|
437
|
+
level += 1 if level < 6
|
438
|
+
|
439
|
+
"#{"#" * level} #{content}"
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
def fix_headers!
|
445
|
+
replace fix_headers
|
446
|
+
end
|
447
|
+
|
448
|
+
##
|
449
|
+
## Adjust header levels so there's no jump greater than 1
|
450
|
+
##
|
451
|
+
## @return Content with adjusted headers
|
452
|
+
##
|
453
|
+
def fix_hierarchy
|
454
|
+
normalize_headers!
|
455
|
+
ensure_h1!
|
456
|
+
fix_headers!
|
457
|
+
headers = to_enum(:scan, /(\#{1,6})([^#].*?)$/m).map { Regexp.last_match }
|
458
|
+
content = dup
|
459
|
+
last = 1
|
460
|
+
headers.each do |h|
|
461
|
+
level = h[1].chars
|
462
|
+
if level <= last + 1
|
463
|
+
last = level
|
464
|
+
next
|
465
|
+
end
|
466
|
+
|
467
|
+
level = last + 1
|
468
|
+
content.sub!(/#{Regexp.escape(h[0])}/, "#{"#" * level} #{h[2].strip}")
|
469
|
+
end
|
470
|
+
|
471
|
+
content
|
472
|
+
end
|
473
|
+
|
474
|
+
##
|
475
|
+
## Convert a string to a regular expression
|
476
|
+
##
|
477
|
+
## If the string matches /xxx/, it will be interpreted
|
478
|
+
## directly as a regex. Otherwise it will be escaped and
|
479
|
+
## converted to regex.
|
480
|
+
##
|
481
|
+
## @return [Regexp] Regexp representation of the string.
|
482
|
+
##
|
335
483
|
def to_rx
|
336
484
|
if self =~ %r{^/(.*?)/([im]+)?$}
|
337
485
|
m = Regexp.last_match
|
@@ -343,6 +491,11 @@ class ::String
|
|
343
491
|
end
|
344
492
|
end
|
345
493
|
|
494
|
+
##
|
495
|
+
## Convert a string containing $1, $2 to a Regexp replace pattern
|
496
|
+
##
|
497
|
+
## @return [String] Pattern representation of the object.
|
498
|
+
##
|
346
499
|
def to_pattern
|
347
500
|
gsub(/\$(\d+)/, '\\\\\1').gsub(/(^["']|["']$)/, "")
|
348
501
|
end
|
@@ -367,7 +520,15 @@ class Filter < String
|
|
367
520
|
end
|
368
521
|
content
|
369
522
|
when /(insert|add|inject)title/
|
370
|
-
|
523
|
+
amt = 0
|
524
|
+
if @params
|
525
|
+
amt = if @params[0] =~ /^[yts]/
|
526
|
+
1
|
527
|
+
else
|
528
|
+
@params[0].to_i
|
529
|
+
end
|
530
|
+
end
|
531
|
+
content.insert_title(shift: amt)
|
371
532
|
when /(insert|add|inject)script/
|
372
533
|
content.append!("\n\n<div>")
|
373
534
|
@params.each do |script|
|
@@ -427,6 +588,8 @@ class Filter < String
|
|
427
588
|
content.replace_one(@params[0], @params[1])
|
428
589
|
when /(auto|self)link/
|
429
590
|
content.autolink
|
591
|
+
when /fix(head(lines|ers)|hierarchy)/
|
592
|
+
content.fix_hierarchy
|
430
593
|
end
|
431
594
|
end
|
432
595
|
end
|
data/lib/conductor/version.rb
CHANGED
data/src/_README.md
CHANGED
@@ -163,6 +163,7 @@ The action can be `script`, `command`, or `filter`.
|
|
163
163
|
| `prepend/appendCode(path)` | insert a file as a code block at beginning or end of content |
|
164
164
|
| `insertCSS(path)` | insert custom CSS into document |
|
165
165
|
| `autoLink()` | Turn bare URLs into \<self-linked\> urls |
|
166
|
+
| `fixHeaders()` | Reorganize headline levels to semantic order |
|
166
167
|
|
167
168
|
For `replace` and `replaceAll`: If *search* is surrounded with forward slashes followed by optional flags (*i* for case-insensitive, *m* to make dot match newlines), e.g. `/contribut(ing)?/i`, it will be interpreted as a regular expression. The *replace* value can include numeric capture groups, e.g. `Follow$2`.
|
168
169
|
|
@@ -170,11 +171,17 @@ For `insertScript`, if path is just a filename it will look for a match in `~/.c
|
|
170
171
|
|
171
172
|
For `insertCSS`, if path is just a filename (with or without .css extension), the file will be searched for in `~/.config/conductor/css` or `~/.config/conductor/files` and injected. CSS will be compressed using the YUI algorithm and inserted at the top of the document, but after any existing metadata.
|
172
173
|
|
174
|
+
For `insertTitle`, if an argument of `true` or a number is given (e.g. `insertTitle(true)`, the headers in the document will be shifted by 1 (or by the number given) so that there's only one H1 in the document.
|
175
|
+
|
173
176
|
If the path for `insertScript` or `insertCSS` is a URL instead of a filename, the URL will be properly inserted instead of a file path. Inserted scripts will be surrounded with `<div>` tags, which fixes a quirk with javascript in Marked.
|
174
177
|
|
175
178
|
For all of the prepend/append file filters, you can store files in `~/.config/conductor/files` and reference them with just a filename. Otherwise a full path will be assumed.
|
176
179
|
|
177
|
-
For `autoLink`, any URL that's not contained in parenthesis or following a `[]: url` pattern will be autolinked (surrounded by angle brackets). URLs must contain `//` to be recognized, but any protocol will work, e.g. `x-marked://refresh`.
|
180
|
+
For `autoLink`, any URL that's not contained in parenthesis or following a `[]: url` pattern will be autolinked (surrounded by angle brackets). URLs must contain `//` to be recognized, but any protocol will work, e.g. `x-marked://refresh`. Must be run on Markdown, not HTML.
|
181
|
+
|
182
|
+
For `fixHeaders`, it will be ensured that the document has an h1, and all header levels will be adapted to never jump more than one header level when increasing. If no H1 exists in the document, the first header of the lowest existing level will be turned into an H1 and all other headers will be decremented to fit the hierarchy. It's not perfect, but it does a pretty good job. When saving the document as Markdown from Marked, the new headers will be applied.
|
183
|
+
|
184
|
+
**Note:** successive filters in a sequence that insert or prepend will always insert content before/above the result of the previous insert filter. So if you have an `insertTitle` filter followed by an `insertCSS` filter, the CSS will appear above the inserted title. If you want elements inserted in reverse order, reverse the order of the inserts in the sequence.
|
178
185
|
|
179
186
|
> Example:
|
180
187
|
>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: marked-conductor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.21
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brett Terpstra
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-07-
|
11
|
+
date: 2024-07-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: awesome_print
|