#===========================================================
# FILE:         Makefile
# PART OF:      PYST-PUB v2.3
# AUTHOR:       F. Javier Payán Somet, Agosto 2026
# VERSION:      v2.3 — 2026/09/18 18:00
# ROLE:         Automatiza pruebas, compilación de ejemplos,
#               instalación en el árbol TEXMF local y empaquetado.
#===========================================================
#
# PYST-PUB necesita una secuencia de compilación que no es la
# habitual: lualatex, luego biber si hay bibliografía, luego
# makeglossaries/xindy si hay índices, y lualatex otras dos veces. El
# Manual de Usuario la describe tipo por tipo. Escribirla a mano en
# cada proyecto es una fuente recurrente de "la bibliografía no
# aparece" y "los índices no se generan" — dos de las preguntas
# frecuentes del manual.
#
# latexmk conoce esa secuencia si se le dice; latexmkrc, junto a este
# fichero, se la dice. Este Makefile es la capa de arriba.
#
#   make help        lista los objetivos
#   make test        suite de humo + regresión
#   make examples    compila todos los ejemplos distribuidos
#   make install     copia el árbol al TEXMF local del usuario
#   make dist        genera pyst-pub.zip completo
#   make dist-sistema  genera pyst-pub-sistema.zip, para actualizar
#   make dist-todo   genera los dos
#   make clean       borra los auxiliares
#===========================================================

VERSION  := 2.3
NAME     := pyst-pub
# DISTNAME es el nombre del PAQUETE que se entrega, y no lleva versión
# a propósito: se descomprime en pyst-pub/ y se instala en pyst-pub/, de
# modo que todo usuario acaba con la misma estructura con independencia
# de cómo lo instale. La carpeta de desarrollo puede seguir llamándose
# pyst-pub-v2.3, o cualquier otra cosa; el paquete no depende de ella.
DISTNAME := $(NAME)

LUALATEX := lualatex
LATEXMK  := latexmk

# Destino de 'make install'. El árbol TEXMF local difiere entre
# sistemas; se detecta con kpsewhich, que es quien tiene la respuesta
# correcta, en lugar de codificar ~/Library/texmf (macOS) o
# ~/texmf (Linux/Windows) y acertar en uno de los dos.
TEXMFHOME := $(shell kpsewhich -var-value=TEXMFHOME 2>/dev/null)
INSTALLDIR := $(TEXMFHOME)/tex/latex/$(DISTNAME)
XINDYDIR   := $(TEXMFHOME)/xindy

