parent
11be11eadc
commit
1013ff5618
|
@ -1,11 +1,11 @@
|
|||
from pathlib import Path
|
||||
|
||||
from pyramid.config import Configurator
|
||||
from pyramid.path import AssetResolver
|
||||
from .resources import Root
|
||||
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
from pathlib import Path
|
||||
from flatfilecms.resources import Root
|
||||
|
||||
|
||||
class PagesEventHandler(FileSystemEventHandler):
|
||||
|
@ -54,10 +54,10 @@ def main(global_config, **settings):
|
|||
watchdog = asbool(settings.get(
|
||||
'watchdog', 'false'))
|
||||
settings['watchdog'] = watchdog
|
||||
settings.setdefault('pages_dir', 'pages')
|
||||
settings.setdefault('data_dir', 'data')
|
||||
settings.setdefault('pages_path', 'pages')
|
||||
settings.setdefault('data_path', 'data')
|
||||
|
||||
pages = Root(settings['pages_dir'])
|
||||
pages = Root(settings['pages_path'], settings['data_path'])
|
||||
|
||||
def factory(request):
|
||||
return pages
|
||||
|
@ -69,7 +69,7 @@ def main(global_config, **settings):
|
|||
config.scan()
|
||||
if watchdog:
|
||||
path = AssetResolver().resolve(
|
||||
settings['pages_dir']).abspath()
|
||||
settings['pages_path']).abspath()
|
||||
observer = Observer()
|
||||
event_handler = PagesEventHandler(pages, path)
|
||||
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 pathlib import PurePath
|
||||
from flatfilecms.models import Folder
|
||||
|
||||
|
||||
def flat(d, path):
|
||||
|
@ -15,61 +14,10 @@ def flat(d, path):
|
|||
return structure
|
||||
|
||||
|
||||
class Folder(dict):
|
||||
def __init__(self, name, parent, path):
|
||||
self.__path__ = path
|
||||
self.__name__ = name
|
||||
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)
|
||||
class Root(Folder):
|
||||
def __init__(self, path, data_path):
|
||||
self.data_path = data_path
|
||||
super(Root, self).__init__('', None, path)
|
||||
|
||||
def structure(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.path import AssetResolver
|
||||
from pathlib import PurePath
|
||||
from ..resources import (Folder, Document, Markdown, YAML, Jinja2)
|
||||
import yaml
|
||||
import frontmatter
|
||||
import markdown
|
||||
|
||||
from ..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_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)
|
||||
from flatfilecms.models import (
|
||||
Folder,
|
||||
Document,
|
||||
IMarkdown,
|
||||
Jinja2,
|
||||
LoaderFactory)
|
||||
|
||||
|
||||
class PagesView:
|
||||
|
@ -87,45 +31,30 @@ class PagesView:
|
|||
@view_config(context=Document)
|
||||
def document(self):
|
||||
return FileResponse(
|
||||
AssetResolver().resolve(self.context.__path__).abspath(),
|
||||
AssetResolver().resolve(self.context.path).abspath(),
|
||||
request=self.request)
|
||||
|
||||
@view_config(context=IMarkdown)
|
||||
def process_yaml(self):
|
||||
if 'redirect' in self.post:
|
||||
return HTTPFound(location=self.post.redirect)
|
||||
if 'menu' not in self.post:
|
||||
self.post['menu'] = yaml.load(
|
||||
post = self.context.page
|
||||
if 'redirect' in post:
|
||||
return HTTPFound(location=post['redirect'])
|
||||
if 'menu' not in post:
|
||||
post['menu'] = yaml.load(
|
||||
AssetResolver().resolve(
|
||||
str(PurePath(self.request.registry.settings['data_dir']) /
|
||||
'menu/default.yaml')).stream(),
|
||||
LoaderFactory(self.request.registry.settings['data_dir']))
|
||||
str(PurePath(self.context.data_path) /
|
||||
'menu/default.yaml')).stream(),
|
||||
LoaderFactory(self.context.data_path))
|
||||
response = render_to_response(
|
||||
'{0}.jinja2'.format(
|
||||
self.post.get('template', 'default')),
|
||||
self.post,
|
||||
post.get('template', 'default')),
|
||||
post,
|
||||
request=self.request
|
||||
)
|
||||
if 'content_type' in self.post:
|
||||
response.content_type = self.post['content_type']
|
||||
if 'content_type' in post:
|
||||
response.content_type = post['content_type']
|
||||
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)
|
||||
def jinja2(self):
|
||||
return render_to_response(
|
||||
|
|
|
@ -4,7 +4,7 @@ import pytest
|
|||
@pytest.fixture
|
||||
def pages():
|
||||
from flatfilecms.resources import Root
|
||||
return Root('../tests/pages')
|
||||
return Root('../tests/pages', '../tests/data')
|
||||
|
||||
|
||||
def test_loading(pages):
|
||||
|
@ -12,5 +12,5 @@ def test_loading(pages):
|
|||
|
||||
|
||||
def test_db_contains_pages(pages):
|
||||
assert pages['index'].title == 'Заглушка для тестов БД'
|
||||
assert pages['index'].page['title'] == 'Заглушка для тестов БД'
|
||||
pass
|
||||
|
|
Reference in New Issue