Coverage for src/ensae_teaching_cs/automation/ftp_publish_helper.py: 30%

166 statements  

« prev     ^ index     » next       coverage.py v7.1.0, created at 2023-04-28 06:23 +0200

1# -*- coding: utf-8 -*- 

2""" 

3@file 

4@brief Helpers to publish the documentation of :epkg:`python` to a website. 

5""" 

6import sys 

7import os 

8from pyquickhelper.filehelper import TransferFTP, FileTreeNode, FolderTransferFTP 

9from pyquickhelper.filehelper.ftp_transfer_files import content_as_binary as pqh_content_as_binary 

10from .teaching_modules import get_teaching_modules 

11 

12 

13def trigger_on_specific_strings(content, filename=None, force_allow=None): 

14 """ 

15 Looks for specific string such as 

16 *USERNAME*, *USERDNSDOMAIN*, *HOMEPATH*, *USERNAME*, *COMPUTERNAME*, 

17 *LOGONSERVER*, *USER*, *HOME*, *LOGNAME* 

18 and returns None if it was found or modifies the content to remove it. 

19 

20 @param content content of a file 

21 @param filename only used when an exception is raised 

22 @param force_allow allow these expressions even if they seem to be credentials 

23 @return modified content 

24 """ 

25 strep = [('somewhere', 'somewhere'), 

26 ('somewhere', 'somewhere')] 

27 for env in ['USERNAME', 'USER']: 

28 if env in os.environ and os.environ[env] != "jenkins": 

29 for sub in ["_data", "GitHub"]: 

30 strep.extend([(r"C:\\%s\\__home_\\%s\\" % (os.environ[env], sub), "somewhere"), 

31 ("C:\\%s\\__home_\\%s\\" % 

32 (os.environ[env], sub), "somewhere"), 

33 ("C:\\%s\\__home_\\%s\\" % 

34 (os.environ[env], sub), "somewhere"), 

35 (f"C:{os.environ[env]}__home_{sub}", 

36 "somewhere"), 

37 (f"{os.environ[env]}__home_{sub}", "somewhere") 

38 ]) 

39 for s, b in strep: 

40 if s in content: 

41 content = content.replace(s, b) 

42 

43 if force_allow is None: 

44 force_allow = set() 

45 else: 

46 force_allow = set(force_allow) 

47 lower_content = content.lower() 

48 for st in ["USERNAME", "USERDNSDOMAIN", "HOMEPATH", "USERNAME", 

49 "COMPUTERNAME", "LOGONSERVER", "USER", 'HOME', 'LOGNAME']: 

50 if st in os.environ: 

51 s = os.environ[st].lower() 

52 if s == 'jenkins': 

53 continue 

54 if s in ('administrateur', 'administrator'): 

55 continue 

56 if s not in force_allow and s in lower_content: 

57 raise RuntimeError( 

58 f'string {st}:{s} was found in\n File "{filename}", line 1') 

59 return content 

60 

61 

62def content_as_binary(filename): 

63 """ 

64 Overloads function `content_as_finary 

65 <http://www.xavierdupre.fr/app/pyquickhelper/helpsphinx/pyquickhelper/filehelper/ftp_transfer_files.html? 

66 highlight=content_as_binary#pyquickhelper.filehelper.ftp_transfer_files.content_as_binary>`_ from 

67 `pyquickhelper <http://www.xavierdupre.fr/app/pyquickhelper/helpsphinx/pyquickhelper/>`_. 

68 Determines if filename is binary or None before transfering it. 

69 

70 @param filename filename 

71 @return boolean 

72 """ 

73 if pqh_content_as_binary(filename): 

74 return True 

75 ff = os.path.split(filename)[-1] 

76 if ff == "footer.html": 

77 return True 

78 return False 

79 

80 

81def text_transform(ftpp, filename, content): 

82 """ 

83 If filename is *rss.xml*, 

84 replaces the string *__BLOG_ROOT__* by *self._root_web*. 

85 

86 @param ftpp object FolderTransferFTP 

87 @param filename filename 

88 @param content content of the file 

89 @return new content 

90 """ 

91 if filename.endswith("rss.xml"): 

92 web = ftpp._root_web 

93 if not web.startswith("http://"): 

94 web = "http://" + web.strip("/") 

