Convert to using a YAML file to describe links

This commit is contained in:
Quantum 2018-12-07 19:02:27 -05:00
parent d1850e44c1
commit ae93124f5d
7 changed files with 212 additions and 82 deletions

View file

@ -13,22 +13,28 @@ Everyone is welcome to contribute! Simply send in a pull request with your
useful link, and if it passes quality control, it will be merged and made
available to the public.
To add a link, add the shortcut link and description to the relevant section in
[`src/index.html`][3]. You can also add new sections if none of the existing
sections fit the bill.
To add a link, add find the relevant section in under [`src/links.yml`][3],
and under the `links` key, add a new item for your link. This item should be a
mapping with three keys:
To add the redirect, add it to [`src/redirects.conf`][4], into the same place
as you did in `src/index.html`. `redirects.conf` is included inside an
[nginx `map` block][5], and the syntax is:
* `name`: the shortcut link, starting with `/`, followed by letters, numbers,
and `-`;
* `target`: the URL to redirect to; and
* `description`: the description of the link shown on the home page.
```
/shortcut "https://example.com/long/url";
```
To be able to run the python scripts locally, run
`pip install -r requirements.txt` to install our dependencies.
To verify that your changes follow the correct format, run automatic sanity
checks with [`python3 check.py`][4].
To generate the HTML for the site, run [`python3 build.py`][5]. Output will be
generated in a directory called `dist`.
Thank you for contributing.
[1]: https://uwat.cf
[2]: https://uwat.cf/exams
[3]: src/index.html
[4]: src/redirects.conf
[5]: https://nginx.org/en/docs/http/ngx_http_map_module.html
[3]: src/links.yml
[4]: check.py
[5]: build.py

View file

@ -1,7 +1,10 @@
#!/usr/bin/env python3
import errno
import os
from html import escape
from hashlib import sha256
import yaml
from rcssmin import cssmin
DIR = os.path.dirname(__file__)
@ -10,8 +13,6 @@ DIST_DIR = os.path.join(DIR, 'dist')
ASSETS_SRC = os.path.join(SRC_DIR, 'assets')
ASSETS_DIST = os.path.join(DIST_DIR, 'assets')
bytes = type(b'')
def build_assets():
name_map = []
@ -26,29 +27,62 @@ def build_assets():
dist_name = '%s-%s%s' % (name, hash, ext)
if ext == '.css':
content = cssmin(content)
content = cssmin(content.decode('utf-8')).encode('utf-8')
with open(os.path.join(ASSETS_DIST, dist_name), 'wb') as f:
f.write(content)
name_map.append((bytes(asset), bytes(dist_name)))
name_map.append((asset, dist_name))
return name_map
def build_files(html_replace):
for name in os.listdir(SRC_DIR):
src_path = os.path.join(SRC_DIR, name)
if not os.path.isfile(src_path):
continue
def build_links(links):
output = []
for section in links['sections']:
output.append(' <h2 id="%s">%s</h2>' % (section['id'], escape(section['name'])))
output.append(' <ul>')
with open(os.path.join(SRC_DIR, name), 'rb') as f:
content = f.read()
for link in section['links']:
output.append(' <li><a href="{url}">{url}</a> &mdash; {description}</li>'.format(
url=escape(link['name']), description=escape(link['description'])
))
if name.endswith('.html'):
for old, new in html_replace:
content = content.replace(old, new)
output.append(' </ul>')
output.append('')
with open(os.path.join(DIST_DIR, name), 'wb') as f:
f.write(content)
return '\n'.join(output)
def build_redirects(links):
output = []
def build_link(link):
output.append('%s "%s";' % (link['name'], link['target']))
if 'other_links' in links:
for link in links['other_links']:
build_link(link)
output.append('')
for section in links['sections']:
output.append('# %s' % (section['name'],))
for link in section['links']:
build_link(link)
output.append('')
with open(os.path.join(DIST_DIR, 'redirects.conf'), 'w', encoding='utf-8') as f:
f.write('\n'.join(output))
def build_index(html_replace, links):
with open(os.path.join(SRC_DIR, 'index.html'), encoding='utf-8') as f:
content = f.read()
for old, new in html_replace:
content = content.replace(old, new)
content = content.replace('{listing}', build_links(links))
with open(os.path.join(DIST_DIR, 'index.html'), 'w', encoding='utf-8') as f:
f.write(content)
def main():
@ -58,8 +92,13 @@ def main():
if e.errno != errno.EEXIST:
raise
with open(os.path.join(SRC_DIR, 'links.yml'), encoding='utf-8') as f:
links = yaml.safe_load(f)
name_map = build_assets()
build_files(name_map)
build_index(name_map, links)
build_redirects(links)
if __name__ == '__main__':
main()

