Skip to content

Commit 162cffb

Browse files
Vendor python-forntmatter completely
We used to vendor parts of python frontmatter and also use it as a dependency which was problematic. This vendors python-frontmatter fully and only uses the code needed. Signed-off-by: Ayan Sinha Mahapatra <ayansmahapatra@gmail.com>
1 parent 84186d0 commit 162cffb

4 files changed

Lines changed: 88 additions & 93 deletions

File tree

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ pygmars==0.7.0
5656
Pygments==2.12.0
5757
pymaven-patch==0.3.0
5858
pyparsing==3.0.9
59-
python-frontmatter==1.0.0
6059
pytz==2022.1
6160
PyYAML==6.0
6261
rdflib==6.2.0

setup.cfg

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ install_requires =
101101
pygmars >= 0.7.0
102102
pygments
103103
pymaven_patch >= 0.2.8
104-
python-frontmatter >= 1.0.0
105104
requests >= 2.7.0
106105
saneyaml >= 0.5.2
107106
spdx_tools >= 0.7.0a3

src/licensedcode/frontmatter.py

Lines changed: 68 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,63 @@
1313
import saneyaml
1414
import re
1515

16-
from frontmatter import detect_format
17-
from frontmatter import handlers
18-
from frontmatter import Post as FrontmatterPost
19-
from frontmatter.default_handlers import BaseHandler
20-
from frontmatter.util import u
2116

22-
from licensedcode.tokenize import query_lines
17+
DEFAULT_POST_TEMPLATE = """\
18+
{start_delimiter}
19+
{metadata}
20+
{end_delimiter}
2321
22+
{content}
23+
"""
2424

2525

26-
class SaneYAMLHandler(BaseHandler):
26+
class SaneYAMLHandler:
2727
"""
2828
Load and export YAML metadata. .
2929
30-
This is similar to the frontmatter.default_handlers.YAMLHandler but
31-
is using nexB/saneyaml instead of pyyaml.
30+
This is similar to the original frontmatter.default_handlers.YAMLHandler
31+
but is using nexB/saneyaml instead of pyyaml.
3232
"""
3333
FM_BOUNDARY = re.compile(r"^-{3,}\s*$", re.MULTILINE)
3434
START_DELIMITER = END_DELIMITER = "---"
3535

36+
def __init__(self):
37+
self.FM_BOUNDARY = self.FM_BOUNDARY
38+
self.START_DELIMITER = self.START_DELIMITER
39+
self.END_DELIMITER = self.END_DELIMITER
40+
41+
def detect(self, text):
42+
"""
43+
Decide whether this handler can parse the given ``text``,
44+
and return True or False.
45+
"""
46+
if self.FM_BOUNDARY.match(text):
47+
return True
48+
return False
49+
50+
def split(self, text):
51+
"""
52+
Split text into frontmatter and content
53+
"""
54+
_, fm, content = self.FM_BOUNDARY.split(text, 2)
55+
return fm, content
56+
57+
def format(self, content, metadata, **kwargs):
58+
"""
59+
Turn a post into a string, used in ``frontmatter.dumps``
60+
"""
61+
start_delimiter = kwargs.pop("start_delimiter", self.START_DELIMITER)
62+
end_delimiter = kwargs.pop("end_delimiter", self.END_DELIMITER)
63+
64+
metadata = self.export(metadata, **kwargs)
65+
66+
return DEFAULT_POST_TEMPLATE.format(
67+
metadata=metadata,
68+
content=content,
69+
start_delimiter=start_delimiter,
70+
end_delimiter=end_delimiter,
71+
).strip()
72+
3673
def load(self, fm, **kwargs):
3774
"""
3875
Parse YAML front matter.
@@ -44,42 +81,34 @@ def export(self, metadata, **kwargs):
4481
Export metadata as YAML.
4582
"""
4683
metadata = saneyaml.dump(metadata, indent=4, encoding='utf-8', **kwargs).strip()
47-
return u(metadata) # ensure unicode
84+
return return_unicode(metadata) # ensure unicode
4885

4986

50-
def get_rule_text(location=None, text=None):
51-
"""
52-
Return the rule ``text`` prepared for indexing.
53-
###############
54-
# IMPORTANT: we use the same process as used to load query text for symmetry
55-
###############
56-
"""
57-
numbered_lines = query_lines(location=location, query_string=text, plain_text=True)
58-
return '\n'.join(l.strip() for _, l in numbered_lines)
87+
def return_unicode(text, encoding="utf-8"):
88+
"Return unicode text, no matter what"
89+
90+
if isinstance(text, bytes):
91+
text = text.decode(encoding)
5992

