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 useful link, and if it passes quality control, it will be merged and made
available to the public. available to the public.
To add a link, add the shortcut link and description to the relevant section in To add a link, add find the relevant section in under [`src/links.yml`][3],
[`src/index.html`][3]. You can also add new sections if none of the existing and under the `links` key, add a new item for your link. This item should be a
sections fit the bill. mapping with three keys:
To add the redirect, add it to [`src/redirects.conf`][4], into the same place * `name`: the shortcut link, starting with `/`, followed by letters, numbers,
as you did in `src/index.html`. `redirects.conf` is included inside an and `-`;
[nginx `map` block][5], and the syntax is: * `target`: the URL to redirect to; and
* `description`: the description of the link shown on the home page.
``` To be able to run the python scripts locally, run
/shortcut "https://example.com/long/url"; `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. Thank you for contributing.
[1]: https://uwat.cf [1]: https://uwat.cf
[2]: https://uwat.cf/exams [2]: https://uwat.cf/exams
[3]: src/index.html [3]: src/links.yml
[4]: src/redirects.conf [4]: check.py
[5]: https://nginx.org/en/docs/http/ngx_http_map_module.html [5]: build.py

View file

@ -1,7 +1,10 @@
#!/usr/bin/env python3
import errno import errno
import os import os
from html import escape
from hashlib import sha256 from hashlib import sha256
import yaml
from rcssmin import cssmin from rcssmin import cssmin
DIR = os.path.dirname(__file__) 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_SRC = os.path.join(SRC_DIR, 'assets')
ASSETS_DIST = os.path.join(DIST_DIR, 'assets') ASSETS_DIST = os.path.join(DIST_DIR, 'assets')
bytes = type(b'')
def build_assets(): def build_assets():
name_map = [] name_map = []
@ -26,28 +27,61 @@ def build_assets():
dist_name = '%s-%s%s' % (name, hash, ext) dist_name = '%s-%s%s' % (name, hash, ext)
if ext == '.css': 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: with open(os.path.join(ASSETS_DIST, dist_name), 'wb') as f:
f.write(content) f.write(content)
name_map.append((bytes(asset), bytes(dist_name))) name_map.append((asset, dist_name))
return name_map return name_map
def build_files(html_replace): def build_links(links):
for name in os.listdir(SRC_DIR): output = []
src_path = os.path.join(SRC_DIR, name) for section in links['sections']:
if not os.path.isfile(src_path): output.append(' <h2 id="%s">%s</h2>' % (section['id'], escape(section['name'])))
continue output.append(' <ul>')
with open(os.path.join(SRC_DIR, name), 'rb') as f: 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'])
))
output.append(' </ul>')
output.append('')
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() content = f.read()
if name.endswith('.html'):
for old, new in html_replace: for old, new in html_replace:
content = content.replace(old, new) content = content.replace(old, new)
content = content.replace('{listing}', build_links(links))
with open(os.path.join(DIST_DIR, name), 'wb') as f: with open(os.path.join(DIST_DIR, 'index.html'), 'w', encoding='utf-8') as f:
f.write(content) f.write(content)
@ -58,8 +92,13 @@ def main():
if e.errno != errno.EEXIST: if e.errno != errno.EEXIST:
raise raise
with open(os.path.join(SRC_DIR, 'links.yml'), encoding='utf-8') as f:
links = yaml.safe_load(f)
name_map = build_assets() name_map = build_assets()
build_files(name_map) build_index(name_map, links)
build_redirects(links)
if __name__ == '__main__': if __name__ == '__main__':
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> <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> <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> {listing}
<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>
<h2 id="about">About</h2> <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> <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";