Coverage for webapp/template_utils.py: 96%
82 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-15 22:06 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-15 22:06 +0000
1# Core
2import hashlib
3import os
4from dateutil import parser
5from markupsafe import Markup
6from emoji import replace_emoji
8from webapp.vite_integration import ViteIntegration
11# generator functions for templates
12def generate_slug(path):
13 """
14 Generate a slug for each page
15 """
16 if path.endswith(
17 (
18 "/snaps",
19 "/listing",
20 "/releases",
21 "/metrics",
22 "/publicise",
23 "/publicise/badges",
24 "/publicise/cards",
25 "/settings",
26 "/account/details",
27 )
28 ):
29 return "account"
31 if path == "/" or path.startswith("/first-snap"):
32 return "home"
34 if path.startswith("/build"):
35 return "build"
37 if path.startswith("/blog"):
38 return "blog"
40 if path.startswith("/iot"):
41 return "iot"
43 if path.startswith("/docs/snap-tutorials"):
44 return "tutorials"
46 if path.startswith("/docs"):
47 return "docs"
49 return "store"
52# template filters
53def contains(arr, contents):
54 """
55 Template helper for detecting if an array contains an item
56 """
58 return contents in arr
61def join(arr, separator=""):
62 """
63 Template helper for joining array items into a string, using a separator
64 """
66 return separator.join(arr)
69def static_url(filename):
70 """
71 Template function for generating URLs to static assets:
72 Given the path for a static file, output a url path
73 with a hex hash as a query string for versioning
74 """
76 filepath = os.path.join("static", filename)
77 url = "/" + filepath
79 if not os.path.isfile(filepath):
80 # Could not find static file
81 return url
83 # Use MD5 as we care about speed a lot
84 # and not security in this case
85 file_hash = hashlib.md5()
86 with open(filepath, "rb") as file_contents:
87 for chunk in iter(lambda: file_contents.read(4096), b""):
88 file_hash.update(chunk)
90 return url + "?v=" + file_hash.hexdigest()[:7]
93def vite_import(entrypoint: str):
94 """
95 Template function that takes a .js/.ts source file as an argument and
96 returns the <script> tags with the correct src URL based on Vite's
97 output (a localhost URL in dev mode, or a static url in prod mode)
98 """
99 entry_url = ViteIntegration.get_asset_url(entrypoint)
100 is_css = entry_url.endswith((".css"))
102 if is_css:
103 return Markup(f'<link rel="stylesheet" href="{entry_url}" />')
105 entry_script = f'<script type="module" src="{entry_url}"></script>'
107 chunks_urls = ViteIntegration.get_imported_chunks(entrypoint)
108 chunks_scripts = [
109 f'<link rel="modulepreload" href="{c}" />' for c in chunks_urls
110 ]
111 css_urls = ViteIntegration.get_imported_css(entrypoint)
112 css_scripts = [f'<link rel="stylesheet" href="{c}" />' for c in css_urls]
114 return Markup(entry_script + "".join(chunks_scripts + css_scripts))
117def vite_dev_tools():
118 """
119 Template function that returns <script> tags for Vite's dev server
120 integration (or an empty string in prod mode)
121 """
122 return Markup(ViteIntegration.get_dev_tools())
125def install_snippet(
126 package_name, default_track, lowest_risk_available, confinement
127):
128 """
129 Template function that returns the snippet value to
130 install a snap to be used in distro pages and/or snap
131 detail pages
132 """
134 snippet_value = "sudo snap install " + package_name
136 if lowest_risk_available != "stable":
137 snippet_value += f" --{lowest_risk_available}"
139 if confinement == "classic":
140 snippet_value += " --classic"
142 return snippet_value
145def format_number(number: int):
146 """
147 Template function that transforms a int into a string
148 with a comma between every thousands
149 """
150 return "{:,}".format(number)
153def format_display_name(display_name):
154 """Template function that formats the displayed name
155 primarily to remove emoji
156 """
157 return replace_emoji(display_name, replace="")
160def display_name(display_name, username):
161 """Template function that returns the displayed name if the username
162 is the same, or the dispayed name and the username if differents
163 """
164 display_name = format_display_name(display_name)
165 if display_name.lower() == username.lower():
166 return display_name
167 else:
168 return f"{display_name} ({username})"
171def format_date(timestamp, format):
172 """Template function that returns a formatted date
173 based on the given timestamp
174 """
175 datestring = parser.parse(timestamp)
177 return datestring.strftime(format)
180def format_member_role(role):
181 """Template function that returns the
182 correct label for a members role
183 """
184 roles = {
185 "admin": "admin",
186 "review": "reviewer",
187 "view": "viewer",
188 "access": "publisher",
189 }
191 return roles[role]
194def format_link(url):
195 """
196 Template function that removes protocol, path and query string from links
197 """
198 url_parts = url.split(":")
200 if url_parts[0] == "mailto":
201 return url_parts[1]
203 if url_parts[0] == "http" or url_parts[0] == "https":
204 url_parts_no_slashes = url_parts[1].split("//")[1]
205 url_parts_no_query = url_parts_no_slashes.split("?")[0]
206 url_parts_no_path = url_parts_no_query.split("/")[0]
208 if url_parts_no_path in [
209 "github.com",
210 "gitlab.com",
211 "bitbucket.org",
212 "launchpad.net",
213 "sourceforge.net",
214 ]:
215 return url_parts_no_query
217 return url_parts_no_path