@@ -194,10 +194,10 @@ def md_parse_misc_anchors(md_lines, anchor_map):
194
194
return anchors
195
195
196
196
197
- def md_check_links (md_lines , errs ):
197
+ def md_check_links (md_lines , errs , sc = False ):
198
198
'''Parse/check links in a cleaned-up/numbered md lines without code blocks.
199
199
Returns {target: [links...]} to run file/path/url/git and such checks on.'''
200
- links , link_urls , st = dict (), dict (), adict (indented_url = None )
200
+ links , link_urls , link_urls_ex , st = dict (), dict (), dict (), adict (indented_url = None )
201
201
202
202
for n , line_raw in md_lines :
203
203
if not (line := md_esc_strip (line_raw .strip ())): continue
@@ -218,19 +218,28 @@ def md_check_links(md_lines, errs):
218
218
219
219
for a , b , title in titles :
220
220
if not title : continue # [] after image-links and such
221
- if title != title .strip (): continue
222
- link = adict (n = n , title = (title := md_esc_restore (title )), url = None , dups = 0 )
221
+ if sc and title != title .strip (): continue # special-case - links with spaced titles
222
+ link = adict (n = n , title = (title := md_esc_restore (title )), url = None )
223
223
if urls := list (extract_toplevel (ls := line [b :], '()' , False )):
224
224
ua , ub , url = urls [0 ]
225
225
if not ua : link .url = md_esc_restore (url )
226
- if (link .url or '' ).startswith ('#' ): # these are special - for anchor-checks only
226
+ if (link .url or '' ).startswith ('#' ): # always special - for anchor-checks only
227
227
title = link .title = f'\uf001 { link .title } '
228
- if (link_chk := links .get (title )) and (link_chk .url or link .url or link_chk .dups ):
229
- errs .append (adict ( n = n , t = 'link_dups' ,
230
- s = f'Duplicate non-uniform link [{ link .title } ], matching one on line-{ link_chk .n } ' ))
231
- link .dups = link_chk .dups + 1
232
- if not link_chk .url : link .url = None # one of them needs an url
233
- links [title ] = link
228
+ links [title ], link_chk = link , links .get (title )
229
+ if not link_chk : continue
230
+ # Special-case allows explicit-URL links to match uniform ones pointing to anchor
231
+ dup_warn = lambda err : errs .append (adict ( n = n ,
232
+ t = 'link_dups' , s = f'Duplicate non-uniform link [{ link .title } ] { err } ' ))
233
+ url_sc = (lu := link_urls .get (link .title )) and sc and lu .url .startswith ('#' )
234
+ if link_chk .url and not url_sc : dup_warn (f'on line-{ link_chk .n } ' )
235
+ if link .url :
236
+ if lud := link_urls_ex .get (title ):
237
+ warn = f'matching one on line-{ lud .n } '
238
+ if lud : warn += f' (url-match={ int (lu .url == lud .url )} )'
239
+ dup_warn (warn )
240
+ elif not url_sc : dup_warn (f'with one on line-{ link_chk .n } ' )
241
+ link_urls_ex [title ] = link
242
+ if not link_chk .url : link .url = None # propagate missing url for a single check below
234
243
235
244
link_map = cs .defaultdict (list )
236
245
for link in links .values ():
@@ -305,8 +314,7 @@ def main(argv=None):
305
314
textwrap .dedent (text ).strip ('\n ' ) + '\n ' ).replace ('\t ' , ' ' )
306
315
parser = argparse .ArgumentParser (
307
316
formatter_class = argparse .RawTextHelpFormatter ,
308
- usage = '%(prog)s [opts] [--] [file.md ...]' ,
309
- description = dd ('''
317
+ usage = '%(prog)s [opts] [--] [file.md ...]' , description = dd ('''
310
318
Check all specified markdown files for common issues.
311
319
Returns non-zero exit code with stderr output if any of the checks detect issues.
312
320
@@ -338,6 +346,13 @@ def main(argv=None):
338
346
Only #-prefix headers are handled as such, all underlined ones are ignored.
339
347
This modifies files and suppresses normal script operation mode.
340
348
Exit code is 0 if no anchors were added, and non-zero otherwise.''' ))
349
+ parser .add_argument ('-s' , '--special-cases' , action = 'store_true' , help = dd ('''
350
+ Apply following exceptions/special-cases for syntax/checks:\n
351
+ - Links with space-prefix/suffix in title, like [ skip this](...) are left unchecked.
352
+ - Links with explicit URLs can have same title as header-links (to #<name> anchors).
353
+ Allows to point links to a repo script(s), while otherwise to its doc section.\n
354
+ Idea is to have this special md sub-syntax to prevent this tool from raising
355
+ warnings in places were it normally should, but are known to be non-issues.''' ))
341
356
342
357
opts = parser .parse_args (sys .argv [1 :] if argv is None else argv )
343
358
@@ -384,7 +399,7 @@ def main(argv=None):
384
399
err_code = 1
385
400
continue
386
401
387
- link_map = md_check_links (md_lines , errs )
402
+ link_map = md_check_links (md_lines , errs , sc = opts . special_cases )
388
403
389
404
anchor_names = dict ((a .name , a ) for a in anchor_map .values ())
390
405
anchor_names .update ( (a .name , a ) for a in
0 commit comments