feat: add dead-link checker to build pipeline

This commit is contained in:
relikd
2023-03-03 20:44:43 +01:00
parent df28bde97f
commit 9854c0f670
3 changed files with 50 additions and 6 deletions

View File

@@ -61,9 +61,6 @@ server-v:
.PHONY: build
build: dist
@$(LEKTOR) build --output-path ../bin --buildstate-path build-state -f ENABLE_PDF_EXPORT
@echo
@echo 'Checking dead links ...'
@python3 extras/find-dead-links.py 'data/development'
.PHONY: deploy
deploy:
@@ -90,6 +87,11 @@ find-links:
|| echo 'nothing found.'
@echo
.PHONY: find-dead-links
find-dead-links:
@echo 'Checking dead links ...'
@python3 extras/find-dead-links.py 'data/development'
.PHONY: find-yield
find-yield:
@echo

View File

@@ -1,11 +1,12 @@
# -*- coding: utf-8 -*-
from lektor.types import Type
from typing import TYPE_CHECKING, List, Optional, Any
from typing import TYPE_CHECKING, List, Optional, Any, Iterator, Tuple
if TYPE_CHECKING:
from lektor.builder import Builder
from lektor.db import Record
from lektor.types.base import RawValue
from .utils import replaceFractions
from .settings import IngredientConfig
from .utils import replaceFractions
class IngredientEntry:
@@ -111,3 +112,34 @@ class IngredientsListType(Type):
def value_from_raw(self, raw: 'RawValue') -> IngredientsDescriptor:
return IngredientsDescriptor(raw.value or None)
##############################
# Check cross-recipe links #
##############################
def _detect_atref_urls(recipe: 'Record') -> Iterator[str]:
''' Internal method to iterate over recipe-links in ingredient notes. '''
for ing in recipe['ingredients']:
if ing.isIngredient and '@' in ing.note:
for part in ing.note.split():
if part.startswith('@../'):
yield part[4:].rstrip('/')
def check_dead_links(builder: 'Builder') -> Iterator[Tuple['Record', str]]:
'''
Iterate over all recipes and all ingredients notes.
If a note contains a recipe link, check if the link is a valid target.
If not, print to log but continue building (soft error).
returns: [recipe, ref-link]
'''
for alt in builder.pad.config.iter_alternatives():
# funny enough, .query('/recipes') does not populate ingredients
all_recipes = builder.pad.get('/recipes', alt=alt).children
all_ids = set(x['_slug'] for x in all_recipes)
for recipe in all_recipes:
for ref in _detect_atref_urls(recipe):
if ref not in all_ids:
yield recipe, f'@../{ref}/'

View File

@@ -2,6 +2,7 @@
from lektor.databags import Databags
from lektor.db import Page # isinstance
from lektor.pluginsystem import Plugin
import os
from datetime import datetime
from typing import TYPE_CHECKING, Any, List, Dict, Tuple, Set, Iterator
if TYPE_CHECKING:
@@ -11,8 +12,9 @@ if TYPE_CHECKING:
from .durationcluster import (
int_to_cluster, cluster_as_str, human_readable_duration
)
from .ingredients import IngredientsListType
from .ingredients import IngredientsListType, check_dead_links
from .latex import TexSources, raw_text_to_tex, html_to_tex
from .log import Log
from .settings import Settings
from .utils import fillupText, replace_atref_urls, cover_image, noUmlauts
@@ -60,6 +62,14 @@ class MainPlugin(Plugin):
# or else latex fails because it cannot find referenced images
TexSources.build(builder)
def on_after_prune(self, builder: 'Builder', **extra: Any) -> None:
root = self.env.root_path # type: str
with Log.group('check dead links', builder):
for recipe, link in check_dead_links(builder):
Log.error('dead-link: {} ({})'.format(
os.path.relpath(recipe.source_filename or '', root),
Log.Style.red(link)))
##############
# Duration #
##############