-
-
Notifications
You must be signed in to change notification settings - Fork 792
Expand file tree
/
Copy pathfrontmatter.py
More file actions
149 lines (115 loc) · 4 KB
/
Copy pathfrontmatter.py
File metadata and controls
149 lines (115 loc) · 4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# -*- coding: utf-8 -*-
"""
Python Frontmatter: Parse and manage posts with YAML frontmatter
Based on and heavily modified/simplified from ``python-frontmatter``
version 1.0.0, to only support nexB/saneyaml instead of pure YAML.
license: mit. See frontmatter.ABOUT file for details.
"""
import codecs
import saneyaml
import re
DEFAULT_POST_TEMPLATE = """\
{start_delimiter}
{metadata}
{end_delimiter}
{content}
"""
class SaneYAMLHandler:
"""
Load and export YAML metadata. .
This is similar to the original frontmatter.default_handlers.YAMLHandler
but is using nexB/saneyaml instead of pyyaml.
"""
FM_BOUNDARY = re.compile(r"^-{3,}\s*$", re.MULTILINE)
START_DELIMITER = END_DELIMITER = "---"
def __init__(self):
self.FM_BOUNDARY = self.FM_BOUNDARY
self.START_DELIMITER = self.START_DELIMITER
self.END_DELIMITER = self.END_DELIMITER
def detect(self, text):
"""
Decide whether this handler can parse the given ``text``,
and return True or False.
"""
if self.FM_BOUNDARY.match(text):
return True
return False
def split(self, text):
"""
Split text into frontmatter and content.
"""
_, fm, content = self.FM_BOUNDARY.split(text, 2)
return fm, content
def format(self, content, metadata, template=DEFAULT_POST_TEMPLATE, **kwargs):
"""
Return string with `content` and `metadata` as YAML frontmatter,
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(metadata, **kwargs)
return template.format(
metadata=metadata,
content=content,
start_delimiter=start_delimiter,
end_delimiter=end_delimiter,
).strip()
def load(self, fm, **kwargs):
"""
Parse YAML front matter.
"""
return saneyaml.load(fm, allow_duplicate_keys=False, **kwargs)
def export(self, metadata, **kwargs):
"""
Export metadata as YAML.
"""
metadata = saneyaml.dump(metadata, indent=4, encoding='utf-8', **kwargs).strip()
return return_unicode(metadata) # ensure unicode
def return_unicode(text, encoding="utf-8"):
"""
Return unicode text, no matter what.
"""
if isinstance(text, bytes):
text = text.decode(encoding)
# it's already unicode
text = text.replace("\r\n", "\n")
return text
def parse_frontmatter(text, encoding="utf-8", handler=SaneYAMLHandler(), **defaults):
"""
Parse text with frontmatter, return `content` and `metadata`.
Pass in optional metadata defaults as keyword args.
If frontmatter is not found, returns an empty metadata dictionary
(or defaults) and original text content.
"""
# ensure unicode first
text = return_unicode(text, encoding)
# metadata starts with defaults
metadata = defaults.copy()
# split on the delimiters
try:
fm, content = handler.split(text)
except ValueError:
# if we can't split, bail
return metadata, text
# parse, now that we have frontmatter
fm = handler.load(fm)
if isinstance(fm, dict):
metadata.update(fm)
return content, metadata
def load_frontmatter(fd, encoding="utf-8", **defaults):
"""
Load and parse a file-like object or filename `fd`, and return
`content` and `metadata` with the text and the frontmatter metadata.
"""
if hasattr(fd, "read"):
text = fd.read()
else:
with open(fd, "r", encoding=encoding) as f:
text = f.read()
text = return_unicode(text, encoding)
return parse_frontmatter(text, encoding, **defaults)
def dumps_frontmatter(content, metadata, handler=SaneYAMLHandler(), **kwargs):
"""
Create a string and return the text from `content` and `metadata`.
"""
return handler.format(content, metadata, **kwargs)