Skip to content

Commit

Permalink
Added generator logic.
Browse files Browse the repository at this point in the history
  • Loading branch information
josephlewis42 committed Aug 12, 2024
1 parent 64a1c39 commit 2c6eeae
Show file tree
Hide file tree
Showing 11 changed files with 866 additions and 9 deletions.
6 changes: 5 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ FROM python:3.12-slim-bookworm
LABEL org.opencontainers.image.source https://github.com/openzim/devdocs

# Install necessary packages
RUN python -m pip install --no-cache-dir -U \
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libmagic1 \
&& rm -rf /var/lib/apt/lists/* \
&& python -m pip install --no-cache-dir -U \
pip

# Copy pyproject.toml and its dependencies
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies = [
"requests==2.32.3",
"pydantic==2.8.2",
"zimscraperlib==3.4.0",
"Jinja2==3.1.3",
]
dynamic = ["authors", "classifiers", "keywords", "license", "version", "urls"]

Expand All @@ -23,7 +24,7 @@ lint = [
"ruff==0.5.1",
]
check = [
"pyright==1.1.370",
"pyright==1.1.374",
]
test = [
"pytest==8.2.2",
Expand Down
13 changes: 13 additions & 0 deletions src/devdocs2zim/assets/COPYRIGHT
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Copyright 2013-2024 Thibaut Courouble and other contributors

This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.

Please do not use the name DevDocs to endorse or promote products
derived from this software without the maintainers' permission, except
as may be necessary to comply with the notice/attribution requirements.

We also wish that any documentation file generated using this software
be attributed to DevDocs. Let's be fair to all contributors by giving
credit where credit's due. Thanks.
373 changes: 373 additions & 0 deletions src/devdocs2zim/assets/LICENSE

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions src/devdocs2zim/assets/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
These files are copied from DevDocs:

https://github.com/freeCodeCamp/devdocs

devdocs_48.png is adapted from:

https://github.com/freeCodeCamp/devdocs/blob/0dd0ad813f81d3c8e3d040095992e61b7398be96/public/images/icon-64.png
Binary file added src/devdocs2zim/assets/devdocs_48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
92 changes: 88 additions & 4 deletions src/devdocs2zim/client.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
import re
from collections import defaultdict
from enum import Enum
from functools import cached_property

import requests
from pydantic import BaseModel, TypeAdapter
from pydantic import BaseModel, TypeAdapter, computed_field

from devdocs2zim.constants import logger

HTTP_TIMEOUT_SECONDS = 15

# These regular expressions are extracted from the DevDocs frontend.
# The expression definitions haven't changed in ~8 years as of 2024-07-28:
# https://github.com/freeCodeCamp/devdocs/blob/e28f81d3218bdbad7eac0540c58c11c7fe1d33d3/assets/javascripts/collections/types.js#L3
BEFORE_CONTENT_PATTERN = re.compile(
r"(^|\()(guides?|tutorials?|reference|book|getting\ started|manual|examples)($|[\):])", # noqa: E501
re.IGNORECASE,
)
AFTER_CONTENT_PATTERN = re.compile(r"appendix", re.IGNORECASE)


class DevdocsMetadataLinks(BaseModel):
"""Project links for a specific documentation set."""
Expand Down Expand Up @@ -74,14 +88,25 @@ class DevdocsIndexEntry(BaseModel):
path: str

# Name of the type (section) the entry is located under.
type: str
# If None, the entry is not displayed.
type: str | None

@property
def path_without_fragment(self) -> str:
"""Key in db.json for the file's contents."""
return self.path.split("#")[0]


class SortPrecedence(Enum):
"""Represents where to place section in the navbar."""

# NOTE: Definition order must match display order.

BEFORE_CONTENT = 0
CONTENT = 1
AFTER_CONTENT = 2


class DevdocsIndexType(BaseModel):
"""A section header for documentation."""

Expand All @@ -94,6 +119,39 @@ class DevdocsIndexType(BaseModel):
# Section slug. This appears to be unused.
slug: str

def sort_precedence(self) -> SortPrecedence:
"""Determines where this section should be displayed in the navigation."""
if BEFORE_CONTENT_PATTERN.match(self.name):
return SortPrecedence.BEFORE_CONTENT

if AFTER_CONTENT_PATTERN.match(self.name):
return SortPrecedence.AFTER_CONTENT

return SortPrecedence.CONTENT


class NavigationSection(BaseModel):
"""Represents a single section of a devdocs navigation tree."""

# Heading information for the group of links.
name: str
# Links to display in the section.
links: list[DevdocsIndexEntry]

@computed_field
@property
def count(self) -> int:
"""Number of links in the section."""
return len(self.links)

Check warning on line 145 in src/devdocs2zim/client.py

View check run for this annotation

Codecov / codecov/patch

src/devdocs2zim/client.py#L145

Added line #L145 was not covered by tests

@cached_property
def _contained_pages(self) -> set[str]:
return {link.path_without_fragment for link in self.links}

def contains_page(self, page_path: str) -> bool:
"""Returns whether this section contains the given page."""
return page_path in self._contained_pages


class DevdocsIndex(BaseModel):
"""Represents entries in the /<slug>/index.json file for each resource."""
Expand All @@ -102,10 +160,36 @@ class DevdocsIndex(BaseModel):
entries: list[DevdocsIndexEntry]

# List of "types" or section headings.
# These are displayed mostly in order, except regular expressions are used to sort:
# https://github.com/freeCodeCamp/devdocs/blob/e28f81d3218bdbad7eac0540c58c11c7fe1d33d3/assets/javascripts/collections/types.js#L3
# These are displayed in the order they're found grouped by sort_precedence.
types: list[DevdocsIndexType]

def build_navigation(self) -> list[NavigationSection]:
"""Builds a navigation hierarchy that's soreted correctly for rendering."""

sections_by_precedence: dict[SortPrecedence, list[DevdocsIndexType]] = (
defaultdict(list)
)
for section in self.types:
sections_by_precedence[section.sort_precedence()].append(section)

links_by_section_name: dict[str, list[DevdocsIndexEntry]] = defaultdict(list)
for entry in self.entries:
if entry.type is None:
continue

Check warning on line 178 in src/devdocs2zim/client.py

View check run for this annotation

Codecov / codecov/patch

src/devdocs2zim/client.py#L178

Added line #L178 was not covered by tests
links_by_section_name[entry.type].append(entry)

output: list[NavigationSection] = []
for precedence in SortPrecedence:
for section in sections_by_precedence[precedence]:
output.append(
NavigationSection(
name=section.name,
links=links_by_section_name[section.name],
)
)

return output


class DevdocsClient:
"""Utility functions to read data from devdocs."""
Expand Down
Loading

0 comments on commit 2c6eeae

Please sign in to comment.