Source code for council.utils.code_parser
from __future__ import annotations
from typing import Iterable, List, Optional
from more_itertools import first, last
[docs]
class CodeBlock:
"""Represents a code block with a language and code."""
def __init__(self, language: Optional[str], code: str) -> None:
self._language = language
self._code = code
@property
def code(self) -> str:
return self._code
@property
def language(self) -> Optional[str]:
return self._language
def is_language(self, language: str) -> bool:
if self._language is None:
return False
return self._language.casefold() == language.casefold()
[docs]
class CodeParser:
"""
Helper class for parsing and extracting code blocks from text.
Provides methods to parse text containing code blocks delimited by triple backticks.
Code blocks can optionally specify a language identifier after the opening delimiter.
Example of a code block:
.. code-block:: text
```python
def hello():
print("Hello world")
```
"""
DELIMITER = "```"
@staticmethod
def iter_code_blocs(language: Optional[str] = None, text: str = "") -> Iterable[CodeBlock]:
return CodeParser._build_generator(language, text)
@staticmethod
def extract_code_blocs(language: Optional[str] = None, text: str = "") -> List[CodeBlock]:
return list(CodeParser._build_generator(language, text))
@staticmethod
def find_first(language: Optional[str] = None, text: str = "") -> Optional[CodeBlock]:
blocks = CodeParser._build_generator(language, text)
return first(blocks, None)
@staticmethod
def find_last(language: Optional[str] = None, text: str = "") -> Optional[CodeBlock]:
blocks = CodeParser._build_generator(language, text)
return last(blocks, None)
@staticmethod
def _build_generator(language: Optional[str], text: str = "") -> Iterable[CodeBlock]:
actual_block_language: Optional[str] = None
code_lines: Optional[List[str]] = None
for line in text.split("\n"):
if line.startswith(CodeParser.DELIMITER) and code_lines is None:
actual_block_language = line[len(CodeParser.DELIMITER) :].strip()
code_lines = []
continue
if line == CodeParser.DELIMITER:
if (actual_block_language == language or language is None) and code_lines is not None:
yield CodeBlock(language, "\n".join(code_lines))
code_lines = None
actual_block_language = None
continue
if code_lines is not None:
code_lines.append(line)