Skip to content

Commit

Permalink
Merge pull request #448 from pep-dortmund/example-report-exercise-steps
Browse files Browse the repository at this point in the history
WIP: Example report exercise steps
  • Loading branch information
chrbeckm authored Oct 4, 2024
2 parents 97b77fd + e094b42 commit ae302c7
Show file tree
Hide file tree
Showing 42 changed files with 2,989 additions and 0 deletions.
213 changes: 213 additions & 0 deletions exercises-toolbox/8-all/example-report/Makefile-loesung
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
datafilenames = Messwerte_Bahn.txt \
Messwerte_Frames_Kugel.txt \
Messwerte_Frames_Zylinder.txt \
Messwerte_Kamera.txt \
Messwerte_Kugel.txt \
Messwerte_Zylinder.txt

advanced_mpl_filenames = matplotlibrc header-matplotlib.tex

steps = 01 02 03 04 05 06 07 08 09 10 11

### Directories

# build/example-report-step-{01,...,11}/
step_dirs = $(foreach step, $(steps),build/example-report-step-$(step)/)

# build/example-report-step-{01,...,11}/loesung
solution_dirs = $(addsuffix loesung/, $(step_dirs))


# build/example-report-step-{06,...,11}/loesung/v16516/
report_dirs = $(addsuffix v16516/, $(wordlist 6,11,$(solution_dirs)))

# build/example-report-step-{01,05}/data/
step_data_dirs = $(addsuffix data/, $(word 1,$(step_dirs)) $(word 5,$(step_dirs)))

# build/example-report-step-{01,...,05}/loesung/data/
solution_data_dirs = $(addsuffix data/, $(wordlist 1,5,$(solution_dirs)))

# build/example-report-step-{06,...,11}/loesung/v16516/data
report_data_dirs = $(addsuffix data/, $(report_dirs))

# all */data dirs
data_dirs = $(solution_data_dirs) $(report_data_dirs) $(step_data_dirs)
data_dirs_without_unc = $(wordlist 1,4,$(data_dirs)) $(word 12,$(data_dirs))
data_dirs_with_unc = $(wordlist 5,11,$(data_dirs)) $(word 13,$(data_dirs))

# build/example-report-step-{09,...,10}/loesung/v16516/graphics/
graphics_dirs = $(addsuffix v16516/graphics/, $(wordlist 9,10,$(solution_dirs))) $(addsuffix graphics/, $(word 9,$(step_dirs)))

# solution_dirs containing tex files
solution_dirs_with_tex = $(wordlist 7,11,$(solution_dirs))
#build/example-report-step-{07,...,11}/loesung/v16516/content/
content_dirs = $(addsuffix v16516/content/, $(solution_dirs_with_tex))

### Taskfiles

task_files = $(addsuffix aufgabe.txt,$(step_dirs))

### Datafiles

append_datafilenames = $(foreach filename,$(datafilenames),$(addsuffix $(filename), $(1)))

data_files_without_unc = $(call append_datafilenames,$(data_dirs_without_unc))
data_files_with_unc = $(call append_datafilenames, $(data_dirs_with_unc))
data_files = $(data_files_without_unc) $(data_files_with_unc)

### Python files
solution_python_files = $(addsuffix auswertung.py, $(wordlist 1,5,$(solution_dirs)))
report_python_files = $(addsuffix auswertung.py, $(report_dirs))
python_files = $(solution_python_files) $(report_python_files)

### LaTeX Files
header_tex_files=$(addsuffix header.tex, $(solution_dirs_with_tex))
main_tex_files= $(addsuffix v16516/v16516.tex, $(solution_dirs_with_tex))
implementation_tex_files=$(addsuffix durchfuehrung.tex, $(content_dirs))
theory_tex_files=$(addsuffix theorie.tex, $(content_dirs))
evaluation_tex_files=$(addsuffix auswertung.tex, $(content_dirs))
discussion_tex_files=$(addsuffix diskussion.tex, $(content_dirs))

setup_image_files = $(addsuffix versuchsaufbau.png, $(graphics_dirs))

lit_bib_files = $(addsuffix lit.bib, $(wordlist 9,11,$(solution_dirs)) $(word 9,$(step_dirs)))
programms_bib_files = $(addsuffix programme.bib, $(wordlist 9,11,$(solution_dirs)) $(word 9,$(step_dirs)))
bib_files = $(lit_bib_files) $(programms_bib_files)

