sgelt 4.8.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sgelt-4.8.1/LICENSE +21 -0
- sgelt-4.8.1/MANIFEST.in +6 -0
- sgelt-4.8.1/PKG-INFO +117 -0
- sgelt-4.8.1/README.md +85 -0
- sgelt-4.8.1/pyproject.toml +69 -0
- sgelt-4.8.1/setup.cfg +4 -0
- sgelt-4.8.1/src/sgelt/__init__.py +11 -0
- sgelt-4.8.1/src/sgelt/__main__.py +6 -0
- sgelt-4.8.1/src/sgelt/__version__.py +1 -0
- sgelt-4.8.1/src/sgelt/calendar.py +105 -0
- sgelt-4.8.1/src/sgelt/config.py +179 -0
- sgelt-4.8.1/src/sgelt/data.py +42 -0
- sgelt-4.8.1/src/sgelt/filters.py +282 -0
- sgelt-4.8.1/src/sgelt/main.py +94 -0
- sgelt-4.8.1/src/sgelt/mdextend.py +16 -0
- sgelt-4.8.1/src/sgelt/mdparse.py +138 -0
- sgelt-4.8.1/src/sgelt/pages.py +973 -0
- sgelt-4.8.1/src/sgelt/py.typed +0 -0
- sgelt-4.8.1/src/sgelt/serve.py +81 -0
- sgelt-4.8.1/src/sgelt/sgelt.py +330 -0
- sgelt-4.8.1/src/sgelt/theme/LICENSE +21 -0
- sgelt-4.8.1/src/sgelt/theme/css/materialize.css +9058 -0
- sgelt-4.8.1/src/sgelt/theme/css/materialize.min.css +13 -0
- sgelt-4.8.1/src/sgelt/theme/css/style.css +695 -0
- sgelt-4.8.1/src/sgelt/theme/img/favicon.ico +0 -0
- sgelt-4.8.1/src/sgelt/theme/img/iss.jpg +0 -0
- sgelt-4.8.1/src/sgelt/theme/img/logo_jinja.png +0 -0
- sgelt-4.8.1/src/sgelt/theme/img/logo_materialize.png +0 -0
- sgelt-4.8.1/src/sgelt/theme/img/logo_pi.png +0 -0
- sgelt-4.8.1/src/sgelt/theme/img/logo_python.png +0 -0
- sgelt-4.8.1/src/sgelt/theme/js/delaunay.js +65 -0
- sgelt-4.8.1/src/sgelt/theme/js/init.js +30 -0
- sgelt-4.8.1/src/sgelt/theme/js/lunr-search-result.js +106 -0
- sgelt-4.8.1/src/sgelt/theme/js/lunr.js +3573 -0
- sgelt-4.8.1/src/sgelt/theme/js/materialize.js +12337 -0
- sgelt-4.8.1/src/sgelt/theme/js/materialize.min.js +6 -0
- sgelt-4.8.1/src/sgelt/theme/js/parallax.js +412 -0
- sgelt-4.8.1/src/sgelt/theme/js/parallax.min.js +6 -0
- sgelt-4.8.1/src/sgelt/theme/templates/actualites-bib.html +13 -0
- sgelt-4.8.1/src/sgelt/theme/templates/actualites.html +39 -0
- sgelt-4.8.1/src/sgelt/theme/templates/agenda.html +25 -0
- sgelt-4.8.1/src/sgelt/theme/templates/article.html +44 -0
- sgelt-4.8.1/src/sgelt/theme/templates/base.html +126 -0
- sgelt-4.8.1/src/sgelt/theme/templates/conference.html +119 -0
- sgelt-4.8.1/src/sgelt/theme/templates/equipe.html +14 -0
- sgelt-4.8.1/src/sgelt/theme/templates/index.html +85 -0
- sgelt-4.8.1/src/sgelt/theme/templates/macros.jinja.html +182 -0
- sgelt-4.8.1/src/sgelt/theme/templates/page.html +24 -0
- sgelt-4.8.1/src/sgelt/theme/templates/partial/date_place.html +17 -0
- sgelt-4.8.1/src/sgelt/theme/templates/partial/footer.html +54 -0
- sgelt-4.8.1/src/sgelt/theme/templates/partial/nav.html +46 -0
- sgelt-4.8.1/src/sgelt/theme/templates/partial/news_button.html +16 -0
- sgelt-4.8.1/src/sgelt/theme/templates/search.html +45 -0
- sgelt-4.8.1/src/sgelt/theme/templates/seminaire.html +56 -0
- sgelt-4.8.1/src/sgelt/theme/templates/seminaires.html +56 -0
- sgelt-4.8.1/src/sgelt/theme/templates/soutenance.html +58 -0
- sgelt-4.8.1/src/sgelt/utils.py +208 -0
- sgelt-4.8.1/src/sgelt/watch.py +81 -0
- sgelt-4.8.1/src/sgelt.egg-info/PKG-INFO +117 -0
- sgelt-4.8.1/src/sgelt.egg-info/SOURCES.txt +74 -0
- sgelt-4.8.1/src/sgelt.egg-info/dependency_links.txt +1 -0
- sgelt-4.8.1/src/sgelt.egg-info/entry_points.txt +2 -0
- sgelt-4.8.1/src/sgelt.egg-info/requires.txt +19 -0
- sgelt-4.8.1/src/sgelt.egg-info/top_level.txt +1 -0
- sgelt-4.8.1/tests/test_calendar.py +206 -0
- sgelt-4.8.1/tests/test_config.py +125 -0
- sgelt-4.8.1/tests/test_data.py +31 -0
- sgelt-4.8.1/tests/test_filters.py +397 -0
- sgelt-4.8.1/tests/test_main.py +160 -0
- sgelt-4.8.1/tests/test_mdparse.py +228 -0
- sgelt-4.8.1/tests/test_pages.py +824 -0
- sgelt-4.8.1/tests/test_serve.py +64 -0
- sgelt-4.8.1/tests/test_serverwatch.py +14 -0
- sgelt-4.8.1/tests/test_site.py +166 -0
- sgelt-4.8.1/tests/test_utils.py +205 -0
- sgelt-4.8.1/tests/test_watch.py +15 -0
sgelt-4.8.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 IRMA
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
sgelt-4.8.1/MANIFEST.in
ADDED
sgelt-4.8.1/PKG-INFO
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: sgelt
|
|
3
|
+
Version: 4.8.1
|
|
4
|
+
Summary: SGELT: Site GEnerator for Labs and Teams
|
|
5
|
+
Author-email: Matthieu Boileau <matthieu.boileau@math.unistra.fr>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://gitlab.math.unistra.fr/irma/sgelt
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: bs4
|
|
15
|
+
Requires-Dist: datetimejson
|
|
16
|
+
Requires-Dist: icalendar==4.1.0
|
|
17
|
+
Requires-Dist: jinja2
|
|
18
|
+
Requires-Dist: livereload
|
|
19
|
+
Requires-Dist: markdown
|
|
20
|
+
Requires-Dist: markdown_link_attr_modifier
|
|
21
|
+
Requires-Dist: python-frontmatter
|
|
22
|
+
Requires-Dist: shortcodes
|
|
23
|
+
Requires-Dist: unidecode
|
|
24
|
+
Requires-Dist: watchdog
|
|
25
|
+
Provides-Extra: tests
|
|
26
|
+
Requires-Dist: faker; extra == "tests"
|
|
27
|
+
Requires-Dist: freezegun; extra == "tests"
|
|
28
|
+
Requires-Dist: pytest; extra == "tests"
|
|
29
|
+
Requires-Dist: pytest-cov; extra == "tests"
|
|
30
|
+
Requires-Dist: pytest-xdist[psutil]; extra == "tests"
|
|
31
|
+
Requires-Dist: pytest-mock; extra == "tests"
|
|
32
|
+
|
|
33
|
+
# SGELT: Site GEnerator for Labs and Teams
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
* Create a python virtual environment :
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
virtualenv .venv/
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
* Activate it
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
source .venv/bin/activate
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
* Install the package and its dependencies :
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install sgelt --index-url https://gitlab.math.unistra.fr/api/v4/projects/653/packages/pypi/simple
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Build a website
|
|
56
|
+
|
|
57
|
+
### Files organization
|
|
58
|
+
|
|
59
|
+
* `config.yml` describes the site configuration
|
|
60
|
+
* `content/` dir contains markdown files
|
|
61
|
+
* `agenda.json` contains all events data
|
|
62
|
+
* `teams.json` contains teams data
|
|
63
|
+
|
|
64
|
+
### Edit content
|
|
65
|
+
|
|
66
|
+
* setup configuration in `config.yml`
|
|
67
|
+
* edit `content/` dir.
|
|
68
|
+
* generate json data files
|
|
69
|
+
|
|
70
|
+
### Build and serve
|
|
71
|
+
|
|
72
|
+
To build all site files, run :
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
sgelt
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
in the project directory.
|
|
79
|
+
Output files will be written in `output/` dir.
|
|
80
|
+
|
|
81
|
+
### Build and serve
|
|
82
|
+
|
|
83
|
+
To serve the website and rebuild automatically when the content has changed:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
sgelt -a -s
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
To open the homepage in a web browser, add:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
sgelt -a -s -b
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Get help with CLI
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
sgelt -h
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Tests
|
|
102
|
+
|
|
103
|
+
### Run tests using tox
|
|
104
|
+
|
|
105
|
+
Tox setup the appropriate environments and run tests using pytest:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pip install tox # install tox
|
|
109
|
+
tox # run
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Run tests using pytest
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
pip install -e .[tests] # install dependencies for tests
|
|
116
|
+
pytest -sv tests/ # run tests
|
|
117
|
+
```
|
sgelt-4.8.1/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# SGELT: Site GEnerator for Labs and Teams
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
* Create a python virtual environment :
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
virtualenv .venv/
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
* Activate it
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
source .venv/bin/activate
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
* Install the package and its dependencies :
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install sgelt --index-url https://gitlab.math.unistra.fr/api/v4/projects/653/packages/pypi/simple
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Build a website
|
|
24
|
+
|
|
25
|
+
### Files organization
|
|
26
|
+
|
|
27
|
+
* `config.yml` describes the site configuration
|
|
28
|
+
* `content/` dir contains markdown files
|
|
29
|
+
* `agenda.json` contains all events data
|
|
30
|
+
* `teams.json` contains teams data
|
|
31
|
+
|
|
32
|
+
### Edit content
|
|
33
|
+
|
|
34
|
+
* setup configuration in `config.yml`
|
|
35
|
+
* edit `content/` dir.
|
|
36
|
+
* generate json data files
|
|
37
|
+
|
|
38
|
+
### Build and serve
|
|
39
|
+
|
|
40
|
+
To build all site files, run :
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
sgelt
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
in the project directory.
|
|
47
|
+
Output files will be written in `output/` dir.
|
|
48
|
+
|
|
49
|
+
### Build and serve
|
|
50
|
+
|
|
51
|
+
To serve the website and rebuild automatically when the content has changed:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
sgelt -a -s
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
To open the homepage in a web browser, add:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
sgelt -a -s -b
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Get help with CLI
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
sgelt -h
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Tests
|
|
70
|
+
|
|
71
|
+
### Run tests using tox
|
|
72
|
+
|
|
73
|
+
Tox setup the appropriate environments and run tests using pytest:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install tox # install tox
|
|
77
|
+
tox # run
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Run tests using pytest
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install -e .[tests] # install dependencies for tests
|
|
84
|
+
pytest -sv tests/ # run tests
|
|
85
|
+
```
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.2"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sgelt"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Matthieu Boileau", email = "matthieu.boileau@math.unistra.fr" },
|
|
9
|
+
]
|
|
10
|
+
description = "SGELT: Site GEnerator for Labs and Teams"
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Operating System :: OS Independent",
|
|
15
|
+
]
|
|
16
|
+
requires-python = ">=3.9"
|
|
17
|
+
dependencies = [
|
|
18
|
+
"bs4",
|
|
19
|
+
"datetimejson",
|
|
20
|
+
"icalendar==4.1.0",
|
|
21
|
+
"jinja2",
|
|
22
|
+
"livereload",
|
|
23
|
+
"markdown",
|
|
24
|
+
"markdown_link_attr_modifier",
|
|
25
|
+
"python-frontmatter",
|
|
26
|
+
"shortcodes",
|
|
27
|
+
"unidecode",
|
|
28
|
+
"watchdog",
|
|
29
|
+
]
|
|
30
|
+
dynamic = ["version"]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
tests = [
|
|
34
|
+
"faker",
|
|
35
|
+
"freezegun",
|
|
36
|
+
"pytest",
|
|
37
|
+
"pytest-cov",
|
|
38
|
+
"pytest-xdist[psutil]",
|
|
39
|
+
"pytest-mock",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.license]
|
|
43
|
+
text = "MIT"
|
|
44
|
+
|
|
45
|
+
[project.readme]
|
|
46
|
+
file = "README.md"
|
|
47
|
+
content-type = "text/markdown"
|
|
48
|
+
|
|
49
|
+
[project.urls]
|
|
50
|
+
Homepage = "https://gitlab.math.unistra.fr/irma/sgelt"
|
|
51
|
+
|
|
52
|
+
[project.scripts]
|
|
53
|
+
sgelt = "sgelt.main:main"
|
|
54
|
+
|
|
55
|
+
[tool.setuptools]
|
|
56
|
+
include-package-data = true
|
|
57
|
+
license-files = ["LICENSE"]
|
|
58
|
+
package-dir = { "" = "src" }
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
[tool.setuptools.packages.find]
|
|
62
|
+
where = ["src"]
|
|
63
|
+
namespaces = false
|
|
64
|
+
|
|
65
|
+
[tool.setuptools.dynamic.version]
|
|
66
|
+
attr = "sgelt.__version__"
|
|
67
|
+
|
|
68
|
+
[tool.setuptools.package-data]
|
|
69
|
+
sgelt = ["py.typed", "sgelt/theme/*"]
|
sgelt-4.8.1/setup.cfg
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Set logging, locale and package_dir"""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import locale
|
|
5
|
+
from .__version__ import __version__
|
|
6
|
+
|
|
7
|
+
__all__ = ['__version__', 'package_dir']
|
|
8
|
+
|
|
9
|
+
locale.setlocale(locale.LC_TIME, 'fr_FR.UTF-8')
|
|
10
|
+
|
|
11
|
+
package_dir = Path(__file__).parent
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '4.8.1'
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A module to export iCalendar events
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from icalendar import Calendar, Event, vText
|
|
6
|
+
import logging
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import pytz
|
|
9
|
+
import random
|
|
10
|
+
import re
|
|
11
|
+
import uuid
|
|
12
|
+
import urllib.parse
|
|
13
|
+
|
|
14
|
+
from sgelt.utils import slugify
|
|
15
|
+
|
|
16
|
+
log = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
rd = random.Random()
|
|
19
|
+
rd.seed(0)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def build_calendar(prodid: str, ical_events: list) -> str:
|
|
23
|
+
"""
|
|
24
|
+
Return a string containing the iCalendar events
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
cal = Calendar()
|
|
28
|
+
cal.add('prodid', prodid)
|
|
29
|
+
cal.add('version', '2.0')
|
|
30
|
+
|
|
31
|
+
tz = pytz.timezone('Europe/Paris')
|
|
32
|
+
|
|
33
|
+
for ical_event in ical_events:
|
|
34
|
+
event = Event()
|
|
35
|
+
event.add('summary', ical_event['summary'])
|
|
36
|
+
event.add('dtstart', tz.localize(ical_event['dtstart']).astimezone(tz))
|
|
37
|
+
event.add('dtend', tz.localize(ical_event['dtend']).astimezone(tz))
|
|
38
|
+
event.add('dtstamp', ical_event['dtstamp'])
|
|
39
|
+
try:
|
|
40
|
+
event['uid'] = ical_event['uuid']
|
|
41
|
+
except KeyError:
|
|
42
|
+
event['uid'] = str(uuid.UUID(int=rd.getrandbits(128)))
|
|
43
|
+
try:
|
|
44
|
+
event['location'] = vText(ical_event['location'])
|
|
45
|
+
except KeyError:
|
|
46
|
+
pass
|
|
47
|
+
try:
|
|
48
|
+
event['description'] = vText(ical_event['description'])
|
|
49
|
+
except KeyError:
|
|
50
|
+
pass
|
|
51
|
+
try:
|
|
52
|
+
# Encode URL for Apple Calendar
|
|
53
|
+
event['url'] = vText(urllib.parse.quote(ical_event['url'],
|
|
54
|
+
safe='/:'))
|
|
55
|
+
except KeyError:
|
|
56
|
+
pass
|
|
57
|
+
event.add('priority', 5)
|
|
58
|
+
cal.add_component(event)
|
|
59
|
+
|
|
60
|
+
return cal.to_ical().decode('UTF-8')
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def merge_calendars(prodid: str, sem_calendar_paths: list,
|
|
64
|
+
event_types: set) -> str:
|
|
65
|
+
"""
|
|
66
|
+
Read a list of seminars iCalendar files and merge them into
|
|
67
|
+
a single iCalendar file
|
|
68
|
+
"""
|
|
69
|
+
cal = Calendar()
|
|
70
|
+
cal.add('prodid', prodid)
|
|
71
|
+
cal.add('version', '2.0')
|
|
72
|
+
|
|
73
|
+
for calendar_path in sorted(sem_calendar_paths):
|
|
74
|
+
with open(calendar_path) as f:
|
|
75
|
+
sem_cal = Calendar.from_ical(f.read())
|
|
76
|
+
# Merge only seminars and Colloquium
|
|
77
|
+
pattern = r"^-\/\/.*\/\/((" + "|".join(event_types) + r") .*)\/\/.*$"
|
|
78
|
+
m = re.match(pattern, sem_cal['prodid'])
|
|
79
|
+
if m:
|
|
80
|
+
summary_prefix = m.group(1)
|
|
81
|
+
for event in sem_cal.walk(name='VEVENT'):
|
|
82
|
+
# Prepend seminar title to event summary
|
|
83
|
+
event['summary'] = f"{summary_prefix} - {event.get('summary')}"
|
|
84
|
+
cal.add_component(event)
|
|
85
|
+
|
|
86
|
+
return cal.to_ical().decode('UTF-8')
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def write_calendar_file(filename: str, conf,
|
|
90
|
+
ical_content: str) -> str:
|
|
91
|
+
"""
|
|
92
|
+
Write calendar content to an .ics file and return encoded url
|
|
93
|
+
"""
|
|
94
|
+
ical_filepath = 'cal' / Path(slugify(filename)).with_suffix('.ics')
|
|
95
|
+
ical_outpath = conf.output_path / ical_filepath
|
|
96
|
+
log.debug(f"Export ical file to {ical_outpath}")
|
|
97
|
+
# Create parent directory if not exists
|
|
98
|
+
ical_outpath.parent.mkdir(exist_ok=True, parents=True)
|
|
99
|
+
ical_outpath.write_text(ical_content)
|
|
100
|
+
if 'url' in conf.site:
|
|
101
|
+
ical_url = \
|
|
102
|
+
f"{conf.site['url']}/{ical_filepath}"
|
|
103
|
+
else:
|
|
104
|
+
ical_url = ical_outpath.as_posix()
|
|
105
|
+
return urllib.parse.quote(ical_url, safe='/:')
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Define site configuration class and instantiate in a conf object"""
|
|
2
|
+
|
|
3
|
+
from datetime import date, datetime
|
|
4
|
+
import jinja2
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
from . import filters, package_dir, __version__
|
|
10
|
+
|
|
11
|
+
log = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Config:
|
|
15
|
+
"""A class to store site configuration"""
|
|
16
|
+
|
|
17
|
+
# Default config dict that may be overriden by user values
|
|
18
|
+
def_conf = dict(
|
|
19
|
+
default_theme_path=package_dir / 'theme',
|
|
20
|
+
theme_path=None,
|
|
21
|
+
output_path='output',
|
|
22
|
+
content_path='content',
|
|
23
|
+
config_path='config.yml',
|
|
24
|
+
static_payload=('css', 'js', 'img'),
|
|
25
|
+
json_agenda_path='agenda_mysql.json',
|
|
26
|
+
json_teams_path='teams.json',
|
|
27
|
+
search_index=True
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def __init__(self, args=None, config_path=None):
|
|
31
|
+
"""Set class attributes from default"""
|
|
32
|
+
self.args = args
|
|
33
|
+
conf = self.def_conf.copy()
|
|
34
|
+
self._set_attributes(conf)
|
|
35
|
+
# Update config.yml default path
|
|
36
|
+
if config_path is not None:
|
|
37
|
+
self.update(config_path=config_path)
|
|
38
|
+
else:
|
|
39
|
+
try:
|
|
40
|
+
self.update(config_path=self.args.config_path)
|
|
41
|
+
except AttributeError:
|
|
42
|
+
pass
|
|
43
|
+
self.site = None
|
|
44
|
+
|
|
45
|
+
def save_args(self) -> dict:
|
|
46
|
+
"""Return a dict of config.__init__() arguments"""
|
|
47
|
+
return {'args': self.args, 'config_path': self.config_path}
|
|
48
|
+
|
|
49
|
+
def update(self, **kwargs):
|
|
50
|
+
"""Update object attributes from names args"""
|
|
51
|
+
conf = vars(self) # get attributes as dict
|
|
52
|
+
# Update conf dict with init args if key exists
|
|
53
|
+
for k, v in kwargs.items():
|
|
54
|
+
if k in self.def_conf:
|
|
55
|
+
conf[k] = v
|
|
56
|
+
else:
|
|
57
|
+
log.warning(f"Cannot update conf dict with unknown key: {k}")
|
|
58
|
+
self._set_attributes(conf)
|
|
59
|
+
|
|
60
|
+
def load(self):
|
|
61
|
+
"""Parse YAML config file and override with CLI args"""
|
|
62
|
+
|
|
63
|
+
# Read config.yml file
|
|
64
|
+
try:
|
|
65
|
+
with open(self.config_path) as f:
|
|
66
|
+
file_conf = yaml.safe_load(f)
|
|
67
|
+
except FileNotFoundError as e:
|
|
68
|
+
log.error(e)
|
|
69
|
+
log.debug(self)
|
|
70
|
+
import sys
|
|
71
|
+
sys.exit(1)
|
|
72
|
+
|
|
73
|
+
# site dict becomes .site attribute
|
|
74
|
+
self.site = file_conf.pop('site')
|
|
75
|
+
|
|
76
|
+
# Convert path/to/file.md -> path/to/file.html
|
|
77
|
+
for dropdown_items in self.site['nav_items'].values():
|
|
78
|
+
try:
|
|
79
|
+
for dropdown_name, filename in dropdown_items.items():
|
|
80
|
+
filepath = Path(filename)
|
|
81
|
+
# Convert markdown paths to html paths
|
|
82
|
+
if filepath.suffix == '.md':
|
|
83
|
+
dropdown_items[dropdown_name] = filepath.with_suffix(
|
|
84
|
+
'.html').as_posix()
|
|
85
|
+
except AttributeError:
|
|
86
|
+
# dropdown_items is not an actual dropdown dict
|
|
87
|
+
pass
|
|
88
|
+
|
|
89
|
+
if 'today' in self.site:
|
|
90
|
+
# Convert today as date if exists
|
|
91
|
+
if type(self.site['today']) is not date:
|
|
92
|
+
self.site['today'] = self.site['today'].date()
|
|
93
|
+
else:
|
|
94
|
+
# Use today date
|
|
95
|
+
self.site['today'] = date.today()
|
|
96
|
+
|
|
97
|
+
# update other attributes (same as in def_conf)
|
|
98
|
+
self.update(**file_conf)
|
|
99
|
+
|
|
100
|
+
# Override with CLI args
|
|
101
|
+
args = self.args
|
|
102
|
+
if args is not None:
|
|
103
|
+
if args.json_agenda:
|
|
104
|
+
self.update(json_agenda_path=args.json_agenda)
|
|
105
|
+
if args.json_teams:
|
|
106
|
+
self.update(json_teams_path=args.json_teams)
|
|
107
|
+
if args.content:
|
|
108
|
+
self.update(content_path=args.content)
|
|
109
|
+
if args.output:
|
|
110
|
+
self.update(output_path=args.output)
|
|
111
|
+
if not args.search_index:
|
|
112
|
+
self.update(search_index=False)
|
|
113
|
+
|
|
114
|
+
def _set_attributes(self, conf: dict):
|
|
115
|
+
"""From conf dict and default values, set class attributes"""
|
|
116
|
+
for k, v in conf.items():
|
|
117
|
+
if k != 'env':
|
|
118
|
+
if isinstance(v, list) or isinstance(v, tuple):
|
|
119
|
+
setattr(self, k, [Path(item) for item in v])
|
|
120
|
+
elif isinstance(v, dict) or isinstance(v, type(None)):
|
|
121
|
+
setattr(self, k, v)
|
|
122
|
+
elif isinstance(v, type(None)):
|
|
123
|
+
pass
|
|
124
|
+
else:
|
|
125
|
+
try:
|
|
126
|
+
setattr(self, k, Path(v))
|
|
127
|
+
except TypeError:
|
|
128
|
+
setattr(self, k, v)
|
|
129
|
+
|
|
130
|
+
self.templates_paths = [self.default_theme_path / Path('templates')]
|
|
131
|
+
try:
|
|
132
|
+
if conf['theme_path'] and conf['theme_path'].is_relative_to(
|
|
133
|
+
self.config_path.parent):
|
|
134
|
+
conf_theme_path = conf['theme_path']
|
|
135
|
+
else:
|
|
136
|
+
conf_theme_path = self.config_path.parent / conf['theme_path']
|
|
137
|
+
user_template_path = conf_theme_path / 'templates'
|
|
138
|
+
if user_template_path.exists(): # Add override template dir
|
|
139
|
+
self.templates_paths.append(user_template_path)
|
|
140
|
+
except TypeError: # self.theme_path is None thus skip
|
|
141
|
+
pass
|
|
142
|
+
self.env = jinja2.Environment(
|
|
143
|
+
loader=jinja2.FileSystemLoader(self.templates_paths)
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Add filters
|
|
147
|
+
self.env.filters['homepage'] = filters.homepage
|
|
148
|
+
self.env.filters['agenda'] = filters.agenda
|
|
149
|
+
self.env.filters['event_date_time'] = filters.event_date_time
|
|
150
|
+
self.env.filters['categ_to_target'] = filters.categ_to_target
|
|
151
|
+
self.env.filters['url'] = filters.url
|
|
152
|
+
self.env.filters['external_url'] = filters.external_url
|
|
153
|
+
self.env.filters['event_selector'] = filters.event_selector
|
|
154
|
+
self.env.filters['sort_by_date'] = filters.sort_by_date
|
|
155
|
+
self.env.filters['homepage_news'] = filters.homepage_news
|
|
156
|
+
self.env.filters['news_page'] = filters.news_page
|
|
157
|
+
self.env.filters['dropdown_items'] = filters.dropdown_items
|
|
158
|
+
self.env.filters['real_teams'] = filters.real_teams
|
|
159
|
+
self.env.filters['teams'] = filters.teams
|
|
160
|
+
self.env.filters['organizing_teams'] = filters.organizing_teams
|
|
161
|
+
|
|
162
|
+
# Global variables
|
|
163
|
+
self.env.globals['datetime_min'] = datetime.min
|
|
164
|
+
self.env.globals['sgelt_version'] = __version__
|
|
165
|
+
|
|
166
|
+
def __repr__(self):
|
|
167
|
+
def _format(k, v):
|
|
168
|
+
if isinstance(v, list):
|
|
169
|
+
return "{}:\n{}".format(k, '\n'.join((f" - {item}"
|
|
170
|
+
for item in v)))
|
|
171
|
+
elif isinstance(v, dict):
|
|
172
|
+
import textwrap
|
|
173
|
+
return f"""\
|
|
174
|
+
{k}:\n{textwrap.indent(yaml.dump(v, allow_unicode=True), 2 * ' ')}"""
|
|
175
|
+
else:
|
|
176
|
+
return f"{k}: {v}"
|
|
177
|
+
return '\n'.join((_format(k, v)
|
|
178
|
+
for k, v in vars(self).items()
|
|
179
|
+
if not isinstance(v, jinja2.Environment)))
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A module to store agenda, news, events and teams data
|
|
3
|
+
To be populated and modified by sgelt module
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .utils import read_data
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Data:
|
|
10
|
+
"""A class to store site data"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, conf):
|
|
13
|
+
"""Initialize with configuration object and empty data"""
|
|
14
|
+
|
|
15
|
+
self.conf = conf # a configuration object
|
|
16
|
+
# read from agenda.json
|
|
17
|
+
self.conferences = []
|
|
18
|
+
self.defenses = []
|
|
19
|
+
self.seminars = []
|
|
20
|
+
|
|
21
|
+
self.teams = {} # read from teams.json
|
|
22
|
+
|
|
23
|
+
self.news = [] # will be populated by registering news
|
|
24
|
+
self.libnews = [] # will be populated by registering libnews
|
|
25
|
+
self.events = [] # will be populated by registering events
|
|
26
|
+
|
|
27
|
+
def read_files(self):
|
|
28
|
+
"""Read data from json files"""
|
|
29
|
+
|
|
30
|
+
# Read agenda data from json file
|
|
31
|
+
agenda = read_data(self.conf.json_agenda_path)
|
|
32
|
+
self.conferences = agenda['conferences']
|
|
33
|
+
self.defenses = agenda['defenses']
|
|
34
|
+
self.seminars = agenda['seminars']
|
|
35
|
+
|
|
36
|
+
# Read team data from json file
|
|
37
|
+
self.teams = read_data(self.conf.json_teams_path)
|
|
38
|
+
|
|
39
|
+
def __repr__(self):
|
|
40
|
+
from pprint import pformat
|
|
41
|
+
return '\n'.join((f"{k}: {pformat(v)}"
|
|
42
|
+
for k, v in vars(self).items()))
|