Raw File
Makefile
################################################################################
##   __  __      _        __ _ _
##  |  \/  |__ _| |_____ / _(_) |___
##  | |\/| / _` | / / -_)  _| | / -_)
##  |_|  |_\__,_|_\_\___|_| |_|_\___|

## 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'
		mkdir -p $$(dirname $@) ## FIXME: this is a bit dirty
	  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'
	  mkdir -p $$(dirname $@) ## FIXME: this is a bit dirty
	  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
back to top