@@ -192,6 +192,22 @@ def __init__(self, parser, md):
192
192
self .end = RE_END
193
193
self .yaml_line = RE_INDENT_YAML_LINE
194
194
195
+ def detab_by_length (self , text , length ):
196
+ """Remove a tab from the front of each line of the given text."""
197
+
198
+ newtext = []
199
+ lines = text .split ('\n ' )
200
+ for line in lines :
201
+ if line .startswith (' ' * length ):
202
+ newtext .append (line [length :])
203
+ elif not line .strip ():
204
+ newtext .append ('' ) # pragma: no cover
205
+ else :
206
+ break
207
+ if newtext :
208
+ return '\n ' .join (newtext ), '\n ' .join (lines [len (newtext ):])
209
+ return '\n ' .join (lines [len (newtext ):]), ''
210
+
195
211
def register (self , b , config ):
196
212
"""Register a block."""
197
213
@@ -251,46 +267,37 @@ def _reset(self):
251
267
self .working = None
252
268
self .trackers = {d : {} for d in self .blocks .keys ()}
253
269
254
- def split_end (self , blocks , length ):
270
+ def split_end (self , block , length ):
255
271
"""Search for end and split the blocks while removing the end."""
256
272
257
- good = []
258
- bad = []
273
+ good = None
274
+ bad = None
259
275
end = False
260
276
261
- # Split on our end notation for the current Block
262
- for e , block in enumerate (blocks ):
263
-
264
- # Find the end of the Block
265
- m = None
266
- for match in self .end .finditer (block ):
267
- if len (match .group (1 )) == length :
268
- m = match
269
- break
270
-
271
- # Separate everything from before the "end" and after
272
- if m :
273
- temp = block [:m .start (0 )]
274
- if temp :
275
- good .append (temp [:- 1 ] if temp .endswith ('\n ' ) else temp )
276
- end = True
277
-
278
- # Since we found our end, everything after is unwanted
279
- temp = block [m .end (0 ):]
280
- if temp :
281
- bad .append (temp )
282
- bad .extend (blocks [e + 1 :])
277
+ # Find the end of the Block
278
+ m = None
279
+ for match in self .end .finditer (block ):
280
+ if len (match .group (1 )) == length :
281
+ m = match
283
282
break
284
- else :
285
- # Gather blocks until we find our end
286
- good .append (block )
287
283
288
- # Augment the blocks
289
- blocks .clear ()
290
- blocks .extend (bad )
284
+ # Separate everything from before the "end" and after
285
+ if m :
286
+ temp = block [:m .start (0 )]
287
+ if temp :
288
+ good = temp [:- 1 ] if temp .endswith ('\n ' ) else temp
289
+ end = True
290
+
291
+ # Since we found our end, everything after is unwanted
292
+ temp = block [m .end (0 ):]
293
+ if temp :
294
+ bad = temp
295
+ else :
296
+ # Gather blocks until we find our end
297
+ good = block
291
298
292
299
# Send back the new list of blocks to parse and note whether we found our end
293
- return good , end
300
+ return good , bad , end
294
301
295
302
def split_header (self , block , length ):
296
303
"""Split, YAML-ish header out."""
@@ -349,52 +356,83 @@ def is_block(self, tag):
349
356
350
357
return tag .tag in self .block_tags
351
358
352
- def parse_blocks (self , blocks , entry ):
359
+ def parse_blocks (self , blocks ):
353
360
"""Parse the blocks."""
354
361
355
362
# Get the target element and parse
363
+ while blocks and self .stack :
364
+ b = blocks .pop (0 )
356
365
357
- for b in blocks :
366
+ # Get the latest block on the stack
367
+ # This is required to avoid some issues with `md_in_html`
368
+ entry = self .stack [- 1 ]
358
369
target = entry .block .on_add (entry .el )
359
370
360
- # The Block does not or no longer accepts more content
361
- if target is None : # pragma: no cover
362
- break
371
+ # Since we are juggling the block parsers on the stack, the pipeline
372
+ # has not fully adjusted list indentation, so look at how many
373
+ # list item parents we have on the stack and adjust the content
374
+ # accordingly.
375
+ li = [e .parent .tag in ('li' , 'dd' ) for e in self .stack [:- 1 ]]
376
+ length = len (li ) * self .tab_length
377
+ b , a = self .detab_by_length (b , length )
378
+ if a :
379
+ blocks .insert (0 , a )
363
380
364
- mode = entry .block .on_markdown ()
365
- if mode not in ('block' , 'inline' , 'raw' ):
366
- mode = 'auto'
367
- is_block = mode == 'block' or (mode == 'auto' and self .is_block (target ))
368
- is_atomic = mode == 'raw' or (mode == 'auto' and self .is_raw (target ))
369
-
370
- # We should revert fenced code in spans or atomic tags.
371
- # Make sure atomic tags have content wrapped as `AtomicString`.
372
- if is_atomic or not is_block :
373
- child = list (target )[- 1 ] if len (target ) else None
374
- text = target .text if child is None else child .tail
375
- b = '\n \n ' .join (unescape_markdown (self .md , [b ], is_atomic )).strip ('\n ' )
376
-
377
- if text :
378
- text += b if not b else '\n \n ' + b
381
+ # Split out blocks we care about
382
+ b , bad , end = self .split_end (b , entry .block .length )
383
+ if bad is not None :
384
+ blocks .insert (0 , bad )
385
+
386
+ # Parse the block under the given target
387
+ if b is not None and target is not None :
388
+ # Resolve modes
389
+ mode = entry .block .on_markdown ()
390
+ if mode not in ('block' , 'inline' , 'raw' ):
391
+ mode = 'auto'
392
+ is_block = mode == 'block' or (mode == 'auto' and self .is_block (target ))
393
+ is_atomic = mode == 'raw' or (mode == 'auto' and self .is_raw (target ))
394
+
395
+ # We should revert fenced code in spans or atomic tags.
396
+ # Make sure atomic tags have content wrapped as `AtomicString`.
397
+ if is_atomic or not is_block :
398
+ child = list (target )[- 1 ] if len (target ) else None
399
+ text = target .text if child is None else child .tail
400
+ b = '\n \n ' .join (unescape_markdown (self .md , [b ], is_atomic )).strip ('\n ' )
401
+
402
+ if text :
403
+ text += b if not b else '\n \n ' + b
404
+ else :
405
+ text = b
406
+
407
+ if child is None :
408
+ target .text = mutil .AtomicString (text ) if is_atomic else text
409
+ else : # pragma: no cover
410
+ # TODO: We would need to build a special plugin to test this,
411
+ # as none of the default ones do this, but we have verified this
412
+ # locally. Once we've written a test, we can remove this.
413
+ child .tail = mutil .AtomicString (text ) if is_atomic else text
414
+
415
+ # Block tags should have content go through the normal block processor
379
416
else :
380
- text = b
417
+ self .parser .state .set ('blocks' )
418
+ working = self .working
419
+ self .working = entry
420
+ self .parser .parseChunk (target , b )
421
+ self .parser .state .reset ()
422
+ self .working = working
423
+
424
+ # Run "on end" event when we finish a block
425
+ if end :
426
+ entry .block ._end (entry .el )
427
+ self .inline_stack .append (entry )
428
+ del self .stack [- 1 ]
381
429
382
- if child is None :
383
- target .text = mutil .AtomicString (text ) if is_atomic else text
384
- else : # pragma: no cover
385
- # TODO: We would need to build a special plugin to test this,
386
- # as none of the default ones do this, but we have verified this
387
- # locally. Once we've written a test, we can remove this.
388
- child .tail = mutil .AtomicString (text ) if is_atomic else text
430
+ # The Block does not or no longer accepts more content
431
+ if target is None : # pragma: no cover
432
+ break
389
433
390
- # Block tags should have content go through the normal block processor
391
- else :
392
- self .parser .state .set ('blocks' )
393
- working = self .working
394
- self .working = entry
395
- self .parser .parseChunk (target , b )
396
- self .parser .state .reset ()
397
- self .working = working
434
+ if self .stack :
435
+ self .stack [- 1 ].hungry = True
398
436
399
437
def run (self , parent , blocks ):
400
438
"""Convert to details/summary block."""
@@ -429,42 +467,16 @@ def run(self, parent, blocks):
429
467
# Push a Block entry on the stack.
430
468
self .stack .append (BlockEntry (generic_block , el , parent ))
431
469
432
- # Split out blocks we care about
433
- ours , end = self .split_end (blocks , generic_block .length )
434
-
435
470
# Parse the text blocks under the Block
436
- index = len (self .stack ) - 1
437
- self .parse_blocks (ours , self .stack [- 1 ])
438
-
439
- # Remove Block from the stack if we are at the end
440
- # or add it to the hungry list.
441
- if end :
442
- # Run the "on end" event
443
- generic_block ._end (el )
444
- self .inline_stack .append (self .stack [index ])
445
- del self .stack [index ]
446
- else :
447
- self .stack [index ].hungry = True
471
+ self .parse_blocks (blocks )
448
472
449
473
else :
450
474
for r in range (len (self .stack )):
451
475
entry = self .stack [r ]
452
476
if entry .hungry and parent is entry .parent :
453
- # Find and remove end from the blocks
454
- ours , end = self .split_end (blocks , entry .block .length )
455
-
456
477
# Get the target element and parse
457
478
entry .hungry = False
458
- self .parse_blocks (ours , entry )
459
-
460
- # Clean up if we completed the Block
461
- if end :
462
- # Run "on end" event
463
- entry .block ._end (entry .el )
464
- self .inline_stack .append (entry )
465
- del self .stack [r ]
466
- else :
467
- entry .hungry = True
479
+ self .parse_blocks (blocks )
468
480
469
481
break
470
482
0 commit comments