My static website generator using poole https://www.xythobuz.de
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

macros.py 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. # -*- coding: utf-8 -*-
  2. import re
  3. import itertools
  4. import email.utils
  5. import os.path
  6. import time
  7. import codecs
  8. from datetime import datetime
  9. DEFAULT_LANG = "en"
  10. BASE_URL = "https://www.xythobuz.de"
  11. # -----------------------------------------------------------------------------
  12. # sub page helper macro
  13. # -----------------------------------------------------------------------------
  14. def backToParent():
  15. url = page.get("parent", "") + ".html"
  16. posts = [p for p in pages if p.url == url]
  17. if len(posts) > 0:
  18. p = posts[0]
  19. print '<span class="listdesc">[...back to ' + p.title + ' overview](' + p.url + ')</span>'
  20. # -----------------------------------------------------------------------------
  21. # table helper macro
  22. # -----------------------------------------------------------------------------
  23. def tableHelper(style, header, content):
  24. print "<table>"
  25. if (header != None) and (len(header) == len(style)):
  26. print "<tr>"
  27. for h in header:
  28. print "<th>" + h + "</th>"
  29. print "</tr>"
  30. for ci in range(0, len(content)):
  31. if len(content[ci]) != len(style):
  32. # invalid call of table helper!
  33. continue
  34. print "<tr>"
  35. for i in range(0, len(style)):
  36. s = style[i]
  37. td_style = ""
  38. if "monospaced" in s:
  39. td_style += " font-family: monospace;"
  40. if "align-last-right" in s:
  41. if ci == (len(content) - 1):
  42. td_style += " text-align: right;"
  43. else:
  44. if "align-center" in s:
  45. td_style += " text-align: center;"
  46. elif "align-right" in s:
  47. td_style += " text-align: right;"
  48. elif "align-center" in s:
  49. td_style += " text-align: center;"
  50. td_args = ""
  51. if td_style != "":
  52. td_args = " style=\"" + td_style + "\""
  53. print "<td" + td_args + ">"
  54. if isinstance(content[ci][i], tuple):
  55. text, link = content[ci][i]
  56. print "<a href=\"" + link + "\">" + text + "</a>"
  57. else:
  58. text = content[ci][i]
  59. print text
  60. print "</td>"
  61. print "</tr>"
  62. print "</table>"
  63. # -----------------------------------------------------------------------------
  64. # menu helper macro
  65. # -----------------------------------------------------------------------------
  66. def githubCommitBadge(p, showInline = False):
  67. ret = ""
  68. if p.get("github", "") != "":
  69. link = p.get("git", p.github)
  70. linkParts = p.github.split("/")
  71. if len(linkParts) >= 5:
  72. ret += "<a href=\"" + link + "\"><img "
  73. if showInline:
  74. ret += "style =\"vertical-align: top;\" "
  75. ret += "src=\"https://img.shields.io/github/last-commit/"
  76. ret += linkParts[3] + "/" + linkParts[4]
  77. ret += ".svg?logo=git&style=flat\" /></a>"
  78. return ret
  79. def printMenuItem(p, yearsAsHeading = False, showDateSpan = False, showOnlyStartDate = False, nicelyFormatFullDate = False, lastyear = "0", lang = "", showLastCommit = True):
  80. title = p.title
  81. if lang != "":
  82. if p.get("title_" + lang, "") != "":
  83. title = p.get("title_" + lang, "")
  84. if p.title == "Blog":
  85. title = p.post
  86. year = p.get("date", "")[0:4]
  87. if year != lastyear:
  88. lastyear = year
  89. if yearsAsHeading:
  90. print "\n\n#### %s\n" % (year)
  91. dateto = ""
  92. if p.get("date", "" != ""):
  93. year = p.get("date", "")[0:4]
  94. if showOnlyStartDate:
  95. dateto = " (%s)" % (year)
  96. if p.get("update", "") != "" and p.get("update", "")[0:4] != year:
  97. if showDateSpan:
  98. dateto = " (%s - %s)" % (year, p.get("update", "")[0:4])
  99. if nicelyFormatFullDate:
  100. dateto = " - " + datetime.strptime(p.get("update", p.date), "%Y-%m-%d").strftime("%B %d, %Y")
  101. print " * **[%s](%s)**%s" % (title, p.url, dateto)
  102. if p.get("description", "") != "":
  103. description = p.get("description", "")
  104. if lang != "":
  105. if p.get("description_" + lang, "") != "":
  106. description = p.get("description_" + lang, "")
  107. print "<br><span class=\"listdesc\">" + description + "</span>"
  108. if showLastCommit:
  109. link = githubCommitBadge(p)
  110. if len(link) > 0:
  111. print "<br>" + link
  112. return lastyear
  113. def printRecentMenu(count = 5):
  114. posts = [p for p in pages if "date" in p and p.lang == "en"]
  115. posts.sort(key=lambda p: p.get("update", p.get("date")), reverse=True)
  116. if count > 0:
  117. posts = posts[0:count]
  118. for p in posts:
  119. printMenuItem(p, False, False, False, True, "0", "", False)
  120. def printBlogMenu():
  121. posts = [p for p in pages if "post" in p]
  122. posts.sort(key=lambda p: p.get("date", "9999-01-01"), reverse=True)
  123. lastyear = "0"
  124. for p in posts:
  125. lastyear = printMenuItem(p, True, False, False, True, lastyear)
  126. def printProjectsMenu():
  127. # prints all pages with parent 'projects' or 'stuff'.
  128. # first the ones without date, sorted by position.
  129. # then afterwards those with date, split by year.
  130. # also supports blog posts with parent.
  131. enpages = [p for p in pages if p.lang == "en"]
  132. dpages = [p for p in enpages if p.get("date", "") == ""]
  133. mpages = [p for p in dpages if any(x in p.get("parent", "") for x in [ 'projects', 'stuff' ])]
  134. mpages.sort(key=lambda p: [int(p.get("position", "999"))])
  135. for p in mpages:
  136. printMenuItem(p)
  137. dpages = [p for p in enpages if p.get("date", "") != ""]
  138. mpages = [p for p in dpages if any(x in p.get("parent", "") for x in [ 'projects', 'stuff' ])]
  139. mpages.sort(key=lambda p: [p.get("date", "9999-01-01")], reverse = True)
  140. lastyear = "0"
  141. for p in mpages:
  142. lastyear = printMenuItem(p, True, True, False, False, lastyear)
  143. def print3DPrintingMenu():
  144. mpages = [p for p in pages if p.get("parent", "") == "3d-printing" and p.lang == "en"]
  145. mpages.sort(key=lambda p: int(p["position"]))
  146. for p in mpages:
  147. printMenuItem(p, False, True, True)
  148. def printSmarthomeMenu():
  149. mpages = [p for p in pages if p.get("parent", "") == "smarthome" and p.lang == "en"]
  150. mpages.sort(key=lambda p: int(p["position"]))
  151. for p in mpages:
  152. printMenuItem(p, False, True, True)
  153. def printQuadcopterMenu():
  154. mpages = [p for p in pages if p.get("parent", "") == "quadcopters" and p.lang == "en"]
  155. mpages.sort(key=lambda p: int(p["position"]))
  156. for p in mpages:
  157. printMenuItem(p, False, True, True)
  158. def printQuadcopterRelatedMenu():
  159. mpages = [p for p in pages if p.get("show_in_quadcopters", "false") == "true"]
  160. mpages.sort(key=lambda p: [p.get("date", "9999-01-01")], reverse = True)
  161. for p in mpages:
  162. printMenuItem(p, False, True, True)
  163. def printRobotMenuEnglish():
  164. mpages = [p for p in pages if p.get("parent", "") == "xyrobot" and p.lang == "en"]
  165. mpages.sort(key=lambda p: int(p["position"]))
  166. for p in mpages:
  167. printMenuItem(p)
  168. def printRobotMenuDeutsch():
  169. mpages = [p for p in pages if p.get("parent", "") == "xyrobot" and p.lang == "de"]
  170. mpages.sort(key=lambda p: int(p["position"]))
  171. for p in mpages:
  172. printMenuItem(p, False, False, False, False, "0", "de")
  173. # -----------------------------------------------------------------------------
  174. # lightgallery helper macro
  175. # -----------------------------------------------------------------------------
  176. # call this macro like this
  177. # lightgallery([
  178. # [ "image-link", "description" ],
  179. # [ "image-link", "thumbnail-link", "description" ],
  180. # [ "youtube-link", "thumbnail-link", "description" ],
  181. # [ "video-link", "mime", "thumbnail-link", "image-link", "description" ]
  182. # ])
  183. def lightgallery(links):
  184. videos = [l for l in links if len(l) == 5]
  185. v_i = -1
  186. for v in videos:
  187. link, mime, thumb, poster, alt = v
  188. v_i += 1
  189. print '<div style="display:none;" id="video' + str(v_i) + '">'
  190. print '<video class="lg-video-object lg-html5" controls preload="none">'
  191. print '<source src="' + link + '" type="' + mime + '">'
  192. print 'Your browser does not support HTML5 video.'
  193. print '</video>'
  194. print '</div>'
  195. print '<div class="lightgallery">'
  196. v_i = -1
  197. for l in links:
  198. if (len(l) == 3) or (len(l) == 2):
  199. link = img = alt = ""
  200. if len(l) == 3:
  201. link, img, alt = l
  202. else:
  203. link, alt = l
  204. x = link.rfind('.')
  205. img = link[:x] + '_small' + link[x:]
  206. print '<div class="border" data-src="' + link + '"><a href="' + link + '"><img class="pic" src="' + img + '" alt="' + alt + '"></a></div>'
  207. elif len(l) == 5:
  208. v_i += 1
  209. link, mime, thumb, poster, alt = videos[v_i]
  210. print '<div class="border" data-poster="' + poster + '" data-sub-html="' + alt + '" data-html="#video' + str(v_i) + '"><a href="' + link + '"><img class="pic" src="' + thumb + '"></a></div>'
  211. else:
  212. raise NameError('Invalid number of arguments for lightgallery')
  213. print '</div>'
  214. # -----------------------------------------------------------------------------
  215. # github helper macros
  216. # -----------------------------------------------------------------------------
  217. import urllib, json
  218. def restRequest(url):
  219. response = urllib.urlopen(url)
  220. data = json.loads(response.read())
  221. return data
  222. def restReleases(user, repo):
  223. s = "https://api.github.com/repos/"
  224. s += user
  225. s += "/"
  226. s += repo
  227. s += "/releases"
  228. return restRequest(s)
  229. def printLatestRelease(user, repo):
  230. repo_url = "https://github.com/" + user + "/" + repo
  231. print("<div class=\"releasecard\">")
  232. print("Release builds for " + repo + " are <a href=\"" + repo_url + "/releases\">available on GitHub</a>.<br>\n")
  233. releases = restReleases(user, repo)
  234. if len(releases) <= 0:
  235. print("No release has been published on GitHub yet.")
  236. print("</div>")
  237. return
  238. releases.sort(key=lambda x: x["published_at"], reverse=True)
  239. r = releases[0]
  240. release_url = r["html_url"]
  241. print("Latest release of <a href=\"" + repo_url + "\">" + repo + "</a>, at the time of this writing: <a href=\"" + release_url + "\">" + r["name"] + "</a> (" + datetime.strptime(r["published_at"], "%Y-%m-%dT%H:%M:%SZ").strftime("%Y-%m-%d %H:%M:%S") + ")\n")
  242. if len(r["assets"]) <= 0:
  243. print("<br>No release assets have been published on GitHub for that.")
  244. print("</div>")
  245. return
  246. print("<ul>")
  247. print("Release Assets:")
  248. for a in r["assets"]:
  249. size = int(a["size"])
  250. ss = " "
  251. if size >= (1024 * 1024):
  252. ss += "(%.1f MiB)" % (size / (1024.0 * 1024.0))
  253. elif size >= 1024:
  254. ss += "(%d KiB)" % (size // 1024)
  255. else:
  256. ss += "(%d Byte)" % (size)
  257. print("<li><a href=\"" + a["browser_download_url"] + "\">" + a["name"] + "</a>" + ss)
  258. print("</ul></div>")
  259. # -----------------------------------------------------------------------------
  260. # preconvert hooks
  261. # -----------------------------------------------------------------------------
  262. # -----------------------------------------------------------------------------
  263. # multi language support
  264. # -----------------------------------------------------------------------------
  265. def hook_preconvert_anotherlang():
  266. MKD_PATT = r'\.(?:md|mkd|mdown|markdown)$'
  267. _re_lang = re.compile(r'^[\s+]?lang[\s+]?[:=]((?:.|\n )*)', re.MULTILINE)
  268. vpages = [] # Set of all virtual pages
  269. for p in pages:
  270. current_lang = DEFAULT_LANG # Default language
  271. langs = [] # List of languages for the current page
  272. page_vpages = {} # Set of virtual pages for the current page
  273. text_lang = re.split(_re_lang, p.source)
  274. text_grouped = dict(zip([current_lang,] + \
  275. [lang.strip() for lang in text_lang[1::2]], \
  276. text_lang[::2]))
  277. for lang, text in text_grouped.iteritems():
  278. spath = p.fname.split(os.path.sep)
  279. langs.append(lang)
  280. if lang == "en":
  281. filename = re.sub(MKD_PATT, "%s\g<0>" % "", p.fname).split(os.path.sep)[-1]
  282. else:
  283. filename = re.sub(MKD_PATT, ".%s\g<0>" % lang, p.fname).split(os.path.sep)[-1]
  284. vp = Page(filename, virtual=text)
  285. # Copy real page attributes to the virtual page
  286. for attr in p:
  287. if not vp.has_key(attr):
  288. vp[attr] = p[attr]
  289. # Define a title in the proper language
  290. vp["title"] = p["title_%s" % lang] \
  291. if p.has_key("title_%s" % lang) \
  292. else p["title"]
  293. # Keep track of the current lang of the virtual page
  294. vp["lang"] = lang
  295. # Fix post name if exists
  296. if vp.has_key("post"):
  297. if lang == "en":
  298. vp["post"] = vp["post"][:]
  299. else:
  300. vp["post"] = vp["post"][:-len(lang) - 1]
  301. page_vpages[lang] = vp
  302. # Each virtual page has to know about its sister vpages
  303. for lang, vpage in page_vpages.iteritems():
  304. vpage["lang_links"] = dict([(l, v["url"]) for l, v in page_vpages.iteritems()])
  305. vpage["other_lang"] = langs # set other langs and link
  306. vpages += page_vpages.values()
  307. pages[:] = vpages
  308. # -----------------------------------------------------------------------------
  309. # compatibility redirect for old website URLs
  310. # -----------------------------------------------------------------------------
  311. _COMPAT = """ case "%s":
  312. $loc = "%s/%s";
  313. break;
  314. """
  315. _COMPAT_404 = """ default:
  316. $loc = "%s";
  317. break;
  318. """
  319. def hook_preconvert_compat():
  320. fp = open(os.path.join(options.project, "output", "index.php"), 'w')
  321. fp.write("<?\n")
  322. fp.write("// Auto generated xyCMS compatibility index.php\n")
  323. fp.write("$loc = 'https://www.xythobuz.de/index.de.html';\n")
  324. fp.write("if (isset($_GET['p'])) {\n")
  325. fp.write(" if (isset($_GET['lang'])) {\n")
  326. fp.write(" $_GET['p'] .= 'EN';\n")
  327. fp.write(" }\n")
  328. fp.write(" switch($_GET['p']) {\n")
  329. for p in pages:
  330. if p.get("compat", "") != "":
  331. tmp = p["compat"]
  332. if p.get("lang", DEFAULT_LANG) == DEFAULT_LANG:
  333. tmp = tmp + "EN"
  334. fp.write(_COMPAT % (tmp, "https://www.xythobuz.de", p.url))
  335. fp.write("\n")
  336. fp.write(_COMPAT_404 % "/404.html")
  337. fp.write(" }\n")
  338. fp.write("}\n")
  339. fp.write("if ($_SERVER['SERVER_PROTOCOL'] == 'HTTP/1.1') {\n")
  340. fp.write(" if (php_sapi_name() == 'cgi') {\n")
  341. fp.write(" header('Status: 301 Moved Permanently');\n")
  342. fp.write(" } else {\n")
  343. fp.write(" header('HTTP/1.1 301 Moved Permanently');\n")
  344. fp.write(" }\n")
  345. fp.write("}\n");
  346. fp.write("header('Location: '.$loc);\n")
  347. fp.write("?>")
  348. fp.close()
  349. # -----------------------------------------------------------------------------
  350. # sitemap generation
  351. # -----------------------------------------------------------------------------
  352. _SITEMAP = """<?xml version="1.0" encoding="UTF-8"?>
  353. <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  354. %s
  355. </urlset>
  356. """
  357. _SITEMAP_URL = """
  358. <url>
  359. <loc>%s/%s</loc>
  360. <lastmod>%s</lastmod>
  361. <changefreq>%s</changefreq>
  362. <priority>%s</priority>
  363. </url>
  364. """
  365. def hook_preconvert_sitemap():
  366. date = datetime.strftime(datetime.now(), "%Y-%m-%d")
  367. urls = []
  368. for p in pages:
  369. urls.append(_SITEMAP_URL % (BASE_URL, p.url, date, p.get("changefreq", "monthly"), p.get("priority", "0.5")))
  370. fname = os.path.join(options.project, "output", "sitemap.xml")
  371. fp = open(fname, 'w')
  372. fp.write(_SITEMAP % "".join(urls))
  373. fp.close()
  374. # -----------------------------------------------------------------------------
  375. # postconvert hooks
  376. # -----------------------------------------------------------------------------
  377. # -----------------------------------------------------------------------------
  378. # rss feed generation
  379. # -----------------------------------------------------------------------------
  380. _RSS = """<?xml version="1.0" encoding="UTF-8"?>
  381. <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  382. <channel>
  383. <title>%s</title>
  384. <link>%s</link>
  385. <atom:link href="%s" rel="self" type="application/rss+xml" />
  386. <description>%s</description>
  387. <language>en-us</language>
  388. <pubDate>%s</pubDate>
  389. <lastBuildDate>%s</lastBuildDate>
  390. <docs>http://blogs.law.harvard.edu/tech/rss</docs>
  391. <generator>Poole</generator>
  392. <ttl>720</ttl>
  393. %s
  394. </channel>
  395. </rss>
  396. """
  397. _RSS_ITEM = """
  398. <item>
  399. <title>%s</title>
  400. <link>%s</link>
  401. <description>%s</description>
  402. <pubDate>%s</pubDate>
  403. <atom:updated>%s</atom:updated>
  404. <guid>%s</guid>
  405. </item>
  406. """
  407. def hook_postconvert_rss():
  408. items = []
  409. # all pages with "date" get put into feed
  410. posts = [p for p in pages if "date" in p]
  411. # sort by update if available, date else
  412. posts.sort(key=lambda p: p.get("update", p.date), reverse=True)
  413. # only put 20 most recent items in feed
  414. posts = posts[:20]
  415. for p in posts:
  416. title = p.title
  417. if "post" in p:
  418. title = p.post
  419. link = "%s/%s" % (BASE_URL, p.url)
  420. desc = p.html.replace("href=\"img", "%s%s%s" % ("href=\"", BASE_URL, "/img"))
  421. desc = desc.replace("src=\"img", "%s%s%s" % ("src=\"", BASE_URL, "/img"))
  422. desc = desc.replace("href=\"/img", "%s%s%s" % ("href=\"", BASE_URL, "/img"))
  423. desc = desc.replace("src=\"/img", "%s%s%s" % ("src=\"", BASE_URL, "/img"))
  424. desc = htmlspecialchars(desc)
  425. date = time.mktime(time.strptime("%s 12" % p.date, "%Y-%m-%d %H"))
  426. date = email.utils.formatdate(date)
  427. update = time.mktime(time.strptime("%s 12" % p.get("update", p.date), "%Y-%m-%d %H"))
  428. update = email.utils.formatdate(update)
  429. items.append(_RSS_ITEM % (title, link, desc, date, update, link))
  430. items = "".join(items)
  431. title = "xythobuz.de Blog"
  432. link = "%s" % BASE_URL
  433. feed = "%s/rss.xml" % BASE_URL
  434. desc = htmlspecialchars("xythobuz Electronics & Software Projects")
  435. date = email.utils.formatdate()
  436. rss = _RSS % (title, link, feed, desc, date, date, items)
  437. fp = codecs.open(os.path.join(output, "rss.xml"), "w", "utf-8")
  438. fp.write(rss)
  439. fp.close()
  440. # -----------------------------------------------------------------------------
  441. # compatibility redirect for old mobile pages
  442. # -----------------------------------------------------------------------------
  443. _COMPAT_MOB = """ case "%s":
  444. $loc = "%s/%s";
  445. break;
  446. """
  447. _COMPAT_404_MOB = """ default:
  448. $loc = "%s";
  449. break;
  450. """
  451. def hook_postconvert_mobilecompat():
  452. directory = os.path.join(output, "mobile")
  453. if not os.path.exists(directory):
  454. os.makedirs(directory)
  455. fp = codecs.open(os.path.join(directory, "index.php"), "w", "utf-8")
  456. fp.write("<?\n")
  457. fp.write("// Auto generated xyCMS compatibility mobile/index.php\n")
  458. fp.write("$loc = 'https://www.xythobuz.de/index.de.html';\n")
  459. fp.write("if (isset($_GET['p'])) {\n")
  460. fp.write(" if (isset($_GET['lang'])) {\n")
  461. fp.write(" $_GET['p'] .= 'EN';\n")
  462. fp.write(" }\n")
  463. fp.write(" switch($_GET['p']) {\n")
  464. for p in pages:
  465. if p.get("compat", "") != "":
  466. tmp = p["compat"]
  467. if p.get("lang", DEFAULT_LANG) == DEFAULT_LANG:
  468. tmp = tmp + "EN"
  469. fp.write(_COMPAT_MOB % (tmp, "https://www.xythobuz.de", re.sub(".html", ".html", p.url)))
  470. fp.write("\n")
  471. fp.write(_COMPAT_404_MOB % "/404.mob.html")
  472. fp.write(" }\n")
  473. fp.write("}\n")
  474. fp.write("if ($_SERVER['SERVER_PROTOCOL'] == 'HTTP/1.1') {\n")
  475. fp.write(" if (php_sapi_name() == 'cgi') {\n")
  476. fp.write(" header('Status: 301 Moved Permanently');\n")
  477. fp.write(" } else {\n")
  478. fp.write(" header('HTTP/1.1 301 Moved Permanently');\n")
  479. fp.write(" }\n")
  480. fp.write("}\n");
  481. fp.write("header('Location: '.$loc);\n")
  482. fp.write("?>")
  483. fp.close()
  484. # -----------------------------------------------------------------------------
  485. # displaying filesize for download links
  486. # -----------------------------------------------------------------------------
  487. def hook_postconvert_size():
  488. file_ext = '|'.join(['pdf', 'zip', 'rar', 'ods', 'odt', 'odp', 'doc', 'xls', 'ppt', 'docx', 'xlsx', 'pptx', 'exe', 'brd', 'plist'])
  489. def matched_link(matchobj):
  490. try:
  491. path = matchobj.group(1)
  492. if path.startswith("http") or path.startswith("//") or path.startswith("ftp"):
  493. return '<a href=\"%s\">%s</a>' % (matchobj.group(1), matchobj.group(3))
  494. elif path.startswith("/"):
  495. path = path.strip("/")
  496. path = os.path.join("static/", path)
  497. size = os.path.getsize(path)
  498. if size >= (1024 * 1024):
  499. return "<a href=\"%s\">%s</a>&nbsp;(%.1f MiB)" % (matchobj.group(1), matchobj.group(3), size / (1024.0 * 1024.0))
  500. elif size >= 1024:
  501. return "<a href=\"%s\">%s</a>&nbsp;(%d KiB)" % (matchobj.group(1), matchobj.group(3), size // 1024)
  502. else:
  503. return "<a href=\"%s\">%s</a>&nbsp;(%d Byte)" % (matchobj.group(1), matchobj.group(3), size)
  504. except:
  505. print "Unable to estimate file size for %s" % matchobj.group(1)
  506. return '<a href=\"%s\">%s</a>' % (matchobj.group(1), matchobj.group(3))
  507. _re_url = '<a href=\"([^\"]*?\.(%s))\">(.*?)<\/a>' % file_ext
  508. for p in pages:
  509. p.html = re.sub(_re_url, matched_link, p.html)