matplotlibrc_files= $(addsuffix matplotlibrc, $(wordlist 10,11,$(solution_dirs)) $(word 10,$(step_dirs)))
header_matplotlib_files = $(addsuffix header-matplotlib.tex, $(wordlist 10,11,$(solution_dirs)) $(word 10,$(step_dirs)))

advanced_mpl_files = $(matplotlibrc_files) $(header_matplotlib_files)

tex_files = $(header_tex_files) \
$(main_tex_files) \
$(implementation_tex_files) \
$(theory_tex_files) \
$(evaluation_tex_files) \
$(discussion_tex_files) \
$(bib_files) \
$(advanced_mpl_files)

### Makefiles
solution_makefile_files = $(addsuffix Makefile-loesung, $(wordlist 2,5,$(solution_dirs))) \
$(addsuffix v16516/Makefile-loesung, $(wordlist 6,9,$(solution_dirs)))

target_makefile_files = $(addsuffix v16516/Makefile, $(wordlist 10,11,$(solution_dirs)))
makefile_files = $(solution_makefile_files) $(target_makefile_files)

python_libs = $(addprefix build/example-report-step-11/loesung/v16516/, curve_fit.py latex_formatting.py)

run: all
# Run all Makefile-loesung
set -- $(wordlist 1, 5,$(solution_dirs)); \
for i do\
make -C $$i -f Makefile-loesung; \
done
# Run all Makefile-loesung in the additional subdir v16516
set -- $(addsuffix v16516/, $(wordlist 6, 9,$(solution_dirs))); \
for i do\
make -C $$i -f Makefile-loesung; \
done
# Run the Makefile in the last two steps
set -- $(addsuffix v16516/, $(wordlist 10, 11,$(solution_dirs))); \
for i do\
make -C $$i -f Makefile; \
done
# copy the final report into the first step_dir
cp $(addsuffix v16516/build/v16516.pdf,$(word 11, $(solution_dirs))) $(addsuffix v16516.pdf,$(word 1, $(solution_dirs)))


all: $(task_files) \
$(python_files) \
$(data_files) \
$(tex_files) \
$(setup_image_files) \
$(makefile_files)\
$(python_libs)

$(word 1, $(python_libs)): templates/curve_fit.py | $(report_dirs)
cp $< $@

$(word 2, $(python_libs)): templates/latex_formatting.py | $(report_dirs)
cp $< $@


build/example-report-step-%/aufgabe.txt &: $(template_task_files) | $(step_dirs)
cp templates/aufgabe-step-$*.txt build/example-report-step-$*/aufgabe.txt

copy_file_loop=set -- $(2); for i do cp $(1) $$i; done

$(solution_makefile_files) &: templates/Makefile-loesung_template | $(solution_dirs) $(report_dirs)
python generate-step-files.py -t $< -s 2 $(addprefix -o=,$(solution_makefile_files))

$(target_makefile_files) &: templates/Makefile | $(report_dirs)
$(call copy_file_loop, $<, $(target_makefile_files))

$(matplotlibrc_files) &: templates/matplotlibrc | $(solution_dirs)
$(call copy_file_loop, $<,$(matplotlibrc_files))

$(programms_bib_files) &: templates/programme.bib | $(solution_dirs)
$(call copy_file_loop, $<, $(programms_bib_files))

$(lit_bib_files) &: templates/lit.bib | $(solution_dirs)
$(call copy_file_loop, $<, $(lit_bib_files))

$(setup_image_files) &: templates/versuchsaufbau.png | $(graphics_dirs)
$(call copy_file_loop, $<, $(setup_image_files))

$(header_matplotlib_files) &: templates/header-matplotlib.tex | $(solution_dirs)
$(call copy_file_loop, $<, $(header_matplotlib_files))

$(header_tex_files) &: templates/latex/header_tex_template | $(solution_dirs)
python generate-step-files.py -t $< -s 7 $(addprefix -o=,$(header_tex_files))

$(main_tex_files) &: templates/latex/v16516_tex_template | $(content_dirs)
python generate-step-files.py -t $< -s 7 $(addprefix -o=,$(main_tex_files))

$(theory_tex_files) &: templates/latex/theorie_tex_template | $(content_dirs)
python generate-step-files.py -t $< -s 7 $(addprefix -o=,$(theory_tex_files))

