Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 046ac21

Browse filesBrowse files
authored
Merge pull request #228 from sphinx-contrib/feature/graceful-asset-names
assets: support graceful asset names
2 parents 009c339 + d55dab8 commit 046ac21
Copy full SHA for 046ac21

File tree

Expand file treeCollapse file tree

8 files changed

+77
-78
lines changed
Filter options
Expand file treeCollapse file tree

8 files changed

+77
-78
lines changed

‎sphinxcontrib/confluencebuilder/assets.py

Copy file name to clipboardExpand all lines: sphinxcontrib/confluencebuilder/assets.py
+67-66Lines changed: 67 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22
"""
3-
:copyright: Copyright 2018 by the contributors (see AUTHORS file).
3+
:copyright: Copyright 2018-2019 by the contributors (see AUTHORS file).
44
:license: BSD-2-Clause, see LICENSE for details.
55
"""
66

@@ -58,8 +58,9 @@ def __init__(self, master, env):
5858
self.assets = []
5959
self.env = env
6060
self.hash2asset = {}
61-
self.key2asset = {}
61+
self.keys = set()
6262
self.master = master
63+
self.path2asset = {}
6364

6465
def build(self):
6566
"""
@@ -88,9 +89,9 @@ def build(self):
8889
data.append(entry)
8990
return data
9091

91-
def asset2docname(self, key):
92+
def fetch(self, node):
9293
"""
93-
return target document name for provided asset
94+
return key and target document name for provided asset
9495
9596
When given a asset, cached information will return the name of the
9697
target document this asset will be published to. In the event an asset
@@ -99,20 +100,26 @@ def asset2docname(self, key):
99100
document name will be returned instead.
100101
101102
Args:
102-
key: the asset key
103+
node: the node to interpret
103104
104105
Returns:
105-
the document name
106+
the key and document name
106107
"""
107108
docname = None
108-
asset = self.key2asset.get(key, None)
109-
if asset:
110-
if len(asset.docnames) > 1:
111-
docname = self.master
112-
else:
113-
docname = next(iter(asset.docnames))
109+
key = None
114110

115-
return docname
111+
path = self._interpretAssetPath(node)
112+
if path:
113+
asset = self.path2asset.get(path, None)
114+
if asset:
115+
key = asset.key
116+
117+
if len(asset.docnames) > 1:
118+
docname = self.master
119+
else:
120+
docname = next(iter(asset.docnames))
121+
122+
return key, docname
116123

117124
def process(self, docnames):
118125
"""
@@ -132,7 +139,7 @@ def process(self, docnames):
132139

