#!/bin/zsh setopt extended_glob setopt glob_star_short local script="${0}" errf() { >&2 printf "${@}" } local errored="no" local invalid_options=() log_invalidf() { errored="yes" invalid_options+=(${2}) errf "${@}" } tracef() { [[ -n ${TRACE} ]] && errf "TRACE: ${@}" } expect_directory() { option=${1} value="${2}" expect_value ${option} "${value}" && if [[ ! -d "${value}" ]]; then log_invalidf 'WARN: option: %s: requires directory, got: %s\n' ${option} "${value}" errf ' file %s\n' "$(file ${value})" errf 'WARN: ignoring %s option and continuing.\n' ${option} return 1 fi } expect_value() { option=${1} if [[ ${#} -lt 1 ]]; then log_invalidf 'option: unexpected end of flags: no value given for: %s\n' ${option} continue fi value="${2}" if [[ "${value}" =~ -- ]]; then log_invalidf 'option: %s requires value, got other option: %s\n' ${option} ${value} return 1 fi if [[ -z "${value}" ]]; then log_invalidf 'ERROR: option: value is empty for: %s\n' ${option} shift 1 return 1 fi } generate_build_script() { local sources local strip_prefix local outdir while [[ ${1} =~ -- ]]; do option=${1} shift 1 case ${option} in "--strip-prefix") if expect_value ${option} "${1}"; then tracef 'option: strip-prefix="%s"\n' "${1}" strip_prefix="${1}" fi shift 1 ;; "--sources") if expect_value ${option} "${1}"; then tracef 'option: sources=(%s)\n' "${1}" sources=(${~1}) fi shift 1 ;; "--outdir") mkdir --parents "${1}" if expect_directory ${option} "${1}"; then tracef 'option: outdir="%s"\n' "${1}" outdir="${1}" fi shift 1 ;; *) log_invalidf 'ERROR: option: unrecognized option: %s\n' ${option} if [[ -n "${1}" && ! (${1} =~ --) ]]; then errf 'ERROR: option: discarding likely argument of unrecognized option (%s): "%s"\n' ${option} "${1}" shift 1 fi ;; esac done if [[ ${#invalid_options} -gt 0 ]]; then errf 'WARN: some option were invalid: (%s)\n' "${invalid_options[*]}" fi if [[ ${errored} == "yes" ]]; then errf 'WARN: some error(s) occurred parsing options, see above.\n' return 1 fi local sources_json="[\"${(j:",":)sources}\"]" cat > .build.tmp.mjs <<EOF import * as fs from 'node:fs/promises' import * as path from 'node:path' import pug from 'pug' function ms_time_diff(start, end) { const us = Number(BigInt.asUintN(64, (end - start) / 1000n)) return us / 1000 } function format_ms(number) { return number.toFixed(3).toString().padStart(8, ' ') } const sources = ${sources_json} const longest_source_length = Math.max.apply(Math, sources.map(s => s.length)) const strip_prefix = "${strip_prefix}" const total_start = process.hrtime.bigint() for (const source of sources) { const source_directory = path.dirname(source) const source_basename = path.basename(source, ".pug") const source_base_index = source_directory.indexOf(strip_prefix) const source_base_path = source_directory.substring(source_base_index + strip_prefix.length) const output_directory = path.join("${outdir}", source_base_path) const output_file_name = \`\${source_basename}.html\` const output_file = path.join(output_directory, output_file_name) const start = process.hrtime.bigint() const html = pug.compileFile(source)({ /* locals */ }) await fs.mkdir(output_directory, { recursive: true }) await fs.writeFile(output_file, html) const end = process.hrtime.bigint() console.log('' + \`+ \${format_ms(ms_time_diff(start, end))}ms\` + \` \${source.padEnd(longest_source_length, ' ')}\` + \` → \${output_file}\` ) } const total_end = process.hrtime.bigint() const total_diff = ms_time_diff(total_start, total_end) console.log(\`σ \${format_ms(total_diff)}ms (internal) total compile time\`) console.log(\`Σ \${format_ms(process.uptime() * 1000)}ms (process) total lifetime\`) EOF } errf 'generating build script...\n' TIMEFMT="TIME: %P cpu for %mE: %J" time (generate_build_script "${@}") if [[ $? -ne 0 ]]; then errf 'FATAL: failed to generate build script!\n' exit 1 fi errf 'invoking generated build script...\n' time node .build.tmp.mjs rm .build.tmp.mjs