Merge pull request #2 from rohanpm/add-ci

Add ci
This commit is contained in:
Rohan McGovern 2022-06-29 16:30:07 +10:00 committed by GitHub
commit 66097c6725
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 429 additions and 48 deletions

28
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,28 @@
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
run-ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.9"
- name: Install dependencies
run: |
python -m pip install -U pip
pip install tox
- name: Run tests
run: |
tox

29
.github/workflows/poetry-publish.yml vendored Normal file
View file

@ -0,0 +1,29 @@
name: Publish on PyPI
on:
push:
tags:
- v*
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.9"
- name: Install dependencies
run: |
python -m pip install -U pip
pip install poetry
- name: Publish
run: |
poetry publish --build -vv --no-interaction
env:
POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }}

View file

@ -0,0 +1,24 @@
on:
workflow_dispatch: {}
schedule:
# Tuesday
- cron: "20 20 * * 2"
name: "poetry: merge PR"
jobs:
poetry_update_merge:
name: poetry update automerge
runs-on: ubuntu-latest
steps:
- name: Look up pull request
uses: juliangruber/find-pull-request-action@v1
id: find-pull-request
with:
branch: deps/poetry-update
- name: Merge Pull Request
uses: juliangruber/merge-pull-request-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
number: ${{ steps.find-pull-request.outputs.number }}
method: merge
if: ${{ steps.find-pull-request.outputs.number }}

38
.github/workflows/poetry-update.yml vendored Normal file
View file

@ -0,0 +1,38 @@
on:
workflow_dispatch: {}
schedule:
# Sunday
- cron: "20 20 * * 0"
name: "poetry: create PR"
jobs:
poetry_update:
name: poetry update
runs-on: ubuntu-latest
steps:
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: 3.9
- name: poetry update
uses: technote-space/create-pr-action@v2
with:
EXECUTE_COMMANDS: |
python -m pip install -U pip
pip install poetry
poetry update
COMMIT_MESSAGE: "chore: scheduled poetry update"
COMMIT_NAME: "GitHub Actions"
COMMIT_EMAIL: "noreply@github.com"
GITHUB_TOKEN: ${{ secrets.APPROVAL_TOKEN }}
PR_BRANCH_PREFIX: deps/
PR_BRANCH_NAME: poetry-update
PR_TITLE: "chore: scheduled poetry update"
PR_BODY: |-
## Update dependencies
This is a scheduled update of Python dependencies within this
repo managed by poetry.
This change will be approved automatically and merged within
a few days if all checks have succeeded.

17
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,17 @@
repos:
- repo: https://github.com/psf/black
rev: 22.3.0
hooks:
- id: black
- repo: https://github.com/pycqa/isort/
rev: 5.10.1
hooks:
- id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace

210
poetry.lock generated
View file

