Skip to content

Commit 9dfe5db

Browse files
committed
2 parents 144b45d + a6aa379 commit 9dfe5db

157 files changed

Lines changed: 23392 additions & 23240 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitattributes

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Auto detect text files and normalize to LF on checkin
2+
* text=auto eol=lf
3+
4+
# LaTeX files should always use LF line endings
5+
*.tex text eol=lf
6+
*.sty text eol=lf
7+
*.cls text eol=lf
8+
*.bib text eol=lf
9+
10+
# Python files should always use LF line endings
11+
*.py text eol=lf
12+
*.ipynb text eol=lf
13+
14+
# YAML files should always use LF line endings
15+
*.yml text eol=lf
16+
*.yaml text eol=lf
17+
18+
# Markdown files should always use LF line endings
19+
*.md text eol=lf
20+
21+
# Binary files
22+
*.pdf binary
23+
*.png binary
24+
*.jpg binary
25+
*.jpeg binary
26+
*.gif binary
27+
*.ico binary
28+
*.zip binary
29+
*.gz binary

.github/workflows/buildPDFs.yml

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,109 @@
1-
name: Build LaTeX PDFs
1+
name: LaTeX + Python Check
22
on:
3-
push:
3+
pull_request:
4+
branches:
5+
- main
46
paths:
5-
- '**.tex'
7+
- '**.tex'
8+
- '**.sty'
9+
- '**.cls'
10+
- '**.py'
11+
- '**.ipynb'
12+
- '**.yml'
13+
push:
14+
branches:
15+
- main
16+
paths:
17+
- '**.tex'
18+
- '**.sty'
19+
- '**.cls'
20+
- '**.py'
21+
- '**.ipynb'
22+
- '**.yml'
623
workflow_dispatch:
724

825
jobs:
26+
format-check:
27+
runs-on: ubuntu-latest
28+
steps:
29+
- name: Set up Git repository
30+
uses: actions/checkout@v4
31+
32+
- name: Initialize and update submodules
33+
run: |
34+
git submodule init
35+
git submodule update
36+
37+
- name: Install latexindent binary
38+
run: |
39+
set -e
40+
41+
curl -L -o latexindent-linux \
42+
https://github.com/cmhughes/latexindent.pl/releases/latest/download/latexindent-linux
43+
chmod +x latexindent-linux
44+
sudo mv latexindent-linux /usr/local/bin/latexindent
45+
echo "latexindent installed at:"
46+
which latexindent || echo "latexindent not found on PATH"
47+
48+
echo "latexindent verbose version info:"
49+
latexindent -vv
50+
51+
- name: Check LaTeX formatting with latexindent (dry-run)
52+
run: |
53+
set -e
54+
tmp_file="tmp_check_file"
55+
for ext in tex sty cls; do
56+
find . -name "*.${ext}" -type f -not -name "$tmp_file" | while read -r file; do
57+
latexindent -m -l=./course_template/.latexindent.yml -o="$tmp_file" "$file"
58+
if ! diff -qZb "$file" "$tmp_file.${ext}" > /dev/null; then
59+
echo "::error file=$file::File is not properly formatted. Please run latexindent locally."
60+
rm -f "$tmp_file.${ext}"
61+
exit 1
62+
fi
63+
rm -f "$tmp_file.${ext}"
64+
done || exit 1
65+
done
66+
echo "All LaTeX files are properly formatted!"
67+
68+
- name: Install Python format tools
69+
run: |
70+
set -e
71+
python -m pip install --upgrade pip
72+
pip install ruff
73+
74+
- name: Check Python + Notebook formatting (Ruff)
75+
run: |
76+
set -e
77+
78+
echo "Checking .py files formatting..."
79+
PY_FAILED=0
80+
for file in $(find . -name "*.py" -not -path "*/.*"); do
81+
if ! ruff format --check --config ./course_template/pyproject.toml "$file"; then
82+
echo "::error file=$file::Formatting check failed for $file."
83+
PY_FAILED=1
84+
fi
85+
done
86+
87+
echo "Checking .ipynb files formatting..."
88+
NB_FAILED=0
89+
for file in $(find . -name "*.ipynb" -not -path "*/.*"); do
90+
if ! ruff format --check --config ./course_template/pyproject.toml "$file"; then
91+
echo "::error file=$file::Formatting check failed for $file."
92+
echo "TIP: There is likely a trailing newline at the end of a code cell."
93+
ruff format --diff --config ./course_template/pyproject.toml "$file"
94+
NB_FAILED=1
95+
fi
96+
done
97+
98+
if [ $PY_FAILED -ne 0 ] || [ $NB_FAILED -ne 0 ]; then
99+
echo "Formatting check failed. You can check by running 'ruff format .' locally."
100+
exit 1
101+
fi
102+
103+
echo "All files passed formatting!"
104+
9105
build_latex:
106+
needs: format-check
10107
runs-on: ubuntu-latest
11108
steps:
12109
- name: Set up Git repository
@@ -45,6 +142,7 @@ jobs:
45142

