md_writer
trestle.core.markdown.md_writer
¤
Create formatted markdown files with optional yaml header.
logger
¤
Classes¤
MDWriter
¤
Simple class to create markdown files.
Source code in trestle/core/markdown/md_writer.py
class MDWriter():
"""Simple class to create markdown files."""
def __init__(self, file_path: pathlib.Path, header_comments_dict: Optional[Dict[str, str]] = None):
"""Initialize the class."""
self._file_path = file_path
self._lines = []
self._indent_level = 0
self._indent_size = 2
self._yaml_header = None
self._header_comments_dict = header_comments_dict
def _current_indent_space(self):
if self._indent_level <= 0:
return ''
return ' ' * (self._indent_level * self._indent_size)
def _add_line_raw(self, line: str) -> None:
out_line = '' if self._is_blank(line) else line
self._lines.append(out_line)
def _add_indent_level(self, delta: int) -> None:
self._indent_level += delta
def exists(self) -> bool:
"""Check if the file already exists."""
return self._file_path.exists()
def add_yaml_header(self, header: dict) -> None:
"""Add the yaml header."""
self._yaml_header = header
def set_indent_level(self, level: int) -> None:
"""Set the current indent level."""
self._indent_level = level
def set_indent_step_size(self, size: int) -> None:
"""Set the indent step size in spaces."""
self._indent_size = size
def _is_blank(self, line: str) -> bool:
return line.strip() == ''
def _prev_blank_line(self) -> bool:
return len(self._lines) > 0 and self._is_blank(self._lines[-1])
def new_line(self, line: str) -> None:
"""Add a line of text to the output."""
# prevent double empty lines
out_line = '' if self._is_blank(line) else self._current_indent_space() + line
if self._prev_blank_line() and out_line == '':
return
self._add_line_raw(out_line)
def new_paraline(self, line: str) -> None:
"""Add a paragraph and a line to output."""
self.new_paragraph()
self.new_line(line)
def new_paragraph(self):
"""Start a new paragraph."""
self.new_line('')
def new_header(self, level: int, title: str, add_new_line_after_header: bool = True) -> None:
"""Add new header."""
# headers might be separated by blank lines
self.new_paragraph()
self.new_line('#' * level + ' ' + title)
if add_new_line_after_header:
self.new_paragraph()
def new_hr(self) -> None:
"""Add horizontal rule."""
self.new_paragraph()
self.new_line(const.SSP_MD_HRULE_LINE)
self.new_paragraph()
def new_list(self, list_: List[Any]) -> None:
"""Add a list to the markdown."""
# in general this is a list of lists
# if string just write it out
if isinstance(list_, str):
if self._is_blank(list_):
self.new_paragraph()
else:
self.new_line('- ' + list_)
# else it is a sublist so indent
else:
self._add_indent_level(1)
self.new_paragraph()
for item in list_:
if self._indent_level <= 0:
self.new_paragraph()
self.new_list(item)
self._add_indent_level(-1)
def new_table(self, table_list: List[List[str]], header: List[str]):
"""Add table to the markdown. All rows must be of equal length."""
header_str = '| ' + ' | '.join(header) + ' |'
sep_str = '|---' * len(header) + '|'
self.new_line(header_str)
self.new_line(sep_str)
for row in table_list:
row_str = '| ' + ' | '.join(row) + ' |'
self.new_line(row_str)
def _check_header(self) -> None:
while len(self._lines) > 0 and self._lines[0] == '':
self._lines = self._lines[1:]
def write_out(self) -> None:
"""Write out the markdown file."""
self._check_header()
try:
self._file_path.parent.mkdir(exist_ok=True, parents=True)
with open(self._file_path, 'w', encoding=const.FILE_ENCODING) as f:
# Make sure yaml header is written first
if self._yaml_header:
f.write('---\n')
yaml = YAML()
yaml.indent(mapping=2, sequence=4, offset=2)
yaml.dump(self._yaml_header, f)
f.write('---\n\n')
f.write('\n'.join(self._lines))
# if last line has text it will need an extra \n at end
if self._lines and self._lines[-1]:
f.write('\n')
# insert helpful comments into the header happens after header is written out
for tag, comment in as_dict(self._header_comments_dict).items():
if tag in as_dict(self._yaml_header):
file_utils.insert_text_in_file(self._file_path, tag, comment)
except IOError as e:
logger.debug(f'md_writer error attempting to write out md file {self._file_path} {e}')
raise TrestleError(f'Error attempting to write out md file {self._file_path} {e}')
def get_lines(self) -> List[str]:
"""Return the current lines in the file."""
return self._lines
def get_text(self) -> str:
"""Get the text as currently written."""
return '\n'.join(self._lines)
def cull_headings(self, md_in: pathlib.Path, cull_list: List[str], strict_match: bool = False) -> None:
"""
Cull headers from the lines of input markdown file with optional strict string match.
Args:
md_in: the path of the markdown file being edited
cull_list: the list of strings in headers that are to be culled
strict_match: whether to require an exact string match on header key or just a substring
Returns None and creates new markdown at the path specified during MDWriter construction
It is allowed to overwrite the original file
"""
markdown_api = MarkdownAPI()
header, content = markdown_api.processor.process_markdown(md_in)
self._yaml_header = header
self._lines = content.delete_nodes_text(cull_list, strict_match)
self.write_out()
Methods¤
__init__(self, file_path, header_comments_dict=None)
special
¤
Initialize the class.
Source code in trestle/core/markdown/md_writer.py
def __init__(self, file_path: pathlib.Path, header_comments_dict: Optional[Dict[str, str]] = None):
"""Initialize the class."""
self._file_path = file_path
self._lines = []
self._indent_level = 0
self._indent_size = 2
self._yaml_header = None
self._header_comments_dict = header_comments_dict
add_yaml_header(self, header)
¤
Add the yaml header.
Source code in trestle/core/markdown/md_writer.py
def add_yaml_header(self, header: dict) -> None:
"""Add the yaml header."""
self._yaml_header = header
cull_headings(self, md_in, cull_list, strict_match=False)
¤
Cull headers from the lines of input markdown file with optional strict string match.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
md_in |
Path |
the path of the markdown file being edited |
required |
cull_list |
List[str] |
the list of strings in headers that are to be culled |
required |
strict_match |
bool |
whether to require an exact string match on header key or just a substring |
False |
Returns None and creates new markdown at the path specified during MDWriter construction It is allowed to overwrite the original file
Source code in trestle/core/markdown/md_writer.py
def cull_headings(self, md_in: pathlib.Path, cull_list: List[str], strict_match: bool = False) -> None:
"""
Cull headers from the lines of input markdown file with optional strict string match.
Args:
md_in: the path of the markdown file being edited
cull_list: the list of strings in headers that are to be culled
strict_match: whether to require an exact string match on header key or just a substring
Returns None and creates new markdown at the path specified during MDWriter construction
It is allowed to overwrite the original file
"""
markdown_api = MarkdownAPI()
header, content = markdown_api.processor.process_markdown(md_in)
self._yaml_header = header
self._lines = content.delete_nodes_text(cull_list, strict_match)
self.write_out()
exists(self)
¤
Check if the file already exists.
Source code in trestle/core/markdown/md_writer.py
def exists(self) -> bool:
"""Check if the file already exists."""
return self._file_path.exists()
get_lines(self)
¤
Return the current lines in the file.
Source code in trestle/core/markdown/md_writer.py
def get_lines(self) -> List[str]:
"""Return the current lines in the file."""
return self._lines
get_text(self)
¤
Get the text as currently written.
Source code in trestle/core/markdown/md_writer.py
def get_text(self) -> str:
"""Get the text as currently written."""
return '\n'.join(self._lines)
new_header(self, level, title, add_new_line_after_header=True)
¤
Add new header.
Source code in trestle/core/markdown/md_writer.py
def new_header(self, level: int, title: str, add_new_line_after_header: bool = True) -> None:
"""Add new header."""
# headers might be separated by blank lines
self.new_paragraph()
self.new_line('#' * level + ' ' + title)
if add_new_line_after_header:
self.new_paragraph()
new_hr(self)
¤
Add horizontal rule.
Source code in trestle/core/markdown/md_writer.py
def new_hr(self) -> None:
"""Add horizontal rule."""
self.new_paragraph()
self.new_line(const.SSP_MD_HRULE_LINE)
self.new_paragraph()
new_line(self, line)
¤
Add a line of text to the output.
Source code in trestle/core/markdown/md_writer.py
def new_line(self, line: str) -> None:
"""Add a line of text to the output."""
# prevent double empty lines
out_line = '' if self._is_blank(line) else self._current_indent_space() + line
if self._prev_blank_line() and out_line == '':
return
self._add_line_raw(out_line)
new_list(self, list_)
¤
Add a list to the markdown.
Source code in trestle/core/markdown/md_writer.py
def new_list(self, list_: List[Any]) -> None:
"""Add a list to the markdown."""
# in general this is a list of lists
# if string just write it out
if isinstance(list_, str):
if self._is_blank(list_):
self.new_paragraph()
else:
self.new_line('- ' + list_)
# else it is a sublist so indent
else:
self._add_indent_level(1)
self.new_paragraph()
for item in list_:
if self._indent_level <= 0:
self.new_paragraph()
self.new_list(item)
self._add_indent_level(-1)
new_paragraph(self)
¤
Start a new paragraph.
Source code in trestle/core/markdown/md_writer.py
def new_paragraph(self):
"""Start a new paragraph."""
self.new_line('')
new_paraline(self, line)
¤
Add a paragraph and a line to output.
Source code in trestle/core/markdown/md_writer.py
def new_paraline(self, line: str) -> None:
"""Add a paragraph and a line to output."""
self.new_paragraph()
self.new_line(line)
new_table(self, table_list, header)
¤
Add table to the markdown. All rows must be of equal length.
Source code in trestle/core/markdown/md_writer.py
def new_table(self, table_list: List[List[str]], header: List[str]):
"""Add table to the markdown. All rows must be of equal length."""
header_str = '| ' + ' | '.join(header) + ' |'
sep_str = '|---' * len(header) + '|'
self.new_line(header_str)
self.new_line(sep_str)
for row in table_list:
row_str = '| ' + ' | '.join(row) + ' |'
self.new_line(row_str)
set_indent_level(self, level)
¤
Set the current indent level.
Source code in trestle/core/markdown/md_writer.py
def set_indent_level(self, level: int) -> None:
"""Set the current indent level."""
self._indent_level = level
set_indent_step_size(self, size)
¤
Set the indent step size in spaces.
Source code in trestle/core/markdown/md_writer.py
def set_indent_step_size(self, size: int) -> None:
"""Set the indent step size in spaces."""
self._indent_size = size
write_out(self)
¤
Write out the markdown file.
Source code in trestle/core/markdown/md_writer.py
def write_out(self) -> None:
"""Write out the markdown file."""
self._check_header()
try:
self._file_path.parent.mkdir(exist_ok=True, parents=True)
with open(self._file_path, 'w', encoding=const.FILE_ENCODING) as f:
# Make sure yaml header is written first
if self._yaml_header:
f.write('---\n')
yaml = YAML()
yaml.indent(mapping=2, sequence=4, offset=2)
yaml.dump(self._yaml_header, f)
f.write('---\n\n')
f.write('\n'.join(self._lines))
# if last line has text it will need an extra \n at end
if self._lines and self._lines[-1]:
f.write('\n')
# insert helpful comments into the header happens after header is written out
for tag, comment in as_dict(self._header_comments_dict).items():
if tag in as_dict(self._yaml_header):
file_utils.insert_text_in_file(self._file_path, tag, comment)
except IOError as e:
logger.debug(f'md_writer error attempting to write out md file {self._file_path} {e}')
raise TrestleError(f'Error attempting to write out md file {self._file_path} {e}')
handler: python