@ -37,6 +37,14 @@ category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "atomicwrites"
version = "1.4.0"
description = "Atomic file writes."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "attrs"
version = "21.4.0"
@ -53,15 +61,23 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>
[[package]]
name = "charset-normalizer"
version = "2.0.12"
version = "2.1.0"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false
python-versions = ">=3.5.0"
python-versions = ">=3.6.0"
[package.extras]
unicode_backport = ["unicodedata2"]
[[package]]
name = "colorama"
version = "0.4.5"
description = "Cross-platform colored terminal text."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "defusedxml"
version = "0.7.1"
@ -86,6 +102,14 @@ category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
name = "iniconfig"
version = "1.1.1"
description = "iniconfig: brain-dead simple config-ini parsing"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "jinja2"
version = "3.1.2"
@ -116,6 +140,111 @@ category = "main"
optional = false
python-versions = ">=3.7"
[[package]]
name = "mypy"
version = "0.961"
description = "Optional static typing for Python"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
mypy-extensions = ">=0.4.3"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing-extensions = ">=3.10"
[package.extras]
dmypy = ["psutil (>=4.0)"]
python2 = ["typed-ast (>=1.4.0,<2)"]
reports = ["lxml"]
[[package]]
name = "mypy-extensions"
version = "0.4.3"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "packaging"
version = "21.3"
description = "Core utilities for Python packages"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]]
name = "pluggy"
version = "1.0.0"
description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "py"
version = "1.11.0"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "pyparsing"
version = "3.0.9"
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
category = "dev"
optional = false
python-versions = ">=3.6.8"
[package.extras]
diagrams = ["railroad-diagrams", "jinja2"]
[[package]]
name = "pytest"
version = "7.1.2"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
attrs = ">=19.2.0"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<2.0"
py = ">=1.8.2"
tomli = ">=1.0.0"
[package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
category = "dev"
optional = false
python-versions = ">=3.7"
[[package]]
name = "typing-extensions"
version = "4.2.0"
description = "Backported and Experimental Type Hints for Python 3.7+"
category = "dev"
optional = false
python-versions = ">=3.7"
[[package]]
name = "yarl"
version = "1.7.2"
@ -130,8 +259,8 @@ multidict = ">=4.0"
[metadata]
lock-version = "1.1"
python-versions = "^3.10"
content-hash = "a608104bb5d9bb1fa717110e3bc51579a748fdfbe58bb7ae36d61a030b8e222c"
python-versions = ">=3.9,<4"
content-hash = "ec537cb22f7488971d12450524aec30918e6d42c6eac78fcd4022fe8293d935a"
[metadata.files]
aiohttp = [
@ -216,13 +345,21 @@ async-timeout = [
{file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
{file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
]
atomicwrites = [
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
]
attrs = [
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
]
charset-normalizer = [
{file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
{file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
{file = "charset-normalizer-2.1.0.tar.gz", hash = "sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"},
{file = "charset_normalizer-2.1.0-py3-none-any.whl", hash = "sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5"},
]
colorama = [
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
]
defusedxml = [
{file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"},
@ -293,6 +430,10 @@ idna = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
]
iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
]
jinja2 = [
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
@ -400,6 +541,63 @@ multidict = [
{file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"},
{file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"},
]
mypy = [
{file = "mypy-0.961-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:697540876638ce349b01b6786bc6094ccdaba88af446a9abb967293ce6eaa2b0"},
{file = "mypy-0.961-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b117650592e1782819829605a193360a08aa99f1fc23d1d71e1a75a142dc7e15"},
{file = "mypy-0.961-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bdd5ca340beffb8c44cb9dc26697628d1b88c6bddf5c2f6eb308c46f269bb6f3"},
{file = "mypy-0.961-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3e09f1f983a71d0672bbc97ae33ee3709d10c779beb613febc36805a6e28bb4e"},
{file = "mypy-0.961-cp310-cp310-win_amd64.whl", hash = "sha256:e999229b9f3198c0c880d5e269f9f8129c8862451ce53a011326cad38b9ccd24"},
{file = "mypy-0.961-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b24be97351084b11582fef18d79004b3e4db572219deee0212078f7cf6352723"},
{file = "mypy-0.961-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f4a21d01fc0ba4e31d82f0fff195682e29f9401a8bdb7173891070eb260aeb3b"},
{file = "mypy-0.961-cp36-cp36m-win_amd64.whl", hash = "sha256:439c726a3b3da7ca84a0199a8ab444cd8896d95012c4a6c4a0d808e3147abf5d"},
{file = "mypy-0.961-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5a0b53747f713f490affdceef835d8f0cb7285187a6a44c33821b6d1f46ed813"},
{file = "mypy-0.961-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e9f70df36405c25cc530a86eeda1e0867863d9471fe76d1273c783df3d35c2e"},
{file = "mypy-0.961-cp37-cp37m-win_amd64.whl", hash = "sha256:b88f784e9e35dcaa075519096dc947a388319cb86811b6af621e3523980f1c8a"},
{file = "mypy-0.961-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d5aaf1edaa7692490f72bdb9fbd941fbf2e201713523bdb3f4038be0af8846c6"},
{file = "mypy-0.961-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9f5f5a74085d9a81a1f9c78081d60a0040c3efb3f28e5c9912b900adf59a16e6"},
{file = "mypy-0.961-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f4b794db44168a4fc886e3450201365c9526a522c46ba089b55e1f11c163750d"},
{file = "mypy-0.961-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:64759a273d590040a592e0f4186539858c948302c653c2eac840c7a3cd29e51b"},
{file = "mypy-0.961-cp38-cp38-win_amd64.whl", hash = "sha256:63e85a03770ebf403291ec50097954cc5caf2a9205c888ce3a61bd3f82e17569"},
{file = "mypy-0.961-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f1332964963d4832a94bebc10f13d3279be3ce8f6c64da563d6ee6e2eeda932"},
{file = "mypy-0.961-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:006be38474216b833eca29ff6b73e143386f352e10e9c2fbe76aa8549e5554f5"},
{file = "mypy-0.961-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9940e6916ed9371809b35b2154baf1f684acba935cd09928952310fbddaba648"},
{file = "mypy-0.961-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a5ea0875a049de1b63b972456542f04643daf320d27dc592d7c3d9cd5d9bf950"},
{file = "mypy-0.961-cp39-cp39-win_amd64.whl", hash = "sha256:1ece702f29270ec6af25db8cf6185c04c02311c6bb21a69f423d40e527b75c56"},
{file = "mypy-0.961-py3-none-any.whl", hash = "sha256:03c6cc893e7563e7b2949b969e63f02c000b32502a1b4d1314cabe391aa87d66"},
{file = "mypy-0.961.tar.gz", hash = "sha256:f730d56cb924d371c26b8eaddeea3cc07d78ff51c521c6d04899ac6904b75492"},
]
mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
]
py = [
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
]
pyparsing = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
]
pytest = [
{file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
{file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
]
tomli = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
typing-extensions = [
{file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"},
{file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"},
]
yarl = [
{file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"},
{file = "yarl-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da6df107b9ccfe52d3a48165e48d72db0eca3e3029b5b8cb4fe6ee3cb870ba8b"},

View file

@ -6,16 +6,21 @@ authors = ["Rohan McGovern <rmcgover@redhat.com>"]
license = "GPLv3"
[tool.poetry.dependencies]
python = "^3.10"
aiohttp = "^3.8.1"
defusedxml = "^0.7.1"
Jinja2 = "^3.1.2"
python = ">=3.9,<4"
aiohttp = ">=3.8.1"
defusedxml = ">=0.7.1"
Jinja2 = ">=3.1.2"
[tool.poetry.dev-dependencies]
pytest = ">=7.1.2"
mypy = ">=0.961"
[tool.poetry.scripts]
"repo-autoindex" = "repo_autoindex.cmd:entrypoint"
[tool.isort]
profile = "black"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

View file

@ -1,3 +1,4 @@
from .api import autoindex
from .base import Fetcher, GeneratedIndex
from .api import autoindex
__all__ = ["autoindex", "Fetcher", "GeneratedIndex"]

View file

@ -1,6 +1,7 @@
import logging
import gzip
import logging
from collections.abc import AsyncGenerator
from typing import Optional
import aiohttp
@ -35,7 +36,7 @@ def http_fetcher(session: aiohttp.ClientSession) -> Fetcher:
async def autoindex(
url: str,
*,
fetcher: Fetcher = None,
fetcher: Optional[Fetcher] = None,
index_href_suffix: str = "",
) -> AsyncGenerator[GeneratedIndex, None]:
if fetcher is None:

View file

@ -1,7 +1,7 @@
from dataclasses import dataclass
from typing import Optional, TypeVar, Type
from collections.abc import Awaitable, Callable, AsyncGenerator
from abc import ABC, abstractmethod
from collections.abc import AsyncGenerator, Awaitable, Callable
from dataclasses import dataclass
from typing import Optional, Type, TypeVar
T = TypeVar("T")
Fetcher = Callable[[str], Awaitable[Optional[str]]]
@ -21,8 +21,8 @@ class GeneratedIndex:
class IndexEntry:
href: str
text: str
time: str = "-"
size: str = "-"
time: str = ""
size: str = ""
padding: str = ""
icon: str = ICON_OTHER
@ -46,7 +46,7 @@ class Repo(ABC):
self.fetcher = fetcher
@abstractmethod
async def render_index(
def render_index(
self, index_href_suffix: str
) -> AsyncGenerator[GeneratedIndex, None]:
pass

View file

@ -1,16 +1,15 @@
from collections.abc import AsyncGenerator
import logging
import argparse
import asyncio
import gzip
import logging
import os
import argparse
from repo_autoindex import autoindex
LOG = logging.getLogger("autoindex")
async def dump_autoindices(args):
async def dump_autoindices(args: argparse.Namespace) -> None:
index_filename = args.index_filename
async for index in autoindex(args.url, index_href_suffix=index_filename):
os.makedirs(index.relative_dir or ".", exist_ok=True)

View file

@ -1,6 +1,6 @@
import os
from collections.abc import Iterable
from dataclasses import replace
import os
import jinja2
@ -10,7 +10,7 @@ TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), "templates")
class TemplateContext:
def __init__(self):
def __init__(self) -> None:
self.env = jinja2.Environment(
autoescape=True, loader=jinja2.FileSystemLoader(TEMPLATE_DIR)
)

View file

@ -1,6 +1,7 @@
from dataclasses import dataclass, field, replace
from collections.abc import Iterable
from .base import IndexEntry, ICON_FOLDER
from .base import ICON_FOLDER, IndexEntry
@dataclass
@ -11,7 +12,9 @@ class TreeNode:
def treeify(
all_entries: list[IndexEntry], relative_dir: str = "", index_href_suffix: str = ""
all_entries: Iterable[IndexEntry],
relative_dir: str = "",
index_href_suffix: str = "",
) -> TreeNode:
out = TreeNode(relative_dir=relative_dir)
@ -24,7 +27,7 @@ def treeify(
)
)
entries_by_leading_dir = {}
entries_by_leading_dir: dict[str, list[IndexEntry]] = {}
for entry in all_entries:
subdir = entry.href.removeprefix(relative_dir + "/")
components = subdir.split("/", 1)

View file

@ -1,21 +1,34 @@
import datetime
import logging
import os
import datetime
from collections.abc import AsyncGenerator, Generator, Iterable
from dataclasses import dataclass
from typing import Type, Optional
from collections.abc import Generator, AsyncGenerator, Iterable
from defusedxml import pulldom
from xml.dom.pulldom import START_ELEMENT, END_ELEMENT, DOMEventStream
from typing import Optional, Type
from xml.dom.minidom import Element
from xml.dom.pulldom import END_ELEMENT, START_ELEMENT, DOMEventStream
from .base import Repo, Fetcher, GeneratedIndex, IndexEntry, ICON_PACKAGE, ICON_FOLDER
from defusedxml import pulldom # type: ignore
from .base import ICON_FOLDER, ICON_PACKAGE, Fetcher, GeneratedIndex, IndexEntry, Repo
from .template import TemplateContext
from .tree import treeify
LOG = logging.getLogger("autoindex")
def get_tag(elem: Element, name: str) -> Element:
elems: list[Element] = elem.getElementsByTagName(name) # type: ignore
return elems[0]
def get_text_tag(elem: Element, name: str) -> str:
tagnode = get_tag(elem, name)
child = tagnode.firstChild
# TODO: raise proper error if missing
assert child
return str(child.toxml())
@dataclass
class Package:
href: str
@ -25,10 +38,10 @@ class Package:
@classmethod
def from_element(cls, elem: Element) -> "Package":
return cls(
href=elem.getElementsByTagName("location")[0].attributes["href"].value,
href=get_tag(elem, "location").attributes["href"].value,
# TODO: tolerate some of these being absent or wrong.
time=elem.getElementsByTagName("time")[0].attributes["file"].value,
size=elem.getElementsByTagName("size")[0].attributes["package"].value,
time=get_tag(elem, "time").attributes["file"].value,
size=get_tag(elem, "size").attributes["package"].value,
)
@property
@ -44,7 +57,7 @@ class Package:
def pulldom_elements(
xml_str: str, path_matcher, attr_matcher=lambda _: True
) -> Generator[Element]:
) -> Generator[Element, None, None]:
stream = pulldom.parseString(xml_str)
current_path = []
for event, node in stream:
@ -90,8 +103,11 @@ class YumRepo(Repo):
)
)
if len(revision_nodes) == 1:
timestamp_node = revision_nodes[0].firstChild
# TODO: raise proper error
assert timestamp_node
time = datetime.datetime.utcfromtimestamp(
float(revision_nodes[0].firstChild.toxml())
int(timestamp_node.toxml())
).isoformat()
out.append(
@ -99,7 +115,7 @@ class YumRepo(Repo):
href="repodata/repomd.xml",
text="repomd.xml",
time=time,
size=size,
size=str(size),
)
)
@ -110,14 +126,14 @@ class YumRepo(Repo):
attr_matcher=lambda attrs: attrs.get("type"),
)
)
data_nodes.sort(key=lambda node: node.attributes["type"].value)
data_nodes.sort(key=lambda node: str(node.attributes["type"].value))
for node in data_nodes:
href = node.getElementsByTagName("location")[0].attributes["href"].value
href = get_tag(node, "location").attributes["href"].value
basename = os.path.basename(href)
timestamp = node.getElementsByTagName("timestamp")[0].firstChild.toxml()
timestamp = get_text_tag(node, "timestamp")
time = datetime.datetime.utcfromtimestamp(float(timestamp)).isoformat()
size = int(node.getElementsByTagName("size")[0].firstChild.toxml())
size = int(get_text_tag(node, "size"))
out.append(
IndexEntry(
@ -143,12 +159,15 @@ class YumRepo(Repo):
assert len(primary_nodes) == 1
primary_node = primary_nodes[0]
location = primary_node.getElementsByTagName("location")[0]
location = get_tag(primary_node, "location")
href = location.attributes["href"].value
primary_url = "/".join([self.base_url, href])
primary_xml = await self.fetcher(primary_url)
# TODO: raise proper error if missing
assert primary_xml
return sorted(
[p.index_entry for p in self.__packages_from_primary(primary_xml)],
key=lambda e: e.text,
@ -174,7 +193,7 @@ class YumRepo(Repo):
self,
entries: Iterable[IndexEntry],
index_href_suffix: str,
) -> Generator[GeneratedIndex, None]:
) -> Generator[GeneratedIndex, None, None]:
ctx = TemplateContext()
nodes = [treeify(entries, index_href_suffix=index_href_suffix)]
while nodes:
@ -196,7 +215,7 @@ class YumRepo(Repo):
if repomd_xml is None:
# not yum repo
return
return None
# it is a yum repo
return cls(url, repomd_xml, fetcher)

3
tests/test_import.py Normal file
View file

@ -0,0 +1,3 @@
def test_can_import():
"""A trivial test, to be removed once real tests are implemented."""
import repo_autoindex

16
tox.ini Normal file
View file

@ -0,0 +1,16 @@
[tox]
isolated_build = True
envlist = py39,mypy
envdir = {toxworkdir}/poetry
[testenv]
deps = poetry
skip_install = True
commands =
poetry install -v
py.test -v {posargs}
[testenv:mypy]
commands =
poetry install -v
poetry run mypy --strict --disable-error-code no-untyped-def repo_autoindex