46143
deploy:
47144
needs: build_latex
145+
if: ${{ github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }}
48146

49147
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
50148
permissions:

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,4 +321,4 @@ simulations/*/*backups*
321321
*.ist
322322
*.equ
323323
*.eqn
324-
*.vscode
324+
*.ruff_cache

.vscode/extensions.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"recommendations": [
3+
"James-Yu.latex-workshop",
4+
"charliermarsh.ruff"
5+
]
6+
}

.vscode/settings.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"latex-workshop.formatting.latexindent.path": "latexindent",
3+
"latex-workshop.formatting.latexindent.args": [
4+
"-m",
5+
"-l=%WORKSPACE_FOLDER%/course_template/.latexindent.yml",
6+
"%TMPFILE%",
7+
"-o=%TMPFILE%"
8+
],
9+
"editor.formatOnSave": true,
10+
"latex-workshop.formatting.latex": "latexindent",
11+
"ruff.configuration": "./course_template/pyproject.toml",
12+
"[python]": {
13+
"editor.formatOnSave": true,
14+
"editor.codeActionsOnSave": {
15+
"source.organizeImports": "explicit"
16+
},
17+
"editor.defaultFormatter": "charliermarsh.ruff"
18+
},
19+
"notebook.formatOnSave.enabled": true,
20+
"notebook.codeActionsOnSave": {
21+
"notebook.source.organizeImports": "explicit"
22+
}
23+
}

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,12 @@ The covered topics are:
5858

5959
![simulation buck converter](https://github.com/user-attachments/assets/2f699cac-79b3-4c94-8332-6910e940948d)
6060

61+
# Contributing
6162

63+
- We recommend using [VSCode](https://code.visualstudio.com/) as code editor.
64+
- [Ruff](https://github.com/astral-sh/ruff) should be installed.
65+
66+
```bash
67+
pip install ruff
68+
```
69+
- It is expected that [LaTeX Workshop](https://marketplace.visualstudio.com/items?itemName=James-Yu.latex-workshop) and [Ruff](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff) extensions are installed so that formatting of the code is achieved automatically.

exam/build.py

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,99 @@
1-
import os
21
import fileinput
2+
import os
33
from subprocess import call
44

5-
call_pdflatex_l = ['pdflatex', '-synctex=1',
6-
'-interaction=nonstopmode', 'main.tex']
5+
call_pdflatex_l = ["pdflatex", "-synctex=1", "-interaction=nonstopmode", "main.tex"]
6+
77

88
def clear_tex_binaries():
99
# Clean up auxiliary LaTeX files
10-
for file in os.listdir('.'):
11-
if file.startswith('main'):
12-
if not file.endswith(('.tex', '.pdf')):
10+
for file in os.listdir("."):
11+
if file.startswith("main"):
12+
if not file.endswith((".tex", ".pdf")):
1313
os.remove(file)
1414

15+
1516
def build_pdf(with_solution):
1617
# Modify and build the main.tex file
1718
clear_tex_binaries()
1819

1920
# Flag to ensure documentclass line is modified correctly
2021
docclass_modified = False
21-
22-
with fileinput.input('main.tex', inplace=True) as f:
22+
23+
with fileinput.input("main.tex", inplace=True) as f:
2324
for line in f:
24-
if 'includeonly{' in line:
25+
if "includeonly{" in line:
2526
# Comment out the includeonly flag
26-
print(f'%{line}', end='')
27+
print(f"%{line}", end="")
2728
# Look for the documentclass line
28-
elif '\\documentclass' in line and 'examClass' in line:
29+
elif "\\documentclass" in line and "examClass" in line:
2930
docclass_modified = True # Set the flag that we have modified the line
3031

3132
# Split documentclass into its components
32-
preamble, class_info = line.split('{', 1)
33-
class_name = class_info.rstrip('}\n') # Remove the trailing }
34-
33+
preamble, class_info = line.split("{", 1)
34+
class_name = class_info.rstrip("}\n") # Remove the trailing }
35+
3536
if with_solution:
3637
# Add the [solution] option if not present
37-
if '[' not in preamble:
38-
preamble = preamble.replace('\\documentclass', '\\documentclass[solution]')
38+
if "[" not in preamble:
39+
preamble = preamble.replace(
40+
"\\documentclass", "\\documentclass[solution]"
41+
)
3942
else:
40-
preamble = preamble.replace('[', '[solution, ')
43+
preamble = preamble.replace("[", "[solution, ")
4144
else:
4245
# Remove the [solution] option if present
43-
preamble = preamble.replace('[solution, ', '[').replace('[solution]', '')
46+
preamble = preamble.replace("[solution, ", "[").replace(
47+
"[solution]", ""
48+
)
4449

4550
# Reassemble the documentclass line
46-
print(f'{preamble}{{{class_name}}}', end='\n')
51+
print(f"{preamble}{{{class_name}}}", end="\n")
4752
else:
48-
print(line, end='')
49-
53+
print(line, end="")
54+
5055
# If we didn't modify the documentclass, raise an exception for debugging
5156
if not docclass_modified:
52-
raise ValueError("documentclass line with 'examClass' not found or not modified.")
57+
raise ValueError(
58+
"documentclass line with 'examClass' not found or not modified."
59+
)
5360

5461
# Run pdflatex twice for proper compilation
5562
call(call_pdflatex_l)
5663
call(call_pdflatex_l)
5764

65+
5866
def process_directory(dir_path):
5967
os.chdir(dir_path)
60-
68+
6169
# Check if main.tex exists
62-
if not os.path.exists('main.tex'):
63-
os.chdir('..') # Go back to the parent directory if no main.tex
70+
if not os.path.exists("main.tex"):
71+
os.chdir("..") # Go back to the parent directory if no main.tex
6472
return
65-
73+
6674
# Build with solutions
6775
build_pdf(with_solution=True)
68-
os.makedirs('../built', exist_ok=True)
69-
os.replace('main.pdf', os.path.join('../../built', f'{os.path.basename(dir_path)}_with_solution.pdf'))
70-
76+
os.makedirs("../built", exist_ok=True)
77+
os.replace(
78+
"main.pdf",
79+
os.path.join("../../built", f"{os.path.basename(dir_path)}_with_solution.pdf"),
80+
)
81+
7182
# Build without solutions
7283
build_pdf(with_solution=False)
73-
os.replace('main.pdf', os.path.join('../../built', f'{os.path.basename(dir_path)}.pdf'))
74-
75-
os.chdir('..')
84+
os.replace(
85+
"main.pdf", os.path.join("../../built", f"{os.path.basename(dir_path)}.pdf")
86+
)
87+
88+
os.chdir("..")
89+
7690

7791
def traverse_and_build(root_dir):
7892
for subdir, _, _ in os.walk(root_dir):
79-
if os.path.exists(os.path.join(subdir, 'main.tex')):
93+
if os.path.exists(os.path.join(subdir, "main.tex")):
8094
process_directory(subdir)
8195

96+
8297
if __name__ == "__main__":
8398
# Start the traversal from the current directory
8499
root_directory = os.getcwd() # You can also specify a different directory if needed

0 commit comments

Comments
 (0)