95 ftpp.fLOG("[text_transform] replace __BLOG_ROOT__ by ", web) 

96 return content.replace("__BLOG_ROOT__", web) 

97 else: 

98 return content 

99 

100 

101def publish_documentation(docs, ftpsite=None, login=None, password=None, 

102 footer_html=None, content_filter=trigger_on_specific_strings, 

103 is_binary=content_as_binary, force_allow=None, 

104 delay=0.5, exc=False, ftps='FTP', 

105 page_transform=None, fLOG=print): 

106 """ 

107 Publishes the documentation and the setups of a python module on a webiste, 

108 it assumes the modules is organized the same way as :epkg:`pyquickhelper`. 

109 

110 @param docs list of dictionaries (see below) 

111 @param ftpsite something like ``ftp.something.`` 

112 @param login login 

113 @param password password 

114 @param footer_html append this HTML code to any uploaded page (such a javascript code to count the audience) 

115 @param content_filter filter the content of a file (it raises an exception if the result is None), 

116 appies only on text files 

117 @param is_binary a function to tell if a content of a file is binary or not 

118 @param force_allow a file is not published if it contains something which looks like credentials 

119 except if this string is part of *force_allow* 

120 @param delay delay between file transferring (in average) 

121 @param exc raise exception if not able to transfer 

122 @param ftps use protocol FTP, TLS, or SFTP 

123 @param page_transform function which transforms 

124 the page before uploading it, 

125 @see fn text_transform 

126 @param fLOG logging function 

127 

128 *docs* is a list of dictionaries which must contain for each folder 

129 to transfer: 

130 

131 - ``local``: local folder 

132 - ``root_local``: local paths will be related to this root 

133 - ``root_web``: prefix to add to the remote paths 

134 - ``status_file``: a file where the function populates the transfered files and some information about them 

135 

136 A local file is composed by ``<local_root>/<relative_path>``, it 

137 will be uploaded to ``<web_root>/<relative_path>``. 

138 """ 

139 

140 params = {"ftpsite": ftpsite, 

141 "login": login, 

142 "password": password} 

143 

144 nbnone = len([v for k, v in params.items() if v is None or len(v) == 0]) 

145 if nbnone > 0: 

146 raise ValueError( 

147 f"One of the following parameters is not specified:\n{params}") 

148 

149 nbnone = [v for k, v in params.items() if v is None or len(v) == 0] 

150 if len(nbnone) > 0: 

151 raise RuntimeError("one of the parameters is None:\n" + str(nbnone)) 

152 

153 password = params["password"] 

154 login = params["login"] 

155 ftpsite = params["ftpsite"] 

156 

157 filter_out = "([/\\\\]((moduletoc.html)|(blogtoc.html)|(searchbox.html)))|([.]buildinfo)|([.]pyc)" 

158 

159 ftp = TransferFTP(ftpsite, login, password, ftps=ftps, fLOG=fLOG) 

160 

161 if page_transform is None: 

162 fct_transform = text_transform 

163 else: 

164 

165 def combined_transform(ftpp, filename, content): 

166 text_transform(ftpp, filename, content) 

167 page_transform(ftpp, filename, content) 

168 

169 fct_transform = combined_transform 

170 

171 for project in docs: 

172 

173 fLOG("######################################################################") 

174 for k, v in sorted(project.items()): 

175 fLOG(f"[publish_documentation] loop {k}='{v}'") 

176 

177 location = project["local"] 

178 root_local = project["root_local"] 

179 root_web = project["root_web"] 

180 

181 sfile = project["status_file"] 

182 rootw = project["root_web"] 

183 

184 # documentation + setup 

185 fLOG(f"[publish_documentation] location='{location}'") 

186 

187 ftn = FileTreeNode(root_local) 

188 fftp = FolderTransferFTP(ftn, ftp, sfile, root_web=rootw, fLOG=fLOG, footer_html=footer_html, 

189 content_filter=content_filter, is_binary=is_binary, 

190 text_transform=fct_transform, filter_out=filter_out, 

191 force_allow=force_allow, exc=exc) 

192 

193 fftp.start_transfering(delay=delay) 

194 

195 ftn = FileTreeNode(os.path.join(root_local, ".."), 

196 filter=lambda root, path, f, dir: not dir) 

