Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specifying input / output directories when building website #667

Merged
merged 13 commits into from
Feb 5, 2024
14 changes: 14 additions & 0 deletions ecosystem/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
"""Ecosystem main module."""
import fire

from ecosystem.cli import CliMembers, CliCI, build_website


def main():
# pylint: disable=missing-function-docstring
fire.Fire(
{
"members": CliMembers,
"build": build_website,
"ci": CliCI,
}
)
2 changes: 1 addition & 1 deletion ecosystem/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""CLI."""
from .members import CliMembers
from .website import CliWebsite
from .website import build_website
from .ci import CliCI
212 changes: 115 additions & 97 deletions ecosystem/cli/website.py
Original file line number Diff line number Diff line change
@@ -1,114 +1,132 @@
"""CliWebsite class for controlling all CLI functions."""
from __future__ import annotations


from pathlib import Path
from typing import Optional
from typing import Any
import json
import os
import toml

from jinja2 import Environment, FileSystemLoader
from jinja2 import Environment, PackageLoader, Template

from ecosystem.daos import DAO
from ecosystem.models.repository import Repository


class CliWebsite:
"""CliMembers class.
Entrypoint for all CLI website commands.
def build_website(resources: str, output: str) -> None:
"""
Generates the ecosystem web page from data in `resources` dir, writing to `output` dir.
"""
resources_dir = Path(resources)
html = _build_html(*_load_from_file(resources_dir))
Path(output, "index.html").write_text(html)

Each public method of this class is CLI command
and arguments for method are options/flags for this command.

Ex: `python manager.py website build_website`
def _load_from_file(
resources_dir: Path,
) -> tuple[list[Repository], dict[str, Any], dict[str, str], dict[str, Template]]:
"""
Loads website data from file.
Returns:
* Projects: List of Repository objects from `members` folder.
* Web data: Strings (title / descriptions etc.) from `website.toml`.
* Label descriptions: from `labels.json`.
* Jinja templates: from `html_templates` folder.
"""
# Projects list
dao = DAO(path=resources_dir)
projects = sorted(
dao.get_all(),
key=lambda item: (
-(item.stars or 0),
item.name,
),
)

def __init__(self, root_path: Optional[str] = None):
"""CliWebsite class."""
self.current_dir = root_path or os.path.abspath(os.getcwd())
resources_dir = Path(self.current_dir, "ecosystem/resources")
self.dao = DAO(path=resources_dir)
self.label_descriptions = {
item["name"]: item["description"]
for item in json.loads(Path(resources_dir, "labels.json").read_text())
}
self.web_data = toml.loads((resources_dir / "website.toml").read_text())

def build_website(self):
"""Generates the ecosystem web page reading the TOML files."""
# pylint: disable=too-many-locals
environment = Environment(loader=FileSystemLoader("ecosystem/html_templates/"))
projects = self.dao.storage.read()
projects_sorted = sorted(
projects.items(),
key=lambda item: (
-item[1].stars if item[1].stars is not None else 0,
item[1].name,
),
)
templates = {
"website": environment.get_template("webpage.html.jinja"),
"card": environment.get_template("card.html.jinja"),
"tag": environment.get_template("tag.html.jinja"),
"link": environment.get_template("link.html.jinja"),
}
sections = {group["id"]: group for group in self.web_data["groups"]}
for section in sections.values():
section.setdefault("html", "")

max_chars_description_visible = 400
min_chars_description_hidden = 100
count_read_more = 1
for _, repo in projects_sorted:
# Card tags
tags = ""
for index, label in enumerate(repo.labels):
tags += templates["tag"].render(
color="purple",
text=label,
tooltip=self.label_descriptions[label],
# Sometimes tooltips are clipped by the browser window.
# While not perfect, the following line solves 95% of cases
alignment="bottom" if (index % 3) == 2 else "bottom-left",
)

# Card links
links = ""
for url, link_text in (
(repo.url, "repository"),
(repo.website, "website"),
(repo.reference_paper, "paper"),
(repo.documentation, "documentation"),
):
if url:
links += templates["link"].render(url=url, place=link_text)

