2023-02-10 15:44:42 +00:00
|
|
|
"""reStructuredText parser.
|
|
|
|
|
|
|
|
Contains parser for md files.
|
|
|
|
|
|
|
|
"""
|
|
|
|
import re
|
|
|
|
from pathlib import Path
|
2023-05-13 08:36:17 +00:00
|
|
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
2023-02-10 15:44:42 +00:00
|
|
|
|
|
|
|
from parser.file.base_parser import BaseParser
|
|
|
|
|
2023-05-12 10:02:25 +00:00
|
|
|
|
2023-02-10 15:44:42 +00:00
|
|
|
class RstParser(BaseParser):
|
|
|
|
"""reStructuredText parser.
|
|
|
|
|
|
|
|
Extract text from .rst files.
|
|
|
|
Returns dictionary with keys as headers and values as the text between headers.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(
|
2023-05-12 10:02:25 +00:00
|
|
|
self,
|
|
|
|
*args: Any,
|
|
|
|
remove_hyperlinks: bool = True,
|
|
|
|
remove_images: bool = True,
|
|
|
|
remove_table_excess: bool = True,
|
|
|
|
remove_interpreters: bool = True,
|
|
|
|
remove_directives: bool = True,
|
|
|
|
remove_whitespaces_excess: bool = True,
|
|
|
|
# Be carefull with remove_characters_excess, might cause data loss
|
|
|
|
remove_characters_excess: bool = True,
|
|
|
|
**kwargs: Any,
|
2023-02-10 15:44:42 +00:00
|
|
|
) -> None:
|
|
|
|
"""Init params."""
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self._remove_hyperlinks = remove_hyperlinks
|
|
|
|
self._remove_images = remove_images
|
|
|
|
self._remove_table_excess = remove_table_excess
|
2023-02-12 18:29:40 +00:00
|
|
|
self._remove_interpreters = remove_interpreters
|
|
|
|
self._remove_directives = remove_directives
|
2023-02-10 15:44:42 +00:00
|
|
|
self._remove_whitespaces_excess = remove_whitespaces_excess
|
|
|
|
self._remove_characters_excess = remove_characters_excess
|
2023-03-08 00:07:53 +00:00
|
|
|
|
2023-02-10 15:44:42 +00:00
|
|
|
def rst_to_tups(self, rst_text: str) -> List[Tuple[Optional[str], str]]:
|
|
|
|
"""Convert a reStructuredText file to a dictionary.
|
|
|
|
|
|
|
|
The keys are the headers and the values are the text under each header.
|
|
|
|
|
|
|
|
"""
|
|
|
|
rst_tups: List[Tuple[Optional[str], str]] = []
|
|
|
|
lines = rst_text.split("\n")
|
|
|
|
|
|
|
|
current_header = None
|
|
|
|
current_text = ""
|
|
|
|
|
|
|
|
for i, line in enumerate(lines):
|
|
|
|
header_match = re.match(r"^[^\S\n]*[-=]+[^\S\n]*$", line)
|
2023-05-12 10:02:25 +00:00
|
|
|
if header_match and i > 0 and (
|
|
|
|
len(lines[i - 1].strip()) == len(header_match.group().strip()) or lines[i - 2] == lines[i - 2]):
|
2023-02-10 15:44:42 +00:00
|
|
|
if current_header is not None:
|
|
|
|
if current_text == "" or None:
|
|
|
|
continue
|
|
|
|
# removes the next heading from current Document
|
|
|
|
if current_text.endswith(lines[i - 1] + "\n"):
|
|
|
|
current_text = current_text[:len(current_text) - len(lines[i - 1] + "\n")]
|
2023-03-13 15:14:33 +00:00
|
|
|
rst_tups.append((current_header, current_text))
|
2023-02-10 15:44:42 +00:00
|
|
|
|
|
|
|
current_header = lines[i - 1]
|
|
|
|
current_text = ""
|
|
|
|
else:
|
|
|
|
current_text += line + "\n"
|
2023-03-08 00:07:53 +00:00
|
|
|
|
2023-03-13 15:14:33 +00:00
|
|
|
rst_tups.append((current_header, current_text))
|
2023-02-10 15:44:42 +00:00
|
|
|
|
2023-05-12 10:02:25 +00:00
|
|
|
# TODO: Format for rst
|
2023-02-10 15:44:42 +00:00
|
|
|
#
|
|
|
|
# if current_header is not None:
|
|
|
|
# # pass linting, assert keys are defined
|
|
|
|
# rst_tups = [
|
|
|
|
# (re.sub(r"#", "", cast(str, key)).strip(), re.sub(r"<.*?>", "", value))
|
|
|
|
# for key, value in rst_tups
|
|
|
|
# ]
|
|
|
|
# else:
|
|
|
|
# rst_tups = [
|
|
|
|
# (key, re.sub("\n", "", value)) for key, value in rst_tups
|
|
|
|
# ]
|
|
|
|
|
|
|
|
if current_header is None:
|
|
|
|
rst_tups = [
|
|
|
|
(key, re.sub("\n", "", value)) for key, value in rst_tups
|
|
|
|
]
|
|
|
|
return rst_tups
|
|
|
|
|
|
|
|
def remove_images(self, content: str) -> str:
|
|
|
|
pattern = r"\.\. image:: (.*)"
|
|
|
|
content = re.sub(pattern, "", content)
|
|
|
|
return content
|
|
|
|
|
|
|
|
def remove_hyperlinks(self, content: str) -> str:
|
|
|
|
pattern = r"`(.*?) <(.*?)>`_"
|
|
|
|
content = re.sub(pattern, r"\1", content)
|
|
|
|
return content
|
|
|
|
|
2023-02-12 18:29:40 +00:00
|
|
|
def remove_directives(self, content: str) -> str:
|
|
|
|
"""Removes reStructuredText Directives"""
|
|
|
|
pattern = r"`\.\.([^:]+)::"
|
|
|
|
content = re.sub(pattern, "", content)
|
|
|
|
return content
|
|
|
|
|
|
|
|
def remove_interpreters(self, content: str) -> str:
|
|
|
|
"""Removes reStructuredText Interpreted Text Roles"""
|
|
|
|
pattern = r":(\w+):"
|
|
|
|
content = re.sub(pattern, "", content)
|
|
|
|
return content
|
|
|
|
|
2023-02-10 15:44:42 +00:00
|
|
|
def remove_table_excess(self, content: str) -> str:
|
|
|
|
"""Pattern to remove grid table separators"""
|
|
|
|
pattern = r"^\+[-]+\+[-]+\+$"
|
|
|
|
content = re.sub(pattern, "", content, flags=re.MULTILINE)
|
|
|
|
return content
|
|
|
|
|
|
|
|
def remove_whitespaces_excess(self, content: List[Tuple[str, Any]]) -> List[Tuple[str, Any]]:
|
|
|
|
"""Pattern to match 2 or more consecutive whitespaces"""
|
|
|
|
pattern = r"\s{2,}"
|
|
|
|
content = [(key, re.sub(pattern, " ", value)) for key, value in content]
|
|
|
|
return content
|
|
|
|
|
|
|
|
def remove_characters_excess(self, content: List[Tuple[str, Any]]) -> List[Tuple[str, Any]]:
|
|
|
|
"""Pattern to match 2 or more consecutive characters"""
|
|
|
|
pattern = r"(\S)\1{2,}"
|
|
|
|
content = [(key, re.sub(pattern, r"\1\1\1", value, flags=re.MULTILINE)) for key, value in content]
|
|
|
|
return content
|
|
|
|
|
|
|
|
def _init_parser(self) -> Dict:
|
|
|
|
"""Initialize the parser with the config."""
|
|
|
|
return {}
|
|
|
|
|
|
|
|
def parse_tups(
|
2023-05-12 10:02:25 +00:00
|
|
|
self, filepath: Path, errors: str = "ignore"
|
2023-02-10 15:44:42 +00:00
|
|
|
) -> List[Tuple[Optional[str], str]]:
|
|
|
|
"""Parse file into tuples."""
|
|
|
|
with open(filepath, "r") as f:
|
|
|
|
content = f.read()
|
|
|
|
if self._remove_hyperlinks:
|
|
|
|
content = self.remove_hyperlinks(content)
|
|
|
|
if self._remove_images:
|
|
|
|
content = self.remove_images(content)
|
|
|
|
if self._remove_table_excess:
|
|
|
|
content = self.remove_table_excess(content)
|
2023-02-12 18:29:40 +00:00
|
|
|
if self._remove_directives:
|
|
|
|
content = self.remove_directives(content)
|
|
|
|
if self._remove_interpreters:
|
|
|
|
content = self.remove_interpreters(content)
|
2023-02-10 15:44:42 +00:00
|
|
|
rst_tups = self.rst_to_tups(content)
|
|
|
|
if self._remove_whitespaces_excess:
|
|
|
|
rst_tups = self.remove_whitespaces_excess(rst_tups)
|
|
|
|
if self._remove_characters_excess:
|
|
|
|
rst_tups = self.remove_characters_excess(rst_tups)
|
|
|
|
return rst_tups
|
|
|
|
|
|
|
|
def parse_file(
|
2023-05-12 10:02:25 +00:00
|
|
|
self, filepath: Path, errors: str = "ignore"
|
2023-02-10 15:44:42 +00:00
|
|
|
) -> Union[str, List[str]]:
|
|
|
|
"""Parse file into string."""
|
|
|
|
tups = self.parse_tups(filepath, errors=errors)
|
|
|
|
results = []
|
|
|
|
# TODO: don't include headers right now
|
|
|
|
for header, value in tups:
|
|
|
|
if header is None:
|
|
|
|
results.append(value)
|
|
|
|
else:
|
|
|
|
results.append(f"\n\n{header}\n{value}")
|
|
|
|
return results
|