Skip to content

Commit 5bf54e9

Browse files
committed
adding sketchviz/graphviz
1 parent 345f540 commit 5bf54e9

File tree

9 files changed

+168
-19
lines changed

9 files changed

+168
-19
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,4 @@ _website/
214214

215215
# python.pe
216216
_static/images/gravatar/
217+
_static/images/sketchviz/

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "deps/sketchviz"]
2+
path = deps/sketchviz
3+
url = git@github.com:gpotter2/sketchviz.git

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,29 @@ sudo port install graphviz
1515

1616
3. apt-based Linux distribution
1717
```sh
18-
apt install graphviz
18+
sudo apt install graphviz
19+
```
20+
21+
Then make sure you have installed node and npm
22+
23+
1. macOS with Homebrew
24+
```sh
25+
brew install node
26+
```
27+
28+
2. macOS with MacPorts
29+
```sh
30+
sudo port install nodejs21 npm10
31+
```
32+
33+
3. apt-based Linux distribution
34+
```sh
35+
sudo apt install nodejs npm
36+
```
37+
38+
Install sketchviz
39+
```sh
40+
sudo ./bin/install-sketchviz.sh
1941
```
2042

2143
Make sure you have Python 3.12 installed. Then install `poetry`:

bin/install-sketchviz.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env sh
2+
pushd ./deps/sketchviz
3+
npm pack
4+
npm i -g sketchviz-0.0.1.tgz
5+
popd

conf.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,6 @@
201201
"sphinx.ext.extlinks",
202202
"sphinx.ext.intersphinx",
203203
"sphinx.ext.todo",
204-
"sphinx.ext.graphviz",
205204
"ablog",
206205
"myst_parser",
207206
"sphinxcontrib.youtube",
@@ -213,6 +212,7 @@
213212
"sphinxext.opengraph",
214213
"embed",
215214
"gravatar",
215+
"sketchviz",
216216
]
217217

218218
myst_enable_extensions = [
@@ -280,6 +280,7 @@
280280
"github_submodule/*",
281281
"LICENSE.md",
282282
"README.md",
283+
"deps/*"
283284
]
284285

285286
# The reST default role (used for this markup: `text`) to use for all

deps/sketchviz

Submodule sketchviz added at d977905