# Card description
if (
len(repo.description) - max_chars_description_visible
>= min_chars_description_hidden
):
description = [
repo.description[:max_chars_description_visible],
repo.description[max_chars_description_visible:],
]
id_read_more = str(count_read_more)
count_read_more += 1
else:
description = [repo.description, ""]
id_read_more = "None"

# Create the card
card = templates["card"].render(
title=repo.name,
tags=tags,
description_visible=description[0],
description_hidden=description[1],
id_read_more=id_read_more,
links=links,
# Label descriptions
label_descriptions = {
item["name"]: item["description"]
for item in json.loads(Path(resources_dir, "labels.json").read_text())
}

# Website strings
web_data = toml.loads((resources_dir / "website.toml").read_text())

# Jinja templates
environment = Environment(loader=PackageLoader("ecosystem", "html_templates/"))
templates = {
"website": environment.get_template("webpage.html.jinja"),
"card": environment.get_template("card.html.jinja"),
"tag": environment.get_template("tag.html.jinja"),
"link": environment.get_template("link.html.jinja"),
}
return projects, web_data, label_descriptions, templates


def _build_html(projects, web_data, label_descriptions, templates) -> str:
"""
Take all data needed to build the website and produce a HTML string.
"""
sections = {group["id"]: group for group in web_data["groups"]}
for section in sections.values():
section.setdefault("html", "")

max_chars_description_visible = 400
min_chars_description_hidden = 100
count_read_more = 1
for repo in projects:
# Card tags
tags = ""
for index, label in enumerate(repo.labels):
tags += templates["tag"].render(
color="purple",
text=label,
tooltip=label_descriptions[label],
# Sometimes tooltips are clipped by the browser window.
# While not perfect, the following line solves 95% of cases
alignment="bottom" if (index % 3) == 2 else "bottom-left",
)

# Adding the card to a section
sections[repo.group]["html"] += card
# Card links
links = ""
for url, link_text in (
(repo.url, "repository"),
(repo.website, "website"),
(repo.reference_paper, "paper"),
(repo.documentation, "documentation"),
):
if url:
links += templates["link"].render(url=url, place=link_text)

# Card description
if (
len(repo.description) - max_chars_description_visible
>= min_chars_description_hidden
):
description = [
repo.description[:max_chars_description_visible],
repo.description[max_chars_description_visible:],
]
id_read_more = str(count_read_more)
count_read_more += 1
else:
description = [repo.description, ""]
id_read_more = "None"

return templates["website"].render(
header=self.web_data["header"],
sections=sections.values(),
# Create the card
card = templates["card"].render(
title=repo.name,
tags=tags,
description_visible=description[0],
description_hidden=description[1],
id_read_more=id_read_more,
links=links,
)

# Adding the card to a section
sections[repo.group]["html"] += card

return templates["website"].render(
header=web_data["header"],
sections=sections.values(),
)
2 changes: 1 addition & 1 deletion ecosystem/daos/dao.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def read(self) -> dict:
{ url (str): repo (Repository) }
"""
data = {}
for path in self.toml_dir.glob("*"):
for path in self.toml_dir.glob("*.toml"):
repo = Repository.from_dict(toml.load(path))
data[repo.url] = repo
return data
Expand Down
13 changes: 2 additions & 11 deletions manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,7 @@
python manager.py website build_website"
```
"""
import fire

from ecosystem.cli import CliMembers, CliWebsite, CliCI

from ecosystem import main
frankharkins marked this conversation as resolved.
Show resolved Hide resolved

if __name__ == "__main__":
fire.Fire(
{
"members": CliMembers,
"website": CliWebsite,
"ci": CliCI,
}
)
main()
4 changes: 4 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@
setuptools.setup(
name="ecosystem",
description="Ecosystem",
entry_points = {
'console_scripts': ['ecosystem=ecosystem:main'],
},
long_description=long_description,
packages=setuptools.find_packages(),
package_data={"ecosystem": ["html_templates/*.jinja"]},
install_requires=install_requires,
python_requires='>=3.6'
)
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ commands = black {posargs} ecosystem tests --check
allowlist_externals = bash
basepython = python3
commands =
bash -ec "python manager.py website build_website > website/index.html"
bash -ec "python manager.py build --resources ecosystem/resources --output website"
Loading