197 fftp = FolderTransferFTP(ftn, ftp, sfile, 

198 root_web=root_web.replace("helpsphinx", ""), fLOG=fLOG, 

199 footer_html=footer_html, content_filter=content_filter, 

200 is_binary=is_binary, text_transform=fct_transform, 

201 filter_out=filter_out) 

202 

203 fftp.start_transfering() 

204 

205 ftp.close() 

206 

207 

208def publish_teachings_to_web(login, ftpsite="ftp.xavierdupre.fr", # pylint: disable=W0102 

209 tracking_id=None, 

210 location="c:\\jenkins\\pymy\\%s\\%s%s\\dist\\%s", 

211 rootw="/www/htdocs/app/%s/%s", 

212 folder_status=".", 

213 layout=[("html", "helpsphinx")], 

214 modules=None, password=None, force_allow=None, 

215 suffix=("_UT_%d%d_std" % sys.version_info[:2],), 

216 delay=0.5, exc=False, exc_transfer=False, 

217 transfer=True, additional_projects=None, 

218 ftps='FTP', page_transform=None, fLOG=print): 

219 """ 

220 Copies the documentation to the website. 

221 

222 @param login login 

223 @param ftpsite ftp site 

224 @param tracking_id tracking_id 

225 @param location location of Jenkins build 

226 @param rootw root on ftp site 

227 @param folder_status folder status 

228 @param modules list of modules to publish, if None, use @see fn get_teaching_modules 

229 @param password if None, if will asked 

230 @param layout last part of the folders 

231 @param suffix suffixes to append to the project name 

232 @param force_allow allow to publish files even if they contain these strings 

233 whereas they seem to be credentials 

234 @param delay delay between two files being transferred 

235 @param exc raise exception if not found (True) or skip (False) 

236 @param exc_transfer raise an exception if cannot be transfered 

237 @param transfer starts transfering, otherwise returns the list of 

238 transfering task to do 

239 @param additional_projects additional projects 

240 @param ftps use protocol FTP, TLS, or SFTP 

241 @param page_transform function which transforms a page before uploading it, 

242 @see fn text_transform 

243 @param fLOG logging function 

244 

245 Example of use:: 

246 

247 import sys 

248 import os 

249 from pyquickhelper.filehelper import TransferFTP, FileTreeNode, FolderTransferFTP 

250 from tkinterquickhelper.funcwin import open_window_params 

251 from ensae_teaching_cs.automation.ftp_publish_helper import publish_teachings_to_web 

252 

253 login = "..." 

254 website = "ftp...." 

255 rootw = "/www/htdocs/app/%s/%s" 

256 

257 password = None 

258 

259 publish_teachings_to_web(login, ftpsite=website, 

260 tracking_id="tracking_id", 

261 location="<something>\\\\%s\\\\%s%s\\\\dist\\\\%s", 

262 rootw=rootw, 

263 folder_status=os.path.abspath("."), 

264 password=password) 

265 

266 Example of an additional projects: 

267 

268 :: 

269 

270 other_projects = [dict(status_file="status_projects.txt"), 

271 root_local="...", root_web="...")] 

272 """ 

273 if modules is None: 

274 modules = get_teaching_modules(branch=False) 

275 

276 if tracking_id is None: 

277 tracking_id = "" 

278 footer = "" 

279 else: 

280 # footer = """ 

281 # """.format(tracking_id) 

282 footer = "" 

283 

284 if password is None and transfer: 

285 raise ValueError("password is empty") 

286 

287 location = os.path.abspath(location) 

288 if folder_status is None: 

289 folder_status = os.path.abspath(os.path.dirname(__file__)) 

290 else: 

291 folder_status = os.path.abspath(folder_status) 

292 

293 if not isinstance(suffix, (tuple, list)): 

294 suffix = [suffix] 

295 

296 projects = [] 

297 for module in modules: 

298 fLOG( 

299 f"[ensae_teaching_cs] PUBLISH '{module}' - layout '{layout}'") 

300 for lay in layout: 

301 for suf in suffix: 

302 root = os.path.abspath(location % 

303 (module, module, suf, lay[0])) 

304 if transfer and os.path.exists(root): 

305 break 

306 if transfer and not os.path.exists(root): 

307 if exc: 

308 p = os.path.abspath(location % 

309 (module, module, suffix[0], lay[0])) 

