Перенос моделей Markdown и YAML из resources в models

#1
master
Антон Касимов 2019-02-16 20:05:09 +03:00
parent 11be11eadc
commit 1013ff5618
5 changed files with 174 additions and 157 deletions

View File

@ -1,11 +1,11 @@
from pathlib import Path
from pyramid.config import Configurator from pyramid.config import Configurator
from pyramid.path import AssetResolver from pyramid.path import AssetResolver
from .resources import Root
from watchdog.observers import Observer from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler from watchdog.events import FileSystemEventHandler
from pathlib import Path from flatfilecms.resources import Root
class PagesEventHandler(FileSystemEventHandler): class PagesEventHandler(FileSystemEventHandler):
@ -54,10 +54,10 @@ def main(global_config, **settings):
watchdog = asbool(settings.get( watchdog = asbool(settings.get(
'watchdog', 'false')) 'watchdog', 'false'))
settings['watchdog'] = watchdog settings['watchdog'] = watchdog
settings.setdefault('pages_dir', 'pages') settings.setdefault('pages_path', 'pages')
settings.setdefault('data_dir', 'data') settings.setdefault('data_path', 'data')
pages = Root(settings['pages_dir']) pages = Root(settings['pages_path'], settings['data_path'])
def factory(request): def factory(request):
return pages return pages
@ -69,7 +69,7 @@ def main(global_config, **settings):
config.scan() config.scan()
if watchdog: if watchdog:
path = AssetResolver().resolve( path = AssetResolver().resolve(
settings['pages_dir']).abspath() settings['pages_path']).abspath()
observer = Observer() observer = Observer()
event_handler = PagesEventHandler(pages, path) event_handler = PagesEventHandler(pages, path)
observer.schedule( observer.schedule(

140
flatfilecms/models.py Normal file
View File

@ -0,0 +1,140 @@
from pyramid.path import AssetResolver
from pathlib import PurePath
import yaml
import frontmatter
import markdown
from zope.interface import (Interface, implementer)
from flatfilecms.filters import IgnoreExtension
class Loader(yaml.Loader):
def __init__(self, stream):
super(Loader, self).__init__(stream)
Loader.add_constructor('!include', Loader.include)
Loader.add_constructor('!markdown', Loader.markdown)
def include(self, node):
if isinstance(node, yaml.ScalarNode):
return self.extractFile(self.construct_scalar(node))
elif isinstance(node, yaml.SequenceNode):
return [self.extractFile(filename)
for filename in self.construct_sequence(node)]
elif isinstance(node, yaml.MappingNode):
result = {}
for k, v in self.construct_mapping(node).items():
result[k] = self.extractFile(v)
return result
else:
raise yaml.constructor.ConstructorError(
"Error:: unrecognised node type in !include statement")
def markdown(self, node):
if not isinstance(node, yaml.ScalarNode):
raise yaml.constructor.ConstructorError(
"Error:: unrecognised node type in !markdown statement")
m = self.construct_scalar(node)
return markdown.markdown(
m,
extensions=[IgnoreExtension(), 'markdown.extensions.extra'],
output_format='html5',
tab_length=2)
def extractFile(self, filename):
path = PurePath(self.data_path) / filename
f = AssetResolver().resolve(str(path)).stream()
if f:
if path.suffix in ['.yaml', '.yml', '.json']:
return yaml.load(f, Loader)
return f.read().decode()
def LoaderFactory(data_path):
cl = Loader
cl.data_path = data_path
return cl
class CustomYAMLHandler(frontmatter.YAMLHandler):
def __init__(self, data_path):
self.loader = LoaderFactory(data_path)
def load(self, fm, **kwargs):
return yaml.load(fm, self.loader)
class Folder(dict):
def __init__(self, name, parent, path):
self.path = path
self.name = name
self.parent = parent
if parent is not None:
self.url = "f{parent.url}/{name}"
self.data_path = parent.data_path
else:
self.url = ''
for entry in AssetResolver().resolve(path).listdir():
asset = f"{path}/{entry}"
if AssetResolver().resolve(asset).isdir():
self.create_dir(asset)
else:
self.create_file(asset)
def create_file(self, asset):
path = PurePath(asset)
if path.suffix == '.md':
self[path.stem] = Markdown(path.stem, self, asset)
elif path.suffix == '.yaml':
self[path.stem] = YAML(path.stem, self, asset)
elif path.suffix == '.j2' or path.suffix == '.jinja2':
self[path.stem] = Jinja2(path.stem, self, asset)
else:
name = path.name
# Если имя файла не index.html,
# то отдавать по имени файла
if name == 'index.html':
name = 'index'
self[name] = Document(name, self, asset)
def create_dir(self, asset):
path = PurePath(asset)
self[path.name] = Folder(path.name, self, asset)
class Document(object):
def __init__(self, name, parent, path):
self.name = name
self.parent = parent
self.path = path
self.url = f"{parent.url}/{name}"
self.data_path = parent.data_path
class IMarkdown(Interface):
""" Интерфейс-маркер для генератора markdown """
@implementer(IMarkdown)
class Markdown(Document):
def __init__(self, name, parent, path):
super(Markdown, self).__init__('', parent, path)
self.page = frontmatter.load(
AssetResolver().resolve(self.path).stream(),
handler=CustomYAMLHandler(self.data_path),
).to_dict()
@implementer(IMarkdown)
class YAML(Document):
def __init__(self, name, parent, path):
super(YAML, self).__init__('', parent, path)
self.page = yaml.load(
AssetResolver().resolve(self.path).stream(),
LoaderFactory(self.data_path))
class Jinja2(Document):
pass

View File

@ -1,5 +1,4 @@
from pyramid.path import AssetResolver from flatfilecms.models import Folder
from pathlib import PurePath
def flat(d, path): def flat(d, path):
@ -15,61 +14,10 @@ def flat(d, path):
return structure return structure
class Folder(dict): class Root(Folder):
def __init__(self, name, parent, path): def __init__(self, path, data_path):
self.__path__ = path self.data_path = data_path
self.__name__ = name super(Root, self).__init__('', None, path)
self.__parent__ = parent
for entry in AssetResolver().resolve(path).listdir():
asset = f"{path}/{entry}"
if AssetResolver().resolve(asset).isdir():
self.create_dir(asset)
else:
self.create_file(asset)
def create_file(self, asset):
path = PurePath(asset)
if path.suffix == '.md':
self[path.stem] = Markdown(path.stem, self, asset)
elif path.suffix == '.yaml':
self[path.stem] = YAML(path.stem, self, asset)
elif path.suffix == '.j2' or path.suffix == '.jinja2':
self[path.stem] = Jinja2(path.stem, self, asset)
else:
name = path.name
# Если имя файла не index.html,
# то отдавать по имени файла
if name == 'index.html':
name = 'index'
self[name] = Document(name, self, asset)
def create_dir(self, asset):
path = PurePath(asset)
self[path.name] = Folder(path.name, self, asset)
def structure(self, base_dir=''): def structure(self, base_dir=''):
return flat(self, base_dir) return flat(self, base_dir)
class Root(Folder):
def __init__(self, path):
super(Root, self).__init__('', None, path)
class Document(object):
def __init__(self, name, parent, path):
self.__name__ = name
self.__parent__ = parent
self.__path__ = path
class Markdown(Document):
pass
class YAML(Document):
pass
class Jinja2(Document):
pass

View File

@ -7,70 +7,14 @@ from pyramid.httpexceptions import (HTTPFound, HTTPNotFound)
from pyramid.response import FileResponse from pyramid.response import FileResponse
from pyramid.path import AssetResolver from pyramid.path import AssetResolver
from pathlib import PurePath from pathlib import PurePath
from ..resources import (Folder, Document, Markdown, YAML, Jinja2)
import yaml import yaml
import frontmatter
import markdown
from ..filters import IgnoreExtension from flatfilecms.models import (
Folder,
Document,
class Loader(yaml.Loader): IMarkdown,
def __init__(self, stream): Jinja2,
super(Loader, self).__init__(stream) LoaderFactory)
Loader.add_constructor('!include', Loader.include)
Loader.add_constructor('!markdown', Loader.markdown)
def include(self, node):
if isinstance(node, yaml.ScalarNode):
return self.extractFile(self.construct_scalar(node))
elif isinstance(node, yaml.SequenceNode):
return [self.extractFile(filename)
for filename in self.construct_sequence(node)]
elif isinstance(node, yaml.MappingNode):
result = {}
for k, v in self.construct_mapping(node).items():
result[k] = self.extractFile(v)
return result
else:
raise yaml.constructor.ConstructorError(
"Error:: unrecognised node type in !include statement")
def markdown(self, node):
if not isinstance(node, yaml.ScalarNode):
raise yaml.constructor.ConstructorError(
"Error:: unrecognised node type in !markdown statement")
m = self.construct_scalar(node)
return markdown.markdown(
m,
extensions=[IgnoreExtension(), 'markdown.extensions.extra'],
output_format='html5',
tab_length=2)
def extractFile(self, filename):
path = PurePath(self.data_dir) / filename
f = AssetResolver().resolve(str(path)).stream()
if f:
if path.suffix in ['.yaml', '.yml', '.json']:
return yaml.load(f, Loader)
return f.read().decode()
def LoaderFactory(data_dir):
cl = Loader
cl.data_dir = data_dir
return cl
class CustomYAMLHandler(frontmatter.YAMLHandler):
def __init__(self, data_dir):
self.loader = LoaderFactory(data_dir)
def load(self, fm, **kwargs):
return yaml.load(fm, self.loader)
class PagesView: class PagesView:
@ -87,45 +31,30 @@ class PagesView:
@view_config(context=Document) @view_config(context=Document)
def document(self): def document(self):
return FileResponse( return FileResponse(
AssetResolver().resolve(self.context.__path__).abspath(), AssetResolver().resolve(self.context.path).abspath(),
request=self.request) request=self.request)
@view_config(context=IMarkdown)
def process_yaml(self): def process_yaml(self):
if 'redirect' in self.post: post = self.context.page
return HTTPFound(location=self.post.redirect) if 'redirect' in post:
if 'menu' not in self.post: return HTTPFound(location=post['redirect'])
self.post['menu'] = yaml.load( if 'menu' not in post:
post['menu'] = yaml.load(
AssetResolver().resolve( AssetResolver().resolve(
str(PurePath(self.request.registry.settings['data_dir']) / str(PurePath(self.context.data_path) /
'menu/default.yaml')).stream(), 'menu/default.yaml')).stream(),
LoaderFactory(self.request.registry.settings['data_dir'])) LoaderFactory(self.context.data_path))
response = render_to_response( response = render_to_response(
'{0}.jinja2'.format( '{0}.jinja2'.format(
self.post.get('template', 'default')), post.get('template', 'default')),
self.post, post,
request=self.request request=self.request
) )
if 'content_type' in self.post: if 'content_type' in post:
response.content_type = self.post['content_type'] response.content_type = post['content_type']
return response return response
@view_config(context=YAML)
def yaml(self):
self.post = yaml.load(
AssetResolver().resolve(
self.context.__path__).stream(),
LoaderFactory(self.request.registry.settings['data_dir']))
return self.process_yaml()
@view_config(context=Markdown)
def markdown(self):
self.post = frontmatter.load(
AssetResolver().resolve(
self.context.__path__).stream(),
handler=CustomYAMLHandler(self.request.registry.settings['data_dir']),
).to_dict()
return self.process_yaml()
@view_config(context=Jinja2) @view_config(context=Jinja2)
def jinja2(self): def jinja2(self):
return render_to_response( return render_to_response(

View File

@ -4,7 +4,7 @@ import pytest
@pytest.fixture @pytest.fixture
def pages(): def pages():
from flatfilecms.resources import Root from flatfilecms.resources import Root
return Root('../tests/pages') return Root('../tests/pages', '../tests/data')
def test_loading(pages): def test_loading(pages):
@ -12,5 +12,5 @@ def test_loading(pages):
def test_db_contains_pages(pages): def test_db_contains_pages(pages):
assert pages['index'].title == 'Заглушка для тестов БД' assert pages['index'].page['title'] == 'Заглушка для тестов БД'
pass pass