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

View File

@@ -1,11 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from lektor.types import Type 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: if TYPE_CHECKING:
from lektor.builder import Builder
from lektor.db import Record from lektor.db import Record
from lektor.types.base import RawValue from lektor.types.base import RawValue
from .utils import replaceFractions
from .settings import IngredientConfig from .settings import IngredientConfig
from .utils import replaceFractions
class IngredientEntry: class IngredientEntry:
@@ -111,3 +112,34 @@ class IngredientsListType(Type):
def value_from_raw(self, raw: 'RawValue') -> IngredientsDescriptor: def value_from_raw(self, raw: 'RawValue') -> IngredientsDescriptor:
return IngredientsDescriptor(raw.value or None) 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.databags import Databags
from lektor.db import Page # isinstance from lektor.db import Page # isinstance
from lektor.pluginsystem import Plugin from lektor.pluginsystem import Plugin
import os
from datetime import datetime from datetime import datetime
from typing import TYPE_CHECKING, Any, List, Dict, Tuple, Set, Iterator from typing import TYPE_CHECKING, Any, List, Dict, Tuple, Set, Iterator
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -11,8 +12,9 @@ if TYPE_CHECKING:
from .durationcluster import ( from .durationcluster import (
int_to_cluster, cluster_as_str, human_readable_duration 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 .latex import TexSources, raw_text_to_tex, html_to_tex
from .log import Log
from .settings import Settings from .settings import Settings
from .utils import fillupText, replace_atref_urls, cover_image, noUmlauts 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 # or else latex fails because it cannot find referenced images
TexSources.build(builder) 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 # # Duration #
############## ##############