310 raise FileNotFoundError( 

311 f"First tried '{root}'\n last tried '{p}'") 

312 else: 

313 continue 

314 fLOG(" ", root) 

315 rw = rootw % (module, lay[1]) 

316 

317 project = dict(status_file=os.path.join(folder_status, f"status_{module}.txt"), 

318 local=root, root_local=root, root_web=rw) 

319 projects.append(project) 

320 

321 if module == "ensae_teaching_cs": 

322 lay = [_ for _ in layout if _[0] == "html"][0] 

323 if transfer and not os.path.exists(root): 

324 if exc: 

325 raise FileNotFoundError(root) 

326 else: 

327 continue 

328 

329 def _update_path(pth): 

330 for a, b in [("\\build", "\\build3"), 

331 ("\\html", "\\html3"), 

332 ("/build", "/build3"), 

333 ("/html", "/html3")]: 

334 pth = pth.replace(a, b) 

335 return pth 

336 

337 local = _update_path(root) 

338 fLOG(f"[ensae_teaching_cs] checking folder '{local}'") 

339 fLOG(f"[ensae_teaching_cs] root is '{root}'") 

340 if os.path.exists(local): 

341 fLOG(f"[ensae_teaching_cs] found '{local}'") 

342 project = dict(status_file=os.path.join(folder_status, f"status_3_{module}.txt"), 

343 local=local, root_local=local, 

344 root_web=(rootw % (module, lay[1])).replace("_no_clean", "").replace("/helpsphinx", "/helpsphinx3")) 

345 projects.append(project) 

346 project = dict(status_file=os.path.join(folder_status, f"status_2_{module}.txt"), 

347 local=local, root_local=local, 

348 root_web=(rootw % (module, lay[1])).replace("_no_clean", "").replace("/helpsphinx", "/helpsphinx2")) 

349 projects.append(project) 

350 else: 

351 fLOG("[ensae_teaching_cs] looking into DOC1, DOC3") 

352 root1 = root.replace("_UT_", "_DOC1_") 

353 if transfer: 

354 if not os.path.exists(root1): 

355 if exc: 

356 raise FileNotFoundError(root1) 

357 else: 

358 pass 

359 else: 

360 project = dict(status_file=os.path.join(folder_status, f"status_doc1_{module}.txt"), 

361 local=root1, root_local=root1, 

362 root_web=(rootw % (module, lay[1])).replace("_no_clean", "")) 

363 projects.append(project) 

364 

365 root3 = root.replace("_UT_", "_DOC3_").replace("html", "html3") 

366 if transfer: 

367 if not os.path.exists(root3): 

368 if exc: 

369 raise FileNotFoundError(root3) 

370 else: 

371 project = dict(status_file=os.path.join(folder_status, f"status_doc3_{module}.txt"), 

372 local=root3, root_local=root3, 

373 root_web=(rootw % (module, lay[1])).replace("_no_clean", "").replace("/helpsphinx", "/helpsphinx3")) 

374 projects.append(project) 

375 project = dict(status_file=os.path.join(folder_status, f"status_doc2_{module}.txt"), 

376 local=root3, root_local=root3, 

377 root_web=(rootw % (module, lay[1])).replace("_no_clean", "").replace("/helpsphinx", "/helpsphinx2")) 

378 projects.append(project) 

379 

380 # publish 

381 if additional_projects: 

382 for proj in additional_projects: 

383 if 'root_local' not in proj: 

384 if 'folder' not in proj: 

385 raise KeyError( 

386 f"Key 'folder' or 'root_local' must be specified in {proj}.") 

387 proj = proj.copy() 

388 proj['root_local'] = proj['folder'] 

389 if 'folder' not in proj: 

390 proj = proj.copy() 

391 proj['folder'] = proj['root_local'] 

392 if 'local' not in proj: 

393 proj = proj.copy() 

394 proj['local'] = os.path.dirname(proj['root_local']) 

395 projects.append(proj) 

396 

397 if transfer: 

398 publish_documentation( 

399 projects, ftpsite=ftpsite, login=login, password=password, 

400 footer_html=footer, force_allow=force_allow, delay=delay, 

401 exc=exc_transfer, ftps=ftps, page_transform=page_transform, 

402 fLOG=fLOG) 

403 return projects