Loading .gitignore +1 −0 Original line number Diff line number Diff line Loading @@ -3,3 +3,4 @@ */ts-* */.python-version .python-version toMkdocs/__pycache__ toMkdocs/gridTableTools.py +123 −106 Original line number Diff line number Diff line Loading @@ -6,10 +6,25 @@ # """ Tools for working with grid tables in markdown files. """ from typing import Optional from typing import Optional, Callable from regexMatches import * _alignLeft = 'align="left"' _alignRight = 'align="right"' _alignCenter = 'align="center"' printInfo = print printDebug = print printError = print def setLoggers(info:Callable=print, debug:Callable=print, error:Callable=print) -> None: global printInfo, printDebug, printError printInfo = info printDebug = debug printError = error class GridCell: """ Represents a grid table cell. """ Loading @@ -27,8 +42,18 @@ class GridCell: self.auxiliarIndex:int = 0 def calculateAndSetAlignment(self, headerDelimiterPositions:list[int], delimiterPositions:list[int], defaultAlignments:list[str], hasHeader:bool) -> None: def calculateAndSetAlignment(self, headerDelimiterPositions:list[int], delimiterPositions:list[int], defaultAlignments:list[str], hasHeader:bool) -> None: """ Set the alignment of the cell based on the position of the delimiter. Args: headerDelimiterPositions: The positions of the header delimiters. delimiterPositions: The positions of the delimiters. defaultAlignments: The default alignments. hasHeader: True if the table has a header, False otherwise. """ if self.position is None: raise ValueError('Cell position must be set before calculating alignment.') Loading @@ -46,17 +71,17 @@ class GridCell: else: raise ValueError('Invalid table formatting') else: body_delimiter_index = 0 while body_delimiter_index in range(len(defaultAlignments)) and self.position > delimiterPositions[body_delimiter_index]: body_delimiter_index += 1 if body_delimiter_index in range(len(defaultAlignments)): if self.position < delimiterPositions[body_delimiter_index]: self.alignment = defaultAlignments[body_delimiter_index] elif self.position == delimiterPositions[body_delimiter_index]: self.alignment = defaultAlignments[body_delimiter_index] body_delimiter_index += 1 bodyDelimiterIndex = 0 while bodyDelimiterIndex < len(defaultAlignments) and self.position > delimiterPositions[bodyDelimiterIndex]: bodyDelimiterIndex += 1 if bodyDelimiterIndex < len(defaultAlignments): if self.position < delimiterPositions[bodyDelimiterIndex]: self.alignment = defaultAlignments[bodyDelimiterIndex] elif self.position == delimiterPositions[bodyDelimiterIndex]: self.alignment = defaultAlignments[bodyDelimiterIndex] bodyDelimiterIndex += 1 else: raise ValueError("Invalid table formatting") raise ValueError('Invalid table formatting') def __str__(self): Loading Loading @@ -91,6 +116,7 @@ class GridRow(): def __repr__(self): return self.__str__() class GridRowsTracker(): """ Represents the document object. """ def __init__(self, size:int) -> None: Loading Loading @@ -155,9 +181,10 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR cell.content = _c + nextListElementMark # Add list element end mark to know when the list element ends elif cell.listFlag and len(_c) > 0: # any other content when handling list is concatenated to the last list element _c = re.sub(r'\\\s*$', '\n', _c) cell.content += _c + nextListElementMark #add the list element end mark elif not _c: # separation between list and other paragraph cell.content += '\n' if not cell['content'].endswith('\n') else "" cell.content = _c + nextListElementMark #add the list element end mark elif not _c: # empty line. separation between list and other paragraph # cell.content = '\n' if not cell.content.endswith('\n') else "" cell.content = '\n' # cell content is always empty / None here. else: cell.content = re.sub(r'\\\s*$', '\n', _c) else: # Cell has content Loading @@ -173,8 +200,8 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR _c = re.sub(r'\\\s*$', '\n', _c) cell.content += " " + _c + nextListElementMark #add list element end mark elif len(_c) == 0: # separation between list and other paragraph if cell.list_flag: cell.list_flag = False if cell.listFlag: cell.listFlag = False cell.content += '\n\n' #end list by \n #content = re.sub(r'\\\s*$', "\n", content.strip()) cell.content += '\n' if not cell.content.endswith('\n') else '' Loading Loading @@ -205,7 +232,8 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR row[columnIndex].colspanAdjusted = True # Mark cell as adjusted def check_delimiter_alignment(line: str, delimiterPositions:list[int], delimiters: str = "|+") -> bool: def checkDelimiterAlignment(line: str, delimiterPositions:list[int], delimiters: str = "|+") -> bool: """ Check if delimiters in a row align with expected positions. Loading @@ -220,33 +248,29 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR if not line or not delimiterPositions: return False print(f"\nChecking line: '{line}'") print(f"Expected delimiter positions: {delimiterPositions}") printDebug(f'\nChecking line: "{line}"') printDebug(f'Expected delimiter positions: {delimiterPositions}') # For full separator lines (only +) if '+' in line and '|' not in line: currentPositions = [i for i, char in enumerate(line) if (char == '+' and i != 0)] print(f"Full separator line - Found + at positions: {currentPositions}") return all(delimiterPositions[-1] in currentPositions and line.startswith("+") and pos in delimiterPositions for pos in currentPositions) currentPositions = [i for i, char in enumerate(line) if (char == '+' and i > 0)] printDebug(f'Full separator line - Found + at positions: {currentPositions}') return all(delimiterPositions[-1] in currentPositions and line.startswith('+') and pos in delimiterPositions for pos in currentPositions) # For data lines (only |) if '|' in line and '+' not in line: currentPositions = [i for i, char in enumerate(line) if (char == '|' and i != 0)] print(f"Data line - Found | at positions: {currentPositions}") return all(delimiterPositions[-1] in currentPositions and line.startswith("|") and pos in delimiterPositions for pos in currentPositions) currentPositions = [i for i, char in enumerate(line) if (char == '|' and i > 0)] printDebug(f'Data line - Found | at positions: {currentPositions}') return all(delimiterPositions[-1] in currentPositions and line.startswith("|") and pos in delimiterPositions for pos in currentPositions) # For partial separators (mix of + and |) currentPositions = [i for i, char in enumerate(line) if (char in delimiters and i != 0)] print(f"Partial separator - Found delimiters at positions: {currentPositions}") print(f"Characters at those positions: {[line[pos] for pos in currentPositions]}") return all(delimiterPositions[-1] in currentPositions and (line.startswith("+") or line.startswith("|")) and pos in delimiterPositions for pos in currentPositions) currentPositions = [i for i, char in enumerate(line) if (char in delimiters and i > 0)] printDebug(f'Partial separator - Found delimiters at positions: {currentPositions}') printDebug(f'Characters at those positions: {[line[pos] for pos in currentPositions]}') return all(delimiterPositions[-1] in currentPositions and line.startswith(('+', '|')) and pos in delimiterPositions for pos in currentPositions) separatorIndices = [i for i, line in enumerate(lines) if isSeparator(line)] Loading @@ -254,8 +278,8 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR raise ValueError('No valid separators found in the provided grid table.') # Calculate max number of columns delimiterPositions:list[int] = [] numberOfColumns = 0 delimiterPositions = [] numberOfColumns:int = 0 for separatorIndex in separatorIndices: if (_cnt := lines[separatorIndex].count('+') - 1) > numberOfColumns: Loading @@ -263,10 +287,10 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR delimiterPositions = [] for rowIndex in range(numberOfColumns): delimiterPositionsStart = delimiterPositions[rowIndex - 1] if rowIndex != 0 else 0 delPositions = [lines[separatorIndex].find(delimiter, delimiterPositionsStart + 1) for delimiter in '+' if delimiter in lines[separatorIndex][delimiterPositionsStart + 1:]] delPositions = [lines[separatorIndex].find(delimiter, delimiterPositionsStart + 1) for delimiter in '+' if delimiter in lines[separatorIndex][delimiterPositionsStart + 1:]] delimiterPositions.append(min(delPositions) if delPositions else -1) # Determine delimter positions and alignments headerRows:GridTableRowList = [] dataRows:GridTableRowList = [] Loading @@ -278,15 +302,22 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR parts = re.split(r'\+', lines[index].strip('+')) #Calculate default alignments and positions of delimiters for partIndex in range(len(parts)): if parts[partIndex].startswith(':') and not parts[partIndex].endswith(':'): # Left alignment defaultAlignments.append('align="left"') elif not parts[partIndex].startswith(':') and parts[partIndex].endswith(':'): # Right alignment defaultAlignments.append('align="right"') # Left alignment if parts[partIndex].startswith(':') and not parts[partIndex].endswith(':'): defaultAlignments.append(_alignLeft) # Right alignment elif not parts[partIndex].startswith(':') and parts[partIndex].endswith(':'): defaultAlignments.append(_alignRight) # Center alignment else: defaultAlignments.append('align="center"') # Center alignment defaultAlignments.append(_alignCenter) # Delimiter position delimiterPositionsStart = delimiterPositions[partIndex - 1] if partIndex != 0 else 0 delPositions = [lines[index].find(delimiter, delimiterPositionsStart + 1) for delimiter in '+' if delimiter in lines[index][delimiterPositionsStart + 1:]] delPositions = [lines[index].find(delimiter, delimiterPositionsStart + 1) for delimiter in '+' if delimiter in lines[index][delimiterPositionsStart + 1:]] headerDelimiterPositions.append(min(delPositions) if delPositions else -1) if not hasHeader: Loading @@ -296,11 +327,13 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR # Calculate default alignments and positions of delimiters for part_index in range(len(parts)): if parts[part_index].startswith(':') and not parts[part_index].endswith(':'): default_alignments.append('align="left"') default_alignments.append(_alignLeft) elif not parts[part_index].startswith(':') and parts[part_index].endswith(':'): default_alignments.append('align="right"') default_alignments.append(_alignRight) else: default_alignments.append('align="center"') default_alignments.append(_alignCenter) for rowNumber in range(len(separatorIndices) - 1): rows:list[GridRow] = [] Loading @@ -314,8 +347,8 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR if isSeparator(line) and not inDataRow: inDataRow = True # Add delimiter alignment check for separator lines if not check_delimiter_alignment(line, delimiterPositions): raise ValueError(f"Misaligned delimiters in separator row: {line}") if not checkDelimiterAlignment(line, delimiterPositions): raise ValueError(f'Misaligned delimiters in separator row: {line}') parts = re.split(r'\s*\+\s*', line.strip('+')) delimiterIndex = 0 Loading Loading @@ -343,17 +376,17 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR # Regular data row or partial separator if matchGridTableBodySeparator.match(line): # Partial separator # Add delimiter alignment check for partial separators if not check_delimiter_alignment(line, delimiterPositions): raise ValueError(f"Misaligned delimiters in partial separator: {line}") if not checkDelimiterAlignment(line, delimiterPositions): raise ValueError(f'Misaligned delimiters in partial separator: {line}') cellsContent = re.split(r"[\|\+]", line.strip('|').strip('+')) # (?<!\\)[\|\+] cellsContent = re.split(r'[\|\+]', line.strip('|').strip('+')) # (?<!\\)[\|\+] #Add another row, set delimiters for each cell rows.append(GridRow(numberOfColumns)) auxDelimiterIndex = 0 auxiliarCellIndex = 0 for columnIndex, content in enumerate(cellsContent): if auxiliarCellIndex in range(numberOfColumns): if auxiliarCellIndex < numberOfColumns: auxDelimiterIndex += len(content) + 1 cell = rows[-1][auxiliarCellIndex] cell.position = auxDelimiterIndex # Position of cell delimiter + Loading Loading @@ -398,15 +431,14 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR continue else: raise ValueError("More cells than columns found") raise ValueError('More cells than columns found') else: # Data row cellsContent = line.strip() cellsContent = re.split(r"\|", line.strip('|')) cellsContent = re.split(r'\|', line.strip('|')) # Add delimiter alignment check if not check_delimiter_alignment(line, delimiterPositions): raise ValueError(f"Misaligned delimiters in row: {line}") if not checkDelimiterAlignment(line, delimiterPositions): raise ValueError(f'Misaligned delimiters in row: {line}') columnCellIndex = 0 if len(cellsContent) < numberOfColumns: # Colspan: Positions of | with respect to + need to be determined Loading @@ -426,9 +458,9 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR rowIndex = rowsTracker[columnIndex] handleCellContent(rows[rowIndex][columnIndex], content) else: raise ValueError("More cells than columns found") raise ValueError('More cells than columns found') else: raise ValueError("No separator line found for row starting") raise ValueError('No separator line found for row starting') if hasHeader and start >= headerSeparatorIndex: # table_row and auxiliar_row are part of data_rows for row in rows: Loading @@ -451,7 +483,7 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR for cell in gridRow: if cell.content is not None: # Replacing "<" by < cell.content = cell.content.replace("<", "<") cell.content = cell.content.replace('<', '<') # Bold replacements # Regex to detect markdown bold formatting in cell content Loading @@ -463,7 +495,6 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR if cell.content is not None: cell.content = matchItalic.sub(r'<i>\g<text></i>', cell.content) # Correct newlines characters for headerRow in headerRows: for cell in headerRow: Loading Loading @@ -525,25 +556,11 @@ def generateHtmlTableWithSpans(gridTable:str) -> str: Returns: The HTML table in string format. """ debug_output = [] def debug_print(msg): debug_output.append(str(msg)) # Convert message to string try: # Redirect print statements to our debug collector global print original_print = print print = debug_print gridHeader, gridBody = parseGridTableWithSpans(gridTable) # Restore original print print = original_print except Exception as e: debug_print("Grid table could not be generated") debug_text = "<br>".join(debug_output) # Now all items are strings return f'HTML TABLE COULD NOT BE GENERATED FROM MARKDOWN GRID TABLE.<br><pre>{debug_text}</pre>' printDebug('Grid table could not be generated') return f'HTML TABLE COULD NOT BE GENERATED FROM MARKDOWN GRID TABLE' # Generate table HTML... html = '<table>\n' Loading @@ -564,47 +581,47 @@ def generateHtmlTableWithSpans(gridTable:str) -> str: continue else: # Prepare content, in case there's a list if cell.content is not None and (matches := re.findall(r"\s*([-*+]|\s*\d+\.)\s+((?:(?!@).)+)@", cell.content)): # Update cell in new row list = "<ul>" if cell.content is not None and (matches := re.findall(r'\s*([-*+]|\s*\d+\.)\s+((?:(?!@).)+)@', cell.content)): # Update cell in new row list = '<ul>' # Build list the matches for match in matches: list += "<li>" + match[1] + "</li>" list += "</ul>" cell.content = re.sub(r"(\s*([-*+]|\s*\d+\.)\s+(?:(?!@).)+@)+", list, cell.content) list += '<li>' + match[1] + '</li>' list += '</ul>' cell.content = re.sub(r'(\s*([-*+]|\s*\d+\.)\s+(?:(?!@).)+@)+', list, cell.content) # Enforce left alignment if cell contains a list cell.alignment = "align=\"left\"" cell.alignment = _alignLeft rowspan = f" rowspan=\"{cell.rowspan}\"" if cell.rowspan > 1 else "" colspan = f" colspan=\"{cell.colspan}\"" if cell.colspan > 1 else "" html += f" <th{rowspan}{colspan} {cell.alignment}>{cell.content}</th>\n" html += " </tr>\n" html += " </thead>\n" rowspan = f' rowspan="{cell.rowspan}"' if cell.rowspan > 1 else '' colspan = f' colspan="{cell.colspan}"' if cell.colspan > 1 else '' html += f' <th{rowspan}{colspan} {cell.alignment}>{cell.content}</th>\n' html += ' </tr>\n' html += ' </thead>\n' html += " <tbody>\n" html += ' <tbody>\n' for row in gridBody: html += " <tr>\n" html += ' <tr>\n' for cell in row: if cell.rowspan == 0 or cell.colspan == 0: continue else: #Prepare content, in case there's a list if cell.content is not None and (matches := re.findall(r"\s*([-*+]|\s*\d+\.)\s+((?:(?!@).)+)@", cell.content)): # Update cell in new row list = "<ul>" if cell.content is not None and (matches := re.findall(r'\s*([-*+]|\s*\d+\.)\s+((?:(?!@).)+)@', cell.content)): # Update cell in new row list = '<ul>' # Build list the matches for match in matches: list += "<li>" + match[1] + "</li>" list += "</ul>" cell.content = re.sub(r"(\s*([-*+]|\s*\d+\.)\s+(?:(?!@).)+@)+",list, cell.content) list += f'<li>{match[1]}</li>' list += '</ul>' cell.content = re.sub(r'(\s*([-*+]|\s*\d+\.)\s+(?:(?!@).)+@)+', list, cell.content) # Enforce left alignment if cell contains a list cell.alignment = "align=\"left\"" cell.alignment = _alignLeft rowspan = f" rowspan=\"{cell.rowspan}\"" if cell.rowspan > 1 else "" colspan = f" colspan=\"{cell.colspan}\"" if cell.colspan > 1 else "" html += f" <td{rowspan}{colspan} {cell.alignment}>{cell.content}</td>\n" html += " </tr>\n" rowspan = f' rowspan="{cell.rowspan}"' if cell.rowspan > 1 else '' colspan = f' colspan="{cell.colspan}"' if cell.colspan > 1 else '' html += f' <td{rowspan}{colspan} {cell.alignment}>{cell.content}</td>\n' html += ' </tr>\n' html += " </tbody>\n" html += "</table>" html += ' </tbody>\n' html += '</table>' return html toMkdocs/makrdownTools.py→toMkdocs/markdownTools.py +37 −21 Original line number Diff line number Diff line Loading @@ -9,12 +9,13 @@ """ Various tools for markdown processing """ from __future__ import annotations from typing import Callable from dataclasses import dataclass import base64, hashlib from enum import Enum, auto from gridTableTools import generateHtmlTableWithSpans from gridTableTools import generateHtmlTableWithSpans, setLoggers as setGridTableLoggers from regexMatches import * # TODO use a verbosity level instead Loading @@ -23,12 +24,18 @@ veryVerbose = False printInfo = print printDebug = print printError = print def setScreenPrinters(info:callable = print, debug:callable = print) -> None: global printInfo, printDebug def setLoggers(info:Callable = print, debug:Callable = print, error:Callable= print) -> None: global printInfo, printDebug, printError printInfo = info printDebug = debug printError = error # Set the loggers for the grid table tools setGridTableLoggers(info, debug, error) def _shortHash(value:str, length:int) -> str: Loading Loading @@ -365,6 +372,28 @@ def analyseMarkdown(filename:str) -> Document: The document object. """ gridTable:str = '' def processGridTable() -> None: """ Process a grid table and convert it to an html table. This function adds the html table to the output clauses and clears the gridTable variable. """ nonlocal gridTable htmltable:str = '' try: htmltable = generateHtmlTableWithSpans(gridTable) printDebug(htmltable) except Exception as e: printError(f"Error: {e}") # TODO move this outside of the analyseMarkdown function !!! for row in htmltable: outClauses[-1].append(Line(row, LineType.TABLEROW)) gridTable = '' printInfo(f'Analyzing "{filename}"') # Read the file. Loading @@ -381,10 +410,7 @@ def analyseMarkdown(filename:str) -> Document: inTable = False tableHasSeparator = False inGridTable = False gridTableHasSeparator = False gridTable = "" for line in inLines: # Detect and handle codefences # For the moment we support only codefences that start and end # with 3 backticks. This is the most common way to define codefences. Loading Loading @@ -439,18 +465,7 @@ def analyseMarkdown(filename:str) -> Document: continue else: inGridTable = False # Mark the previous line as the last row in the table #outClauses[-1].lines[-1].lineType = LineType.TABLELASTROW # print(gridTable) try: htmltable = generateHtmlTableWithSpans(gridTable) print(htmltable) except Exception as e: print(f"Error: {e}") # TODO move this outside of the analyseMarkdown function !!! for row in htmltable: outClauses[-1].append(Line(row, LineType.TABLEROW)) gridTable = "" processGridTable() # continue with other matches # Detect notes Loading Loading @@ -486,9 +501,10 @@ def analyseMarkdown(filename:str) -> Document: # Just add the line to the current clause as text outClauses[-1].append(Line(line, _lineType)) return Document(outClauses, footnotes) # Process still unfinished cases if gridTable: processGridTable() return Document(outClauses, footnotes) toMkdocs/toMkdocs.py +47 −12 Original line number Diff line number Diff line Loading @@ -12,13 +12,49 @@ from __future__ import annotations import argparse, os, shutil from rich import print from makrdownTools import Line, Document, analyseMarkdown, setScreenPrinters from markdownTools import Line, Document, analyseMarkdown, setLoggers from regexMatches import match2spaceListIndention verbose = False veryVerbose = False def printDebug(text:str) -> None: """ Print a debug message. Args: text: The text of the debug message. """ if verbose: print(f'[dim]{text}') def printInfo(text:str) -> None: """ Print an information message. Args: text: The text of the information message. """ print(f'[green]{text}') def printWarning(text:str) -> None: """ Print a warning message. Args: text: The text of the warning message. """ print(f'[yellow]{text}') def printError(text:str) -> None: """ Print an error message. Args: text: The text of the error message. """ print(f'[red]{text}') def prepareForMkdocs(document:Document, includeHangingParagraphs:bool = False) -> None: """ Prepare the clauses for MkDocs. This includes removing the heading from the clauses and marking the clauses that are only for navigation. Loading Loading @@ -47,7 +83,7 @@ def prepareForMkdocs(document:Document, includeHangingParagraphs:bool = False) - # Check if there is a sub-clause in the next clause if i + 1 < len(document.clauses) and document.clauses[i+1].level > clause.level: # This is a hanging paragraph. Remove the text from the current clause. print(f'[yellow]Hanging paragraph in clause "{clause.title}" {"(removed)" if not includeHangingParagraphs else "(kept)"}') printWarning(f'Hanging paragraph in clause "{clause.title}" {"(removed)" if not includeHangingParagraphs else "(kept)"}') if not includeHangingParagraphs: document.clauses[i].lines = [] else: Loading @@ -72,15 +108,14 @@ def writeClausesMkDocs(document:Document, filename:str, navTitle:str, addNavTitl addNavTitle: Add the title as an extra navigation level to the navigation file. """ print(f'[green]Writing clauses to files') printInfo(f'Writing clauses to files') # create directory first os.makedirs(f'{os.path.dirname(filename)}/{navTitle}', exist_ok = True) # Write the files for i, f in enumerate(document.clauses): # write to single files, even empty ones if verbose: print(f'[dim]Writing "{f.clauseNumber}.md" - "{f.title}"') printDebug(f'Writing "{f.clauseNumber}.md" - "{f.title}"') with open(f'{os.path.dirname(filename)}/{navTitle}/{f.clauseNumber}.md', 'w') as file: # Add one empty line before the clause. This is done to avoid # a bug in MkDocs that does not display the first line of a clause Loading @@ -90,11 +125,10 @@ def writeClausesMkDocs(document:Document, filename:str, navTitle:str, addNavTitl # write nav.yml file print(f'[green]Writing "_nav.yml"') printInfo(f'Writing "_nav.yml"') indentation = ' ' if addNavTitle else '' # TODO make number of spaces configurable with open(f'{os.path.dirname(filename)}/_nav.yml', 'w') as file: if veryVerbose: print(f'[dim]Writing navigation file') printDebug(f'Writing navigation file') if addNavTitle: file.write(f'{indentation}- {navTitle}:\n') for i, f in enumerate(document.clauses): Loading Loading @@ -130,10 +164,10 @@ def copyMediaFiles(filename:str, navTitle:str, mediaDirectory:str = 'media') -> targetDirectory = f'{os.path.dirname(filename)}/{navTitle}/{mediaDirectory}' if os.path.exists(sourceDirectory): print(f'[green]Copying media files from "{sourceDirectory}" to "{targetDirectory}"') printInfo(f'Copying media files from "{sourceDirectory}" to "{targetDirectory}"') shutil.copytree(sourceDirectory, targetDirectory, dirs_exist_ok = True) else: print(f'[red]Media directory "{sourceDirectory}" does not exist') printError(f'Media directory "{sourceDirectory}" does not exist') def processDocument(args:argparse.Namespace) -> None: Loading Loading @@ -177,8 +211,9 @@ def main() -> None: parser.add_argument('document', type = str, help = 'a oneM2M markdown specification document to process') args = parser.parse_args() setScreenPrinters(info = lambda text: print(f'[green]{text}'), debug = lambda text: print(f'[dim]{text}')) setLoggers(info = printInfo, debug = printDebug, error = printError) processDocument(args) Loading Loading
.gitignore +1 −0 Original line number Diff line number Diff line Loading @@ -3,3 +3,4 @@ */ts-* */.python-version .python-version toMkdocs/__pycache__
toMkdocs/gridTableTools.py +123 −106 Original line number Diff line number Diff line Loading @@ -6,10 +6,25 @@ # """ Tools for working with grid tables in markdown files. """ from typing import Optional from typing import Optional, Callable from regexMatches import * _alignLeft = 'align="left"' _alignRight = 'align="right"' _alignCenter = 'align="center"' printInfo = print printDebug = print printError = print def setLoggers(info:Callable=print, debug:Callable=print, error:Callable=print) -> None: global printInfo, printDebug, printError printInfo = info printDebug = debug printError = error class GridCell: """ Represents a grid table cell. """ Loading @@ -27,8 +42,18 @@ class GridCell: self.auxiliarIndex:int = 0 def calculateAndSetAlignment(self, headerDelimiterPositions:list[int], delimiterPositions:list[int], defaultAlignments:list[str], hasHeader:bool) -> None: def calculateAndSetAlignment(self, headerDelimiterPositions:list[int], delimiterPositions:list[int], defaultAlignments:list[str], hasHeader:bool) -> None: """ Set the alignment of the cell based on the position of the delimiter. Args: headerDelimiterPositions: The positions of the header delimiters. delimiterPositions: The positions of the delimiters. defaultAlignments: The default alignments. hasHeader: True if the table has a header, False otherwise. """ if self.position is None: raise ValueError('Cell position must be set before calculating alignment.') Loading @@ -46,17 +71,17 @@ class GridCell: else: raise ValueError('Invalid table formatting') else: body_delimiter_index = 0 while body_delimiter_index in range(len(defaultAlignments)) and self.position > delimiterPositions[body_delimiter_index]: body_delimiter_index += 1 if body_delimiter_index in range(len(defaultAlignments)): if self.position < delimiterPositions[body_delimiter_index]: self.alignment = defaultAlignments[body_delimiter_index] elif self.position == delimiterPositions[body_delimiter_index]: self.alignment = defaultAlignments[body_delimiter_index] body_delimiter_index += 1 bodyDelimiterIndex = 0 while bodyDelimiterIndex < len(defaultAlignments) and self.position > delimiterPositions[bodyDelimiterIndex]: bodyDelimiterIndex += 1 if bodyDelimiterIndex < len(defaultAlignments): if self.position < delimiterPositions[bodyDelimiterIndex]: self.alignment = defaultAlignments[bodyDelimiterIndex] elif self.position == delimiterPositions[bodyDelimiterIndex]: self.alignment = defaultAlignments[bodyDelimiterIndex] bodyDelimiterIndex += 1 else: raise ValueError("Invalid table formatting") raise ValueError('Invalid table formatting') def __str__(self): Loading Loading @@ -91,6 +116,7 @@ class GridRow(): def __repr__(self): return self.__str__() class GridRowsTracker(): """ Represents the document object. """ def __init__(self, size:int) -> None: Loading Loading @@ -155,9 +181,10 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR cell.content = _c + nextListElementMark # Add list element end mark to know when the list element ends elif cell.listFlag and len(_c) > 0: # any other content when handling list is concatenated to the last list element _c = re.sub(r'\\\s*$', '\n', _c) cell.content += _c + nextListElementMark #add the list element end mark elif not _c: # separation between list and other paragraph cell.content += '\n' if not cell['content'].endswith('\n') else "" cell.content = _c + nextListElementMark #add the list element end mark elif not _c: # empty line. separation between list and other paragraph # cell.content = '\n' if not cell.content.endswith('\n') else "" cell.content = '\n' # cell content is always empty / None here. else: cell.content = re.sub(r'\\\s*$', '\n', _c) else: # Cell has content Loading @@ -173,8 +200,8 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR _c = re.sub(r'\\\s*$', '\n', _c) cell.content += " " + _c + nextListElementMark #add list element end mark elif len(_c) == 0: # separation between list and other paragraph if cell.list_flag: cell.list_flag = False if cell.listFlag: cell.listFlag = False cell.content += '\n\n' #end list by \n #content = re.sub(r'\\\s*$', "\n", content.strip()) cell.content += '\n' if not cell.content.endswith('\n') else '' Loading Loading @@ -205,7 +232,8 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR row[columnIndex].colspanAdjusted = True # Mark cell as adjusted def check_delimiter_alignment(line: str, delimiterPositions:list[int], delimiters: str = "|+") -> bool: def checkDelimiterAlignment(line: str, delimiterPositions:list[int], delimiters: str = "|+") -> bool: """ Check if delimiters in a row align with expected positions. Loading @@ -220,33 +248,29 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR if not line or not delimiterPositions: return False print(f"\nChecking line: '{line}'") print(f"Expected delimiter positions: {delimiterPositions}") printDebug(f'\nChecking line: "{line}"') printDebug(f'Expected delimiter positions: {delimiterPositions}') # For full separator lines (only +) if '+' in line and '|' not in line: currentPositions = [i for i, char in enumerate(line) if (char == '+' and i != 0)] print(f"Full separator line - Found + at positions: {currentPositions}") return all(delimiterPositions[-1] in currentPositions and line.startswith("+") and pos in delimiterPositions for pos in currentPositions) currentPositions = [i for i, char in enumerate(line) if (char == '+' and i > 0)] printDebug(f'Full separator line - Found + at positions: {currentPositions}') return all(delimiterPositions[-1] in currentPositions and line.startswith('+') and pos in delimiterPositions for pos in currentPositions) # For data lines (only |) if '|' in line and '+' not in line: currentPositions = [i for i, char in enumerate(line) if (char == '|' and i != 0)] print(f"Data line - Found | at positions: {currentPositions}") return all(delimiterPositions[-1] in currentPositions and line.startswith("|") and pos in delimiterPositions for pos in currentPositions) currentPositions = [i for i, char in enumerate(line) if (char == '|' and i > 0)] printDebug(f'Data line - Found | at positions: {currentPositions}') return all(delimiterPositions[-1] in currentPositions and line.startswith("|") and pos in delimiterPositions for pos in currentPositions) # For partial separators (mix of + and |) currentPositions = [i for i, char in enumerate(line) if (char in delimiters and i != 0)] print(f"Partial separator - Found delimiters at positions: {currentPositions}") print(f"Characters at those positions: {[line[pos] for pos in currentPositions]}") return all(delimiterPositions[-1] in currentPositions and (line.startswith("+") or line.startswith("|")) and pos in delimiterPositions for pos in currentPositions) currentPositions = [i for i, char in enumerate(line) if (char in delimiters and i > 0)] printDebug(f'Partial separator - Found delimiters at positions: {currentPositions}') printDebug(f'Characters at those positions: {[line[pos] for pos in currentPositions]}') return all(delimiterPositions[-1] in currentPositions and line.startswith(('+', '|')) and pos in delimiterPositions for pos in currentPositions) separatorIndices = [i for i, line in enumerate(lines) if isSeparator(line)] Loading @@ -254,8 +278,8 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR raise ValueError('No valid separators found in the provided grid table.') # Calculate max number of columns delimiterPositions:list[int] = [] numberOfColumns = 0 delimiterPositions = [] numberOfColumns:int = 0 for separatorIndex in separatorIndices: if (_cnt := lines[separatorIndex].count('+') - 1) > numberOfColumns: Loading @@ -263,10 +287,10 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR delimiterPositions = [] for rowIndex in range(numberOfColumns): delimiterPositionsStart = delimiterPositions[rowIndex - 1] if rowIndex != 0 else 0 delPositions = [lines[separatorIndex].find(delimiter, delimiterPositionsStart + 1) for delimiter in '+' if delimiter in lines[separatorIndex][delimiterPositionsStart + 1:]] delPositions = [lines[separatorIndex].find(delimiter, delimiterPositionsStart + 1) for delimiter in '+' if delimiter in lines[separatorIndex][delimiterPositionsStart + 1:]] delimiterPositions.append(min(delPositions) if delPositions else -1) # Determine delimter positions and alignments headerRows:GridTableRowList = [] dataRows:GridTableRowList = [] Loading @@ -278,15 +302,22 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR parts = re.split(r'\+', lines[index].strip('+')) #Calculate default alignments and positions of delimiters for partIndex in range(len(parts)): if parts[partIndex].startswith(':') and not parts[partIndex].endswith(':'): # Left alignment defaultAlignments.append('align="left"') elif not parts[partIndex].startswith(':') and parts[partIndex].endswith(':'): # Right alignment defaultAlignments.append('align="right"') # Left alignment if parts[partIndex].startswith(':') and not parts[partIndex].endswith(':'): defaultAlignments.append(_alignLeft) # Right alignment elif not parts[partIndex].startswith(':') and parts[partIndex].endswith(':'): defaultAlignments.append(_alignRight) # Center alignment else: defaultAlignments.append('align="center"') # Center alignment defaultAlignments.append(_alignCenter) # Delimiter position delimiterPositionsStart = delimiterPositions[partIndex - 1] if partIndex != 0 else 0 delPositions = [lines[index].find(delimiter, delimiterPositionsStart + 1) for delimiter in '+' if delimiter in lines[index][delimiterPositionsStart + 1:]] delPositions = [lines[index].find(delimiter, delimiterPositionsStart + 1) for delimiter in '+' if delimiter in lines[index][delimiterPositionsStart + 1:]] headerDelimiterPositions.append(min(delPositions) if delPositions else -1) if not hasHeader: Loading @@ -296,11 +327,13 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR # Calculate default alignments and positions of delimiters for part_index in range(len(parts)): if parts[part_index].startswith(':') and not parts[part_index].endswith(':'): default_alignments.append('align="left"') default_alignments.append(_alignLeft) elif not parts[part_index].startswith(':') and parts[part_index].endswith(':'): default_alignments.append('align="right"') default_alignments.append(_alignRight) else: default_alignments.append('align="center"') default_alignments.append(_alignCenter) for rowNumber in range(len(separatorIndices) - 1): rows:list[GridRow] = [] Loading @@ -314,8 +347,8 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR if isSeparator(line) and not inDataRow: inDataRow = True # Add delimiter alignment check for separator lines if not check_delimiter_alignment(line, delimiterPositions): raise ValueError(f"Misaligned delimiters in separator row: {line}") if not checkDelimiterAlignment(line, delimiterPositions): raise ValueError(f'Misaligned delimiters in separator row: {line}') parts = re.split(r'\s*\+\s*', line.strip('+')) delimiterIndex = 0 Loading Loading @@ -343,17 +376,17 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR # Regular data row or partial separator if matchGridTableBodySeparator.match(line): # Partial separator # Add delimiter alignment check for partial separators if not check_delimiter_alignment(line, delimiterPositions): raise ValueError(f"Misaligned delimiters in partial separator: {line}") if not checkDelimiterAlignment(line, delimiterPositions): raise ValueError(f'Misaligned delimiters in partial separator: {line}') cellsContent = re.split(r"[\|\+]", line.strip('|').strip('+')) # (?<!\\)[\|\+] cellsContent = re.split(r'[\|\+]', line.strip('|').strip('+')) # (?<!\\)[\|\+] #Add another row, set delimiters for each cell rows.append(GridRow(numberOfColumns)) auxDelimiterIndex = 0 auxiliarCellIndex = 0 for columnIndex, content in enumerate(cellsContent): if auxiliarCellIndex in range(numberOfColumns): if auxiliarCellIndex < numberOfColumns: auxDelimiterIndex += len(content) + 1 cell = rows[-1][auxiliarCellIndex] cell.position = auxDelimiterIndex # Position of cell delimiter + Loading Loading @@ -398,15 +431,14 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR continue else: raise ValueError("More cells than columns found") raise ValueError('More cells than columns found') else: # Data row cellsContent = line.strip() cellsContent = re.split(r"\|", line.strip('|')) cellsContent = re.split(r'\|', line.strip('|')) # Add delimiter alignment check if not check_delimiter_alignment(line, delimiterPositions): raise ValueError(f"Misaligned delimiters in row: {line}") if not checkDelimiterAlignment(line, delimiterPositions): raise ValueError(f'Misaligned delimiters in row: {line}') columnCellIndex = 0 if len(cellsContent) < numberOfColumns: # Colspan: Positions of | with respect to + need to be determined Loading @@ -426,9 +458,9 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR rowIndex = rowsTracker[columnIndex] handleCellContent(rows[rowIndex][columnIndex], content) else: raise ValueError("More cells than columns found") raise ValueError('More cells than columns found') else: raise ValueError("No separator line found for row starting") raise ValueError('No separator line found for row starting') if hasHeader and start >= headerSeparatorIndex: # table_row and auxiliar_row are part of data_rows for row in rows: Loading @@ -451,7 +483,7 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR for cell in gridRow: if cell.content is not None: # Replacing "<" by < cell.content = cell.content.replace("<", "<") cell.content = cell.content.replace('<', '<') # Bold replacements # Regex to detect markdown bold formatting in cell content Loading @@ -463,7 +495,6 @@ def parseGridTableWithSpans(gridTable:str) -> tuple[GridTableRowList, GridTableR if cell.content is not None: cell.content = matchItalic.sub(r'<i>\g<text></i>', cell.content) # Correct newlines characters for headerRow in headerRows: for cell in headerRow: Loading Loading @@ -525,25 +556,11 @@ def generateHtmlTableWithSpans(gridTable:str) -> str: Returns: The HTML table in string format. """ debug_output = [] def debug_print(msg): debug_output.append(str(msg)) # Convert message to string try: # Redirect print statements to our debug collector global print original_print = print print = debug_print gridHeader, gridBody = parseGridTableWithSpans(gridTable) # Restore original print print = original_print except Exception as e: debug_print("Grid table could not be generated") debug_text = "<br>".join(debug_output) # Now all items are strings return f'HTML TABLE COULD NOT BE GENERATED FROM MARKDOWN GRID TABLE.<br><pre>{debug_text}</pre>' printDebug('Grid table could not be generated') return f'HTML TABLE COULD NOT BE GENERATED FROM MARKDOWN GRID TABLE' # Generate table HTML... html = '<table>\n' Loading @@ -564,47 +581,47 @@ def generateHtmlTableWithSpans(gridTable:str) -> str: continue else: # Prepare content, in case there's a list if cell.content is not None and (matches := re.findall(r"\s*([-*+]|\s*\d+\.)\s+((?:(?!@).)+)@", cell.content)): # Update cell in new row list = "<ul>" if cell.content is not None and (matches := re.findall(r'\s*([-*+]|\s*\d+\.)\s+((?:(?!@).)+)@', cell.content)): # Update cell in new row list = '<ul>' # Build list the matches for match in matches: list += "<li>" + match[1] + "</li>" list += "</ul>" cell.content = re.sub(r"(\s*([-*+]|\s*\d+\.)\s+(?:(?!@).)+@)+", list, cell.content) list += '<li>' + match[1] + '</li>' list += '</ul>' cell.content = re.sub(r'(\s*([-*+]|\s*\d+\.)\s+(?:(?!@).)+@)+', list, cell.content) # Enforce left alignment if cell contains a list cell.alignment = "align=\"left\"" cell.alignment = _alignLeft rowspan = f" rowspan=\"{cell.rowspan}\"" if cell.rowspan > 1 else "" colspan = f" colspan=\"{cell.colspan}\"" if cell.colspan > 1 else "" html += f" <th{rowspan}{colspan} {cell.alignment}>{cell.content}</th>\n" html += " </tr>\n" html += " </thead>\n" rowspan = f' rowspan="{cell.rowspan}"' if cell.rowspan > 1 else '' colspan = f' colspan="{cell.colspan}"' if cell.colspan > 1 else '' html += f' <th{rowspan}{colspan} {cell.alignment}>{cell.content}</th>\n' html += ' </tr>\n' html += ' </thead>\n' html += " <tbody>\n" html += ' <tbody>\n' for row in gridBody: html += " <tr>\n" html += ' <tr>\n' for cell in row: if cell.rowspan == 0 or cell.colspan == 0: continue else: #Prepare content, in case there's a list if cell.content is not None and (matches := re.findall(r"\s*([-*+]|\s*\d+\.)\s+((?:(?!@).)+)@", cell.content)): # Update cell in new row list = "<ul>" if cell.content is not None and (matches := re.findall(r'\s*([-*+]|\s*\d+\.)\s+((?:(?!@).)+)@', cell.content)): # Update cell in new row list = '<ul>' # Build list the matches for match in matches: list += "<li>" + match[1] + "</li>" list += "</ul>" cell.content = re.sub(r"(\s*([-*+]|\s*\d+\.)\s+(?:(?!@).)+@)+",list, cell.content) list += f'<li>{match[1]}</li>' list += '</ul>' cell.content = re.sub(r'(\s*([-*+]|\s*\d+\.)\s+(?:(?!@).)+@)+', list, cell.content) # Enforce left alignment if cell contains a list cell.alignment = "align=\"left\"" cell.alignment = _alignLeft rowspan = f" rowspan=\"{cell.rowspan}\"" if cell.rowspan > 1 else "" colspan = f" colspan=\"{cell.colspan}\"" if cell.colspan > 1 else "" html += f" <td{rowspan}{colspan} {cell.alignment}>{cell.content}</td>\n" html += " </tr>\n" rowspan = f' rowspan="{cell.rowspan}"' if cell.rowspan > 1 else '' colspan = f' colspan="{cell.colspan}"' if cell.colspan > 1 else '' html += f' <td{rowspan}{colspan} {cell.alignment}>{cell.content}</td>\n' html += ' </tr>\n' html += " </tbody>\n" html += "</table>" html += ' </tbody>\n' html += '</table>' return html
toMkdocs/makrdownTools.py→toMkdocs/markdownTools.py +37 −21 Original line number Diff line number Diff line Loading @@ -9,12 +9,13 @@ """ Various tools for markdown processing """ from __future__ import annotations from typing import Callable from dataclasses import dataclass import base64, hashlib from enum import Enum, auto from gridTableTools import generateHtmlTableWithSpans from gridTableTools import generateHtmlTableWithSpans, setLoggers as setGridTableLoggers from regexMatches import * # TODO use a verbosity level instead Loading @@ -23,12 +24,18 @@ veryVerbose = False printInfo = print printDebug = print printError = print def setScreenPrinters(info:callable = print, debug:callable = print) -> None: global printInfo, printDebug def setLoggers(info:Callable = print, debug:Callable = print, error:Callable= print) -> None: global printInfo, printDebug, printError printInfo = info printDebug = debug printError = error # Set the loggers for the grid table tools setGridTableLoggers(info, debug, error) def _shortHash(value:str, length:int) -> str: Loading Loading @@ -365,6 +372,28 @@ def analyseMarkdown(filename:str) -> Document: The document object. """ gridTable:str = '' def processGridTable() -> None: """ Process a grid table and convert it to an html table. This function adds the html table to the output clauses and clears the gridTable variable. """ nonlocal gridTable htmltable:str = '' try: htmltable = generateHtmlTableWithSpans(gridTable) printDebug(htmltable) except Exception as e: printError(f"Error: {e}") # TODO move this outside of the analyseMarkdown function !!! for row in htmltable: outClauses[-1].append(Line(row, LineType.TABLEROW)) gridTable = '' printInfo(f'Analyzing "{filename}"') # Read the file. Loading @@ -381,10 +410,7 @@ def analyseMarkdown(filename:str) -> Document: inTable = False tableHasSeparator = False inGridTable = False gridTableHasSeparator = False gridTable = "" for line in inLines: # Detect and handle codefences # For the moment we support only codefences that start and end # with 3 backticks. This is the most common way to define codefences. Loading Loading @@ -439,18 +465,7 @@ def analyseMarkdown(filename:str) -> Document: continue else: inGridTable = False # Mark the previous line as the last row in the table #outClauses[-1].lines[-1].lineType = LineType.TABLELASTROW # print(gridTable) try: htmltable = generateHtmlTableWithSpans(gridTable) print(htmltable) except Exception as e: print(f"Error: {e}") # TODO move this outside of the analyseMarkdown function !!! for row in htmltable: outClauses[-1].append(Line(row, LineType.TABLEROW)) gridTable = "" processGridTable() # continue with other matches # Detect notes Loading Loading @@ -486,9 +501,10 @@ def analyseMarkdown(filename:str) -> Document: # Just add the line to the current clause as text outClauses[-1].append(Line(line, _lineType)) return Document(outClauses, footnotes) # Process still unfinished cases if gridTable: processGridTable() return Document(outClauses, footnotes)
toMkdocs/toMkdocs.py +47 −12 Original line number Diff line number Diff line Loading @@ -12,13 +12,49 @@ from __future__ import annotations import argparse, os, shutil from rich import print from makrdownTools import Line, Document, analyseMarkdown, setScreenPrinters from markdownTools import Line, Document, analyseMarkdown, setLoggers from regexMatches import match2spaceListIndention verbose = False veryVerbose = False def printDebug(text:str) -> None: """ Print a debug message. Args: text: The text of the debug message. """ if verbose: print(f'[dim]{text}') def printInfo(text:str) -> None: """ Print an information message. Args: text: The text of the information message. """ print(f'[green]{text}') def printWarning(text:str) -> None: """ Print a warning message. Args: text: The text of the warning message. """ print(f'[yellow]{text}') def printError(text:str) -> None: """ Print an error message. Args: text: The text of the error message. """ print(f'[red]{text}') def prepareForMkdocs(document:Document, includeHangingParagraphs:bool = False) -> None: """ Prepare the clauses for MkDocs. This includes removing the heading from the clauses and marking the clauses that are only for navigation. Loading Loading @@ -47,7 +83,7 @@ def prepareForMkdocs(document:Document, includeHangingParagraphs:bool = False) - # Check if there is a sub-clause in the next clause if i + 1 < len(document.clauses) and document.clauses[i+1].level > clause.level: # This is a hanging paragraph. Remove the text from the current clause. print(f'[yellow]Hanging paragraph in clause "{clause.title}" {"(removed)" if not includeHangingParagraphs else "(kept)"}') printWarning(f'Hanging paragraph in clause "{clause.title}" {"(removed)" if not includeHangingParagraphs else "(kept)"}') if not includeHangingParagraphs: document.clauses[i].lines = [] else: Loading @@ -72,15 +108,14 @@ def writeClausesMkDocs(document:Document, filename:str, navTitle:str, addNavTitl addNavTitle: Add the title as an extra navigation level to the navigation file. """ print(f'[green]Writing clauses to files') printInfo(f'Writing clauses to files') # create directory first os.makedirs(f'{os.path.dirname(filename)}/{navTitle}', exist_ok = True) # Write the files for i, f in enumerate(document.clauses): # write to single files, even empty ones if verbose: print(f'[dim]Writing "{f.clauseNumber}.md" - "{f.title}"') printDebug(f'Writing "{f.clauseNumber}.md" - "{f.title}"') with open(f'{os.path.dirname(filename)}/{navTitle}/{f.clauseNumber}.md', 'w') as file: # Add one empty line before the clause. This is done to avoid # a bug in MkDocs that does not display the first line of a clause Loading @@ -90,11 +125,10 @@ def writeClausesMkDocs(document:Document, filename:str, navTitle:str, addNavTitl # write nav.yml file print(f'[green]Writing "_nav.yml"') printInfo(f'Writing "_nav.yml"') indentation = ' ' if addNavTitle else '' # TODO make number of spaces configurable with open(f'{os.path.dirname(filename)}/_nav.yml', 'w') as file: if veryVerbose: print(f'[dim]Writing navigation file') printDebug(f'Writing navigation file') if addNavTitle: file.write(f'{indentation}- {navTitle}:\n') for i, f in enumerate(document.clauses): Loading Loading @@ -130,10 +164,10 @@ def copyMediaFiles(filename:str, navTitle:str, mediaDirectory:str = 'media') -> targetDirectory = f'{os.path.dirname(filename)}/{navTitle}/{mediaDirectory}' if os.path.exists(sourceDirectory): print(f'[green]Copying media files from "{sourceDirectory}" to "{targetDirectory}"') printInfo(f'Copying media files from "{sourceDirectory}" to "{targetDirectory}"') shutil.copytree(sourceDirectory, targetDirectory, dirs_exist_ok = True) else: print(f'[red]Media directory "{sourceDirectory}" does not exist') printError(f'Media directory "{sourceDirectory}" does not exist') def processDocument(args:argparse.Namespace) -> None: Loading Loading @@ -177,8 +211,9 @@ def main() -> None: parser.add_argument('document', type = str, help = 'a oneM2M markdown specification document to process') args = parser.parse_args() setScreenPrinters(info = lambda text: print(f'[green]{text}'), debug = lambda text: print(f'[dim]{text}')) setLoggers(info = printInfo, debug = printDebug, error = printError) processDocument(args) Loading