################################################################################ ## __ __ _ __ _ _ ## | \/ |__ _| |_____ / _(_) |___ ## | |\/| / _` | / / -_) _| | / -_) ## |_| |_\__,_|_\_\___|_| |_|_\___| ## Use `help` as default target for this Makefile. ## .DEFAULT: help ## Do not print recipes for this Makefile. We will take care of the printing ## ourselves. ## .SILENT: ## Recipes are sent to Shell in one go, instead of line-by-line. We make sure to ## add the `-e` flag to compensate (as well as the useful `-u` and `-C`). We ## also have to add `-c` explicitly when we play with `.SHELLFLAGS`. ## .ONESHELL: SHELL = sh .SHELLFLAGS = -euC -c ################################################################################ ## ___ _ _ ## / __|___ _ _ __| |_ __ _ _ _| |_ ___ ## | (__/ _ \ ' \(_-< _/ _` | ' \ _(_-< ## \___\___/_||_/__/\__\__,_|_||_\__/__/ ## ## This section defines constants used everywhere else in this Makefile. They ## describe where to find files, where to put them, what utilities to use, and ## some lists of objects gotten from the database. ## The target build directory. output := ./_build website-output := $(output)/website tests-output := $(output)/tests ## Where to find the database and the views. database := ./database views := ./views tests := ./tests ## Where to find some utilities. yaml2json := yq --output-format json lilypond := lilypond --loglevel=warning -dno-point-and-click inkscape := HOME=$$(mktemp -d) xvfb-run inkscape ## The list of dances in the database and their target names in $(website-output). dances := $(notdir $(basename $(wildcard $(database)/dance/*.yaml))) built_dances := $(addprefix $(website-output)/dance/, $(dances)) ## The list of tunes in the database and their target names in $(website-output). tunes := $(notdir $(basename $(wildcard $(database)/tune/*.yaml))) built_tunes := $(addprefix $(website-output)/tune/, $(tunes)) ## The list of books in the database and their target names in $(website-output). books := $(notdir $(basename $(wildcard $(database)/book/*.yaml))) built_books := $(addprefix $(website-output)/book/, $(books)) ################################################################################ ## _ _ _ __ ___ _ ## | || |___| |_ __ / _|___ / __| |___ __ _ _ _ ## | __ / -_) | '_ \ > _|_ _| | (__| / -_) _` | ' \ ## |_||_\___|_| .__/ \_____| \___|_\___\__,_|_||_| ## |_| ## The default endpoint for this endpoint is `help`, which prints a help ## message about the other endpoints. .PHONY: help clean help: printf 'Just try `make website`.\n' clean: printf 'Cleaning up.\n' rm -Rf $(output) ################################################################################ ## ___ _ _ _ ## | _ )_ _(_) |__| | ## | _ \ || | | / _` | ## |___/\_,_|_|_\__,_| ## ## How to build the shape of the $(website-output) directory. The rules later on will ## depend on this shape, unless they clearly depend on something that implies ## that the shape already exists. $(output): mkdir $(output) $(website-output): $(output) mkdir $(website-output) $(website-output)/dance: $(website-output) mkdir $(website-output)/dance $(website-output)/tune: $(website-output) mkdir $(website-output)/tune $(website-output)/book: $(website-output) mkdir $(website-output)/book $(tests-output): $(output) mkdir $(tests-output) ############################################################ ## Individual dances ## Generate a raw JSON file out of a database dance entry. ## $(website-output)/dance/%.raw.json: $(database)/dance/%.yaml $(website-output)/dance printf 'Making `dance/%s.raw.json`...\n' $* cat $< | $(yaml2json) | jq '{dance:., slug:"$*"}' > $@ ## Generate a JSON file out of a raw dance JSON file. ## $(website-output)/dance/%.json: $(website-output)/dance/%.raw.json $(website-output)/all.raw.json printf 'Making `dance/%s.json`...\n' $* cat $< \ | jq '. + $$all + {title:(.dance.name + " | Dance"), root:".."}' \ --argjson all "$$(cat $(website-output)/all.raw.json)" \ > $@ ## Generate a TeX file out of a dance JSON file. ## $(website-output)/dance/%.tex: $(website-output)/dance/%.json printf 'Making `dance/%s.tex`...\n' $* { cat $(views)/tex/preamble.tex j2 $(views)/tex/dance.tex.j2 $< --filters $(views)/j2filters.py } > $@ ## Generate a PDF file out of a dance TeX file. ## $(website-output)/dance/%.pdf: $(website-output)/dance/%.tex printf 'Making `dance/%s.pdf`...\n' $* cd $(dir $<) output=$$( xelatex -halt-on-error $(notdir $<) 2>&1 ) && true return_code=$$? if [ $$return_code -ne 0 ]; then printf ' => \e[1;31munexpected failure while compiling PDF\e[0m.\n' printf ' Here is the output from XeLaTeX:\n' printf '\n\e[37m%s\e[0m\n\n' "$$output" | sed 's|^\(.*\)| \1|' exit $$return_code fi ## Generate a HTML file out of a dance JSON file. ## $(website-output)/dance/%.html: $(website-output)/dance/%.json printf 'Making `dance/%s.html`...\n' $* j2 $(views)/html/dance.html.j2 $< --filters $(views)/j2filters.py > $@ ############################################################ ## Index of dances $(website-output)/dances.raw.json: $(addsuffix .raw.json, $(built_dances)) printf 'Making `dances.raw.json`...\n' if [ -n '$^' ]; then jq -s 'map({(.slug): (.dance)}) | .+[{}] | add | {dances:.}' $^ > $@ else printf ' => trivial file because no built dances\n' jq -n '{dances:[]}' > $@ fi $(website-output)/dances.json: $(website-output)/dances.raw.json printf 'Making `dances.json`...\n' cat $< | jq '. + {root:"."}' > $@ $(website-output)/dances.html: $(website-output)/dances.json printf 'Making `dances.html`...\n' j2 $(views)/html/dances.html.j2 $< --filters $(views)/j2filters.py > $@ ############################################################ ## Individual tunes ## Generate a raw JSON file out of a database tune entry. ## $(website-output)/tune/%.raw.json: $(database)/tune/%.yaml $(website-output)/tune printf 'Making `tune/%s.raw.json`...\n' $* cat $< | $(yaml2json) | jq '{tune:., slug:"$*"}' > $@ ## Generate a JSON file out of a raw tune JSON file. ## $(website-output)/tune/%.json: $(website-output)/tune/%.raw.json $(website-output)/all.raw.json printf 'Making `tune/%s.json`...\n' $* cat $< \ | jq '. + $$all + {title:(.tune.name + " | Tune"), root:".."}' \ --argjson all "$$(cat $(website-output)/all.raw.json)" \ > $@ ## Generate a LilyPond file out of a tune JSON file. ## $(website-output)/tune/%.ly: $(website-output)/tune/%.json printf 'Making `tune/%s.ly`...\n' $* { cat $(views)/ly/version.ly cat $(views)/ly/repeat-aware.ly cat $(views)/ly/bar-number-in-instrument-name-engraver.ly cat $(views)/ly/beginning-of-line.ly cat $(views)/ly/repeat-volta-fancy.ly cat $(views)/ly/preamble.ly j2 $(views)/ly/tune.ly.j2 $< --filters $(views)/j2filters.py } > $@ ## Generate a PDF file out of a tune LilyPond file. ## $(website-output)/tune/%.pdf: $(website-output)/tune/%.ly printf 'Making `tune/%s.pdf`...\n' $* cd $(dir $<) $(lilypond) $* ## Generate a short LilyPond file out of a tune JSON file. ## $(website-output)/tune/%.short.ly: $(website-output)/tune/%.json printf 'Making `tune/%s.short.ly`...\n' $* { cat $(views)/ly/version.ly cat $(views)/ly/repeat-aware.ly cat $(views)/ly/bar-number-in-instrument-name-engraver.ly cat $(views)/ly/beginning-of-line.ly cat $(views)/ly/repeat-volta-fancy.ly cat $(views)/ly/preamble.ly cat $(views)/ly/preamble.short.ly j2 $(views)/ly/tune.ly.j2 $< --filters $(views)/j2filters.py } > $@ ## Generate a SVG file out of a tune short LilyPond file. $(website-output)/tune/%.svg: $(website-output)/tune/%.short.ly printf 'Making `tune/%s.svg`...\n' $* cd $(dir $<) $(lilypond) -dbackend=svg $*.short.ly $(inkscape) --batch-process --export-area-drawing --export-plain-svg \ --export-filename=$*.svg $*.short.svg 2>/dev/null rm $*.short.svg ## Generate a HTML file out of a tune JSON file. ## $(website-output)/tune/%.html: $(website-output)/tune/%.json printf 'Making `tune/%s.html`...\n' $* j2 $(views)/html/tune.html.j2 $< --filters $(views)/j2filters.py > $@ ############################################################ ## Index of tunes $(website-output)/tunes.raw.json: $(addsuffix .raw.json, $(built_tunes)) printf 'Making `tunes.raw.json`...\n' if [ -n '$^' ]; then jq -s 'map({(.slug): (.tune)}) | .+[{}] | add | {tunes:., root:"."}' $^ > $@ else printf ' => trivial file because no built tunes\n' jq -n '{tunes:[], root:"."}' > $@ fi $(website-output)/tunes.json: $(website-output)/tunes.raw.json printf 'Making `tunes.json`...\n' cat $< | jq '. + {root:"."}' > $@ $(website-output)/tunes.html: $(website-output)/tunes.json printf 'Making `tunes.html`...\n' j2 $(views)/html/tunes.html.j2 $< --filters $(views)/j2filters.py > $@ ############################################################ ## Individual books ## Generate a raw JSON file out of a database book entry. ## $(website-output)/book/%.raw.json: $(database)/book/%.yaml $(website-output)/book printf 'Making `book/%s.raw.json`...\n' $* cat $< | $(yaml2json) | jq '{book:., slug:"$*"}' > $@ ## Generate a JSON file out of a raw book JSON file. ## $(website-output)/book/%.json: $(website-output)/book/%.raw.json $(website-output)/all.raw.json printf 'Making `book/%s.json`...\n' $* cat $< \ | jq '. + $$all + {title:(.book.title + " | Book"), root:".."}' \ --argjson all "$$(cat $(website-output)/all.raw.json)" \ > $@ ## Generate a HTML file out of a book JSON file. ## $(website-output)/book/%.html: $(website-output)/book/%.json printf 'Making `book/%s.html`...\n' $* j2 $(views)/html/book.html.j2 $< --filters $(views)/j2filters.py > $@ ############################################################ ## Index of books $(website-output)/books.raw.json: $(addsuffix .raw.json, $(built_books)) printf 'Making `books.raw.json`...\n' if [ -n '$^' ]; then jq -s 'map({(.slug): (.book)}) | .+[{}] | add | {books:.}' $^ > $@ else printf ' => trivial file because no built books\n' jq -n '{books:{}}' > $@ fi $(website-output)/books.json: $(website-output)/books.raw.json printf 'Making `books.json`...\n' cat $< | jq '. + {root:"."}' > $@ $(website-output)/books.html: $(website-output)/books.json printf 'Making `books.html`...\n' j2 $(views)/html/books.html.j2 $< --filters $(views)/j2filters.py > $@ ############################################################ ## Index & $(website-output)/all.raw.json: $(website-output)/dances.raw.json $(website-output)/tunes.raw.json $(website-output)/books.raw.json printf 'Making `all.raw.json`...\n' jq -s '{dances:.[0].dances, tunes:.[1].tunes, books:.[2].books}' $^ > $@ $(website-output)/index.json: $(website-output)/all.raw.json printf 'Making `index.json`...\n' cat $< | jq '. + {root:"."}' > $@ $(website-output)/index.html: $(website-output)/index.json printf 'Making `index.html`...\n' j2 $(views)/html/index.html.j2 $< --filters $(views)/j2filters.py > $@ $(website-output)/non-scddb.json: $(website-output)/all.raw.json printf 'Making `non-scddb.json`...\n' cat $< | jq '. + {root:"."}' > $@ $(website-output)/non-scddb.html: $(website-output)/non-scddb.json printf 'Making `non-scddb.html`...\n' j2 $(views)/html/non-scddb.html.j2 $< --filters $(views)/j2filters.py > $@ ############################################################ ## All .PHONY: dances tunes books index css static website dances: $(addsuffix .html, $(built_dances)) $(addsuffix .pdf, $(built_dances)) $(website-output)/dances.html tunes: $(addsuffix .html, $(built_tunes)) $(addsuffix .svg, $(built_tunes)) $(addsuffix .pdf, $(built_tunes)) $(website-output)/tunes.html books: $(addsuffix .html, $(built_books)) $(website-output)/books.html index: $(website-output)/index.html $(website-output)/non-scddb.html css: $(website-output) cp $(views)/css/reset.css $(website-output) sassc $(views)/css/style.scss $(website-output)/style.css static: $(website-output) printf 'Copying static files`...\n' cp -R $(views)/static/* $(website-output) website: dances tunes books index css static ################################################################################ ## _____ _ ## |_ _|__ __| |_ ___ ## | |/ -_|_-< _(_-< ## |_|\___/__/\__/__/ .PHONY: test-website test-website: dances=$$(yq --unwrapScalar '.build-arguments.dances.[]' $(tests)/meta.yaml) tunes=$$(yq --unwrapScalar '.build-arguments.tunes.[]' $(tests)/meta.yaml) books=$$(yq --unwrapScalar '.build-arguments.books.[]' $(tests)/meta.yaml) make website dances="$$dances" tunes="$$tunes" books="$$books" .PHONY: tests tests: $(tests-output) if ! [ -d $(website-output) ]; then printf 'The website need to be built first for tests to run.\n' exit 7 fi dissimilarities=0 unexpected_failures=0 paths=$$(yq '.paths | length' $(tests)/meta.yaml) for ii in $$(seq 1 $$paths); do i=$$((ii - 1)) path=$$(yq ".paths[$$i]" $(tests)/meta.yaml) printf 'Path #%d of %d. `%s`:\n' "$$ii" "$$paths" "$$path" mkdir -p "$$(dirname $(tests-output)/"$$path")" viewports=$$(yq '.viewports | length' $(tests)/meta.yaml) for jj in $$(seq 1 $$viewports); do j=$$((jj - 1)) name=$$(yq ".viewports[$$j].name" $(tests)/meta.yaml) width=$$(yq ".viewports[$$j].width" $(tests)/meta.yaml) printf ' Viewport #%d of %d: `%s` (width: %d).\n' "$$jj" "$$viewports" "$$name" "$$width" output_path="$$path"."$$width".png firefox_output=$$( tests/take-screenshot \ file://$$PWD/$(website-output)/"$$path" \ $(tests-output)/"$$output_path" \ $$width \ 2>&1 ) && true if [ -e $(tests-output)/"$$output_path" ]; then chmod 644 $(tests-output)/"$$output_path" else unexpected_failures=$$((unexpected_failures + 1)) printf ' => \e[1;31munexpected failure while taking screenshot\e[0m.\n' printf ' Here is the output from Firefox:\n' printf '\n\e[37m%s\e[0m\n\n' "$$firefox_output" | sed 's|^\(.*\)| \1|' continue fi if ! [ -e $(tests)/outputs/"$$output_path" ]; then dissimilarities=$$((dissimilarities + 1)) printf ' => \e[31mno reference to compare to\e[0m.\n' continue fi diff_path="$$path"."$$width".diff.png compare_output=$$( compare -compose src -metric AE -format '' \ $(tests)/outputs/"$$output_path" $(tests-output)/"$$output_path" \ $(tests-output)/"$$diff_path" \ 2>&1 ) && true return_code=$$? if [ $$return_code -eq 1 ]; then dissimilarities=$$((dissimilarities + 1)) printf ' => \e[31mdissimilarity\e[0m.\n' elif [ $$return_code -ge 2 ]; then unexpected_failures=$$((unexpected_failures + 1)) printf ' => \e[1;31munexpected failure while comparing\e[0m.\n' printf ' Here is the output from ImageMagick:\n' printf '\n\e[37m%s\e[0m\n\n' "$$compare_output" | sed 's|^\(.*\)| \1|' fi done done if [ $$unexpected_failures -gt 0 ]; then printf 'There were \e[1;31m%d unexpected failures\e[0m.\n' "$$unexpected_failures" exit 2 fi if [ $$dissimilarities -gt 0 ]; then printf 'There were \e[31m%d dissimilarities\e[0m.\n' "$$dissimilarities" exit 1 fi .PHONY: promote-test-outputs promote-test-outputs: if ! [ -d $(tests-output) ]; then printf 'The tests need to have been run before being promoted.\n' exit 7 fi printf 'Promoting all the tests outputs.\n' paths=$$(yq '.paths | length' $(tests)/meta.yaml) for ii in $$(seq 1 $$paths); do i=$$((ii - 1)) path=$$(yq ".paths[$$i]" $(tests)/meta.yaml) printf ' Path #%d of %d. `%s`:\n' "$$ii" "$$paths" "$$path" mkdir -p "$$(dirname $(tests)/outputs/"$$path")" viewports=$$(yq '.viewports | length' $(tests)/meta.yaml) for jj in $$(seq 1 $$viewports); do j=$$((jj - 1)) name=$$(yq ".viewports[$$j].name" $(tests)/meta.yaml) width=$$(yq ".viewports[$$j].width" $(tests)/meta.yaml) printf ' Viewport #%d of %d: `%s` (%d).\n' "$$jj" "$$viewports" "$$name" "$$width" output_path="$$path"."$$width".png cp $(tests-output)/"$$output_path" $(tests)/outputs/"$$output_path" done done