93+
# it's already unicode
94+
text = text.replace("\r\n", "\n")
95+
return text
6096

61-
def parse_frontmatter(text, encoding="utf-8", handler=None, **defaults):
97+
98+
def parse_frontmatter(text, encoding="utf-8", handler=SaneYAMLHandler(), **defaults):
6299
"""
63100
Parse text with frontmatter, return metadata and content.
64101
Pass in optional metadata defaults as keyword args.
65102
66103
If frontmatter is not found, returns an empty metadata dictionary
67104
(or defaults) and original text content.
68-
69-
This is similar to the frontmatter.parse but is using `get_rule_text`
70-
to use the same process as loading quary text for symmetry.
71105
"""
72106
# ensure unicode first
73-
text = u(text, encoding).strip()
107+
text = return_unicode(text, encoding)
74108

75109
# metadata starts with defaults
76110
metadata = defaults.copy()
77111

78-
# this will only run if a handler hasn't been set higher up
79-
handler = handler or detect_format(text, handlers)
80-
if handler is None:
81-
return metadata, text
82-
83112
# split on the delimiters
84113
try:
85114
fm, content = handler.split(text)
@@ -92,57 +121,27 @@ def parse_frontmatter(text, encoding="utf-8", handler=None, **defaults):
92121
if isinstance(fm, dict):
93122
metadata.update(fm)
94123

95-
text = get_rule_text(text=content)
124+
return content, metadata
96125

97-
return metadata, text
98126

99-
100-
def loads_frontmatter(text, encoding="utf-8", handler=None, **defaults):
127+
def load_frontmatter(fd, encoding="utf-8", **defaults):
101128
"""
102-
Parse text (binary or unicode) and return a :py:class:`post <frontmatter.Post>`.
103-
104-
This is similar to the frontmatter.loads but is using the `parse`
105-
function defined above.
106-
"""
107-
text = u(text, encoding)
108-
handler = handler or detect_format(text, handlers)
109-
metadata, content = parse_frontmatter(text, encoding, handler, **defaults)
110-
return FrontmatterPost(content, handler, **metadata)
111-
112-
113-
def load_frontmatter(fd, encoding="utf-8", handler=None, **defaults):
114-
"""
115-
Load and parse a file-like object or filename,
116-
return a :py:class:`post <frontmatter.Post>`.
117-
118-
This is similar to the frontmatter.load but is using the `loads`
119-
function defined above.
129+
Load and parse a file-like object or filename, and return
130+
`content` and `metadata` with the text and the frontmatter metadata.
120131
"""
121132
if hasattr(fd, "read"):
122133
text = fd.read()
123134

124135
else:
125136
with codecs.open(fd, "r", encoding) as f:
126137
text = f.read()
127-
128-
handler = handler or detect_format(text, handlers)
129-
return loads_frontmatter(text, encoding, handler, **defaults)
138+
139+
text = return_unicode(text, encoding)
140+
return parse_frontmatter(text, encoding, **defaults)
130141

131142

132-
def dumps_frontmatter(post, handler=None, **kwargs):
143+
def dumps_frontmatter(content, metadata, handler=SaneYAMLHandler(), **kwargs):
133144
"""
134-
Serialize a :py:class:`post <frontmatter.Post>` to a string and return text.
135-
This always returns unicode text, which can then be encoded.
136-
137-
Passing ``handler`` will change how metadata is turned into text. A handler
138-
passed as an argument will override ``post.handler``, with
139-
:py:class:`SaneYAMLHandler <frontmatter.SaneYAMLHandler>` used as
140-
a default.
141-
142-
This is similar to the frontmatter.dumps but is using the `SaneYAMLHandler`
143-
defined above as default instead of frontmatter.default_handlers.YAMLHandler.
145+
Create a string and return the text from `content` and `metadata`.
144146
"""
145-
if handler is None:
146-
handler = getattr(post, "handler", None) or SaneYAMLHandler()
147-
148-
return handler.format(post, **kwargs)
147+
return handler.format(content, metadata, **kwargs)

