From 95a00d61f77ca741c3815cdd5596ae5ced9ccf73 Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Sat, 6 Mar 2021 21:07:39 -0500 Subject: [PATCH 1/7] Stubbing out a new testing approach --- setup.py | 2 +- tests/{ => empty}/empty-frontmatter.txt | 0 tests/{ => empty}/no-frontmatter.txt | 0 .../hello-json.md} | 0 tests/test_docs.py | 10 +++++ tests/test_files.py | 40 +++++++++++++++++++ .../hello-toml.md} | 0 tests/{ => yaml}/chinese.txt | 0 tests/{ => yaml}/extra-dash.txt | 0 tests/{ => yaml}/extra-space.txt | 0 .../hello-markdown.md} | 0 .../hello-world.txt} | 0 .../network-diagrams.md} | 0 tests/{ => yaml}/unpretty.md | 0 14 files changed, 51 insertions(+), 1 deletion(-) rename tests/{ => empty}/empty-frontmatter.txt (100%) rename tests/{ => empty}/no-frontmatter.txt (100%) rename tests/{hello-json.markdown => json/hello-json.md} (100%) create mode 100644 tests/test_docs.py create mode 100644 tests/test_files.py rename tests/{hello-toml.markdown => toml/hello-toml.md} (100%) rename tests/{ => yaml}/chinese.txt (100%) rename tests/{ => yaml}/extra-dash.txt (100%) rename tests/{ => yaml}/extra-space.txt (100%) rename tests/{hello-markdown.markdown => yaml/hello-markdown.md} (100%) rename tests/{hello-world.markdown => yaml/hello-world.txt} (100%) rename tests/{network-diagrams.markdown => yaml/network-diagrams.md} (100%) rename tests/{ => yaml}/unpretty.md (100%) diff --git a/setup.py b/setup.py index 6b14e4b..675b3dc 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ packages=["frontmatter"], include_package_data=True, install_requires=["PyYAML"], - extras_require={"test": ["pytest", "toml"]}, + extras_require={"test": ["pytest", "toml", "pyaml"]}, tests_require=["python-frontmatter[test]"], license="MIT", zip_safe=False, diff --git a/tests/empty-frontmatter.txt b/tests/empty/empty-frontmatter.txt similarity index 100% rename from tests/empty-frontmatter.txt rename to tests/empty/empty-frontmatter.txt diff --git a/tests/no-frontmatter.txt b/tests/empty/no-frontmatter.txt similarity index 100% rename from tests/no-frontmatter.txt rename to tests/empty/no-frontmatter.txt diff --git a/tests/hello-json.markdown b/tests/json/hello-json.md similarity index 100% rename from tests/hello-json.markdown rename to tests/json/hello-json.md diff --git a/tests/test_docs.py b/tests/test_docs.py new file mode 100644 index 0000000..02d9c08 --- /dev/null +++ b/tests/test_docs.py @@ -0,0 +1,10 @@ +# all the doctests here +import doctest +import frontmatter + + +def test_docs(): + doctest.testfile("../README.md", extraglobs={"frontmatter": frontmatter}) + doctest.testmod( + frontmatter.default_handlers, extraglobs={"frontmatter": frontmatter} + ) diff --git a/tests/test_files.py b/tests/test_files.py new file mode 100644 index 0000000..2ad6d93 --- /dev/null +++ b/tests/test_files.py @@ -0,0 +1,40 @@ +""" +Test individual files with frontmatter against expected results. +Files should be in a subdirectory under `tests`, usually sorted by format (yaml, toml, json). + +For a file called hello-world.markdown, there should be a corresponding file called hello-world.json +matching the expected output. +""" +import os +from itertools import chain +from pathlib import Path + +import frontmatter +import pytest + + +def files(): + tests = Path(__file__).parent + md = tests.glob("**/*.md") + txt = tests.glob("**/*.txt") + return chain(md, txt) + + +def get_result_filename(path): + root, ext = os.path.splitext(path) + return f"{root}.result.json" + + +@pytest.mark.parametrize("filename", list(files())) +def test_can_parse(filename): + "Check we can load every file in our test directories without raising an error" + for filename in files(): + post = frontmatter.load(filename) + assert isinstance(post, frontmatter.Post) + + +@pytest.mark.parametrize("filename", list(files())) +def test_file(filename): + result = Path(get_result_filename(filename)) + if not result.exists(): + pytest.fail(f"{result.name} does not exist") diff --git a/tests/hello-toml.markdown b/tests/toml/hello-toml.md similarity index 100% rename from tests/hello-toml.markdown rename to tests/toml/hello-toml.md diff --git a/tests/chinese.txt b/tests/yaml/chinese.txt similarity index 100% rename from tests/chinese.txt rename to tests/yaml/chinese.txt diff --git a/tests/extra-dash.txt b/tests/yaml/extra-dash.txt similarity index 100% rename from tests/extra-dash.txt rename to tests/yaml/extra-dash.txt diff --git a/tests/extra-space.txt b/tests/yaml/extra-space.txt similarity index 100% rename from tests/extra-space.txt rename to tests/yaml/extra-space.txt diff --git a/tests/hello-markdown.markdown b/tests/yaml/hello-markdown.md similarity index 100% rename from tests/hello-markdown.markdown rename to tests/yaml/hello-markdown.md diff --git a/tests/hello-world.markdown b/tests/yaml/hello-world.txt similarity index 100% rename from tests/hello-world.markdown rename to tests/yaml/hello-world.txt diff --git a/tests/network-diagrams.markdown b/tests/yaml/network-diagrams.md similarity index 100% rename from tests/network-diagrams.markdown rename to tests/yaml/network-diagrams.md diff --git a/tests/unpretty.md b/tests/yaml/unpretty.md similarity index 100% rename from tests/unpretty.md rename to tests/yaml/unpretty.md From ca54ed247e6c64ee882a634334822c7b3cd1612f Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Sat, 6 Mar 2021 21:33:34 -0500 Subject: [PATCH 2/7] Add content-based tests --- tests/empty/empty-frontmatter.result.json | 3 +++ tests/empty/no-frontmatter.result.json | 3 +++ tests/json/hello-json.result.json | 6 ++++++ tests/stub_tests.py | 21 +++++++++++++++++++++ tests/test_files.py | 6 ++++++ tests/toml/hello-toml.result.json | 6 ++++++ tests/yaml/chinese.result.json | 5 +++++ tests/yaml/extra-dash.result.json | 5 +++++ tests/yaml/extra-space.result.json | 5 +++++ tests/yaml/hello-markdown.result.json | 6 ++++++ tests/yaml/hello-world.result.json | 5 +++++ tests/yaml/network-diagrams.result.json | 9 +++++++++ tests/yaml/unpretty.result.json | 23 +++++++++++++++++++++++ 13 files changed, 103 insertions(+) create mode 100644 tests/empty/empty-frontmatter.result.json create mode 100644 tests/empty/no-frontmatter.result.json create mode 100644 tests/json/hello-json.result.json create mode 100755 tests/stub_tests.py create mode 100644 tests/toml/hello-toml.result.json create mode 100644 tests/yaml/chinese.result.json create mode 100644 tests/yaml/extra-dash.result.json create mode 100644 tests/yaml/extra-space.result.json create mode 100644 tests/yaml/hello-markdown.result.json create mode 100644 tests/yaml/hello-world.result.json create mode 100644 tests/yaml/network-diagrams.result.json create mode 100644 tests/yaml/unpretty.result.json diff --git a/tests/empty/empty-frontmatter.result.json b/tests/empty/empty-frontmatter.result.json new file mode 100644 index 0000000..f9b8ff1 --- /dev/null +++ b/tests/empty/empty-frontmatter.result.json @@ -0,0 +1,3 @@ +{ + "content": "I have frontmatter but no metadata." +} \ No newline at end of file diff --git a/tests/empty/no-frontmatter.result.json b/tests/empty/no-frontmatter.result.json new file mode 100644 index 0000000..ac41f67 --- /dev/null +++ b/tests/empty/no-frontmatter.result.json @@ -0,0 +1,3 @@ +{ + "content": "I have no frontmatter." +} \ No newline at end of file diff --git a/tests/json/hello-json.result.json b/tests/json/hello-json.result.json new file mode 100644 index 0000000..3abb5fb --- /dev/null +++ b/tests/json/hello-json.result.json @@ -0,0 +1,6 @@ +{ + "test": "tester", + "author": "bob", + "something": "else", + "content": "Title\n=====\n\ntitle2\n------\n\nHello.\n\nJust need three dashes\n---\n\nAnd this might break." +} \ No newline at end of file diff --git a/tests/stub_tests.py b/tests/stub_tests.py new file mode 100755 index 0000000..2b7cc89 --- /dev/null +++ b/tests/stub_tests.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +""" +Generate result files for test content. Won't overwrite any that exit. +""" +import json +from pathlib import Path + +import frontmatter +from test_files import files, get_result_filename + + +def main(): + for path in files(): + result = Path(get_result_filename(path)) + if not result.exists(): + post = frontmatter.loads(path.read_text()) + result.write_text(json.dumps(post.to_dict(), indent=2), "utf-8") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tests/test_files.py b/tests/test_files.py index 2ad6d93..430cef8 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -6,6 +6,7 @@ matching the expected output. """ import os +import json from itertools import chain from pathlib import Path @@ -38,3 +39,8 @@ def test_file(filename): result = Path(get_result_filename(filename)) if not result.exists(): pytest.fail(f"{result.name} does not exist") + + post = frontmatter.load(filename) + result = json.loads(result.read_text()) + + assert post.to_dict() == result \ No newline at end of file diff --git a/tests/toml/hello-toml.result.json b/tests/toml/hello-toml.result.json new file mode 100644 index 0000000..00f3a24 --- /dev/null +++ b/tests/toml/hello-toml.result.json @@ -0,0 +1,6 @@ +{ + "author": "bob", + "something": "else", + "test": "tester", + "content": "Title\n=====\n\ntitle2\n------\n\nHello.\n\nJust need three dashes\n---\n\nAnd this shouldn't break." +} \ No newline at end of file diff --git a/tests/yaml/chinese.result.json b/tests/yaml/chinese.result.json new file mode 100644 index 0000000..7920675 --- /dev/null +++ b/tests/yaml/chinese.result.json @@ -0,0 +1,5 @@ +{ + "title": "Let's try unicode", + "language": "\u4e2d\u6587", + "content": "\u6b22\u8fce\u6765\u5230\u5927\u8fde\u6c34\u4ea7\u5b66\u9662\uff01" +} \ No newline at end of file diff --git a/tests/yaml/extra-dash.result.json b/tests/yaml/extra-dash.result.json new file mode 100644 index 0000000..e874ce3 --- /dev/null +++ b/tests/yaml/extra-dash.result.json @@ -0,0 +1,5 @@ +{ + "test": "bob", + "else": "kate", + "content": "Here's some content." +} \ No newline at end of file diff --git a/tests/yaml/extra-space.result.json b/tests/yaml/extra-space.result.json new file mode 100644 index 0000000..842fc7a --- /dev/null +++ b/tests/yaml/extra-space.result.json @@ -0,0 +1,5 @@ +{ + "test": "tester", + "something": "else", + "content": "This file has an extra space on the opening line of the frontmatter." +} \ No newline at end of file diff --git a/tests/yaml/hello-markdown.result.json b/tests/yaml/hello-markdown.result.json new file mode 100644 index 0000000..90d62a0 --- /dev/null +++ b/tests/yaml/hello-markdown.result.json @@ -0,0 +1,6 @@ +{ + "test": "tester", + "author": "bob", + "something": "else", + "content": "Title\n=====\n\ntitle2\n------\n\nHello.\n\nJust need three dashes\n---\n\nAnd this shouldn't break." +} \ No newline at end of file diff --git a/tests/yaml/hello-world.result.json b/tests/yaml/hello-world.result.json new file mode 100644 index 0000000..2f4bb56 --- /dev/null +++ b/tests/yaml/hello-world.result.json @@ -0,0 +1,5 @@ +{ + "title": "Hello, world!", + "layout": "post", + "content": "Well, hello there, world." +} \ No newline at end of file diff --git a/tests/yaml/network-diagrams.result.json b/tests/yaml/network-diagrams.result.json new file mode 100644 index 0000000..2ae4877 --- /dev/null +++ b/tests/yaml/network-diagrams.result.json @@ -0,0 +1,9 @@ +{ + "title": "TODO: Understand Network Diagrams", + "layout": "post", + "published": true, + "tags": [ + "todo" + ], + "content": "Kim Rees, sitting in for Nathan Yau at [Flowing Data](http://flowingdata.com), has been posting examples of [network diagrams](http://flowingdata.com/category/visualization/network-visualization/) lately. I have to confess I'm stumped by most of them them. Or maybe I'm just overwhelmed. Maybe I'm [not alone](http://flowingdata.com/2012/05/28/network-diagrams-simplified/):\n\n> Network diagrams are notoriously messy. Even a small number of nodes can be overwhelmed by their chaotic placement and relationships. [Cody Dunne](http://www.cs.umd.edu/~cdunne/) of [HCIL](http://www.cs.umd.edu/hcil/) showed off [his new work in simplifying these complex structures](http://www.cs.umd.edu/localphp/hcil/tech-reports-search.php?number=2012-11). In essence, he aggregates leaf nodes into a fan glyph that describes the underlying data in its size, arc, and color. Span nodes are similarly captured into crescent glyphs. The result is an easy to read, high level look at the network. You can easily compare different sections of the network, understand areas that may have been occluded by the lines in a traditional diagram, and see relationships far more quickly.\n\nThis seems like the kind of thing that could be useful for news, where we're often trying to understand and illustrate complex relationships. I'll have to find a good dataset to play with." +} \ No newline at end of file diff --git a/tests/yaml/unpretty.result.json b/tests/yaml/unpretty.result.json new file mode 100644 index 0000000..c826041 --- /dev/null +++ b/tests/yaml/unpretty.result.json @@ -0,0 +1,23 @@ +{ + "destination": { + "encoding": { + "xz": { + "enabled": true, + "min_size": 5120, + "options": null, + "path_filter": null + } + }, + "result": { + "append_to_file": null, + "append_to_lafs_dir": null, + "print_to_stdout": true + }, + "url": "http://localhost:3456/uri" + }, + "filter": [ + "\u0414\u043b\u0438\u043d\u043d\u044b\u0439 \u0441\u0442\u0440\u0438\u043d\u0433 \u043d\u0430 \u0440\u0443\u0441\u0441\u043a\u043e\u043c", + "\u0415\u0449\u0435 \u043e\u0434\u043d\u0430 \u0434\u043b\u0438\u043d\u043d\u0430\u044f \u0441\u0442\u0440\u043e\u043a\u0430" + ], + "content": "This is a test of both unicode and prettier dumping. The above metadata comes from the [pretty-yaml](https://github.com/mk-fg/pretty-yaml) readme file.\n\nMetadata should be dumped in order." +} \ No newline at end of file From 21cdbcad374727b774ab9969281c4f3ef3ceaee4 Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Sat, 6 Mar 2021 21:40:37 -0500 Subject: [PATCH 3/7] actually use pytest --- .github/workflows/publish.yml | 2 +- .github/workflows/test.yml | 3 +-- .travis.yml | 6 ++---- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 86343c5..29a03fa 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -27,7 +27,7 @@ jobs: run: | pip install -e '.[test]' - name: Run tests - run: python test.py + run: pytest deploy: runs-on: ubuntu-latest needs: [test] diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e439c1b..f9b7030 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,5 +25,4 @@ jobs: run: | pip install -e '.[test]' - name: Run tests - run: | - python test.py + run: pytest diff --git a/.travis.yml b/.travis.yml index bcaec6c..044d643 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,8 +8,6 @@ python: - "3.9" # command to install dependencies install: - # use pyaml for tests above py26 - - pip install -r requirements.txt - - "python setup.py install" + - "pip install -e .[test]" # command to run tests -script: python test.py +script: pytest From d256d995d3574e5955ab6cd9f4c897d9a7764638 Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Sat, 6 Mar 2021 21:44:27 -0500 Subject: [PATCH 4/7] f strings started in 3.6 --- .github/workflows/publish.yml | 2 +- .github/workflows/test.yml | 2 +- .travis.yml | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 29a03fa..320a9ab 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f9b7030..9f48607 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/.travis.yml b/.travis.yml index 044d643..d1f8a23 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: python dist: xenial python: - - "3.5" - "3.6" - "3.7" - "3.8" From 47b44693b2f575bab5b3c88ef245a714d898c336 Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Sun, 7 Mar 2021 14:40:57 -0500 Subject: [PATCH 5/7] Got all the doctests sorted --- README.md | 8 ++-- docs/conf.py | 4 +- docs/index.rst | 8 ++-- frontmatter/__init__.py | 68 +++++++++++++++++++++++++-------- frontmatter/default_handlers.py | 7 +++- setup.py | 2 +- test.py | 61 ++++++++++++++--------------- tests/test_docs.py | 9 ++++- 8 files changed, 106 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 6dd0dd2..30a797c 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,13 @@ import frontmatter Load a post from a filename: ```python -post = frontmatter.load('tests/hello-world.markdown') +post = frontmatter.load('tests/yaml/hello-world.txt') ``` Or a file (or file-like object): ```python ->>> with open('tests/hello-world.markdown') as f: +>>> with open('tests/yaml/hello-world.txt') as f: ... post = frontmatter.load(f) ``` @@ -33,7 +33,7 @@ Or a file (or file-like object): Or load from text: ```python ->>> with open('tests/hello-world.markdown') as f: +>>> with open('tests/yaml/hello-world.txt') as f: ... post = frontmatter.loads(f.read()) ``` @@ -74,7 +74,7 @@ Metadata is a dictionary, with some handy proxies: If you don't need the whole post object, just parse: ```python ->>> with open('tests/hello-world.markdown') as f: +>>> with open('tests/yaml/hello-world.txt') as f: ... metadata, content = frontmatter.parse(f.read()) >>> print(metadata['title']) Hello, world! diff --git a/docs/conf.py b/docs/conf.py index c3697ca..ef09deb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -58,9 +58,9 @@ # built documents. # # The short X.Y version. -version = u"0.4.2" +version = u"0.5.0" # The full version, including alpha/beta/rc tags. -release = u"0.4.2" +release = u"0.5.0" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/index.rst b/docs/index.rst index 80c45f8..2cc131b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -34,20 +34,20 @@ Load a post from a filename: :: - >>> post = frontmatter.load('tests/hello-world.markdown') + >>> post = frontmatter.load('tests/yaml/hello-world.txt') Or a file (or file-like object): :: - >>> with open('tests/hello-world.markdown') as f: + >>> with open('tests/yaml/hello-world.txt') as f: ... post = frontmatter.load(f) Or load from text: :: - >>> with open('tests/hello-world.markdown') as f: + >>> with open('tests/yaml/hello-world.txt') as f: ... post = frontmatter.loads(f.read()) Access content: @@ -84,7 +84,7 @@ If you don't need the whole post object, just parse: :: - >>> with open('tests/hello-world.markdown') as f: + >>> with open('tests/yaml/hello-world.txt') as f: ... metadata, content = frontmatter.parse(f.read()) >>> print(metadata['title']) Hello, world! diff --git a/frontmatter/__init__.py b/frontmatter/__init__.py index 032ac76..b4b290c 100644 --- a/frontmatter/__init__.py +++ b/frontmatter/__init__.py @@ -55,9 +55,13 @@ def parse(text, encoding="utf-8", handler=None, **defaults): If frontmatter is not found, returns an empty metadata dictionary (or defaults) and original text content. - :: + .. testsetup:: * + + import frontmatter - >>> with open('tests/hello-world.markdown') as f: + .. doctest:: + + >>> with open('../tests/yaml/hello-world.txt') as f: ... metadata, content = frontmatter.parse(f.read()) >>> print(metadata['title']) Hello, world! @@ -96,9 +100,9 @@ def check(fd, encoding="utf-8"): If it contains a frontmatter but it is empty, return True as well. - :: + .. doctest:: - >>> frontmatter.check('tests/hello-world.markdown') + >>> frontmatter.check('../tests/yaml/hello-world.txt') True """ @@ -119,9 +123,9 @@ def checks(text, encoding="utf-8"): If it contains a frontmatter but it is empty, return True as well. - :: + .. doctest:: - >>> with open('tests/hello-world.markdown') as f: + >>> with open('../tests/yaml/hello-world.txt') as f: ... frontmatter.checks(f.read()) True @@ -135,10 +139,10 @@ def load(fd, encoding="utf-8", handler=None, **defaults): Load and parse a file-like object or filename, return a :py:class:`post `. - :: + .. doctest:: - >>> post = frontmatter.load('tests/hello-world.markdown') - >>> with open('tests/hello-world.markdown') as f: + >>> post = frontmatter.load('../tests/yaml/hello-world.txt') + >>> with open('../tests/yaml/hello-world.txt') as f: ... post = frontmatter.load(f) """ @@ -157,9 +161,9 @@ def loads(text, encoding="utf-8", handler=None, **defaults): """ Parse text (binary or unicode) and return a :py:class:`post `. - :: + .. doctest:: - >>> with open('tests/hello-world.markdown') as f: + >>> with open('../tests/yaml/hello-world.txt') as f: ... post = frontmatter.loads(f.read()) """ @@ -177,17 +181,35 @@ def dump(post, fd, encoding="utf-8", handler=None, **kwargs): :: >>> from io import BytesIO + >>> post = frontmatter.load('../tests/yaml/hello-world.txt') >>> f = BytesIO() >>> frontmatter.dump(post, f) - >>> print(f.getvalue()) + >>> print(f.getvalue().decode('utf-8')) --- - excerpt: tl;dr layout: post title: Hello, world! --- + Well, hello there, world. + .. testcode:: + + from io import BytesIO + post = frontmatter.load('../tests/yaml/hello-world.txt') + f = BytesIO() + frontmatter.dump(post, f) + print(f.getvalue().decode('utf-8')) + + .. testoutput:: + + --- + layout: post + title: Hello, world! + --- + + Well, hello there, world. + """ content = dumps(post, handler, **kwargs) if hasattr(fd, "write"): @@ -207,14 +229,30 @@ def dumps(post, handler=None, **kwargs): passed as an argument will override ``post.handler``, with :py:class:`YAMLHandler ` used as a default. + :: - >>> print(frontmatter.dumps(post)) + >>> post = frontmatter.load('../tests/yaml/hello-world.txt') + >>> print(frontmatter.dumps(post)) # doctest: +NORMALIZE_WHITESPACE --- - excerpt: tl;dr layout: post title: Hello, world! --- + + Well, hello there, world. + + .. testcode:: + + post = frontmatter.load('../tests/yaml/hello-world.txt') + print(frontmatter.dumps(post)) + + .. testoutput:: + + --- + layout: post + title: Hello, world! + --- + Well, hello there, world. """ diff --git a/frontmatter/default_handlers.py b/frontmatter/default_handlers.py index 0fe4773..46b40be 100644 --- a/frontmatter/default_handlers.py +++ b/frontmatter/default_handlers.py @@ -1,5 +1,9 @@ # -*- coding: utf-8 -*- """ +.. testsetup:: handlers + + import frontmatter + By default, ``frontmatter`` reads and writes YAML metadata. But maybe you don't like YAML. Maybe enjoy writing metadata in JSON, or TOML, or some other exotic markup not yet invented. For this, there are handlers. @@ -31,11 +35,12 @@ object. By default, calling :py:func:`frontmatter.dumps ` on the post will use the attached handler. + :: >>> import frontmatter >>> from frontmatter.default_handlers import YAMLHandler, TOMLHandler - >>> post = frontmatter.load('tests/hello-toml.markdown', handler=TOMLHandler()) + >>> post = frontmatter.load('tests/toml/hello-toml.markdown', handler=TOMLHandler()) >>> post.handler #doctest: +ELLIPSIS diff --git a/setup.py b/setup.py index 675b3dc..ac02e5b 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ packages=["frontmatter"], include_package_data=True, install_requires=["PyYAML"], - extras_require={"test": ["pytest", "toml", "pyaml"]}, + extras_require={"test": ["pytest", "toml", "pyaml"], "docs": ["sphinx"]}, tests_require=["python-frontmatter[test]"], license="MIT", zip_safe=False, diff --git a/test.py b/test.py index 77c481a..b3e0541 100644 --- a/test.py +++ b/test.py @@ -33,14 +33,9 @@ class FrontmatterTest(unittest.TestCase): maxDiff = None - def test_all_the_tests(self): - "Sanity check that everything in the tests folder loads without errors" - for filename in glob.glob("tests/*"): - frontmatter.load(filename) - def test_with_markdown_content(self): "Parse frontmatter and only the frontmatter" - post = frontmatter.load("tests/hello-markdown.markdown") + post = frontmatter.load("tests/yaml/hello-markdown.md") metadata = {"author": "bob", "something": "else", "test": "tester"} for k, v in metadata.items(): @@ -48,7 +43,7 @@ def test_with_markdown_content(self): def test_unicode_post(self): "Ensure unicode is parsed correctly" - chinese = frontmatter.load("tests/chinese.txt", "utf-8") + chinese = frontmatter.load("tests/yaml/chinese.txt", "utf-8") output = frontmatter.dumps(chinese) zh = "中文" @@ -62,20 +57,20 @@ def test_unicode_post(self): def test_check_no_frontmatter(self): "Checks if a file does not have a frontmatter" - ret = frontmatter.check("tests/no-frontmatter.txt") + ret = frontmatter.check("tests/empty/no-frontmatter.txt") self.assertEqual(ret, False) def test_check_empty_frontmatter(self): "Checks if a file has a frontmatter (empty or not)" - ret = frontmatter.check("tests/empty-frontmatter.txt") + ret = frontmatter.check("tests/empty/empty-frontmatter.txt") self.assertEqual(ret, True) def test_no_frontmatter(self): "This is not a zen exercise." - post = frontmatter.load("tests/no-frontmatter.txt") - with codecs.open("tests/no-frontmatter.txt", "r", "utf-8") as f: + post = frontmatter.load("tests/empty/no-frontmatter.txt") + with codecs.open("tests/empty/no-frontmatter.txt", "r", "utf-8") as f: content = f.read().strip() self.assertEqual(post.metadata, {}) @@ -83,7 +78,7 @@ def test_no_frontmatter(self): def test_empty_frontmatter(self): "Frontmatter, but no metadata" - post = frontmatter.load("tests/empty-frontmatter.txt") + post = frontmatter.load("tests/empty/empty-frontmatter.txt") content = "I have frontmatter but no metadata." self.assertEqual(post.metadata, {}) @@ -91,7 +86,7 @@ def test_empty_frontmatter(self): def test_extra_space(self): "Extra space in frontmatter delimiter" - post = frontmatter.load("tests/extra-space.txt") + post = frontmatter.load("tests/yaml/extra-space.txt") content = "This file has an extra space on the opening line of the frontmatter." self.assertEqual(post.content, content) @@ -101,7 +96,7 @@ def test_extra_space(self): def test_to_dict(self): "Dump a post as a dict, for serializing" - post = frontmatter.load("tests/network-diagrams.markdown") + post = frontmatter.load("tests/yaml/network-diagrams.md") post_dict = post.to_dict() for k, v in post.metadata.items(): @@ -111,7 +106,7 @@ def test_to_dict(self): def test_to_string(self): "Calling str(post) returns post.content" - post = frontmatter.load("tests/hello-world.markdown") + post = frontmatter.load("tests/yaml/hello-world.txt") # test unicode and bytes text = "Well, hello there, world." @@ -123,10 +118,10 @@ def test_pretty_dumping(self): # pyaml only runs on 2.7 and above if pyaml is not None: - with codecs.open("tests/unpretty.md", "r", "utf-8") as f: + with codecs.open("tests/yaml/unpretty.md", "r", "utf-8") as f: data = f.read() - post = frontmatter.load("tests/unpretty.md") + post = frontmatter.load("tests/yaml/unpretty.md") yaml = pyaml.dump(post.metadata) # the unsafe dumper gives you nicer output, for times you want that @@ -142,14 +137,14 @@ def test_with_crlf_string(self): def test_dumping_with_custom_delimiters(self): "dump with custom delimiters" - post = frontmatter.load("tests/hello-world.markdown") + post = frontmatter.load("tests/yaml/hello-world.txt") dump = frontmatter.dumps(post, start_delimiter="+++", end_delimiter="+++") self.assertTrue("+++" in dump) def test_dump_to_file(self): "dump post to filename" - post = frontmatter.load("tests/hello-world.markdown") + post = frontmatter.load("tests/yaml/hello-world.txt") tempdir = tempfile.mkdtemp() filename = os.path.join(tempdir, "hello.md") @@ -168,9 +163,9 @@ class HandlerTest(unittest.TestCase): """ TEST_FILES = { - "tests/hello-world.markdown": YAMLHandler, - "tests/hello-json.markdown": JSONHandler, - "tests/hello-toml.markdown": TOMLHandler, + "tests/yaml/hello-world.txt": YAMLHandler, + "tests/json/hello-json.md": JSONHandler, + "tests/toml/hello-toml.md": TOMLHandler, } def sanity_check(self, filename, handler_type): @@ -201,7 +196,7 @@ def test_sanity_all(self): def test_no_handler(self): "default to YAMLHandler when no handler is attached" - post = frontmatter.load("tests/hello-world.markdown") + post = frontmatter.load("tests/yaml/hello-world.txt") del post.handler text = frontmatter.dumps(post) @@ -242,14 +237,14 @@ def test_toml(self): "load toml frontmatter" if toml is None: return - post = frontmatter.load("tests/hello-toml.markdown") + post = frontmatter.load("tests/toml/hello-toml.md") metadata = {"author": "bob", "something": "else", "test": "tester"} for k, v in metadata.items(): self.assertEqual(post[k], v) def test_json(self): "load raw JSON frontmatter" - post = frontmatter.load("tests/hello-json.markdown") + post = frontmatter.load("tests/json/hello-json.md") metadata = {"author": "bob", "something": "else", "test": "tester"} for k, v in metadata.items(): self.assertEqual(post[k], v) @@ -266,7 +261,7 @@ def setUp(self): """ self.handler = None self.data = { - "filename": "tests/hello-world.markdown", + "filename": "tests/yaml/hello-world.txt", "content": """\ """, "metadata": {}, @@ -339,7 +334,7 @@ class YAMLHandlerTest(HandlerBaseTest, unittest.TestCase): def setUp(self): self.handler = YAMLHandler() self.data = { - "filename": "tests/hello-markdown.markdown", + "filename": "tests/yaml/hello-markdown.md", # TODO: YAMLHandler.split() is prepending '\n' to the content "content": """\ @@ -363,7 +358,7 @@ class JSONHandlerTest(HandlerBaseTest, unittest.TestCase): def setUp(self): self.handler = JSONHandler() self.data = { - "filename": "tests/hello-json.markdown", + "filename": "tests/json/hello-json.md", # TODO: JSONHandler.split() is prepending '\n' to the content "content": """\ @@ -389,7 +384,7 @@ class TOMLHandlerTest(HandlerBaseTest, unittest.TestCase): def setUp(self): self.handler = TOMLHandler() self.data = { - "filename": "tests/hello-toml.markdown", + "filename": "tests/toml/hello-toml.md", # TODO: TOMLHandler.split() is prepending '\n' to the content "content": """\ @@ -411,8 +406,8 @@ def setUp(self): if __name__ == "__main__": - doctest.testfile("README.md", extraglobs={"frontmatter": frontmatter}) - doctest.testmod( - frontmatter.default_handlers, extraglobs={"frontmatter": frontmatter} - ) + # doctest.testfile("README.md", extraglobs={"frontmatter": frontmatter}) + # doctest.testmod( + # frontmatter.default_handlers, extraglobs={"frontmatter": frontmatter} + # ) unittest.main() diff --git a/tests/test_docs.py b/tests/test_docs.py index 02d9c08..9d08afa 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -3,8 +3,15 @@ import frontmatter -def test_docs(): +def test_readme(): doctest.testfile("../README.md", extraglobs={"frontmatter": frontmatter}) + + +def test_api_docs(): + doctest.testmod(frontmatter, extraglobs={"frontmatter": frontmatter}) + + +def test_handler_docs(): doctest.testmod( frontmatter.default_handlers, extraglobs={"frontmatter": frontmatter} ) From 1d85a20c4bf88beec68cd4916a77e8121e9922d5 Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Sun, 7 Mar 2021 14:59:10 -0500 Subject: [PATCH 6/7] Move old unit tests into tests/ directory, at least for now --- LICENSE | 2 +- docs/conf.py | 2 +- test.py => tests/unit_test.py | 4 ---- 3 files changed, 2 insertions(+), 6 deletions(-) rename test.py => tests/unit_test.py (98%) diff --git a/LICENSE b/LICENSE index 3012155..92e6b96 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2019 Chris Amico, Glass Eye Media LLC +Copyright (c) 2021 Chris Amico Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/docs/conf.py b/docs/conf.py index ef09deb..a329d67 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,7 +50,7 @@ # General information about the project. project = u"Python Frontmatter" -copyright = u"2017, Chris Amico" +copyright = u"2021, Chris Amico" author = u"Chris Amico" # The version info for the project you're documenting, acts as replacement for diff --git a/test.py b/tests/unit_test.py similarity index 98% rename from test.py rename to tests/unit_test.py index b3e0541..47f41f2 100644 --- a/test.py +++ b/tests/unit_test.py @@ -406,8 +406,4 @@ def setUp(self): if __name__ == "__main__": - # doctest.testfile("README.md", extraglobs={"frontmatter": frontmatter}) - # doctest.testmod( - # frontmatter.default_handlers, extraglobs={"frontmatter": frontmatter} - # ) unittest.main() From c524df39c89d485da58eed29fc9b30c7a448e0d4 Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Sun, 7 Mar 2021 15:08:58 -0500 Subject: [PATCH 7/7] some edits --- README.md | 2 ++ docs/index.rst | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 30a797c..af3bdfd 100644 --- a/README.md +++ b/README.md @@ -109,3 +109,5 @@ title: Hello, world! Well, hello there, world. ``` + +For more examples, see files in the `tests/` directory. Each sample file has a corresponding `.result.json` file showing the expected parsed output. diff --git a/docs/index.rst b/docs/index.rst index 2cc131b..611cd65 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,7 +12,7 @@ Python Frontmatter useful way to add arbitrary, structured metadata to text documents, regardless of type. -This is a small package to load and parse files (or just text) with YAML +This is a package to load and parse files (or text strings) with YAML front matter. @@ -80,7 +80,7 @@ Metadata is a dictionary, with some handy proxies: >>> pprint(post.metadata) {'excerpt': 'tl;dr', 'layout': 'post', 'title': 'Hello, world!'} -If you don't need the whole post object, just parse: +If you don't need the whole post object, use `frontmatter.parse` to return metadata and content separately: ::