autoslides 0.1.0__tar.gz

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.
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2018 The Python Packaging Authority
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: autoslides
3
+ Version: 0.1.0
4
+ Summary: Convert markdown documents into summarized slides
5
+ Author-email: Rubelito Abella <bbadoodles@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/HowDoIGitHelp/resource_builder.git
8
+ Requires-Python: >=3.14
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Dynamic: license-file
@@ -0,0 +1,22 @@
1
+ [project]
2
+ name = "autoslides"
3
+ version = "0.1.0"
4
+ authors = [
5
+ { name = "Rubelito Abella", email = "bbadoodles@gmail.com" },
6
+ ]
7
+ description = "Convert markdown documents into summarized slides"
8
+ readme = "README.md"
9
+ requires-python = ">=3.14"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+
13
+ [project.urls]
14
+ Homepage = "https://github.com/HowDoIGitHelp/resource_builder.git"
15
+
16
+ [project.scripts]
17
+ autoslides = "autoslides.main:main"
18
+
19
+ [build-system]
20
+ requires = ["setuptools"]
21
+ build-backend = "setuptools.build_meta"
22
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,597 @@
1
+ import json
2
+ from string import Template
3
+ from abc import ABC, abstractmethod
4
+ from mistletoe import Document
5
+ from mistletoe.markdown_renderer import MarkdownRenderer, LinkReferenceDefinition, BlankLine
6
+ from mistletoe.block_token import Paragraph, Heading, List, ListItem, BlockToken, CodeFence, Quote, Table, TableRow, TableCell
7
+ from mistletoe.span_token import LineBreak, RawText, Strong, Emphasis, Image, EscapeSequence, SpanToken
8
+ from mistletoe.token import Token
9
+ import math
10
+ import re
11
+
12
+
13
+ LINES = 6
14
+ LINEWIDTH = 50
15
+
16
+
17
+ class Component(ABC):
18
+ '''
19
+ CompositeBlocks are made up of Components
20
+ '''
21
+
22
+ @abstractmethod
23
+ def height(self, lineWidth=LINEWIDTH):
24
+ pass
25
+
26
+
27
+ class Sentence(Component):
28
+ '''
29
+ base class that can be decorated by StrongSentence and EmphasizedSentence
30
+ '''
31
+
32
+ def __init__(self,sentence:str):
33
+ self.__sentence = sentence
34
+
35
+ def __str__(self) -> str:
36
+ return str(self.__sentence)
37
+
38
+ def importantParts(self) -> list:
39
+ return []
40
+
41
+ def size(self) -> int:
42
+ return len(self.__sentence)
43
+
44
+ def height(self, lineWidth=LINEWIDTH):
45
+ return math.ceil(self.size() / lineWidth)
46
+
47
+
48
+ class Head:
49
+
50
+ def __init__(self, mdHeading:Heading):
51
+ self.__level = mdHeading.level
52
+ self.__content = collapse(mdHeading.children)
53
+
54
+ def height(self, lineWidth=LINEWIDTH):
55
+ return 0
56
+
57
+ def headText(self) -> str:
58
+ return str(self.__content)
59
+
60
+
61
+ class Block(ABC):
62
+
63
+ @abstractmethod
64
+ def height(self) -> int:
65
+ pass
66
+
67
+ def items(self, level=0, indentSize=0, leader=''):
68
+ return [IndentedListItem(self, level=level, indentSize=indentSize, leader=leader)]
69
+
70
+ @abstractmethod
71
+ def slideContent(self, head:Head):
72
+ pass
73
+
74
+ def mdSlides(self, head:Head, lines=LINES):
75
+ return f'{self.slideContent(head)}\n---\n\n'
76
+
77
+
78
+ class CompositeBlock(Block):
79
+ '''
80
+ composite blocks can be split into multiple slides, split is based on number of lines
81
+ '''
82
+
83
+ def slides(self, head:Head, lines=LINES, lineWidth=LINEWIDTH) -> list:
84
+ slides = []
85
+ currentHeight = 0
86
+ currentSlide = []
87
+ for component in self.components():
88
+ if currentHeight + component.height(lineWidth) > lines:
89
+ slides.append(self.slideContent(currentSlide, head))
90
+ currentSlide = [component]
91
+ currentHeight = component.height(lineWidth)
92
+ else:
93
+ currentSlide.append(component)
94
+ currentHeight += component.height(lineWidth)
95
+ slides.append(self.slideContent(currentSlide, head))
96
+ return slides
97
+
98
+ def mdSlides(self, head:Head, lines=LINES) -> str:
99
+ subDeck = ''
100
+ for slide in self.slides(head, lines):
101
+ subDeck += f'{slide}\n'
102
+ subDeck += '\n---\n\n'
103
+ return subDeck
104
+
105
+ @abstractmethod
106
+ def components(self) -> list:
107
+ pass
108
+
109
+ def height(self, lineWidth=LINEWIDTH):
110
+ cumulativeHeight = 0
111
+ for component in self.components():
112
+ cumulativeHeight += component.height(lineWidth)
113
+ return cumulativeHeight
114
+
115
+
116
+ class StrongSentence(Sentence):
117
+
118
+ def __init__(self,sentence:Sentence, strongParts:list):
119
+ self.__sentence = sentence
120
+ self.__strongParts = strongParts
121
+
122
+ def __str__(self) -> str:
123
+ return str(self.__sentence)
124
+
125
+ def importantParts(self) -> list:
126
+ innerImportantParts = []
127
+ for part in self.__strongParts:
128
+ innerImportantParts += part.importantParts()
129
+ return self.__sentence.importantParts() + self.__strongParts + innerImportantParts
130
+
131
+ def size(self) -> int:
132
+ return self.__sentence.size()
133
+
134
+
135
+ class EmphasizedSentence(Sentence):
136
+
137
+ def __init__(self,sentence:Sentence,emphasizedParts:list):
138
+ self.__sentence = sentence
139
+ self.__emphasizedParts = emphasizedParts
140
+
141
+ def __str__(self) -> str:
142
+ return str(self.__sentence)
143
+
144
+ def importantParts(self) -> list:
145
+ innerImportantParts = []
146
+ for part in self.__emphasizedParts:
147
+ innerImportantParts += part.importantParts()
148
+ return self.__sentence.importantParts() + self.__emphasizedParts + innerImportantParts
149
+
150
+ def size(self) -> int:
151
+ return self.__sentence.size()
152
+
153
+
154
+ class UnsupportedTokenException(Exception):
155
+
156
+ def __init__(self, token:BlockToken):
157
+ self.unsupportedToken = token
158
+
159
+ def isMathBlock(paragraph:Paragraph, mathFence = '$$') -> bool:
160
+ '''
161
+ mathblocks are not native to commonmark or mistletoe, this function checks if a paragraph is surrounded by the math fence token
162
+ '''
163
+ startsWithDollars = isinstance(paragraph.children[0], RawText) and paragraph.children[0].content.startswith(mathFence)
164
+ endsWithDollars = isinstance(paragraph.children[-1], RawText) and paragraph.children[-1].content.endswith(mathFence)
165
+ return startsWithDollars and endsWithDollars
166
+
167
+ def isInvisible(token:Token):
168
+ if isinstance(token, BlankLine):
169
+ return True
170
+ if isinstance(token, LinkReferenceDefinition):
171
+ return True
172
+ else:
173
+ return False
174
+
175
+ def asBlock(token:BlockToken) -> Block:
176
+ '''
177
+ convert mistletoe.block_token.BlockToken into Blocks
178
+ '''
179
+ if isinstance(token, Paragraph) and isMathBlock(token):
180
+ return MathBlock(token)
181
+ elif isinstance(token, Paragraph) and isImageBlock(token):
182
+ return ImageBlock(token)
183
+ elif isinstance(token, Paragraph):
184
+ return ParagraphBlock(token)
185
+ elif isinstance(token, Heading):
186
+ return Head(token)
187
+ elif isinstance(token, List):
188
+ return ListBlock(token)
189
+ elif isinstance(token, ListItem):
190
+ return Item(token)
191
+ elif isinstance(token, CodeFence):
192
+ return CodeBlock(token)
193
+ elif isinstance(token, Quote):
194
+ return QuoteBlock(token)
195
+ elif isinstance(token, Table):
196
+ return TableBlock(token)
197
+ else:
198
+ raise UnsupportedTokenException(token)
199
+
200
+ class SentenceDelimiter(SpanToken):
201
+
202
+ def __init__(self):
203
+ self.content = ''
204
+
205
+ def delimitedTextToken(textToken:RawText) -> list:
206
+ '''
207
+ this function takes a RawText span token and splits it into multiple RawText tokens with SentenceDelimiters in between
208
+ the delimiter is the string '. '
209
+ this function will not split inline math pharases
210
+ '''
211
+ pattern = re.compile(r'\$.*?\$')
212
+ inlineMathParts = re.findall(pattern, textToken.content)
213
+ rawTextContent = pattern.sub('$inlineMath$', textToken.content)
214
+ cumulativeTokenList = []
215
+ tokens = rawTextContent.split('. ')
216
+ for token in tokens[:-1]:
217
+ cumulativeTokenList += [RawText(f'{token}.'), SentenceDelimiter()]
218
+ if tokens[-1] != '':
219
+ cumulativeTokenList.append(RawText(tokens[-1]))
220
+ i = 0
221
+ while i < len(inlineMathParts):
222
+ for token in cumulativeTokenList:
223
+ for j in range(token.content.count('$inlineMath$')):
224
+ token.content = token.content.replace('$inlineMath$', inlineMathParts[i] ,1)
225
+ i += 1
226
+ return cumulativeTokenList
227
+
228
+ class ParagraphBlock(CompositeBlock):
229
+
230
+ def __init__(self, mdParagraph:Paragraph):
231
+ self.__mdParagraph = mdParagraph
232
+ self.__sentences = [collapse(spanList) for spanList in self.decompose()]
233
+
234
+ def decompose(self) -> list:
235
+ '''
236
+ Decompose a paragraph into list of span tokens based on SoftBreaks (single line breaks)
237
+ '''
238
+ spanTokenList = []
239
+ for child in self.__mdParagraph.children:
240
+ if isinstance(child, RawText):
241
+ spanTokenList += delimitedTextToken(child)
242
+ else:
243
+ spanTokenList.append(child)
244
+ sentenceComposites = []
245
+ rawSentences = []
246
+ for spanToken in spanTokenList:
247
+ if isinstance(spanToken, LineBreak) or isinstance(spanToken, SentenceDelimiter):
248
+ rawSentences.append(sentenceComposites)
249
+ sentenceComposites = []
250
+ else:
251
+ sentenceComposites.append(spanToken)
252
+ rawSentences.append(sentenceComposites)
253
+ return rawSentences
254
+
255
+ def components(self):
256
+ return self.__sentences
257
+
258
+ def slideContent(self, components:list, head:Head) -> str:
259
+ md = ''
260
+ md += f'# {head.headText()}\n'
261
+ md += '\n'
262
+ for sentence in components:
263
+ md += f'- {sentence}\n'
264
+ return md
265
+
266
+ def __str__(self) -> str:
267
+ cumulativeString = ''
268
+ for sentence in self.__sentences:
269
+ cumulativeString += f'{sentence} '
270
+ return cumulativeString[:-1]
271
+
272
+
273
+ class IndentedListItem(Component):
274
+
275
+ def __init__(self, content:Block, indentSize=0, level=0, leader=''):
276
+ self.__level = level
277
+ self.__content = content
278
+ self.__leader = leader
279
+ self.__indentSize = indentSize
280
+ self.__prefix = ' ' * ((indentSize * level) - len(leader))
281
+ self.__prefix += leader
282
+
283
+ def __str__(self) -> str:
284
+ return f'{self.__prefix}{self.__content}'
285
+
286
+ def height(self, lineWidth=LINEWIDTH) -> int:
287
+ return math.ceil(len(str(self)) / lineWidth)
288
+
289
+
290
+ class ListBlock(CompositeBlock):
291
+
292
+ def __init__(self, mdList:List):
293
+ self.__mdList = mdList
294
+ self.__items = []
295
+ for item in self.__mdList.children:
296
+ self.__items.append(asBlock(item))
297
+
298
+ def height(self, lineWidth=LINEWIDTH):
299
+ cumulativeHeight = 0
300
+ for item in self.__items:
301
+ cumulativeHeight += item.height(lineWidth)
302
+ return cumulativeHeight
303
+
304
+ def nthItem(self, n:int) -> Block:
305
+ return self.__items[n]
306
+
307
+ def items(self, level=0, indentSize=0, leader='') -> list:
308
+ itemsDFS = []
309
+ for item in self.__items:
310
+ itemsDFS += item.items(level=level, indentSize=indentSize, leader=leader)
311
+ return itemsDFS
312
+
313
+ def components(self) -> list:
314
+ return self.items()
315
+
316
+ def __str__(self) -> str:
317
+ cumulativeString = ''
318
+ for items in self.items():
319
+ cumulativeString += f'{items}\n'
320
+ return cumulativeString[:-1]
321
+
322
+ def slideContent(self, components:list, head:Head) -> str:
323
+ md = ''
324
+ md += f'# {head.headText()}\n'
325
+ md += '\n'
326
+ for line in components:
327
+ md += f'{line}\n'
328
+ return md
329
+ #override CompositeBlock.slides() to prevent orphaned list item children
330
+
331
+
332
+ class CodeLine(Component):
333
+
334
+ def __init__(self, content):
335
+ self.__content = content
336
+
337
+ def height(self, lineWidth=LINEWIDTH):
338
+ return math.ceil(len(self.__content) / lineWidth)
339
+
340
+ def __str__(self) -> str:
341
+ return self.__content
342
+
343
+ class CodeBlock(CompositeBlock):
344
+
345
+ def __init__(self, mdCodeFence:CodeFence):
346
+ self.__language = mdCodeFence.language
347
+ self.__lines = []
348
+ for lineContent in mdCodeFence.children[0].content.split('\n'):
349
+ self.__lines.append(CodeLine(lineContent))
350
+
351
+ def components(self):
352
+ return self.__lines
353
+
354
+ def slideContent(self, components:list, head:Head) -> str:
355
+ md = ''
356
+ md += f'# {head.headText()}\n'
357
+ md += '\n'
358
+ md += f'```{self.__language}\n'
359
+ for line in components:
360
+ md += f'{line}\n'
361
+ md += '```'
362
+ return md
363
+
364
+
365
+ class QuoteBlock(CompositeBlock):
366
+
367
+ def __init__(self, mdContent):
368
+ self.__mdQuote = mdContent
369
+ self.__children = []
370
+ for child in self.__mdQuote.children:
371
+ self.__children.append(asBlock(child))
372
+
373
+ def components(self):
374
+ return self.__children
375
+
376
+ def slideContent(self, components:list, head:Head) -> str:
377
+ md = ''
378
+ md += f'# {head.headText()}\n'
379
+ md += '\n'
380
+ for line in components:
381
+ md += f'> {line}\n'
382
+ return md
383
+
384
+ def __str__(self) -> str:
385
+ cumulativeString = ''
386
+ for child in self.__children:
387
+ cumulativeString += f'> {child} '
388
+ return cumulativeString[:-1]
389
+
390
+ def height(self, lineWidth=LINEWIDTH) -> int:
391
+ cumulativeHeight = 0
392
+ for child in self.__children:
393
+ cumulativeHeight += math.ceil(len(str(child)) / lineWidth)#change height calculation
394
+ return cumulativeHeight
395
+
396
+
397
+ def extractedMathEnvironments(mathBlock, environment) -> dict:
398
+ '''
399
+ this function extracts multiline math environments (e.g. matrix, bmatrix) so that the MathBlock.__init__() can safely split math blocks by newlines
400
+ it does not support nested multiline environments e.g. matrix inside a matrix
401
+ '''
402
+ pattern = Template(r'\\begin{$env}.*?\\end{$env}')
403
+ multilinePattern = re.compile(pattern.substitute(env=environment))
404
+ multilineBlocks = multilinePattern.findall(mathBlock)
405
+ replacedMathBlock = re.sub(multilinePattern, f'$${environment}$$', mathBlock)
406
+ return {'extractedBlocks':multilineBlocks, 'replacedMathBlock':replacedMathBlock}
407
+
408
+
409
+ class MathLine(Component):
410
+
411
+ def __init__(self, mathTeX:str):
412
+ self.__mathTeX = mathTeX
413
+
414
+ def height(self, lineWidth=LINEWIDTH):
415
+ return self.__mathTeX.count('\\\\') + 1
416
+
417
+ def __str__(self) -> str:
418
+ return self.__mathTeX
419
+
420
+
421
+ def rawTex(mathParagraph):
422
+ rawLines = [line for line in mathParagraph.children]
423
+ cumulativeString = ''
424
+ for span in mathParagraph.children:
425
+ if isinstance(span, EscapeSequence):
426
+ cumulativeString += f'\\{span.children[0].content}'
427
+ else:
428
+ cumulativeString += span.content
429
+ return cumulativeString[2:-2]
430
+
431
+
432
+
433
+ class MathBlock(CompositeBlock):
434
+
435
+ def __init__(self, mdParagraph:Paragraph):
436
+ self.__isAligned = False
437
+ jointLines = rawTex(mdParagraph)
438
+ if jointLines.startswith('\\begin{aligned}') and jointLines.endswith('\\end{aligned}'):
439
+ self.__isAligned = True
440
+ jointLines = jointLines[15:-13]
441
+ multilineEnvironments = ['bmatrix','matrix']
442
+ extractedBlocks = {}
443
+ #remove all multiline blocks (e.g. matrices) while saving removed blocks to extractedBlocks
444
+ for env in multilineEnvironments:
445
+ exME = extractedMathEnvironments(jointLines, env)
446
+ jointLines = exME['replacedMathBlock']
447
+ extractedBlocks[env] = exME['extractedBlocks']
448
+ #now that all multiline blocks are gone, replace newline separators with $$nl$$ token
449
+ #this replacement is now safe since there are no multiline environments in the string
450
+ jointLines = jointLines.replace('\\\\', '$$nl$$')
451
+ #place all extracted blocks back
452
+ for env in multilineEnvironments:
453
+ for block in extractedBlocks[env]:
454
+ jointLines = jointLines.replace(f'$${env}$$', block, 1)
455
+ #safely split the math block using the $$nl$$ token
456
+ self.__lines = [MathLine(line) for line in jointLines.split('$$nl$$')]
457
+
458
+ def components(self):
459
+ return self.__lines
460
+
461
+ def slideContent(self, components:list, head:Head) -> str:
462
+ md = ''
463
+ md += f'# {head.headText()}\n'
464
+ md += '\n'
465
+ md += f'<div>\n$$\n'
466
+ if self.__isAligned:
467
+ md += '\\begin{aligned}\n'
468
+ for line in components:
469
+ md += f'{line}\\\\\n'
470
+ if len(components) > 0:
471
+ md = f'{md[:-3]}\n'#remove the latex newline '\\' on the last line
472
+ if self.__isAligned:
473
+ md += '\\end{aligned}\n'
474
+ md += '$$\n</div>'
475
+ return md
476
+
477
+ def __str__(self) -> str:
478
+ cumulativeString = ''
479
+ for component in self.__lines:
480
+ cumulativeString += f'{str(component)} \\\\'
481
+
482
+ if self.__isAligned:
483
+ return '<div> $$ \\begin{aligned} ' + cumulativeString + ' \\end{aligned} $$ </div>'
484
+ else:
485
+ return '<div> $$ ' + cumulativeString + ' $$ </div>'
486
+
487
+
488
+ class Cell:
489
+
490
+ def __init__(self, content:TableCell):
491
+ self.__content = collapse(content.children)
492
+ self.__align = content.align
493
+
494
+ def height(self, lineWidth=LINEWIDTH) -> int:
495
+ return self.__content.height(lineWidth)
496
+
497
+ def __str__(self) -> str:
498
+ return str(self.__content)
499
+
500
+ class Row(Component):
501
+
502
+ def __init__(self, content:TableRow):
503
+ self.__cells = [Cell(child) for child in content.children]
504
+
505
+ def height(self, lineWidth=LINEWIDTH):
506
+ tallestCell = self.__cells[0]
507
+ for cell in self.__cells[1:]:
508
+ if cell.height(lineWidth) > tallestCell.height(lineWidth):
509
+ tallestCell = cell
510
+ return tallestCell.height(lineWidth)
511
+
512
+ def __str__(self) -> str:
513
+ cumulativeString = '|' if len(self.__cells) > 0 else ''
514
+ for cell in self.__cells:
515
+ cumulativeString += f' {str(cell)} |'
516
+ return cumulativeString
517
+
518
+
519
+ class TableBlock(CompositeBlock):
520
+
521
+ def __init__(self, content:Table):
522
+ self.__header = Row(content.header)
523
+ self.__rows = [Row(child) for child in content.children]
524
+ self.__alignmentRow = '|' if len(content.header.children) > 0 else ''
525
+ for alignCode in content.column_align:
526
+ if alignCode is None:
527
+ self.__alignmentRow += '-----|'
528
+ elif alignCode == 0:
529
+ self.__alignmentRow += ':---:|'
530
+ else:
531
+ self.__alignmentRow += '----:|'
532
+
533
+
534
+ def height(self, lineWidth=LINEWIDTH):
535
+ cumulativeHeight = self.__header.height(lineWidth)
536
+ for row in self.__rows:
537
+ cumulativeHeight += row.height(lineWidth)
538
+ return cumulativeHeight
539
+
540
+ def components(self):
541
+ return self.__rows
542
+
543
+ def slideContent(self, components:list, head:Head):
544
+ md = ''
545
+ md += f'# {head.headText()}\n'
546
+ md += '\n'
547
+ md += f'{str(self.__header)}\n'
548
+ md += f'{str(self.__alignmentRow)}\n'
549
+ for row in components:
550
+ md += f'{str(row)}\n'
551
+ return md
552
+
553
+
554
+ def isImageBlock(paragraph:Paragraph):
555
+ return len(paragraph.children) == 1 and isinstance(paragraph.children[0], Image)
556
+
557
+ class ImageBlock(Block):
558
+
559
+ def __init__(self, paragraph:Paragraph):
560
+ self.__mdImage = paragraph.children[0]
561
+ self.__altText:Sentence = collapse(self.__mdImage.children)
562
+ self.__title = self.__mdImage.title
563
+ self.__src = self.__mdImage.src
564
+
565
+ def height(self, lineWidth=LINEWIDTH):
566
+ return 1
567
+
568
+ def slideContent(self, head:Head):
569
+ md = ''
570
+ md += f'# {head.headText()}\n'
571
+ md += '\n'
572
+ md += f'![{self.__altText}]({self.__src})'
573
+ return md
574
+
575
+
576
+ def collapse(spanList:list) -> Sentence:
577
+ '''
578
+ Collapses a list of span tokens and returns Sentence
579
+ '''
580
+ emphasizedParts = []
581
+ strongParts = []
582
+ mdSpanList = []
583
+ for token in spanList:
584
+ if isinstance(token, Emphasis):
585
+ emphasizedParts.append(collapse(token.children))
586
+ elif isinstance(token, Strong):
587
+ strongParts.append(collapse(token.children))
588
+ with MarkdownRenderer() as renderer:
589
+ mdSpanList.append(renderer.render(token)[:-1])
590
+
591
+ s = Sentence(''.join(mdSpanList))
592
+ if len(emphasizedParts) > 0:
593
+ s = EmphasizedSentence(s,emphasizedParts)
594
+ if len(strongParts) > 0:
595
+ s = StrongSentence(s,strongParts)
596
+ return s
597
+
File without changes
@@ -0,0 +1,2 @@
1
+ LINES = 8
2
+ LINEWIDTH = 100