From 1013ff5618e836ea5acf5c07233d45fa37ae438c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=9A=D0=B0=D1=81=D0=B8?= =?UTF-8?q?=D0=BC=D0=BE=D0=B2?= Date: Sat, 16 Feb 2019 20:05:09 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BD=D0=BE=D1=81=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D0=B5=D0=B9=20Markdown=20=D0=B8?= =?UTF-8?q?=20YAML=20=D0=B8=D0=B7=20resources=20=D0=B2=20models=20#1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flatfilecms/__init__.py | 14 ++-- flatfilecms/models.py | 140 +++++++++++++++++++++++++++++++++++++ flatfilecms/resources.py | 62 ++-------------- flatfilecms/views/pages.py | 111 ++++++----------------------- tests/test_pages.py | 4 +- 5 files changed, 174 insertions(+), 157 deletions(-) create mode 100644 flatfilecms/models.py diff --git a/flatfilecms/__init__.py b/flatfilecms/__init__.py index 5bba17f..5f49a18 100644 --- a/flatfilecms/__init__.py +++ b/flatfilecms/__init__.py @@ -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( diff --git a/flatfilecms/models.py b/flatfilecms/models.py new file mode 100644 index 0000000..88ee93e --- /dev/null +++ b/flatfilecms/models.py @@ -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 diff --git a/flatfilecms/resources.py b/flatfilecms/resources.py index 061bb49..f224347 100644 --- a/flatfilecms/resources.py +++ b/flatfilecms/resources.py @@ -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 diff --git a/flatfilecms/views/pages.py b/flatfilecms/views/pages.py index dcb986b..7b31eae 100644 --- a/flatfilecms/views/pages.py +++ b/flatfilecms/views/pages.py @@ -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( diff --git a/tests/test_pages.py b/tests/test_pages.py index 82d09d3..d8b4067 100644 --- a/tests/test_pages.py +++ b/tests/test_pages.py @@ -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