133140
def processDocument(self, doctree, docname, standalone=False):
134141
"""
135-
process a docment for assets
142+
process a document for assets
136143
137144
This method will search each the provided document's doctree for
138145
supported assets which could be published. Asset information is tracked
@@ -148,117 +155,111 @@ def processDocument(self, doctree, docname, standalone=False):
148155
for node in image_nodes:
149156
uri = node['uri']
150157
if not uri.startswith('data:') and uri.find('://') == -1:
151-
key, path = self.interpretAssetKeyPath(node)
152-
if key not in self.key2asset:
158+
path = self._interpretAssetPath(node)
159+
if path not in self.path2asset:
153160
hash = ConfluenceUtil.hashAsset(path)
154161
type = guess_mimetype(path, default=DEFAULT_CONTENT_TYPE)
155162
else:
156-
hash = self.key2asset[key].hash
157-
type = self.key2asset[key].type
158-
self._handleEntry(key, path, type, hash, docname, standalone)
163+
hash = self.path2asset[path].hash
164+
type = self.path2asset[path].type
165+
self._handleEntry(path, type, hash, docname, standalone)
159166

160167
file_nodes = doctree.traverse(addnodes.download_reference)
161168
for node in file_nodes:
162169
target = node['reftarget']
163170
if target.find('://') == -1:
164-
key, path = self.interpretAssetKeyPath(node)
165-
if key not in self.key2asset:
171+
path = self._interpretAssetPath(node)
172+
if path not in self.path2asset:
166173
hash = ConfluenceUtil.hashAsset(path)
167174
type = guess_mimetype(path, default=DEFAULT_CONTENT_TYPE)
168175
else:
169-
hash = self.key2asset[key].hash
170-
type = self.key2asset[key].type
171-
self._handleEntry(key, path, type, hash, docname, standalone)
176+
hash = self.path2asset[path].hash
177+
type = self.path2asset[path].type
178+
self._handleEntry(path, type, hash, docname, standalone)
172179

173-
def _handleEntry(self, key, path, type, hash, docname, standalone=False):
180+
def _handleEntry(self, path, type, hash, docname, standalone=False):
174181
"""
175182
handle an asset entry
176183
177184
When an asset is detected in a document, the information about the asset
178185
is tracked in this manager. When an asset is detected, there are
179-
considerations to be made. If an asset key has already been registered
180-
(e.x. an asset used twice), only a single asset entry will be created.
186+
considerations to be made. If an asset path has already been registered
187+
(e.g. an asset used twice), only a single asset entry will be created.
181188
If an asset matches the hash of another asset, another entry is *not
182189
created (i.e. a documentation set has duplicate assets; *with the
183190
exception of when ``standalone`` is set to ``True``). In all cases where
184191
an asset is detected, the asset reference is updated to track which
185192
document the asset belongs to.
186193
187194
Args:
188-
key: the asset key
189195
path: the absolute path to the asset
190196
type: the content type of the asset
191197
hash: the hash of the asset
192198
docname: the document name this asset was found in
193199
standalone (optional): ignore hash mappings (defaults to False)
194200
"""
195-
asset = self.key2asset.get(key, None)
201+
asset = self.path2asset.get(path, None)
196202
if not asset:
197203
hash_exists = hash in self.hash2asset
198204
if not hash_exists or standalone:
199205
# no asset entry and no hash entry (or standalone); new asset
206+
key = os.path.basename(path)
207+
208+
# Confluence does not allow attachments with select characters.
209+
# Filter out the asset name to a compatible key value.
210+
for rep in INVALID_CHARS:
211+
key = key.replace(rep, '_')
212+
213+
filename, file_ext = os.path.splitext(key)
214+
idx = 1
215+
while key in self.keys:
216+
idx += 1
217+
key = '{}_{}{}'.format(filename, idx, file_ext)
218+
self.keys.add(key)
219+
200220
asset = ConfluenceAsset(key, path, type, hash)
201221
self.assets.append(asset)
202-
self.key2asset[key] = asset
222+
self.path2asset[path] = asset
203223
if not hash_exists:
204-
self.hash2asset[key] = asset
224+
self.hash2asset[hash] = asset
205225
else:
206226
# duplicate asset detected; build an asset alias
207227
asset = self.hash2asset[hash]
208-
self.key2asset[key] = asset
228+
self.path2asset[path] = asset
209229
else:
210-
assert(self.hash2asset[key] == asset)
230+
assert(self.hash2asset[asset.hash] == asset)
211231

212232
# track (if not already) that this document uses this asset
213233
asset.docnames.add(docname)
214234

215-
def interpretAssetKeyPath(self, node):
235+
def _interpretAssetPath(self, node):
216236
"""
217-
find a key value and absolute path for a target assert
237+
find an absolute path for a target assert
218238
219-
Returns a "key" value (a normalized path to the asset relative to the
220-
documentation's root) as well as the absolute path to the assert. For
221-
unsupported asset types, this method will return ``None`` values. This
222-
method should not be invoked on external assets (i.e. URLs).
239+
Returns the absolute path to an assert. For unsupported asset types,
240+
this method will return ``None`` values. This method should not be
241+
invoked on external assets (i.e. URLs).
223242
224243
Args:
225244
node: the node to parse
226245
227246
Returns:
228-
the key and absolute path
247+
the absolute path
229248
"""
230-
key = None
249+
path = None
231250
if isinstance(node, nodes.image):
232-
# uri's will be relative to documentation root. Normalize for a key
233-
# value to handle assets found in parent directories.
234-
uri = node['uri']
235-
uri = os.path.normpath(uri)
236-
path = uri
237-
238-
# If this URI is found outside the relative path of the
239-
# documentation set, grab the basename and add a prefix for
240-
# (hopefully) a unique name.
241-
if os.path.isabs(uri):
242-
key = 'extern_{}'.format(os.path.basename(uri))
243-
else:
244-
key = uri
251+
# uri's will be relative to documentation root.
252+
path = node['uri']
245253
elif isinstance(node, addnodes.download_reference):
246254
# reftarget will be a reference to the asset with respect to the
247255
# document (refdoc) holding this reference. Use reftarget and refdoc
248-
# to find a proper key value (normalized to handle parent directory
249-
# references).
256+
# to find a proper path.
250257
docdir = os.path.dirname(node['refdoc'])
251-
key = os.path.join(docdir, node['reftarget'])
252-
key = os.path.normpath(key)
253-
path = key
258+
path = os.path.join(docdir, node['reftarget'])
254259

255260
abspath = None
256-
if key:
261+
if path:
262+
path = os.path.normpath(path)
257263
abspath = os.path.join(self.env.srcdir, path)
258264

259-
# Confluence does not allow attachments with select characters.
260-
# Filter out the asset name to a compatible key value.
261-
for rep in INVALID_CHARS:
262-
key = key.replace(rep, '_')
263-
264-
return key, abspath
265+
return abspath

‎sphinxcontrib/confluencebuilder/translator.py

Copy file name to clipboardExpand all lines: sphinxcontrib/confluencebuilder/translator.py
+2-4Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,8 +1184,7 @@ def visit_image(self, node):
11841184
suffix=self.nl, empty=True, **{'ri:value': uri}))
11851185
self.body.append(self._end_ac_image(node))
11861186
else:
1187-
image_key, _ = self.assets.interpretAssetKeyPath(node)
1188-
hosting_docname = self.assets.asset2docname(image_key)
1187+
image_key, hosting_docname = self.assets.fetch(node)
11891188
hosting_doctitle = ConfluenceState.title(hosting_docname)
11901189
hosting_doctitle = self._escape_sf(hosting_doctitle)
11911190

@@ -1215,8 +1214,7 @@ def visit_download_reference(self, node):
12151214
self.body.append(self._start_tag(node, 'a', **{'href': uri}))
12161215
self.context.append(self._end_tag(node, suffix=''))
12171216
else:
1218-
file_key, _ = self.assets.interpretAssetKeyPath(node)
1219-
hosting_docname = self.assets.asset2docname(file_key)
1217+
file_key, hosting_docname = self.assets.fetch(node)
12201218
hosting_doctitle = ConfluenceState.title(hosting_docname)
12211219
hosting_doctitle = self._escape_sf(hosting_doctitle)
12221220

225 Bytes
Loading
225 Bytes
Loading

‎test/unit-tests/common/expected-center/figure.conf

Copy file name to clipboardExpand all lines: test/unit-tests/common/expected-center/figure.conf
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<ac:parameter ac:name="icon">false</ac:parameter>
2323
<ac:rich-text-body>
2424
<ac:image ac:align="center">
25-
<ri:attachment ri:filename=".._assets_image02.png"></ri:attachment>
25+
<ri:attachment ri:filename="image02.png"></ri:attachment>
2626
</ac:image><p style="text-align: center;">caption</p>
2727
<p><p>legend</p>
2828
</p>
@@ -32,7 +32,7 @@
3232
<ac:parameter ac:name="icon">false</ac:parameter>
3333
<ac:rich-text-body>
3434
<ac:image ac:align="right" ac:style="float: right;">
35-
<ri:attachment ri:filename=".._assets_image03.png"><ri:page ri:content-title="table of contents" />
35+
<ri:attachment ri:filename="image03.png"><ri:page ri:content-title="table of contents" />
3636
</ri:attachment>
3737
</ac:image><p>caption</p>
3838
<p><p>legend</p>
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<p><ac:structured-macro ac:name="view-file">
2-
<ac:parameter ac:name="name"><ri:attachment ri:filename=".._assets_example.pdf"></ri:attachment>
2+
<ac:parameter ac:name="name"><ri:attachment ri:filename="example.pdf"></ri:attachment>
33
</ac:parameter>
44
</ac:structured-macro>
55
</p>
66
<p><ac:link>
7-
<ri:attachment ri:filename=".._assets_example.pdf"></ri:attachment>
7+
<ri:attachment ri:filename="example.pdf"></ri:attachment>
88
<ac:plain-text-link-body><![CDATA[label]]></ac:plain-text-link-body>
99
</ac:link></p>

‎test/unit-tests/common/expected/figure.conf

Copy file name to clipboardExpand all lines: test/unit-tests/common/expected/figure.conf
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<ac:parameter ac:name="icon">false</ac:parameter>
2323
<ac:rich-text-body>
2424
<ac:image ac:align="center">
25-
<ri:attachment ri:filename=".._assets_image02.png"></ri:attachment>
25+
<ri:attachment ri:filename="image02.png"></ri:attachment>
2626
</ac:image><p style="text-align: center;">caption</p>
2727
<p><p>legend</p>
2828
</p>
@@ -32,7 +32,7 @@
3232
<ac:parameter ac:name="icon">false</ac:parameter>
3333
<ac:rich-text-body>
3434
<ac:image ac:align="right" ac:style="float: right;">
35-
<ri:attachment ri:filename=".._assets_image03.png"><ri:page ri:content-title="table of contents" />
35+
<ri:attachment ri:filename="image03.png"><ri:page ri:content-title="table of contents" />
3636
</ri:attachment>
3737
</ac:image><p>caption</p>
3838
<p><p>legend</p>
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<ac:image>
22
<ri:url ri:value="https://www.example.com/image.png" />
33
</ac:image><ac:image ac:alt="alt text" ac:width="200px">
4-
<ri:attachment ri:filename=".._assets_image01.png"></ri:attachment>
4+
<ri:attachment ri:filename="image01.png"></ri:attachment>
55
</ac:image><ac:image ac:align="right" ac:style="float: right;">
6-
<ri:attachment ri:filename=".._assets_image03.png"><ri:page ri:content-title="table of contents" />
6+
<ri:attachment ri:filename="image03.png"><ri:page ri:content-title="table of contents" />
77
</ri:attachment>
88
</ac:image>

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.