forked from esphome/esphome-docs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
seo.py
156 lines (125 loc) · 4.75 KB
/
seo.py
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
150
151
152
153
154
155
156
import re
from pathlib import Path
from docutils import nodes
from docutils.parsers.rst import Directive, directives
from docutils.writers._html_base import HTMLTranslator
class SEONode(nodes.General, nodes.Element):
def __init__(
self,
title=None,
description=None,
image=None,
author=None,
author_twitter=None,
keywords=None,
):
super(SEONode, self).__init__()
self.title = title
self.description = description.replace("\n", " ")
self.image = image
self.author = author
self.author_twitter = author_twitter
self.keywords = keywords
class RedirectNode(nodes.General, nodes.Element):
def __init__(self, url=None):
super(RedirectNode, self).__init__()
self.url = url
def seo_visit(self: HTMLTranslator, node: SEONode):
def encode_text(text):
special_characters = {
ord("&"): "&",
ord("<"): "<",
ord('"'): """,
ord(">"): ">",
}
return text.translate(special_characters)
def create_content_meta(name, content):
if content is None:
return
self.meta.append(
'<meta name="{}" content="{}">\n'.format(name, encode_text(content))
)
def create_itemprop_meta(name, content):
if content is None:
return
self.meta.append(
'<meta itemprop="{}" content="{}">\n'.format(name, encode_text(content))
)
def create_property_meta(name, content):
if content is None:
return
self.meta.append(
'<meta property="{}" content="{}">\n'.format(name, encode_text(content))
)
# Base
create_content_meta("description", node.description)
create_content_meta("keywords", node.keywords)
# Schema.org
create_itemprop_meta("name", node.title)
create_itemprop_meta("description", node.description)
create_itemprop_meta("image", node.image)
# Twitter
create_content_meta("twitter:title", node.title)
create_content_meta("twitter:image:src", node.image)
if node.author:
create_content_meta("twitter:card", "summary_large_image")
else:
create_content_meta("twitter:card", "summary")
create_content_meta("twitter:site", "@OttoWinter_")
create_content_meta("twitter:creator", node.author_twitter)
create_content_meta("twitter:description", node.description)
# Open Graph
create_property_meta("og:title", node.title)
create_property_meta("og:image", node.image)
create_property_meta("og:type", "article" if node.author is not None else "website")
create_property_meta("og:description", node.description)
def redirect_visit(self: HTMLTranslator, node: RedirectNode):
self.meta.append('<meta http-equiv="refresh" content="0; url={}">'.format(node.url))
self.body.append(
self.starttag(
node, "p", 'Redirecting to <a href="{0}">{0}</a>'.format(node.url)
)
)
def seo_depart(self, _):
pass
def redirect_depart(self, _):
self.body.append("</p>")
class SEODirective(Directive):
option_spec = {
"title": directives.unchanged,
"description": directives.unchanged,
"image": directives.path,
"author": directives.unchanged,
"author_twitter": directives.unchanged,
"keywords": directives.unchanged,
}
def run(self):
env = self.state.document.settings.env
title_match = re.match(r".+<title>(.+)</title>.+", str(self.state.document))
if title_match is not None and "title" not in self.options:
self.options["title"] = title_match.group(1)
image = self.options.get("image")
if image is not None:
local_img = image
if not image.startswith("/"):
local_img = f"/images/{image}"
image = "/_images/" + image
p = Path(__file__).parent / local_img[1:]
if not p.is_file():
raise ValueError(f"File {p} for seo tag does not exist {self.state.document}")
if image.endswith(".svg"):
image = image[:-len(".svg")] + ".png"
self.options["image"] = env.config.html_baseurl + image
return [SEONode(**self.options)]
class RedirectDirective(Directive):
option_spec = {
"url": directives.unchanged,
}
def run(self):
return [RedirectNode(**self.options)]
def setup(app):
app.add_directive("seo", SEODirective)
app.add_node(SEONode, html=(seo_visit, seo_depart))
app.add_directive("redirect", RedirectDirective)
app.add_node(RedirectNode, html=(redirect_visit, redirect_depart))
return {"version": "1.0.0", "parallel_read_safe": True, "parallel_write_safe": True}