From 43f95d118d70ab75e64f509ef0242bd6c266ac49 Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Sat, 13 Mar 2021 20:52:03 -0500 Subject: [PATCH 1/5] Basic formatting api sketched out --- examples/__init__.py | 0 examples/content/reversed.txt | 28 +++++++++++++++++ examples/reversed.py | 55 +++++++++++++++++++++++++++++++++ frontmatter/default_handlers.py | 3 -- 4 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 examples/__init__.py create mode 100644 examples/content/reversed.txt create mode 100644 examples/reversed.py diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/content/reversed.txt b/examples/content/reversed.txt new file mode 100644 index 0000000..bbb952d --- /dev/null +++ b/examples/content/reversed.txt @@ -0,0 +1,28 @@ +This is txt format to prevent reformatting +============================================ + +Dextra tempore deus +------------------- + +Lorem markdownum est dicere pariter es dat si non, praesignis Styge, non +Maenalon magnae miserrimus. Corpora frustra committere insuetum et fecit +**Hippothousque arbore solio** inopem utraque concepit illa comantem me mortis +epulis protinus putares! Piceis *manibus*. Erinys et parum morsusque repugnat +ore corna sacris, pollice movet currus gestamina. + +Genitoris forti circumfuso videbit fertur vulnere simillima +----------------------------------------------------------- + +Audit enim, est illa nervis loco inque hoc, et rigido! Monstris vatibus laetos +contemptor Calydonia. Et visa capillo referens regia: usus: odiique nostro. +**Vim** sensit inpulit virginis metuens secum cogit, corpus. + +Humus ater Dromas est honorem, Titanida glandibus sinit, e terras capillos +cremet retinentibus male. Tertia et cedit eliso flectere haec, cute nihil +marmore armo. Mihi [Olympi](http://que.org/saepepoenas), iam sustinet addidit +humana similis. + +--- +title: Front matter, reversed +ref: https://github.com/eyeseast/python-frontmatter/issues/67 +--- diff --git a/examples/reversed.py b/examples/reversed.py new file mode 100644 index 0000000..6de012e --- /dev/null +++ b/examples/reversed.py @@ -0,0 +1,55 @@ +import frontmatter + +POST_TEMPLATE = """\ +{content} + +{start_delimiter} +{metadata} +{end_delimiter} +""" + + +class ReverseYAMLHandler(frontmatter.YAMLHandler): + """ + This is an example of using Handler.parse and Handler.format to move Frontmatter to the bottom + of a file, both for parsing and output. + + >>> with open("./content/reversed.txt") as f: + ... text = f.read() + >>> handler = ReverseYAMLHandler() + >>> post = frontmatter.loads(text, handler=handler) + >>> print(post['title']) + Front matter, reversed + >>> print(post['ref']) + https://github.com/eyeseast/python-frontmatter/issues/67 + >>> frontmatter.dumps(post, handler=handler) == text.strip() + True + """ + + # FM_BOUNDARY as a string, so we can rsplit + FM_BOUNDARY = "---" + + def split(self, text): + """ + Split text into frontmatter and content + """ + content, fm, _ = text.rsplit(self.FM_BOUNDARY, 2) + return fm, content + + def format(post, **kwargs): + start_delimiter = kwargs.pop("start_delimiter", handler.START_DELIMITER) + end_delimiter = kwargs.pop("end_delimiter", handler.END_DELIMITER) + metadata = self.export(post.metadata, **kwargs) + + return POST_TEMPLATE.format( + content=post.content, + metadata=metadata, + start_delimiter=start_delimiter, + end_delimiter=end_delimiter, + ).strip() + + +if __name__ == "__main__": + import doctest + + doctest.testmod() \ No newline at end of file diff --git a/frontmatter/default_handlers.py b/frontmatter/default_handlers.py index 46b40be..a69c045 100644 --- a/frontmatter/default_handlers.py +++ b/frontmatter/default_handlers.py @@ -97,15 +97,12 @@ # set YAML format when dumping, but the old handler attached >>> t1 = frontmatter.dumps(post, handler=YAMLHandler()) - # set a new handler, changing all future exports >>> post.handler = YAMLHandler() >>> t2 = frontmatter.dumps(post) - # remove handler, defaulting back to YAML >>> post.handler = None >>> t3 = frontmatter.dumps(post) - >>> t1 == t2 == t3 True From 2a399a689a6a7eabbb94ee6fad6873981f40d5e6 Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Sat, 13 Mar 2021 21:36:58 -0500 Subject: [PATCH 2/5] Fix tests, with one expected fail --- frontmatter/conftest.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 frontmatter/conftest.py diff --git a/frontmatter/conftest.py b/frontmatter/conftest.py new file mode 100644 index 0000000..e804a6e --- /dev/null +++ b/frontmatter/conftest.py @@ -0,0 +1,8 @@ +import pytest + + +@pytest.fixture(autouse=True) +def add_globals(doctest_namespace): + import frontmatter + + doctest_namespace["frontmatter"] = frontmatter From 110ba96f86e10aaff36537419839748b7dd2db3e Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Sat, 13 Mar 2021 21:37:10 -0500 Subject: [PATCH 3/5] Fix tests, with one expected fail --- .github/workflows/test.yml | 3 ++- README.md | 6 ++++-- examples/reversed.py | 8 +------- frontmatter/__init__.py | 28 ++++++++++++++-------------- frontmatter/default_handlers.py | 8 +++----- 5 files changed, 24 insertions(+), 29 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9f48607..bdb1c1b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,4 +25,5 @@ jobs: run: | pip install -e '.[test]' - name: Run tests - run: pytest + run: | + pytest . --doctest-modules diff --git a/README.md b/README.md index af3bdfd..618870b 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,15 @@ This is a small package to load and parse files (or just text) with YAML front m ## Usage: ```python -import frontmatter +>>> import frontmatter + ``` Load a post from a filename: ```python -post = frontmatter.load('tests/yaml/hello-world.txt') +>>> post = frontmatter.load('tests/yaml/hello-world.txt') + ``` Or a file (or file-like object): diff --git a/examples/reversed.py b/examples/reversed.py index 6de012e..2ddfdbc 100644 --- a/examples/reversed.py +++ b/examples/reversed.py @@ -14,7 +14,7 @@ class ReverseYAMLHandler(frontmatter.YAMLHandler): This is an example of using Handler.parse and Handler.format to move Frontmatter to the bottom of a file, both for parsing and output. - >>> with open("./content/reversed.txt") as f: + >>> with open("examples/content/reversed.txt") as f: ... text = f.read() >>> handler = ReverseYAMLHandler() >>> post = frontmatter.loads(text, handler=handler) @@ -47,9 +47,3 @@ def format(post, **kwargs): start_delimiter=start_delimiter, end_delimiter=end_delimiter, ).strip() - - -if __name__ == "__main__": - import doctest - - doctest.testmod() \ No newline at end of file diff --git a/frontmatter/__init__.py b/frontmatter/__init__.py index b4b290c..98bb73c 100644 --- a/frontmatter/__init__.py +++ b/frontmatter/__init__.py @@ -57,11 +57,11 @@ def parse(text, encoding="utf-8", handler=None, **defaults): .. testsetup:: * - import frontmatter + >>> import frontmatter .. doctest:: - >>> with open('../tests/yaml/hello-world.txt') as f: + >>> with open('tests/yaml/hello-world.txt') as f: ... metadata, content = frontmatter.parse(f.read()) >>> print(metadata['title']) Hello, world! @@ -102,7 +102,7 @@ def check(fd, encoding="utf-8"): .. doctest:: - >>> frontmatter.check('../tests/yaml/hello-world.txt') + >>> frontmatter.check('tests/yaml/hello-world.txt') True """ @@ -125,7 +125,7 @@ def checks(text, encoding="utf-8"): .. doctest:: - >>> with open('../tests/yaml/hello-world.txt') as f: + >>> with open('tests/yaml/hello-world.txt') as f: ... frontmatter.checks(f.read()) True @@ -141,8 +141,8 @@ def load(fd, encoding="utf-8", handler=None, **defaults): .. doctest:: - >>> post = frontmatter.load('../tests/yaml/hello-world.txt') - >>> with open('../tests/yaml/hello-world.txt') as f: + >>> post = frontmatter.load('tests/yaml/hello-world.txt') + >>> with open('tests/yaml/hello-world.txt') as f: ... post = frontmatter.load(f) """ @@ -163,7 +163,7 @@ def loads(text, encoding="utf-8", handler=None, **defaults): .. doctest:: - >>> with open('../tests/yaml/hello-world.txt') as f: + >>> with open('tests/yaml/hello-world.txt') as f: ... post = frontmatter.loads(f.read()) """ @@ -181,7 +181,7 @@ def dump(post, fd, encoding="utf-8", handler=None, **kwargs): :: >>> from io import BytesIO - >>> post = frontmatter.load('../tests/yaml/hello-world.txt') + >>> post = frontmatter.load('tests/yaml/hello-world.txt') >>> f = BytesIO() >>> frontmatter.dump(post, f) >>> print(f.getvalue().decode('utf-8')) @@ -189,14 +189,14 @@ def dump(post, fd, encoding="utf-8", handler=None, **kwargs): layout: post title: Hello, world! --- - + Well, hello there, world. .. testcode:: from io import BytesIO - post = frontmatter.load('../tests/yaml/hello-world.txt') + post = frontmatter.load('tests/yaml/hello-world.txt') f = BytesIO() frontmatter.dump(post, f) print(f.getvalue().decode('utf-8')) @@ -207,7 +207,7 @@ def dump(post, fd, encoding="utf-8", handler=None, **kwargs): layout: post title: Hello, world! --- - + Well, hello there, world. """ @@ -232,18 +232,18 @@ def dumps(post, handler=None, **kwargs): :: - >>> post = frontmatter.load('../tests/yaml/hello-world.txt') + >>> post = frontmatter.load('tests/yaml/hello-world.txt') >>> print(frontmatter.dumps(post)) # doctest: +NORMALIZE_WHITESPACE --- layout: post title: Hello, world! --- - + Well, hello there, world. .. testcode:: - post = frontmatter.load('../tests/yaml/hello-world.txt') + post = frontmatter.load('tests/yaml/hello-world.txt') print(frontmatter.dumps(post)) .. testoutput:: diff --git a/frontmatter/default_handlers.py b/frontmatter/default_handlers.py index a69c045..a6a0ab1 100644 --- a/frontmatter/default_handlers.py +++ b/frontmatter/default_handlers.py @@ -40,7 +40,7 @@ >>> import frontmatter >>> from frontmatter.default_handlers import YAMLHandler, TOMLHandler - >>> post = frontmatter.load('tests/toml/hello-toml.markdown', handler=TOMLHandler()) + >>> post = frontmatter.load('tests/toml/hello-toml.md', handler=TOMLHandler()) >>> post.handler #doctest: +ELLIPSIS @@ -97,11 +97,9 @@ # set YAML format when dumping, but the old handler attached >>> t1 = frontmatter.dumps(post, handler=YAMLHandler()) - # set a new handler, changing all future exports - >>> post.handler = YAMLHandler() + >>> post.handler = YAMLHandler() # set a new handler, changing all future exports >>> t2 = frontmatter.dumps(post) - # remove handler, defaulting back to YAML - >>> post.handler = None + >>> post.handler = None # remove handler, defaulting back to YAML >>> t3 = frontmatter.dumps(post) >>> t1 == t2 == t3 True From 131b00a3b5a48aeb50356e0114c9ba4a9b54cabc Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Sun, 14 Mar 2021 10:30:38 -0400 Subject: [PATCH 4/5] Add handler.format to customize string output --- .github/workflows/test.yml | 2 +- examples/reversed.py | 37 ++++++++++++++++++++++++++++----- frontmatter/__init__.py | 19 +---------------- frontmatter/default_handlers.py | 28 ++++++++++++++++++++++++- 4 files changed, 61 insertions(+), 25 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bdb1c1b..dd2c033 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,4 +26,4 @@ jobs: pip install -e '.[test]' - name: Run tests run: | - pytest . --doctest-modules + pytest . --doctest-modules --doctest-glob "README.md" diff --git a/examples/reversed.py b/examples/reversed.py index 2ddfdbc..56715f1 100644 --- a/examples/reversed.py +++ b/examples/reversed.py @@ -22,8 +22,35 @@ class ReverseYAMLHandler(frontmatter.YAMLHandler): Front matter, reversed >>> print(post['ref']) https://github.com/eyeseast/python-frontmatter/issues/67 - >>> frontmatter.dumps(post, handler=handler) == text.strip() - True + >>> print(frontmatter.dumps(post, handler=handler)) + This is txt format to prevent reformatting + ============================================ + + Dextra tempore deus + ------------------- + + Lorem markdownum est dicere pariter es dat si non, praesignis Styge, non + Maenalon magnae miserrimus. Corpora frustra committere insuetum et fecit + **Hippothousque arbore solio** inopem utraque concepit illa comantem me mortis + epulis protinus putares! Piceis *manibus*. Erinys et parum morsusque repugnat + ore corna sacris, pollice movet currus gestamina. + + Genitoris forti circumfuso videbit fertur vulnere simillima + ----------------------------------------------------------- + + Audit enim, est illa nervis loco inque hoc, et rigido! Monstris vatibus laetos + contemptor Calydonia. Et visa capillo referens regia: usus: odiique nostro. + **Vim** sensit inpulit virginis metuens secum cogit, corpus. + + Humus ater Dromas est honorem, Titanida glandibus sinit, e terras capillos + cremet retinentibus male. Tertia et cedit eliso flectere haec, cute nihil + marmore armo. Mihi [Olympi](http://que.org/saepepoenas), iam sustinet addidit + humana similis. + + --- + ref: https://github.com/eyeseast/python-frontmatter/issues/67 + title: Front matter, reversed + --- """ # FM_BOUNDARY as a string, so we can rsplit @@ -36,9 +63,9 @@ def split(self, text): content, fm, _ = text.rsplit(self.FM_BOUNDARY, 2) return fm, content - def format(post, **kwargs): - start_delimiter = kwargs.pop("start_delimiter", handler.START_DELIMITER) - end_delimiter = kwargs.pop("end_delimiter", handler.END_DELIMITER) + def format(self, post, **kwargs): + start_delimiter = kwargs.pop("start_delimiter", self.START_DELIMITER) + end_delimiter = kwargs.pop("end_delimiter", self.END_DELIMITER) metadata = self.export(post.metadata, **kwargs) return POST_TEMPLATE.format( diff --git a/frontmatter/__init__.py b/frontmatter/__init__.py index 98bb73c..92b6323 100644 --- a/frontmatter/__init__.py +++ b/frontmatter/__init__.py @@ -13,13 +13,6 @@ __all__ = ["parse", "load", "loads", "dump", "dumps"] -POST_TEMPLATE = """\ -{start_delimiter} -{metadata} -{end_delimiter} - -{content} -""" # global handlers handlers = { @@ -259,17 +252,7 @@ def dumps(post, handler=None, **kwargs): if handler is None: handler = getattr(post, "handler", None) or YAMLHandler() - start_delimiter = kwargs.pop("start_delimiter", handler.START_DELIMITER) - end_delimiter = kwargs.pop("end_delimiter", handler.END_DELIMITER) - - metadata = handler.export(post.metadata, **kwargs) - - return POST_TEMPLATE.format( - metadata=metadata, - content=post.content, - start_delimiter=start_delimiter, - end_delimiter=end_delimiter, - ).strip() + return handler.format(post, **kwargs) class Post(object): diff --git a/frontmatter/default_handlers.py b/frontmatter/default_handlers.py index a6a0ab1..07ecbb5 100644 --- a/frontmatter/default_handlers.py +++ b/frontmatter/default_handlers.py @@ -109,6 +109,7 @@ - split metadata and content, based on a boundary pattern (``handler.split``) - parse plain text metadata into a Python dictionary (``handler.load``) - export a dictionary back into plain text (``handler.export``) +- format exported metadata and content into a single string (``handler.format``) """ @@ -138,7 +139,16 @@ __all__.append("TOMLHandler") -class BaseHandler(object): +DEFAULT_POST_TEMPLATE = """\ +{start_delimiter} +{metadata} +{end_delimiter} + +{content} +""" + + +class BaseHandler: """ BaseHandler lays out all the steps to detecting, splitting, parsing and exporting front matter metadata. @@ -194,6 +204,22 @@ def export(self, metadata, **kwargs): """ raise NotImplementedError + def format(self, post, **kwargs): + """ + Turn a post into a string, used in ``frontmatter.dumps`` + """ + start_delimiter = kwargs.pop("start_delimiter", self.START_DELIMITER) + end_delimiter = kwargs.pop("end_delimiter", self.END_DELIMITER) + + metadata = self.export(post.metadata, **kwargs) + + return DEFAULT_POST_TEMPLATE.format( + metadata=metadata, + content=post.content, + start_delimiter=start_delimiter, + end_delimiter=end_delimiter, + ).strip() + class YAMLHandler(BaseHandler): """ From 2a9a5952be6e01fe6015b684aa9accdc583d9257 Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Sun, 14 Mar 2021 10:41:44 -0400 Subject: [PATCH 5/5] fix a warning --- docs/handlers.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/handlers.rst b/docs/handlers.rst index c6dc6be..b82002d 100644 --- a/docs/handlers.rst +++ b/docs/handlers.rst @@ -1,8 +1,6 @@ Customizing input and output ============================ -.. module:: frontmatter - .. automodule:: frontmatter.default_handlers .. autoclass:: frontmatter.default_handlers.BaseHandler