Merge branch 'master' of ssh://git.betalupi.com:33/Mark/ormc-handouts

This commit is contained in:
Mark 2023-10-12 08:44:22 -07:00
commit 204ee9068e
Signed by: Mark
GPG Key ID: AD62BB059C2AAEE4
9 changed files with 135 additions and 264 deletions

6
.gitignore vendored
View File

@ -4,13 +4,11 @@ __pycache__
*-ignore
# Output files
/output
/output.zip
main.pdf
**/build
# CI files
/_build
/_output
# TeX build files
*.synctex.gz*
*.latexmk

View File

@ -8,7 +8,7 @@ Create a multiplication table for $\mathbb{Z}_4$:
\begin{center}
\begin{tabular}{c | c c c c}
\times & 0 & 1 & 2 & 3 \\
$\times$ & 0 & 1 & 2 & 3 \\
\hline
0 & ? & ? & ? & ? \\
1 & ? & ? & ? & ? \\

View File

@ -220,10 +220,10 @@
Show that for any two functions $g: Y \to W$ and $h: Y \to W$, if
$g \circ f = h \circ f \implies g = h$.
\item[\star] Let $f: X \to Y$ be a function where for any set $Z$ and functions $g: Z \to X$ and $h: Z \to X$,
\item[$\star$] Let $f: X \to Y$ be a function where for any set $Z$ and functions $g: Z \to X$ and $h: Z \to X$,
$f \circ g = f \circ h \implies g = h$. Show that $f$ is injective.
\item[\star] Let $f: X \to Y$ be a function where for any set $W$ and functions $g: Y \to W$ and $h: Y \to W$,
\item[$\star$] Let $f: X \to Y$ be a function where for any set $W$ and functions $g: Y \to W$ and $h: Y \to W$,
$g \circ f = h \circ f \implies g = h$. Show f is surjective.
\end{itemize}

View File