ext/gravatar.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
{% if width %} width="{{ width }}" height="{{ width }}"{% endif %}>
2626
</div>
2727
"""
28-
2928
FILENAME_EXT = ".png"
3029

3130

@@ -99,21 +98,21 @@ def run(self):
9998
# Try to make the dir if it doesn't exist
10099
os.makedirs(save_path, exist_ok=True)
101100

102-
logger.info(f"Getting Gravatar image for email: {email}")
101+
logger.info(f"Gravatar: Getting Gravatar image for email: {email}")
103102
url = Gravatar(email).get_image()
104103
if width is not None:
105104
url = f"{url}?s={width}"
106-
logger.info(f"Requesting Gravatar image from URL: {url}")
105+
logger.info(f"Gravatar: Requesting Gravatar image from URL: {url}")
107106

108107
filename = sha256(url.encode()).digest().hex()
109108
filename = f"{filename}{FILENAME_EXT}"
110109
save_path = os.path.join(save_path, filename)
111110
urllib.request.urlretrieve(url, save_path)
112-
logger.info(f"Retrieving image into: {save_path}")
111+
logger.info(f"Gravatar: Retrieving image into: {save_path}")
113112

114113
if with_circle_clip or with_grayscale:
115114
self._process_image(save_path, with_circle_clip, with_grayscale)
116-
logger.info("Applying image post-processing")
115+
logger.info("Gravatar: Applying image post-processing")
117116

118117
template = Environment(
119118
loader=BaseLoader, trim_blocks=True, lstrip_blocks=True

ext/sketchviz.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"""A directive to generate an embedded svg image using Graphviz/Sketchviz
2+
backend parsers
3+
"""
4+
import os
5+
from tempfile import NamedTemporaryFile
6+
import subprocess
7+
import hashlib
8+
9+
from docutils import nodes
10+
from ablog.commands import find_confdir, read_conf
11+
from docutils.parsers.rst import Directive
12+
from jinja2 import BaseLoader, Environment
13+
from sphinx.application import Sphinx
14+
from sphinx.util import logging
15+
16+
logger = logging.getLogger(__name__)
17+
18+
TEMPLATE = """
19+
<div class="sketchviz-image">
20+
<object type="image/svg+xml" data="/{{ url }}"></object>
21+
</div>
22+
"""
23+
FILENAME_EXT = ".svg"
24+
25+
26+
class SketchvizError(Exception):
27+
pass
28+
29+
30+
class Sketchviz(Directive):
31+
arguments = 1
32+
has_content = True
33+
final_argument_whitespace = False
34+
option_spec = {
35+
"static-subdir": lambda a: a.strip(),
36+
}
37+
38+
def run(self):
39+
static_subdir = self.options.get("static-subdir")
40+
confdir = find_confdir()
41+
conf = read_conf(confdir)
42+
html_static_path = getattr(conf, "html_static_path", [])
43+
44+
if len(html_static_path) == 0:
45+
raise SketchvizError(
46+
"html_static_path should have at least one path configured"
47+
)
48+
49+
# We choose the first path as the default path for the image
50+
save_path = os.path.join(html_static_path[0], static_subdir)
51+
52+
# Try to make the dir if it doesn't exist
53+
os.makedirs(save_path, exist_ok=True)
54+
55+
temp_name = None
56+
h = hashlib.new("sha256")
57+
with NamedTemporaryFile(
58+
mode="w",
59+
encoding="utf-8",
60+
delete=False,
61+
delete_on_close=False) as fd:
62+
for content in self.content:
63+
line = f"{content}\n"
64+
h.update(line.encode())
65+
fd.write(line)
66+
temp_name = fd.name
67+
68+
filename = h.digest().hex()
69+
filename = f"{filename}{FILENAME_EXT}"
70+
save_path = os.path.join(save_path, filename)
71+
subprocess.run(
72+
["sketchviz", temp_name, save_path], check=True)
73+
os.remove(temp_name)
74+
logger.info("Sketchviz: Created SVG image: {save_path}")
75+
76+
template = Environment(loader=BaseLoader).from_string(TEMPLATE)
77+
78+
out = template.render(url=save_path)
79+
# User a raw pass-through node
80+
para = nodes.raw("", out, format="html")
81+
return [para]
82+
83+
84+
def setup(app: Sphinx):
85+
app.add_directive("sketchviz", Sketchviz)
86+
87+
return {
88+
"version": "0.1",
89+
"parallel_read_safe": True,
90+
"parallel_write_safe": True,
91+
}

learning-path/index.md

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,44 @@ Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non
1515
diam sodales hendrerit.
1616

1717

18-
```{graphviz}
19-
digraph Learning_path {
20-
a [label="Principiante" href="beginner/" fillcolor="blue" style="filled" fontcolor="white" target="_top"];
21-
b [label="Intermedio" href="intermediate/" fillcolor="yellow" style="filled" target="_top"];
22-
c [label="Avanzado A" href="advanced-a/" fillcolor="green" style="filled" target="_top"];
23-
d [label="Avanzado B" href="advanced-b/" fillcolor="green" style="filled" target="_top"];
24-
e [label="Pro" href="pro/" fillcolor="red" style="filled" fontcolor="white" target="_top"];
25-
a -> b;
26-
b -> c;
27-
b -> d;
28-
c -> e;
29-
d -> e;
18+
```{sketchviz}
19+
---
20+
static-subdir: images/sketchviz
21+
---
22+
digraph G {
23+
graph [fontname = "Handlee"];
24+
node [fontname = "Handlee"];
25+
edge [fontname = "Handlee"];
26+
27+
bgcolor=transparent;
28+
29+
subgraph cluster_0 {
30+
style=filled;
31+
color=lightgrey;
32+
node [style=filled,color=pink];
33+
a0 [href="https://python.org" target="_top"];
34+
a0 -> a1 -> a2 -> a3;
35+
label = "*process #1*";
36+
fontsize = 20;
37+
}
38+
39+
subgraph cluster_1 {
40+
node [style=filled];
41+
b0 -> b1 -> b2 -> b3;
42+
label = "*process #2*";
43+
fontsize = 20;
44+
color=blue
45+
}
46+
start -> a0;
47+
start -> b0;
48+
a1 -> b3;
49+
b2 -> a3;
50+
a3 -> a0;
51+
a3 -> end;
52+
b3 -> end;
53+
54+
start [shape=Mdiamond];
55+
end [shape=Msquare];
3056
}
3157
```
3258

0 commit comments

Comments
 (0)