78
check.py Normal file
View file

@ -0,0 +1,78 @@
#!/usr/bin/env python3
import os
import re
import yaml
SRC_DIR = os.path.join(os.path.dirname(__file__), 'src')
def ensure(cond, output):
if not cond:
raise SystemExit(output)
def check_link(link, description=True):
ensure(isinstance(link, dict), 'a link must be dict, not: %s' % (link,))
ensure('name' in link, 'a link must contain a name: %s' % (link,))
ensure(isinstance(link['name'], str), 'key "name" under link must be string: %s' % (link,))
ensure(link['name'].startswith('/'), 'the name of a link must start with /: %s' % (link,))
ensure(re.match('^/[a-z0-9-]+$', link['name']),
'the name of a link must be / followed by letters, numbers, and -, not %s' % (link['name'],))
ensure('target' in link, 'link "%s" must contain a target' % (link['name'],))
ensure(isinstance(link['target'], str), 'key "target" under link "%s" must be string' % (link['name'],))
if description:
ensure('description' in link, 'link "%s" must contain a description' % (link['name'],))
ensure(isinstance(link['description'], str),
'key "description" under link "%s" must be string' % (link['name'],))
def main():
with open(os.path.join(SRC_DIR, 'links.yml'), encoding='utf-8') as f:
links = yaml.safe_load(f)
unique = set()
ensure('sections' in links, 'links.yml should contain key "sections"')
ensure(isinstance(links['sections'], list), 'key "sections" should map to a list')
for section in links['sections']:
ensure(isinstance(section, dict), 'every item in "sections" should be a dict')
ensure('id' in section, 'every section must have an id')
ensure(isinstance(section['id'], str), 'section IDs must be strings')
ensure(re.match('^[a-z-]+$', section['id']), 'section IDs should only contain lowercase letters and -')
ensure('name' in section, 'every section must have a name')
ensure(isinstance(section['name'], str), 'section names must be strings')
ensure('links' in section, 'every section must have links')
ensure(isinstance(section['links'], list), 'links under %s must be a list' % (section['id'],))
for link in section['links']:
check_link(link)
if link['name'] in unique:
raise SystemExit('duplicate link "%s"' % link['name'])
unique.add(link['name'])
if 'other_links' in links:
ensure(isinstance(links['other_links'], list), 'other_links must be a list')
for link in links['other_links']:
check_link(link, description=False)
if link['name'] in unique:
raise SystemExit('duplicate link "%s"' % link['name'])
unique.add(link['name'])
with open(os.path.join(SRC_DIR, 'index.html'), encoding='utf-8') as f:
contents = f.read()
ensure('{listing}' in contents, 'index.html should have {listing}')
if __name__ == '__main__':
main()

2
requirements.txt Normal file
View file

@ -0,0 +1,2 @@
pyyaml
rcssmin

View file

