parent
11be11eadc
commit
1013ff5618
|
@ -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(
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
Reference in New Issue