feat: add dead-link checker to build pipeline
This commit is contained in:
8
Makefile
8
Makefile
@@ -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
|
||||||
|
|||||||
@@ -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}/'
|
||||||
|
|||||||
@@ -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 #
|
||||||
##############
|
##############
|
||||||
|
|||||||
Reference in New Issue
Block a user