@ -12,36 +12,7 @@
<h1>Useful UWaterloo Links</h1>
<p><em>All <a href="/link">/link</a>s can be accessed as <a href="https://uwat.cf/link">uwat.cf/link</a>.</em></p>
<h2 id="general">General</h2>
<ul>
<li><a href="/learn">/learn</a> &mdash; UWaterloo Learn</li>
<li><a href="/quest">/quest</a> &mdash; Quest</li>
<li><a href="/wp">/wp</a> &mdash; WatIAM white pages</li>
<li><a href="/watcard">/watcard</a> &mdash; Manage my WatCard</li>
<li><a href="/exams">/exams</a> &mdash; Exam seating and schedules</li>
</ul>
<h2 id="math">Faculty of Mathematics</h2>
<ul>
<li><a href="/marmoset">/marmoset</a> &mdash; Marmoset</li>
<li><a href="/mathexams">/mathexams</a> &mdash; MathSoc Exam Bank</li>
</ul>
<h2 id="eng">Faculty of Engineering</h2>
<ul>
<li><a href="/engexams">/engexams</a> &mdash; EngSoc Exam Bank</li>
<li><a href="/engrank">/engrank</a> &mdash; Undergrad student rankings</li>
</ul>
<h2 id="coop">Co-operative Education</h2>
<ul>
<li><a href="/coopcal">/coopcal</a> &mdash; Co-op important dates</li>
</ul>
<h2 id="finance">Finances</h2>
<ul>
<li><a href="/endow">/endow</a> &mdash; Endowment refund</li>
</ul>
{listing}
<h2 id="about">About</h2>
<p>Want to add more links? Send us a pull request on <a href="https://github.com/quantum5/uwat.cf">GitHub</a>!</p>

57
src/links.yml Normal file
View file

@ -0,0 +1,57 @@
other_links:
- name: /link
target: /
sections:
- id: general
name: General
links:
- name: /learn
target: https://learn.uwaterloo.ca/d2l/home
description: LEARN
- name: /quest
target: https://quest.pecs.uwaterloo.ca/psp/SS/ACADEMIC/SA/?cmd=login&languageCd=ENG
description: Quest
- name: /wp
target: https://idm.uwaterloo.ca/search/authen/
description: WatIAM white pages
- name: /watcard
target: https://watcard.uwaterloo.ca/OneWeb/Account/LogOn
description: Manage my WatCard
- name: /exams
target: https://odyssey.uwaterloo.ca/teaching/schedule
description: Exam seating and schedules
- id: math
name: Faculty of Mathematics
links:
- name: /marmoset
target: https://marmoset.student.cs.uwaterloo.ca/
description: Marmoset
- name: /mathexams
target: http://mathsoc.uwaterloo.ca/exambank
description: MathSoc Exam Bank
- id: eng
name: Faculty of Engineering
links:
- name: /engexams
target: https://exams.engsoc.uwaterloo.ca/
description: EngSoc Exam Bank
- name: /engrank
target: https://engug.uwaterloo.ca/
description: Undergrad student rankings
- id: coop
name: Co-operative Education
links:
- name: /coopcal
target: https://uwaterloo.ca/co-operative-education/important-dates
description: Co-op important dates
- id: finance
name: Finances
links:
- name: /endow
target: https://uwaterloo.ca/forms/finance/user?destination=endowment_request
description: Endowment refund

View file

@ -1,23 +0,0 @@
# Example link
/link "https://uwat.cf/";
# General
/learn "https://learn.uwaterloo.ca/d2l/home";
/quest "https://quest.pecs.uwaterloo.ca/psp/SS/ACADEMIC/SA/?cmd=login&languageCd=ENG";
/wp "https://idm.uwaterloo.ca/search/authen/";
/watcard "https://watcard.uwaterloo.ca/OneWeb/Account/LogOn";
/exams "https://odyssey.uwaterloo.ca/teaching/schedule";
# Faculty of Math
/marmoset "https://marmoset.student.cs.uwaterloo.ca/";
/mathexams "http://mathsoc.uwaterloo.ca/exambank";
# Faculty of Engineering
/engexams "https://exams.engsoc.uwaterloo.ca/";
/engrank "https://engug.uwaterloo.ca/";
# Co-op
/coopcal "https://uwaterloo.ca/co-operative-education/important-dates";
# Finances
/endow "https://uwaterloo.ca/forms/finance/user?destination=endowment_request";