@ -1,7 +1,7 @@
# ORMC Handouts
This repository contains all the handouts I've written for the [ORMC](https://circles.math.ucla.edu/circles/). \
You can find the latest PDFs [here](https://nc.betalupi.com/s/ormc-handouts).
You can find the latest PDFs [here](https://static.betalupi.com/ormc).
**For my students:** Handouts will appear here a few days before class. Please don't look at them (or their solutions) beforehand, that spoils all the fun!
@ -21,7 +21,7 @@ Grade levels are estimates.
## 🛠️ Building these Handouts
Automatic builds are [here](https://nc.betalupi.com/s/ormc-handouts), if you just want the PDFs.
Automatic builds are [here](https://static.betalupi.com/ormc), if you just want the PDFs.
If you want to edit these files, you'll need to download a custom document class. \
To compile one handout, do the following:

128
build.sh Executable file
View File

@ -0,0 +1,128 @@
#!/usr/bin/env bash
#
# This script builds every document in this repo,
# (handout and solutions, if they exist), and creates
# a zip of all resulting files.
# Where we're running this script
run_dir=$(pwd)
# Output files
target_dir="${run_dir}/output"
target_zip="${run_dir}/output.zip"
# Clean up previous build
rm -drf "${target_dir}"
rm -f "${target_zip}"
# Build one document.
# Args: <target_dir> <job_name> <doc_dir> <main_file>
# target_dir: move output pdf to this directory
# job_name: name of this document. Output is saved as job_name.pdf
# doc_dir: cd here before building.
# main_file: build this tex file. Usually main.tex
function build() {
local target_dir="${1}"
local job_name="${2}"
local doc_dir="${3}"
local main_file="${4}"
echo "|> Building ${job_name}..."
cd "${doc_dir}"
# Build handout
echo "\\def\\argNoSolutions{1}\\input{${main_file}}" | \
tectonic \
--outfmt pdf \
--chatter minimal \
-
stat=$?
if [[ $stat == 0 ]]; then
mkdir -p "${target_dir}"
mv texput.pdf "${target_dir}/${job_name}.pdf"
else
rmdir --ignore-fail-on-non-empty "${target_dir}"
rm -f texput.pdf
echo "|> Handout build failed"
echo ""
fi
# Build solutions
echo "\\def\\argYesSolutions{1}\\input{${main_file}}" | \
tectonic \
--outfmt pdf \
--chatter minimal \
-
stat=$?
if [[ $stat == 0 ]]; then
mkdir -p "${target_dir}"
mv texput.pdf "${target_dir}/${job_name}.sols.pdf"
else
rmdir --ignore-fail-on-non-empty "${target_dir}"
rm -f texput.pdf
echo "|> Solution build failed"
fi
# Clean up if files contents are identical
if cmp --silent -- "${target_dir}/${job_name}.sols.pdf" "${target_dir}/${job_name}.pdf"; then
echo "|> Versions identical, removing ${job_name}.sols.pdf"
rm "${target_dir}/${job_name}.sols.pdf"
fi
echo ""
cd "${run_dir}"
}
for d in "${run_dir}/Misc/Warm-Ups"/*.tex ; do
file="$(basename "${d}")"
build \
"${target_dir}/Warm-Ups" \
"${file%.*}" \
"${run_dir}/Misc/Warm-Ups" \
"${file}"
done
for d in "${run_dir}/Advanced"/*/ ; do
doc_dir=$(realpath "${d}")
job_name="$(basename "${doc_dir}")"
build \
"${target_dir}/Advanced" \
"${job_name}" \
"${doc_dir}" \
"main.tex"
done
for d in "${run_dir}/Intermediate"/*/ ; do
doc_dir=$(realpath "${d}")
job_name="$(basename "${doc_dir}")"
build \
"${target_dir}/Intermediate" \
"${job_name}" \
"${doc_dir}" \
"main.tex"
done
# cd so paths in zip are relative
cd "${target_dir}"
zip -FSr "${target_zip}" .

View File

@ -1,28 +0,0 @@
pipeline {
agent none
environment {
NC_CRED = credentials("nc-jenkins-cred")
NO_TEST = true
}
stages {
stage("Build") {
agent {
docker {
image "git.betalupi.com/mark/latex:latest"
}
}
steps {
// Keep these in one "sh" directive.
// Todo: create setup files so we don't
// have to pip install manually here.
// Maybe integrate python packages into the docker container?
sh '''
python -m venv venv
source venv/bin/activate
pip install tomli webdavclient3
python resources/scripts/build.py
'''
}
}
}
}

View File

@ -1,155 +0,0 @@
# Build script for CI.
# This file is run from the root of the repository
import os
import tomli
from pathlib import Path
import shutil
from webdav3.client import Client
import helpers.tex as tex
conf_path = "resources/scripts/build.toml"
# Prepare envvars
e = os.environ.copy()
e["TEXINPUTS"] = f"::/usr/share/texmf-dist/tex//"
print(Path.cwd())
# If true, rebuild everything
# without checking for changes.
# Currently unused.
if "FORCE_ALL" in e:
force_all = e["FORCE_ALL"] == "true"
else:
force_all = False
if "NO_TEST" in e:
test = e["NO_TEST"] == "false"
else:
test = True
# Used only for debug
class FakeClient:
def __init__(self):
pass
def mkdir(self, path):
print(f"[Dry run] Making dir {path}")
def upload_sync(self, local_path, remote_path):
print(f"[Dry run] Synced {local_path} to {remote_path}")
def clean(self, path):
print(f"[Dry run] Cleaning {path}")
# Load configuration
with open(conf_path, mode="rb") as fp:
config = tomli.load(fp)
# Prepare directories
builddir = Path().absolute() / config["core"]["build_dir"]
output = Path().absolute() / config["core"]["output_dir"]
output.mkdir(parents = True, exist_ok = True)
builddir.mkdir(parents = True, exist_ok = True)
if not test:
# Connect to webdav
client = Client({
"webdav_login": e["NC_CRED_USR"],
"webdav_password": e["NC_CRED_PSW"],
"webdav_hostname": e["DAV_HOSTNAME"], # https://host:port
"webdav_root": e["DAV_ROOT"] # the rest of the url
# client.list() will break if we do not seperate the hostname
# and the root path.
})
else:
client = FakeClient()
def upload_dir(local_path: Path):
for p in local_path.rglob("*"):
r = p.relative_to(output)
if p.is_dir():
client.mkdir(str(r))
elif p.is_file():
client.upload_sync( # type: ignore
remote_path = str(r),
local_path = p
)
# Print envvars for debug
#print(e)
for i, item in enumerate(config["dir-of-dirs"]):
p = Path(item["path"])
for s in p.iterdir():
# s: path to directory,
# # relative to repo root
if not s.is_dir():
continue
print(f"Building {s}/main.tex")
# Make pdf
pdf_nosolutions = tex.build(
s,
mainfile = "main.tex",
jobname = f"{i}-nosols",
build_dir = builddir,
solutions = False,
env = e,
test = test
)
pdf_solutions = tex.build(
s,
mainfile = "main.tex",
jobname = f"{i}-sols",
build_dir = builddir,
solutions = True,
env = e,
test = test
)
target_dir = output / s.parent
target_dir.mkdir(parents = True, exist_ok = True)
shutil.copy(
pdf_nosolutions,
target_dir / (s.stem + ".pdf")
)
shutil.copy(
pdf_solutions,
target_dir / (s.stem + ".sols.pdf")
)
# TODO: this breaks when the output directory is empty.
# Fix it eventually.
if not test:
# Delete old files
for i in client.list():
if i.endswith("/"):
i = i[:-1]
client.clean(i)
upload_dir(output)
# Remove output files so they don't persist
# shutil.rmtree(output)
# Shouldn't be necessary if you tell
# jenkins to "clean before checkout."

View File

@ -1,12 +0,0 @@
# All paths are relative to the root of this repository.
[core]
build_dir = "_build"
output_dir = "_output"
[[dir-of-dirs]]
path = "Advanced"
[[dir-of-dirs]]
path = "Intermediate"

View File

@ -1,60 +0,0 @@
from pathlib import Path
import subprocess
def build(
path: Path,
*,
solutions: bool = True,
mainfile: str = "main.tex",
jobname: str = "main",
build_dir: Path = Path("build"),
test = False,
env: dict = {},
) -> Path:
"""
Build a directory using XeTeX.
Args:
path (Path): directory to build
solutions (bool, optional): Create a handout with solutions? Defaults to True.
mainfile (str, optional): Path to main TeX file, relative to path. Defaults to "main.tex".
jobname (str, optional): TeX job name. Defaults to "main".
build_dir (Path, optional): Where to place build files. Defaults to Path("build").
test (bool, optional): If true, dry run. Defaults to False.
env (dict): Dictionary of environment variables.
Returns:
Path: Absolute path to output pdf.
"""
# Make temporary file for TeX build arg hack
if solutions:
with (build_dir/"tmp.tex").open("w") as f:
f.write("\\def\\argYesSolutions{1}\\input{" + mainfile + "}")
else:
with (build_dir/"tmp.tex").open("w") as f:
f.write("\\def\\argNoSolutions{1}\\input{" + mainfile + "}")
cmd = subprocess.run([
"latexmk",
"-interaction=nonstopmode",
"-file-line-error",
f"-outdir={build_dir}",
"-xelatex",
f"-jobname={jobname}",
build_dir/"tmp.tex"
],
cwd = path,
env = env,
stdout = None if test else subprocess.DEVNULL,
stderr = None if test else subprocess.DEVNULL
)
assert cmd.returncode == 0
# Remove tmp file
(build_dir / "tmp.tex").unlink()
return build_dir / f"{jobname}.pdf"