Skip to content

Commit 1aa4e7e

Browse files
committed
refactor(template): Combing and document srcdir, outdir, reldir
1 parent 3ce0b2b commit 1aa4e7e

File tree

1 file changed

+71
-48
lines changed

1 file changed

+71
-48
lines changed

src/sphinxnotes/any/template.py

Lines changed: 71 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,18 @@
2222
import jinja2
2323
from wand.image import Image
2424

25-
ANYDIR = '_any'
2625
logger = logging.getLogger(__name__)
2726

2827
class Environment(jinja2.Environment):
28+
2929
_builder:Builder
3030
# Exclusive outdir for template filters
3131
_outdir:str
3232
# Exclusive srcdir for template filters
3333
# Actually it is a softlink link to _outdir.
3434
_srcdir:str
35+
# Same to _srcdir, but relative to Sphinx's srcdir.
36+
_reldir: str
3537

3638

3739
@classmethod
@@ -44,15 +46,36 @@ def setup(cls, app:Sphinx):
4446
@classmethod
4547
def _on_builder_inited(cls, app:Sphinx):
4648
cls._builder = app.builder
47-
cls._outdir = path.join(app.outdir, ANYDIR)
48-
ensuredir(cls._outdir)
49-
cls._srcdir = path.join(app.srcdir, ANYDIR)
49+
50+
# Template filters (like thumbnail_filter) may produces and new files,
51+
# they will be referenced in documents. While usually directive
52+
# (like ..image::) can only access file inside sphinx's srcdir(source/).
53+
#
54+
# So we create a dir in sphinx's outdir(_build/), and link it from srcdir,
55+
# then files can be referenced, then we won't messup the srcdir
56+
# (usually it is trakced by git), and our files can be cleaned up by
57+
# removing outdir.
58+
#
59+
# NOTE: we use builder name as suffix to avoid conflicts between multiple
60+
# builders.
61+
ANYDIR = ".any"
62+
reldir = ANYDIR + '_' + app.builder.name
63+
cls._outdir = path.join(app.outdir, reldir)
64+
cls._srcdir = path.join(app.srcdir, reldir)
65+
cls._reldir = path.join('/', reldir) # abspath relatived to srcdir
66+
67+
# Cleanup possible residual symlink.
5068
if path.islink(cls._srcdir):
5169
os.unlink(cls._srcdir)
70+
if path.exists(cls._srcdir):
71+
os.remove(cls._srcdir)
72+
# Link them.
73+
ensuredir(cls._outdir)
5274
os.symlink(cls._outdir, cls._srcdir)
5375

54-
logger.info(f'[any] exclusive srcdir: {cls._srcdir}')
55-
logger.info(f'[any] exclusive outdir: {cls._outdir}')
76+
logger.debug(f'[any] srcdir: {cls._srcdir}')
77+
logger.debug(f'[any] outdir: {cls._outdir}')
78+
5679

5780
@classmethod
5881
def _on_build_finished(cls, app:Sphinx, exception):
@@ -67,38 +90,33 @@ def __init__(self, *args, **kwargs):
6790

6891

6992
def thumbnail_filter(self, imgfn:str) -> str:
70-
imgfn = self._ensure_rel(imgfn)
71-
infn, outfn, relfn = self._get_in_out_rel(imgfn)
72-
73-
if not self._is_outdated(outfn, infn):
74-
# No need to make thumbnail
75-
return relfn
76-
77-
with Image(filename=infn) as img:
78-
# Remove any associated profiles
79-
img.thumbnail()
80-
# If larger than 640x480, fit within box, preserving aspect ratio
81-
img.transform(resize='640x480>')
82-
img.save(filename=outfn)
93+
srcfn, outfn, relfn = self._get_src_out_rel(imgfn)
94+
if not self._is_outdated(outfn, srcfn):
95+
return relfn # no need to make thumbnail
96+
try:
97+
with Image(filename=srcfn) as img:
98+
# Remove any associated profiles
99+
img.thumbnail()
100+
# If larger than 640x480, fit within box, preserving aspect ratio
101+
img.transform(resize='640x480>')
102+
img.save(filename=outfn)
103+
except Exception as e:
104+
logger.warning('failed to create thumbnail for %s: %s', imgfn, e)
83105
return relfn
84106

85107

86108
def install_filter(self, fn:str) -> str:
87109
"""
88110
Install file to sphinx outdir, return the relative uri of current docname.
89111
"""
90-
91-
fn = self._ensure_rel(fn)
92-
src = path.join(self._builder.srcdir, fn)
93-
target = path.join(self._builder.outdir, ANYDIR, fn)
94-
95-
if not self._is_outdated(target, src):
96-
# No need to install file
97-
return relfn
98-
99-
ensuredir(path.dirname(target))
100-
shutil.copy(src, target)
101-
return self._relative_uri(ANYDIR, fn)
112+
srcfn, outfn, relfn = self._get_src_out_rel(fn)
113+
if not self._is_outdated(outfn, srcfn):
114+
return relfn # no need to install file
115+
try:
116+
shutil.copy(srcfn, outfn)
117+
except Exception as e:
118+
logger.warning('failed to install %s: %s', fn, e)
119+
return relfn
102120

103121

104122
def watermark_filter(self, imgfn:str) -> str:
@@ -119,26 +137,31 @@ def _relative_uri(self, *args):
119137
return relative_uri(base, posixpath.join(*args))
120138

121139

122-
def _get_in_out_rel(self, fn:str) -> tuple[str,str,str]:
123-
# The pass-in filenames must be relative
124-
assert not path.isabs(fn)
140+
def _get_src_out_rel(self, fn:str) -> tuple[str,str,str]:
141+
"""Return three paths (srcfn, outfn, relfn).
142+
:srcfn: abs path of fn, must inside sphinx's srcdir
143+
:outfn: abs path to motified file, must inside self._srcdir
144+
:relfn: path to outfn relatived to sphinx's srcdir
145+
"""
146+
isabs = path.isabs(fn)
147+
if isabs:
148+
fn = fn[1:] # skip os.sep so that it can be join
149+
else:
150+
docname = self._builder.env.docname
151+
a, b = self._builder.env.relfn2path(fn, docname)
152+
fn = a
125153

126-
infn = path.join(self._builder.srcdir, fn)
127-
if infn.startswith(self._srcdir):
154+
srcfn = path.join(self._builder.srcdir, fn)
155+
if srcfn.startswith(self._srcdir):
128156
# fn is outputted by other filters
129-
outfn = infn
157+
outfn = srcfn
158+
relfn = path.join('/', fn)
130159
else:
131-
# fn is specified by user
132-
outfn = path.join(self._srcdir, fn)
133-
# Make sure output dir exists
134-
ensuredir(path.dirname(outfn))
135-
relfn = self._relative_uri(ANYDIR, fn)
136-
return (infn, outfn, relfn)
137-
138-
139-
def _ensure_rel(self, fn: str) -> str:
140-
"""Convert site-wide absoulte path to relative path."""
141-
return path.relpath(fn, '/') if path.isabs(fn) else fn
160+
outfn = path.join(self._srcdir, fn) # fn is specified by user
161+
relfn = path.join(self._reldir, fn)
162+
ensuredir(path.dirname(outfn)) # make sure output dir exists
163+
logger.debug('[any] srcfn: %s, outfn: %s, relfn: %s', srcfn, outfn, relfn)
164+
return (srcfn, outfn, relfn)
142165

143166

144167
def _is_outdated(self, target:str, src: str) -> bool:

0 commit comments

Comments
 (0)