Source code for mini_wiki.models

import os
import yaml
import markdown
from .utils import filename_to_title

md = markdown.Markdown(extensions=['markdown.extensions.codehilite',
                                   'markdown.extensions.toc'])


[docs]class PageError(Exception): """ A generic exception for all issues with pages. """
[docs]class ParseError(PageError): """ Exception raised when there was an error parsing a page. """
[docs]class NoRenderEngineError(PageError): """ Exception raised when there are no supported render engines. """
[docs]class Page: def __init__(self, filename=None, content=None, repo=None, config=None): self.filename = filename self.content = content or '' self.repo = repo self.config = config if config else {} if 'title' not in self.config: self.config['title'] = filename_to_title(self.filename) else: raise ValueError("You can't set the page's title in the metadata") self.title = self.config['title']
[docs] def save(self): """ Write a copy of the """ if not self.filename: raise PageError('No filename set') elif not self.repo: raise PageError('No git repository set') else: with open(self.filename, 'w') as f: f.write(self.format()) # Then do a `git add some_file.txt` self.repo.index.add([self.filename])
[docs] def commit(self, message, author): """ Commit the file. (i.e. run "git commit -m $message") Parameters ---------- message: str The commit message author: git.Actor The person making a change to the file """ self.save() # git add this_file.txt self.repo.index.commit(message, author=author)
[docs] def header(self): """ Create a jekyll-style header for the file. """ head = [] head.append('---') config = yaml.dump(self.config, default_flow_style=False).strip() head.append(config) head.append('---') return '\n'.join(head)
[docs] def format(self): """ Get a formatted form of the header and page content. """ text = [] text.append(self.header()) text.append('') text.append(self.content) return '\n'.join(text)
[docs] def to_html(self): """ Get a html version of the page's content. Depending on the 'format' key in the page's metadata, different rendering methods will be used. Rendering Methods: markdown Use the [markdown](https://pythonhosted.org/Markdown/) library. html Don't do anything to the content and return it as-is. """ if self.config.get('format', 'markdown').lower() in ('markdown', 'md'): return md.convert(self.content) elif self.config.get('format', '').lower() in ('html'): return self.content else: raise NoRenderEngineError('No render engine for file: {}'.format(self.filename))
def __str__(self): return self.format() @classmethod
[docs] def from_file(cls, filename): """ Given a path to a file, parse its contents and get the corresponding Page object. """ if not os.path.exists(filename): raise FileNotFoundError("{} doesn't exist".format(filename)) text = open(filename).read() header, body = cls.parse_text(text) header.pop('title', None) new_page = cls(filename=filename, config=header, content=body) return new_page
@staticmethod
[docs] def parse_text(text): """ Given a string of text, parse it and retrieve the header and content. The text must have the following format: - Line with '---' to specify the start of the header section - One or more lines of valid YAML that contain metadata about the file - Line with '---' to specify the end of the header section - A blank line - Body of the document (may be empty) Parameters ---------- text: str The text to parse Returns ------- header: dict A dictionary containing metadata about the file body: str The body of the file Raises ------ ParseError When the file isn't formatted correctly """ if len(text.strip()) == 0: raise ParseError('Cannot parse an empty file') lines = (line for line in text.splitlines()) # Read in the first line current_line = next(lines) if current_line.strip() != '---': raise ParseError('File must have a header section') # Read from line 2 up to and including the end of the header current_line = next(lines) header_buffer = [] while current_line.strip() != '---': if current_line.strip() == '': # Found a blank line raise ParseError('Empty line in page header') else: header_buffer.append(current_line) current_line = next(lines) # Parse the header header = yaml.load('\n'.join(header_buffer)) # Next read the empty line after the header current_line = next(lines) if current_line.strip() != '': raise ParseError('There must be an empty line after the header section') # Every line from now on is the body, so just concatenate that body = '\n'.join(lines) body = body.strip() return header, body