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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b36eba533a0b58958b2a4a9cb9c1a4ec2e9d93a1f57fb91524fead2ef93f55a5
4
- data.tar.gz: 37897dda228ea0b7f8e84c233fb5703c4ed2e9573fe412ae54b724f7d0c6ef4f
3
+ metadata.gz: 5e859f85b430fafdb74633e795caefd6cfc14ce26585970026d77c20ddce06c2
4
+ data.tar.gz: 292fbc78bdeaeb3480092872afa558c25fa156c1cb80784eee98c87dca55f253
5
5
  SHA512:
6
- metadata.gz: 15a5ec6f5a56f5def420bddbd26c4c644f0ce37e3b32a430d13ba51d35957811f882778ce5f9cd396bde255607ef6697b6c07ce7b64a7a3191548f3302705a76
7
- data.tar.gz: 73472bc3cb6add78f1566d79e7b5c032da312af0021fc7ccebb17523ba719ca1e7f65f5ab478d05a4e5f29782a62141c169c74136997085230be45c23f2f607f
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
  >
@@ -15,6 +15,8 @@ module Conductor
15
15
  @tracks = @config["tracks"].symbolize_keys
16
16
  end
17
17
 
18
+ private
19
+
18
20
  def create_config(config_file)
19
21
  config_dir = File.dirname(config_file)
20
22
  scripts_dir = File.dirname(File.join(config_dir, "scripts"))
@@ -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 && max.to_i.positive? ? " 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
- lines = split(/\n/)
240
- insert_point = meta_insert_point
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
- content.insert_title
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Conductor
4
- VERSION = '1.0.19'
4
+ VERSION = '1.0.21'
5
5
  end
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.19
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-02 00:00:00.000000000 Z
11
+ date: 2024-07-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: awesome_print