EXAMPLES := $(wildcard examples/*/*.tex)

#-----------------------------------------------------------
# QUÉ NO SE DISTRIBUYE
#
# Estas dos listas son el único sitio donde hay que tocar para añadir o
# quitar algo del paquete. Están aquí arriba, y no enterradas dentro de
# la receta de 'dist', para que se puedan editar sin leer el resto.
#
# DIST_PRUNE_DIRS — directorios que se podan completos. La comparación
#   es POR PREFIJO y SIN DISTINGUIR MAYÚSCULAS, así que 'BCK' cubre
#   también bck/, Bck/, 'BCK copia/' y BCK-2025/. Ver la nota del
#   objetivo dist sobre por qué eso importa.
#
# DIST_EXCLUDE_FILES — ficheros concretos, por nombre exacto.
#-----------------------------------------------------------
DIST_PRUNE_DIRS    = BCK comparacion Build-Changelog othertools \
                     .git .github __MACOSX .build-
DIST_EXCLUDE_FILES = Makefilebad

# Expresiones de find construidas a partir de las listas. El -false
# final cierra la cadena de -o y evita tener que suprimir el último
# separador.
DIST_PRUNE_EXPR = $(patsubst %,-iname '%*' -o,$(DIST_PRUNE_DIRS)) -false
DIST_FILE_EXPR  = $(patsubst %,! -name '%',$(DIST_EXCLUDE_FILES))

# Los mismos directorios, protegidos también de 'make clean': son
# copias de archivo y salidas de herramientas, y lo razonable es
# dejarlas como están. -ipath, por el mismo motivo de mayúsculas.
CLEAN_PROTECT = $(patsubst %,-not -ipath '*/%*/*',$(DIST_PRUNE_DIRS))

# La misma lista, como alternativa de expresión regular para la
# verificación posterior de 'dist'. Derivarla en lugar de repetirla
# evita que las dos se desincronicen: si se añade un directorio arriba,
# la verificación lo vigila sin tocar nada más.
DIST_GUARD = $(subst $() $(),|,$(strip $(DIST_PRUNE_DIRS)))

.PHONY: help test smoke regression examples install uninstall dist dist-sistema dist-todo clean distclean check-version check-metadata check-conditionals compare

help:
	@echo "PYST-PUB v$(VERSION) — objetivos disponibles"
	@echo
	@echo "  make test         TODAS las comprobaciones (es la que normalmente quieres)"
	@echo "  make smoke        sólo la suite de humo (matriz por defecto)"
	@echo "  make smoke-full   matriz completa: todos los tipos x todas las fuentes"
	@echo "  make regression   sólo las pruebas de regresión (defectos ya corregidos)"
	@echo "  make check-version  verifica que los 92 ficheros declaran v$(VERSION)"
	@echo "  make check-metadata audita campos de metadatos y saltos de linea fragiles"
	@echo "  make check-conditionals  verifica que todo condicional TeX este cerrado"
	@echo "  make compare OLD=<ruta>  compara la composicion contra una version anterior"
	@echo
	@echo "  make examples     compila todos los ejemplos de examples/"
	@echo "  make install      instala en $(INSTALLDIR)"
	@echo "  make uninstall    desinstala de ahí"
	@echo
	@echo "  make dist         genera $(DISTNAME).zip completo (~39 MB)"
	@echo "  make dist-sistema genera $(DISTNAME)-sistema.zip, sin los PDF"
	@echo "                    de los ejemplos (~7 MB), para actualizar"
	@echo "  make dist-todo    genera los dos"
	@echo "  make clean        borra auxiliares (.aux .log .toc ...)"
	@echo "  make distclean    clean + borra los PDF generados"

#-----------------------------------------------------------
# Pruebas
#-----------------------------------------------------------
# Todo lo que verifica, en un solo objetivo y en orden de coste
# creciente: primero las dos comprobaciones estaticas, que no compilan
# nada y tardan un segundo, y solo despues las que llaman a lualatex.
# Si algo estatico esta mal, no tiene sentido esperar cinco minutos a
# que compilen cincuenta documentos para enterarse.
test: check-version check-metadata check-conditionals regression smoke

smoke:
	@bash tests/smoke.sh

smoke-full:
	@bash tests/smoke.sh --full

regression:
	@bash tests/regression.sh

# Que ningún fichero se haya quedado atrás en un cambio de versión. Es
# el mismo problema que \pyst_check_pre_versions: resuelve en tiempo de
# compilación para los cuatro ficheros de la fase PRE, extendido a los
# 92 del árbol y disponible sin compilar nada.
#
# La comprobación vive en tools/check-version.py y no en una línea de
# grep aquí: 36 de los 92 ficheros escriben el argumento opcional de
# \ProvidesFile en una línea de continuación, y un grep de una línea
# los pasaba por alto en silencio. Ver la cabecera del script.
check-version:
	@if [ -f tools/check-version.py ]; then \
	  python3 tools/check-version.py $(VERSION) 2026/09/12; \
	else \
	  echo "ERROR: falta tools/check-version.py"; exit 1; \
	fi

# Si la herramienta no está en el árbol, se avisa y se sigue en lugar de
# abortar. 'make test' encadena las cinco comprobaciones, y que la
# ausencia de una herramienta auxiliar rompiera el flujo principal
# obligaría a recordar cuál de los cinco objetivos hay que saltarse.
check-metadata:
	@if [ -f tools/check-metadata.py ]; then \
	  python3 tools/check-metadata.py; \
	else \
	  echo "aviso: falta tools/check-metadata.py; auditoria de metadatos omitida"; \
	fi

# Que ningún \if... se quede sin su \fi. core/post/pyst-refs.ltx tuvo
# uno así (v2.3, \ifPystAdvancedMath): compilaba sin queja porque el
# desequilibrio se cerraba de rebote con un \fi ajeno más adelante en
# el flujo de tokens. Ver la cabecera de tools/check-conditionals.py.
check-conditionals:
	@if [ -f tools/check-conditionals.py ]; then \
	  python3 tools/check-conditionals.py; \
	else \
	  echo "aviso: falta tools/check-conditionals.py; comprobacion de condicionales omitida"; \
	fi

# Comparacion visual y objetiva contra una version anterior.
#   make compare OLD=~/texmf/tex/latex/pyst-pub-v2.1
#   make compare OLD=../pyst-pub-v2.1 FONTS="minion fira termes"
compare:
	@if [ -z "$(OLD)" ]; then \
	  echo "Uso: make compare OLD=<ruta-al-arbol-anterior> [FONTS=\"minion fira\"]"; \
	  exit 2; \
	fi
	@bash tools/compare-versions.sh "$(OLD)" $(FONTS)

#-----------------------------------------------------------
# Ejemplos
#
# Se compilan con latexmk y el latexmkrc del árbol, que sabe encadenar
# biber y xindy. TEXINPUTS apunta al árbol para poder compilar sin
# haber instalado nada, que es lo que necesita la CI.
#-----------------------------------------------------------
examples:
	@root=$$(pwd); rc=0; \
	for f in $(EXAMPLES); do \
	  d=$$(dirname $$f); b=$$(basename $$f .tex); \
	  printf '%-50s' "$$f"; \
	  if ( cd $$d && TEXINPUTS="$$root//:" $(LATEXMK) -r $$root/latexmkrc \
	         -lualatex -interaction=nonstopmode -halt-on-error $$b \
	         >/dev/null 2>&1 ); then echo "ok"; \
	  else echo "FAIL"; rc=1; fi; \
	done; exit $$rc

#-----------------------------------------------------------
# Instalación
#
# Copia el árbol completo salvo lo que no pertenece a una instalación:
# pruebas, CI, y los auxiliares de los ejemplos. pyst.xdy va aparte,
# al subárbol xindy/, porque es donde xindy lo busca (Manual de
# Usuario, "El fichero pyst.xdy").
#-----------------------------------------------------------
install:
	@if [ -z "$(TEXMFHOME)" ]; then \
	  echo "ERROR: kpsewhich no ha sabido decir dónde está TEXMFHOME."; \
	  echo "Instala manualmente según el Manual de Usuario, sección 'El árbol TEXMF local'."; \
	  exit 1; \
	fi
	@mkdir -p "$(INSTALLDIR)" "$(XINDYDIR)"
	@tar -cf - \
	     --exclude='.git*' --exclude='tests' --exclude='__MACOSX' \
	     --exclude='.DS_Store' --exclude='*.aux' --exclude='*.log' \
	     --exclude='*.fls' --exclude='*.fdb_latexmk' --exclude='*.synctex.gz' \
	     pyst-pub.cls pyst-types.cfg pyst-depts.cfg pyst.xdy \
	     core coverstyle defs logos math modules examples \
	  | tar -xf - -C "$(INSTALLDIR)"
	@cp pyst.xdy "$(XINDYDIR)/"
	@mktexlsr "$(TEXMFHOME)" 2>/dev/null || true
	@echo "instalado en $(INSTALLDIR)"
	@echo "verificando:"
	@# Desde /tmp, NO desde el árbol. Ejecutado aquí dentro, kpsewhich
	@# encuentra el pyst-pub.cls del directorio actual y responde
	@# "./pyst-pub.cls" tanto si la instalación ha funcionado como si
	@# no. Una verificación que siempre pasa no verifica nada.
	@found=$$(cd /tmp && kpsewhich pyst-pub.cls); \
	if [ -n "$$found" ]; then \
	  echo "  $$found"; \
	else \
	  echo "  AVISO: kpsewhich todavía no encuentra pyst-pub.cls."; \
	  echo "  Ejecuta:  mktexlsr $(TEXMFHOME)"; \
	fi

uninstall:
	@rm -rf "$(INSTALLDIR)" "$(XINDYDIR)/pyst.xdy"
	@mktexlsr "$(TEXMFHOME)" 2>/dev/null || true
	@echo "desinstalado de $(INSTALLDIR)"

#-----------------------------------------------------------
# Empaquetado
#
# El zip que se distribuye NO debe llevar lo que el árbol de trabajo
# acumula. v2.1 se distribuyó con 365 ficheros en __MACOSX y seis
# .DS_Store dentro; esta regla es la razón por la que eso no vuelve a
# ocurrir aunque el árbol de trabajo los tenga.
#
# EL PAQUETE SE CONSTRUYE DESDE EL MANIFEST
#
# Las dos versiones anteriores de este objetivo funcionaban por
# exclusión: coger el árbol entero y quitar lo que no debía ir. Falló
# dos veces seguidas, y por la misma razón estructural: una lista de
# exclusiones enumera lo que NO se conoce, y por tanto es siempre
# incompleta. Primero se coló BCK/, después comparacion/,
# Build-Changelog/, othertools/, Makefilebad y cuatro extensiones de
# auxiliar que nadie había previsto.
#
# Ahora se construye por inclusión, leyendo el MANIFEST, que enumera lo
# que sí se conoce y que ya existía porque define la obra a efectos de
# la LPPL. El modo de fallo se invierte y a mejor: antes se distribuía
# de más en silencio; ahora, si falta algo, se aborta diciendo qué.
#
# Toda la lógica está en tools/mkdist.sh, porque son cuarenta líneas de
# shell —lectura del MANIFEST, expansión de patrones, montaje,
# verificación— y dentro de una receta, con tabuladores y $$, serían
# ilegibles.
#
# El paquete se llama $(DISTNAME) y NO lleva versión: se descomprime en
# pyst-pub/ y se instala en pyst-pub/, con independencia de que la
# carpeta de desarrollo se llame pyst-pub-v2.3.
#
# Las variables DIST_PRUNE_DIRS y DIST_EXCLUDE_FILES de arriba ya no
# intervienen en dist. Se conservan porque CLEAN_PROTECT las usa para
# que 'make clean' no entre en BCK/ ni en comparacion/.
dist: clean check-version
	@bash tools/mkdist.sh $(DISTNAME)

#-----------------------------------------------------------
# Paquete de ACTUALIZACIÓN
#
# El sistema sin los PDF compilados de los ejemplos, que son 33 de los
# 35 MB de examples/. Pasa de 39 MB a 7.
#
# Omite SÓLO los PDF y no examples/ entera, y la diferencia importa:
# quien actualiza recibe los fuentes .tex al día —que son los que
# cambian entre versiones— y conserva los PDF que ya tuviera. Quitando
# la carpeta completa se quedaría sin los fuentes nuevos, que es justo
# lo que necesita.
#
# --carpeta $(DISTNAME) hace que se descomprima en la MISMA carpeta que
# el paquete completo. Es la condición para poder copiarlo encima de
# una instalación existente; sin esa opción saldría en
# pyst-pub-sistema/ y no serviría de nada.
#
# NO sirve para instalar de cero: dejaría los ejemplos sin compilar.
# Para eso está 'make dist'.
#-----------------------------------------------------------
dist-sistema: clean check-version
	@bash tools/mkdist.sh $(DISTNAME)-sistema --carpeta $(DISTNAME) --sin ejemplos-pdf

# Los dos de una vez, que es lo normal al publicar una versión.
dist-todo: dist dist-sistema

#-----------------------------------------------------------
# -not -ipath '*/bck*/*' protege las copias antiguas: sin él, un
# 'make clean' entraría en BCK/ y le quitaría los auxiliares a todas las
# versiones guardadas. Son copias de archivo, y lo razonable es dejarlas
# exactamente como se guardaron.
#
# -ipath y no -path, y por prefijo y no por nombre exacto, por el mismo
# motivo que en el objetivo dist: esa carpeta aparece en la práctica
# como BCK, bck, Bck, 'BCK copia' o BCK-2025, y un filtro sensible a
# mayúsculas falla en cuanto alguien la renombra.
clean:
	@find . \( -name '*.aux' -o -name '*.log' -o -name '*.toc' \
	        -o -name '*.lof' -o -name '*.lot' -o -name '*.out' \
	        -o -name '*.typo' -o -name '*.thm' -o -name '*.slnc' \
	        -o -name '*.bbl' -o -name '*.bcf' -o -name '*.blg' \
	        -o -name '*.run.xml' -o -name '*.fls' -o -name '*.fdb_latexmk' \
	        -o -name '*.synctex.gz' -o -name '*.idx' -o -name '*.ind' \
	        -o -name '*.ilg' -o -name '*.nav' -o -name '*.snm' \
	        -o -name '*.vrb' -o -name '*.xdv' \) \
	     -not -path './.git/*' $(CLEAN_PROTECT) -delete
	@echo "auxiliares borrados"

distclean: clean
	@find examples -name '*.pdf' $(CLEAN_PROTECT) -delete
	@rm -f $(DISTNAME).zip
	@echo "PDF de ejemplos borrados"
