1
1
# -*- coding: utf-8 -*-
2
2
"""
3
- :copyright: Copyright 2018 by the contributors (see AUTHORS file).
3
+ :copyright: Copyright 2018-2019 by the contributors (see AUTHORS file).
4
4
:license: BSD-2-Clause, see LICENSE for details.
5
5
"""
6
6
@@ -58,8 +58,9 @@ def __init__(self, master, env):
58
58
self .assets = []
59
59
self .env = env
60
60
self .hash2asset = {}
61
- self .key2asset = {}
61
+ self .keys = set ()
62
62
self .master = master
63
+ self .path2asset = {}
63
64
64
65
def build (self ):
65
66
"""
@@ -88,9 +89,9 @@ def build(self):
88
89
data .append (entry )
89
90
return data
90
91
91
- def asset2docname (self , key ):
92
+ def fetch (self , node ):
92
93
"""
93
- return target document name for provided asset
94
+ return key and target document name for provided asset
94
95
95
96
When given a asset, cached information will return the name of the
96
97
target document this asset will be published to. In the event an asset
@@ -99,20 +100,26 @@ def asset2docname(self, key):
99
100
document name will be returned instead.
100
101
101
102
Args:
102
- key : the asset key
103
+ node : the node to interpret
103
104
104
105
Returns:
105
- the document name
106
+ the key and document name
106
107
"""
107
108
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
114
110
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
116
123
117
124
def process (self , docnames ):
118
125
"""
@@ -132,7 +139,7 @@ def process(self, docnames):
132
139
133
140
def processDocument (self , doctree , docname , standalone = False ):
134
141
"""
135
- process a docment for assets
142
+ process a document for assets
136
143
137
144
This method will search each the provided document's doctree for
138
145
supported assets which could be published. Asset information is tracked
@@ -148,117 +155,111 @@ def processDocument(self, doctree, docname, standalone=False):
148
155
for node in image_nodes :
149
156
uri = node ['uri' ]
150
157
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 :
153
160
hash = ConfluenceUtil .hashAsset (path )
154
161
type = guess_mimetype (path , default = DEFAULT_CONTENT_TYPE )
155
162
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 )
159
166
160
167
file_nodes = doctree .traverse (addnodes .download_reference )
161
168
for node in file_nodes :
162
169
target = node ['reftarget' ]
163
170
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 :
166
173
hash = ConfluenceUtil .hashAsset (path )
167
174
type = guess_mimetype (path , default = DEFAULT_CONTENT_TYPE )
168
175
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 )
172
179
173
- def _handleEntry (self , key , path , type , hash , docname , standalone = False ):
180
+ def _handleEntry (self , path , type , hash , docname , standalone = False ):
174
181
"""
175
182
handle an asset entry
176
183
177
184
When an asset is detected in a document, the information about the asset
178
185
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.
181
188
If an asset matches the hash of another asset, another entry is *not
182
189
created (i.e. a documentation set has duplicate assets; *with the
183
190
exception of when ``standalone`` is set to ``True``). In all cases where
184
191
an asset is detected, the asset reference is updated to track which
185
192
document the asset belongs to.
186
193
187
194
Args:
188
- key: the asset key
189
195
path: the absolute path to the asset
190
196
type: the content type of the asset
191
197
hash: the hash of the asset
192
198
docname: the document name this asset was found in
193
199
standalone (optional): ignore hash mappings (defaults to False)
194
200
"""
195
- asset = self .key2asset .get (key , None )
201
+ asset = self .path2asset .get (path , None )
196
202
if not asset :
197
203
hash_exists = hash in self .hash2asset
198
204
if not hash_exists or standalone :
199
205
# 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
+
200
220
asset = ConfluenceAsset (key , path , type , hash )
201
221
self .assets .append (asset )
202
- self .key2asset [ key ] = asset
222
+ self .path2asset [ path ] = asset
203
223
if not hash_exists :
204
- self .hash2asset [key ] = asset
224
+ self .hash2asset [hash ] = asset
205
225
else :
206
226
# duplicate asset detected; build an asset alias
207
227
asset = self .hash2asset [hash ]
208
- self .key2asset [ key ] = asset
228
+ self .path2asset [ path ] = asset
209
229
else :
210
- assert (self .hash2asset [key ] == asset )
230
+ assert (self .hash2asset [asset . hash ] == asset )
211
231
212
232
# track (if not already) that this document uses this asset
213
233
asset .docnames .add (docname )
214
234
215
- def interpretAssetKeyPath (self , node ):
235
+ def _interpretAssetPath (self , node ):
216
236
"""
217
- find a key value and absolute path for a target assert
237
+ find an absolute path for a target assert
218
238
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).
223
242
224
243
Args:
225
244
node: the node to parse
226
245
227
246
Returns:
228
- the key and absolute path
247
+ the absolute path
229
248
"""
230
- key = None
249
+ path = None
231
250
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' ]
245
253
elif isinstance (node , addnodes .download_reference ):
246
254
# reftarget will be a reference to the asset with respect to the
247
255
# 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.
250
257
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' ])
254
259
255
260
abspath = None
256
- if key :
261
+ if path :
262
+ path = os .path .normpath (path )
257
263
abspath = os .path .join (self .env .srcdir , path )
258
264
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
0 commit comments