$(implementation_tex_files) &: templates/latex/durchfuehrung_tex_template | $(content_dirs)
python generate-step-files.py -t $< -s 7 $(addprefix -o=,$(implementation_tex_files))

$(evaluation_tex_files) &: templates/latex/auswertung_tex_template | $(content_dirs)
python generate-step-files.py -t $< -s 7 $(addprefix -o=,$(evaluation_tex_files))

$(discussion_tex_files) &: templates/latex/diskussion_tex_template | $(content_dirs)
python generate-step-files.py -t $< -s 7 $(addprefix -o=,$(discussion_tex_files))

$(python_files) &: templates/auswertung_py_template | $(solution_dirs) $(report_dirs)
touch build/example-report-step-01/loesung/auswertung.py
python generate-step-files.py -t templates/auswertung_py_template $(addprefix -o=,$(python_files))


copy_dir_loop=set -- $(2); for i do cp -r $(1) $$i; done
$(data_files_without_unc) &: templates/data_without_uncertainties/* | $(solution_dirs) $(report_dirs)
$(call copy_dir_loop, templates/data_without_uncertainties, $(data_dirs_without_unc))

$(data_files_with_unc) &: templates/data_with_uncertainties/* | $(solution_dirs) $(report_dirs)
$(call copy_dir_loop, templates/data_with_uncertainties, $(data_dirs_with_unc))

$(content_dirs) &:|$(report_dirs)
mkdir -p $(content_dirs)

$(graphics_dirs)&: |$(report_dirs)
mkdir -p $(graphics_dirs)

$(report_dirs)&: | $(solution_dirs)
mkdir -p $(report_dirs)

$(solution_dirs) &: | $(step_dirs)
mkdir -p $(solution_dirs)

$(step_dirs): | build
mkdir -p $(step_dirs)

build:
mkdir -p build

clean:
rm -r build

.PHONY: all clean

151 changes: 151 additions & 0 deletions exercises-toolbox/8-all/example-report/generate-step-files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import argparse
import re
import sys
from collections import defaultdict
from dataclasses import dataclass
from collections.abc import Iterable

STEPRANGEREGEX = re.compile(r"(?:#|%)\s*<(\d*)(-(\d*)|)>")

@dataclass
class Templateline:
linenumber: int
line: str
output_file_indices: Iterable
pattern: str

def __str__(self):
pattern_length = len(self.pattern)
indicies = f"{self.output_file_indices[0]},...,{self.output_file_indices[-1]}"
# magic number 10: the amount of padding necessary to print a pattern with two two-digit numbers
return f"({self.pattern} → [{indicies}]) {" "*(10-pattern_length)} {self.linenumber:>3} {self.line}"


def setup_arg_parser():
parser = argparse.ArgumentParser(
prog="generate-step-files",
description="Generate multiple (step)files containing lines from a template file",
epilog='')

parser.add_argument('-v', '--verbose', action='store_true',
help="Get info on parsed args, the step maximum steprange in the template and to be removed lines.")
parser.add_argument('-n', '--dry-run', action='store_true',
help="Print the content of each output file to stdout instead of generating any files.")
# This option allows to use the same step numbering in differnt independent files
parser.add_argument('-s', '--start-step', type=int, default=1,
help="Set the step that should be considered step 1 for this file")
parser.add_argument('-t','--template_filepath', dest='template_filepath', required=True,
help="Filepath to the template file.")
parser.add_argument('-o', '--output_filepaths', dest='output_filepaths', action="append", required=True,
help="Filepaths to the output files (you need to use one Option -o <file> for each file). Hint: use -o=file{1,2,3} for example, to generate the multiple options.)")
return parser

def parse_lines_and_stepranges(lines, start_step, end_step):
template_lines = []
removed_lines = []
for i,l in enumerate(lines, start=1):
found = STEPRANGEREGEX.search(l)

# a line containing no pattern will not be saved to any output file, hence 'removed'
if found is None:
removed_lines.append(Templateline(i,l, [], ""))
continue
groups = found.groups()

try:
lower_step_limit = int(groups[0])
except ValueError:
raise ValueError(f"The pattern ({found.group()}) in line {i} of the template file has not starting step.")

# Since the end_step is calculated from the number of output files given
# the lower index has to be changed to be less then the upper index
lower_index = lower_step_limit - start_step
if lower_index < 0:
raise ValueError(f"The given star_step {start_step} is higher then the lower limit in the pattern '{found.group()}' in line {i} of the template file.")

# The dash and second number can be omitted:
# e.g. <4> is equivalent to <4-4>
if (not groups[1]) and (groups[2] is None):
# To include anything the upper index has to be increased by 1
step_limits = (lower_index, lower_index + 1)

# If the second number is omitted but the dash is not, the upper limit is the last step:
# e.g. <4-> is equivalent to <4-10> if 10 output files are generated
elif not groups[2]:
# the number of files given is used for the upper index (it is already 1 greater then the highest possible index)
upper_index = end_step
step_limits = (lower_index, upper_index)

# both numbers are present in the pattern
else:
upper_step_limit = int(groups[2])
upper_index = upper_step_limit - start_step + 1

step_limits = (lower_index, upper_index)

num_files_with_current_line = (step_limits[1] - step_limits[0])
if num_files_with_current_line > end_step:
raise ValueError(f"The number of given output files is {end_step},"
f" but line {i} of the template file is expected to appear in {num_files_with_current_line} output files (pattern: {found.group()}).")

# remove the steprange pattern and spaces between line content and pattern
line_content = STEPRANGEREGEX.sub("", l).rstrip()
template_lines.append(Templateline(i, line_content, list(range(*step_limits)), found.group()))

return template_lines, removed_lines


def split_stepfile_lines(template_lines, output_filepaths):

lines_per_stepfile = defaultdict(list)
for line in template_lines:
for step in line.output_file_indices:
lines_per_stepfile[output_filepaths[step]].append(line)

return lines_per_stepfile


def main():
parser = setup_arg_parser()
args = parser.parse_args()

template_filepath = args.template_filepath
output_filepaths = args.output_filepaths


if args.verbose:
print("Input:")
print(' '.join(sys.argv))
print("Parsed:")
print(f" - template file: {template_filepath}")
print(f" - output files: {output_filepaths}")
print(f" - start step: {args.start_step}")


with open(template_filepath) as fh:
template_lines = fh.readlines()

template_lines, removed_lines = parse_lines_and_stepranges(template_lines, args.start_step, len(output_filepaths))

lines_per_stepfiles = split_stepfile_lines(template_lines, output_filepaths)

if args.verbose:
print("Removed lines:")
print(f"{'\n'.join(str(rl) for rl in removed_lines)}")
print(f"Found steps: {lines_per_stepfiles.keys()}")


if args.dry_run:
for stepfile, lines in lines_per_stepfiles.items():
print(f"\nOutput file '{stepfile}' with index ({output_filepaths.index(stepfile)}) would contain these lines from the template file:")
print(f"pattern → indicies{" "*9}ln line")
print(f"{"\n".join(str(l) for l in lines)}")
return


for stepfile, lines in lines_per_stepfiles.items():
with open(stepfile, "w") as fh:
fh.write("\n".join(l.line for l in lines).strip())

if __name__ == "__main__":
main()
33 changes: 33 additions & 0 deletions exercises-toolbox/8-all/example-report/templates/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
all: build/v16516.pdf

# hier Python-Skripte:
build/plot-I_kugel.pdf build/plot-I_zylinder.pdf build/plot-g_kugel.pdf build/plot-g_zylinder.pdf: auswertung.py ../matplotlibrc ../header-matplotlib.tex | build
# so that matplotlib can find the tex header when running
# LaTeX in the tmp directory
# and set the matplotlibrc
TEXINPUTS=$$(pwd)/..: MATPLOTLIBRC=../matplotlibrc python auswertung.py

# hier weitere Abhängigkeiten für build/vXXX.pdf deklarieren:
build/v16516.pdf: build/plot-I_kugel.pdf build/plot-I_zylinder.pdf build/plot-g_kugel.pdf build/plot-g_zylinder.pdf

build/v16516.pdf: FORCE | build
# to find header and bib files in the main directory
TEXINPUTS=..: \
BIBINPUTS=..: \
max_print_line=1048576 \
latexmk \
--lualatex \
--output-directory=build \
--interaction=nonstopmode \
--halt-on-error \
v16516.tex

build:
mkdir -p build

clean:
rm -rf build

FORCE:

.PHONY: all clean
Loading

0 comments on commit ae302c7

Please sign in to comment.