src/licensedcode/models.py

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,16 @@
3333
from licensedcode import MIN_MATCH_HIGH_LENGTH
3434
from licensedcode import MIN_MATCH_LENGTH
3535
from licensedcode import SMALL_RULE
36-
from licensedcode.frontmatter import SaneYAMLHandler
37-
from licensedcode.frontmatter import FrontmatterPost
3836
from licensedcode.frontmatter import dumps_frontmatter
3937
from licensedcode.frontmatter import load_frontmatter
40-
from licensedcode.frontmatter import get_rule_text
4138
from licensedcode.languages import LANG_INFO as known_languages
4239
from licensedcode.spans import Span
4340
from licensedcode.tokenize import index_tokenizer
4441
from licensedcode.tokenize import index_tokenizer_with_stopwords
4542
from licensedcode.tokenize import key_phrase_tokenizer
4643
from licensedcode.tokenize import KEY_PHRASE_OPEN
4744
from licensedcode.tokenize import KEY_PHRASE_CLOSE
45+
from licensedcode.tokenize import query_lines
4846

4947
"""
5048
Reference License and license Rule structures persisted as a combo of a YAML
@@ -427,17 +425,9 @@ def dump(self, licenses_data_dir):
427425
- the license data as YAML frontmatter
428426
- the license text
429427
"""
430-
431-
def write(location, byte_string):
432-
# we write as binary because rules and licenses texts and data are
433-
# UTF-8-encoded bytes
434-
with io.open(location, 'wb') as of:
435-
of.write(byte_string)
436-
437428
metadata = self.to_dict(include_builtin=False)
438429
content = self.text
439-
rule_post = FrontmatterPost(content=content, handler=SaneYAMLHandler(), **metadata)
440-
output = dumps_frontmatter(post=rule_post)
430+
output = dumps_frontmatter(content=content, metadata=metadata)
441431
license_file = self.license_file(licenses_data_dir=licenses_data_dir)
442432
with open(license_file, 'w') as of:
443433
of.write(output)
@@ -449,16 +439,15 @@ def load(self, license_file, check_consistency=True):
449439
Unknown fields are ignored and not bound to the License object.
450440
"""
451441
try:
452-
post = load_frontmatter(license_file)
453-
data = post.metadata
442+
content, data = load_frontmatter(license_file)
454443
if check_consistency:
455444
if not data:
456445
raise InvalidLicense(
457446
f'Cannot load License with empty YAML frontmatter: '
458447
f'{self}: file://{license_file}'
459448
)
460449

461-
if not post.content:
450+
if not content:
462451
if check_consistency:
463452
if not any(
464453
attribute in data
@@ -472,7 +461,7 @@ def load(self, license_file, check_consistency=True):
472461

473462
self.text = ''
474463
else:
475-
self.text = post.content.lstrip("\n")
464+
self.text = content.lstrip("\n")
476465

477466
for k, v in data.items():
478467
if k == 'minimum_coverage':
@@ -1043,6 +1032,17 @@ def build_rule_from_license(license_obj):
10431032
return rule
10441033

10451034

1035+
def get_rule_text(location=None, text=None):
1036+
"""
1037+
Return the rule ``text`` prepared for indexing.
1038+
###############
1039+
# IMPORTANT: we use the same process as used to load query text for symmetry
1040+
###############
1041+
"""
1042+
numbered_lines = query_lines(location=location, query_string=text, plain_text=True)
1043+
return '\n'.join(l.strip() for _, l in numbered_lines)
1044+
1045+
10461046
def get_all_spdx_keys(licenses_db):
10471047
"""
10481048
Return an iterable of SPDX license keys collected from a `licenses_db`
@@ -2084,8 +2084,7 @@ def dump(self, rules_data_dir):
20842084

20852085
metadata = self.to_dict()
20862086
content = self.text
2087-
rule_post = FrontmatterPost(content=content, handler=SaneYAMLHandler(), **metadata)
2088-
output = dumps_frontmatter(post=rule_post)
2087+
output = dumps_frontmatter(content=content, metadata=metadata)
20892088
with open(rule_file, 'w') as of:
20902089
of.write(output)
20912090

@@ -2096,15 +2095,14 @@ def load(self, rule_file, with_checks=True):
20962095
Optionally check for consistency if ``with_checks`` is True.
20972096
"""
20982097
try:
2099-
post = load_frontmatter(rule_file)
2100-
data = post.metadata
2101-
if not post.content:
2098+
content, data = load_frontmatter(rule_file)
2099+
if not content:
21022100
raise InvalidRule(
21032101
f'Cannot load rule with empty text: '
21042102
f'{self}: file://{rule_file}'
21052103
)
21062104

2107-
self.text = post.content.lstrip()
2105+
self.text = content.lstrip()
21082106

21092107
except Exception as e:
21102108
print('#############################')

0 commit comments

Comments
 (0)