1
1
from __future__ import annotations
2
2
3
+ import filecmp
3
4
import shutil
4
- from dataclasses import dataclass
5
+ from dataclasses import dataclass , replace
5
6
from functools import partial
6
7
from pathlib import Path
7
8
from string import Template
9
+ from tempfile import NamedTemporaryFile
8
10
from typing import Any , List , NewType , Optional , Set , Tuple , Union , overload
9
11
from urllib .parse import urlparse
10
12
@@ -74,6 +76,9 @@ def module_from_url(
74
76
)
75
77
76
78
79
+ _FROM_TEMPLATE_DIR = "__from_template__"
80
+
81
+
77
82
def module_from_template (
78
83
template : str ,
79
84
package : str ,
@@ -83,6 +88,7 @@ def module_from_template(
83
88
resolve_exports : bool = IDOM_DEBUG_MODE .current ,
84
89
resolve_exports_depth : int = 5 ,
85
90
unmount_before_update : bool = False ,
91
+ replace_existing : bool = False ,
86
92
) -> WebModule :
87
93
"""Create a :class:`WebModule` from a framework template
88
94
@@ -121,6 +127,9 @@ def module_from_template(
121
127
only be used if the imported package failes to re-render when props change.
122
128
Using this option has negative performance consequences since all DOM
123
129
elements must be changed on each render. See :issue:`461` for more info.
130
+ replace_existing:
131
+ Whether to replace the source for a module with the same name if it already
132
+ exists and has different content. Otherwise raise an error.
124
133
"""
125
134
# We do this since the package may be any valid URL path. Thus we may need to strip
126
135
# object parameters or query information so we save the resulting template under the
@@ -140,27 +149,30 @@ def module_from_template(
140
149
if not template_file .exists ():
141
150
raise ValueError (f"No template for { template_file_name !r} exists" )
142
151
143
- target_file = _web_module_path (package_name , "from-template" )
144
- if not target_file .exists ():
145
- target_file .parent .mkdir (parents = True , exist_ok = True )
146
- target_file .write_text (
147
- Template (template_file .read_text ()).substitute (
148
- {"PACKAGE" : package , "CDN" : cdn }
149
- )
152
+ variables = {"PACKAGE" : package , "CDN" : cdn }
153
+ content = Template (template_file .read_text ()).substitute (variables )
154
+
155
+ with NamedTemporaryFile (mode = "r+" ) as file :
156
+ file .write (content )
157
+ file .seek (0 ) # set the cursor back to begining of file
158
+
159
+ module = module_from_file (
160
+ (
161
+ _FROM_TEMPLATE_DIR
162
+ + "/"
163
+ + package_name
164
+ + module_name_suffix (package_name )
165
+ ),
166
+ file .name ,
167
+ fallback ,
168
+ resolve_exports ,
169
+ resolve_exports_depth ,
170
+ symlink = False ,
171
+ unmount_before_update = unmount_before_update ,
172
+ replace_existing = replace_existing ,
150
173
)
151
174
152
- return WebModule (
153
- source = "from-template/" + package_name + module_name_suffix (package_name ),
154
- source_type = NAME_SOURCE ,
155
- default_fallback = fallback ,
156
- file = target_file ,
157
- export_names = (
158
- resolve_module_exports_from_file (target_file , resolve_exports_depth )
159
- if resolve_exports
160
- else None
161
- ),
162
- unmount_before_update = unmount_before_update ,
163
- )
175
+ return replace (module , file = None )
164
176
165
177
166
178
def module_from_file (
@@ -196,23 +208,29 @@ def module_from_file(
196
208
elements must be changed on each render. See :issue:`461` for more info.
197
209
replace_existing:
198
210
Whether to replace the source for a module with the same name if it already
199
- exists. Otherwise raise an error.
211
+ exists and has different content . Otherwise raise an error.
200
212
"""
201
213
source_file = Path (file )
202
214
target_file = _web_module_path (name )
203
215
if not source_file .exists ():
204
216
raise FileNotFoundError (f"Source file does not exist: { source_file } " )
205
- elif target_file .exists () or target_file .is_symlink ():
206
- if not replace_existing :
207
- raise FileExistsError (f"{ name !r} already exists as { target_file .resolve ()} " )
208
- else :
209
- target_file .unlink ()
210
217
211
- target_file .parent .mkdir (parents = True , exist_ok = True )
212
- if symlink :
213
- target_file .symlink_to (source_file )
214
- else :
215
- shutil .copy (source_file , target_file )
218
+ if not target_file .exists ():
219
+ _copy_file (target_file , source_file , symlink )
220
+ elif not (
221
+ symlink
222
+ and target_file .is_symlink ()
223
+ and target_file .resolve () == source_file .resolve ()
224
+ ):
225
+ if replace_existing :
226
+ target_file .unlink ()
227
+ _copy_file (target_file , source_file , symlink )
228
+ elif not filecmp .cmp (
229
+ str (source_file .resolve ()),
230
+ str (target_file .resolve ()),
231
+ shallow = False ,
232
+ ):
233
+ raise FileExistsError (f"{ name !r} already exists as { target_file .resolve ()} " )
216
234
217
235
return WebModule (
218
236
source = name + module_name_suffix (name ),
@@ -228,6 +246,14 @@ def module_from_file(
228
246
)
229
247
230
248
249
+ def _copy_file (target : Path , source : Path , symlink : bool ) -> None :
250
+ target .parent .mkdir (parents = True , exist_ok = True )
251
+ if symlink :
252
+ target .symlink_to (source )
253
+ else :
254
+ shutil .copy (source , target )
255
+
256
+
231
257
class _VdomDictConstructor (Protocol ):
232
258
def __call__ (
233
259
self ,
0 commit comments