diff --git a/.gitignore b/.gitignore
index f54094be..50dce689 100644
--- a/.gitignore
+++ b/.gitignore
@@ -479,3 +479,6 @@ target/
# Generated by nginx/ssl/generate-cert.sh -- never commit a private key.
Programming/ERSMS-project/nginx/ssl/nginx.key
Programming/ERSMS-project/nginx/ssl/nginx.crt
+
+# Output of ./run.sh run
for the C++ courses.
+build/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index b4033e8a..8c508e6e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -63,9 +63,13 @@ repos:
language: system
files: \.(md|markdown)$
+ # -x so shellcheck FOLLOWS sourced files. The 44 per-course run.sh stubs
+ # source scripts/course_lib.sh; without -x every one of them reports
+ # SC1091 "not following", and the only other way to silence that is a
+ # per-file disable, which is banned here.
- id: shellcheck
name: shellcheck
- entry: bash -c 'printf "%s\0" "$@" | xargs -0 -n 40 shellcheck' --
+ entry: bash -c 'printf "%s\0" "$@" | xargs -0 -n 40 shellcheck -x' --
language: system
types: [shell]
exclude_types: [zsh]
diff --git a/DOCS-runnability.md b/DOCS-runnability.md
index 365edbfb..06354bde 100644
--- a/DOCS-runnability.md
+++ b/DOCS-runnability.md
@@ -8,47 +8,47 @@ Unity and no .NET SDK. CI compares only its own column.
| Course | local | Detail |
|---|---|---|
-| `NotProgramming/DPZ` | unprobed | no run.sh yet |
-| `NotProgramming/ECOGR` | unprobed | no run.sh yet |
-| `NotProgramming/EDABA` | unprobed | no run.sh yet |
-| `NotProgramming/EDABA-LAB` | unprobed | no run.sh yet |
-| `NotProgramming/EDISE` | unprobed | no run.sh yet |
-| `NotProgramming/EDISP` | unprobed | no run.sh yet |
-| `NotProgramming/ELAC` | unprobed | no run.sh yet |
-| `NotProgramming/EMISY` | unprobed | no run.sh yet |
-| `NotProgramming/ENUME` | unprobed | no run.sh yet |
-| `NotProgramming/EPHY2` | unprobed | no run.sh yet |
-| `NotProgramming/EPOGAI` | unprobed | no run.sh yet |
-| `NotProgramming/ERPM` | unprobed | no run.sh yet |
-| `NotProgramming/ESDP1` | unprobed | no run.sh yet |
-| `NotProgramming/ESOEN` | unprobed | no run.sh yet |
-| `NotProgramming/MOM` | unprobed | no run.sh yet |
-| `NotProgramming/SDM` | unprobed | no run.sh yet |
-| `NotProgramming/SDM2` | unprobed | no run.sh yet |
-| `NotProgramming/SPD` | unprobed | no run.sh yet |
-| `NotProgramming/STUP` | unprobed | no run.sh yet |
-| `Programming/AIS` | unprobed | no run.sh yet |
-| `Programming/BD2` | unprobed | no run.sh yet |
-| `Programming/BSS` | unprobed | no run.sh yet |
-| `Programming/EADS` | unprobed | no run.sh yet |
-| `Programming/EARIN` | unprobed | no run.sh yet |
-| `Programming/ECOAR` | unprobed | no run.sh yet |
-| `Programming/ECOTE` | unprobed | no run.sh yet |
-| `Programming/ECRYPT` | unprobed | no run.sh yet |
-| `Programming/ECRYPT_PROJECT` | unprobed | no run.sh yet |
-| `Programming/EGUI` | unprobed | no run.sh yet |
-| `Programming/EOOP` | unprobed | no run.sh yet |
-| `Programming/EOPSY` | unprobed | no run.sh yet |
-| `Programming/EPFU` | unprobed | no run.sh yet |
-| `Programming/E-PSYOPS` | unprobed | no run.sh yet |
-| `Programming/ERSMS-project` | unprobed | no run.sh yet |
-| `Programming/NLP` | unprobed | no run.sh yet |
-| `Programming/PBAD` | unprobed | no run.sh yet |
-| `Programming/PORR` | unprobed | no run.sh yet |
-| `Programming/PSD` | unprobed | pre-existing run.sh, no --probe support |
-| `Programming/psd_project` | unprobed | no run.sh yet |
-| `Programming/TRAK` | unprobed | no run.sh yet |
-| `Programming/twm_4` | unprobed | no run.sh yet |
-| `Programming/USD` | unprobed | no run.sh yet |
-| `Programming/WDWR` | unprobed | no run.sh yet |
-| `Programming/ZPOB` | unprobed | no run.sh yet |
+| `NotProgramming/DPZ` | runnable | builds the report with latexmk |
+| `NotProgramming/ECOGR` | runnable | builds the report with latexmk |
+| `NotProgramming/EDABA` | runnable | builds the report with latexmk |
+| `NotProgramming/EDABA-LAB` | runnable | builds the report with latexmk |
+| `NotProgramming/EDISE` | runnable | builds the report with latexmk |
+| `NotProgramming/EDISP` | no-code | submitted lab reports as PDFs only; no source was produced |
+| `NotProgramming/ELAC` | runnable | builds the report with latexmk |
+| `NotProgramming/EMISY` | runnable | builds the report with latexmk |
+| `NotProgramming/ENUME` | blocked | needs MATLAB or Octave (none of: matlab octave) |
+| `NotProgramming/EPHY2` | no-code | submitted lab reports as PDFs only; no source was produced |
+| `NotProgramming/EPOGAI` | no-code | a presentation only; no source was produced |
+| `NotProgramming/ERPM` | runnable | builds the report with latexmk |
+| `NotProgramming/ESDP1` | runnable | builds the report with latexmk |
+| `NotProgramming/ESOEN` | runnable | builds the report with latexmk |
+| `NotProgramming/MOM` | runnable | builds the report with latexmk |
+| `NotProgramming/SDM` | runnable | builds the report with latexmk |
+| `NotProgramming/SDM2` | runnable | builds the report with latexmk |
+| `NotProgramming/SPD` | runnable | builds the report with latexmk |
+| `NotProgramming/STUP` | runnable | npm install && npm start |
+| `Programming/AIS` | runnable | builds the report with latexmk |
+| `Programming/BD2` | runnable | npm install && npm start |
+| `Programming/BSS` | runnable | builds the report with latexmk |
+| `Programming/EADS` | runnable | compiles each standalone .cpp |
+| `Programming/EARIN` | runnable | python3 the entry script |
+| `Programming/ECOAR` | runnable | make |
+| `Programming/ECOTE` | runnable | python3 /home/kuhy/WUT_Computer_Science/Programming/ECOTE/program/code/main.py |
+| `Programming/ECRYPT` | no-code | test1.py is a 0-byte stub; this course produced no submitted code |
+| `Programming/ECRYPT_PROJECT` | runnable | python3 /home/kuhy/WUT_Computer_Science/Programming/ECRYPT_PROJECT/code |
+| `Programming/EGUI` | runnable | dotnet build |
+| `Programming/EOOP` | runnable | compiles each standalone .cpp |
+| `Programming/EOPSY` | runnable | make |
+| `Programming/EPFU` | runnable | make |
+| `Programming/E-PSYOPS` | runnable | opens in the Unity editor |
+| `Programming/ERSMS-project` | runnable | docker compose up |
+| `Programming/NLP` | runnable | python3 the entry script |
+| `Programming/PBAD` | runnable | jupyter nbconvert --execute |
+| `Programming/PORR` | runnable | python3 /home/kuhy/WUT_Computer_Science/Programming/PORR/code |
+| `Programming/PSD` | runnable | starts the Kafka/Flink stack (installs packages with sudo) |
+| `Programming/psd_project` | runnable | mvn package |
+| `Programming/TRAK` | runnable | python3 the entry script |
+| `Programming/twm_4` | runnable | jupyter nbconvert --execute |
+| `Programming/USD` | no-code | README describes a MetaDrive RL assignment; no implementation was submitted |
+| `Programming/WDWR` | runnable | python3 the entry script |
+| `Programming/ZPOB` | runnable | builds the report with latexmk |
diff --git a/NotProgramming/DPZ/run.sh b/NotProgramming/DPZ/run.sh
new file mode 100755
index 00000000..03387c51
--- /dev/null
+++ b/NotProgramming/DPZ/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# NotProgramming/DPZ -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind tex \
+ -- "$@"
diff --git a/NotProgramming/ECOGR/run.sh b/NotProgramming/ECOGR/run.sh
new file mode 100755
index 00000000..c9774fd4
--- /dev/null
+++ b/NotProgramming/ECOGR/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# NotProgramming/ECOGR -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind tex \
+ -- "$@"
diff --git a/NotProgramming/EDABA-LAB/run.sh b/NotProgramming/EDABA-LAB/run.sh
new file mode 100755
index 00000000..50d61dbc
--- /dev/null
+++ b/NotProgramming/EDABA-LAB/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# NotProgramming/EDABA-LAB -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind tex \
+ -- "$@"
diff --git a/NotProgramming/EDABA/run.sh b/NotProgramming/EDABA/run.sh
new file mode 100755
index 00000000..c7ce644c
--- /dev/null
+++ b/NotProgramming/EDABA/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# NotProgramming/EDABA -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind tex \
+ -- "$@"
diff --git a/NotProgramming/EDISE/run.sh b/NotProgramming/EDISE/run.sh
new file mode 100755
index 00000000..6639ac68
--- /dev/null
+++ b/NotProgramming/EDISE/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# NotProgramming/EDISE -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind tex \
+ -- "$@"
diff --git a/NotProgramming/EDISP/run.sh b/NotProgramming/EDISP/run.sh
new file mode 100755
index 00000000..f21def47
--- /dev/null
+++ b/NotProgramming/EDISP/run.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# NotProgramming/EDISP -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind none \
+ --note "submitted lab reports as PDFs only; no source was produced" \
+ -- "$@"
diff --git a/NotProgramming/ELAC/run.sh b/NotProgramming/ELAC/run.sh
new file mode 100755
index 00000000..e7651738
--- /dev/null
+++ b/NotProgramming/ELAC/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# NotProgramming/ELAC -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind tex \
+ -- "$@"
diff --git a/NotProgramming/EMISY/run.sh b/NotProgramming/EMISY/run.sh
new file mode 100755
index 00000000..a6c7e3f4
--- /dev/null
+++ b/NotProgramming/EMISY/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# NotProgramming/EMISY -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind tex \
+ -- "$@"
diff --git a/NotProgramming/ENUME/run.sh b/NotProgramming/ENUME/run.sh
new file mode 100755
index 00000000..a700b055
--- /dev/null
+++ b/NotProgramming/ENUME/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# NotProgramming/ENUME -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind matlab \
+ -- "$@"
diff --git a/NotProgramming/EPHY2/run.sh b/NotProgramming/EPHY2/run.sh
new file mode 100755
index 00000000..659c8aa2
--- /dev/null
+++ b/NotProgramming/EPHY2/run.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# NotProgramming/EPHY2 -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind none \
+ --note "submitted lab reports as PDFs only; no source was produced" \
+ -- "$@"
diff --git a/NotProgramming/EPOGAI/run.sh b/NotProgramming/EPOGAI/run.sh
new file mode 100755
index 00000000..d024f7b4
--- /dev/null
+++ b/NotProgramming/EPOGAI/run.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# NotProgramming/EPOGAI -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind none \
+ --note "a presentation only; no source was produced" \
+ -- "$@"
diff --git a/NotProgramming/ERPM/run.sh b/NotProgramming/ERPM/run.sh
new file mode 100755
index 00000000..743548f6
--- /dev/null
+++ b/NotProgramming/ERPM/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# NotProgramming/ERPM -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind tex \
+ -- "$@"
diff --git a/NotProgramming/ESDP1/run.sh b/NotProgramming/ESDP1/run.sh
new file mode 100755
index 00000000..94837d28
--- /dev/null
+++ b/NotProgramming/ESDP1/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# NotProgramming/ESDP1 -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind tex \
+ -- "$@"
diff --git a/NotProgramming/ESOEN/run.sh b/NotProgramming/ESOEN/run.sh
new file mode 100755
index 00000000..adc65122
--- /dev/null
+++ b/NotProgramming/ESOEN/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# NotProgramming/ESOEN -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind tex \
+ -- "$@"
diff --git a/NotProgramming/MOM/run.sh b/NotProgramming/MOM/run.sh
new file mode 100755
index 00000000..604b2c26
--- /dev/null
+++ b/NotProgramming/MOM/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# NotProgramming/MOM -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind tex \
+ -- "$@"
diff --git a/NotProgramming/SDM/run.sh b/NotProgramming/SDM/run.sh
new file mode 100755
index 00000000..28653f68
--- /dev/null
+++ b/NotProgramming/SDM/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# NotProgramming/SDM -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind tex \
+ -- "$@"
diff --git a/NotProgramming/SDM2/run.sh b/NotProgramming/SDM2/run.sh
new file mode 100755
index 00000000..b1d25148
--- /dev/null
+++ b/NotProgramming/SDM2/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# NotProgramming/SDM2 -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind tex \
+ -- "$@"
diff --git a/NotProgramming/SPD/run.sh b/NotProgramming/SPD/run.sh
new file mode 100755
index 00000000..6dfb3ec2
--- /dev/null
+++ b/NotProgramming/SPD/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# NotProgramming/SPD -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind tex \
+ -- "$@"
diff --git a/NotProgramming/STUP/run.sh b/NotProgramming/STUP/run.sh
new file mode 100755
index 00000000..c86a4bb7
--- /dev/null
+++ b/NotProgramming/STUP/run.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# NotProgramming/STUP -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind node \
+ --entry "$(dirname "$(readlink -f "$0")")/makieta" \
+ -- "$@"
diff --git a/Programming/AIS/run.sh b/Programming/AIS/run.sh
new file mode 100755
index 00000000..a04a8164
--- /dev/null
+++ b/Programming/AIS/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# Programming/AIS -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind tex \
+ -- "$@"
diff --git a/Programming/BD2/run.sh b/Programming/BD2/run.sh
new file mode 100755
index 00000000..71d49ab5
--- /dev/null
+++ b/Programming/BD2/run.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Programming/BD2 -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind node \
+ --entry "$(dirname "$(readlink -f "$0")")/monorepo" \
+ -- "$@"
diff --git a/Programming/BSS/run.sh b/Programming/BSS/run.sh
new file mode 100755
index 00000000..6d8e045d
--- /dev/null
+++ b/Programming/BSS/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# Programming/BSS -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind tex \
+ -- "$@"
diff --git a/Programming/E-PSYOPS/run.sh b/Programming/E-PSYOPS/run.sh
new file mode 100755
index 00000000..63be0f22
--- /dev/null
+++ b/Programming/E-PSYOPS/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# Programming/E-PSYOPS -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind unity \
+ -- "$@"
diff --git a/Programming/EADS/MidTerm/task2.cpp b/Programming/EADS/MidTerm/task2.cpp
index 863514f0..eb9bd414 100644
--- a/Programming/EADS/MidTerm/task2.cpp
+++ b/Programming/EADS/MidTerm/task2.cpp
@@ -1,55 +1,60 @@
-// Krzysztof Rudnicki, 307585
-
-struct treeNode
-{
- int key;
- treeNode *left;
- treeNode *right;
-};
-
-class Tree{
-
- private:
- treeNode *root;
- public:
- bool checkKeys(int givenKey);
- bool findNode(treeNode*& nodeToFound, int givenKey);
- int sumNodeSub(treeNode* start);
-};
-
-int Tree::sumNodeSub(const treeNode* start)
-{
- return (sumNodeSub(start->right)
- + sumNodeSub(start->left)
- + start->key);
-}
-
-bool Tree::findNode(treeNode*& nodeToFound, int givenKey);
-{
- treeNode* curr = root;
- treeNode* Cleft = curr->left;
- treeNode* Cright = curr->right;
- while(curr != nullptr)
- {
- if(curr -> key == givenKey)
- {
- nodeToFound = curr;
- return 1;
- }else
- {
- if(Cleft.findNode(nodeToFound, givenKey)) return 1;
- if(Cright.findNode(nodeToFound, givenKey)) return 1;
- }
- }
- return 0;
-}
-
-
-
-bool Tree::checkKeys(int key)
-{
- treeNode* start;
- if (!findNode(start, key)) return 0;
- // if we did not find the node we cannot compare the keys of it's left and right subtrees
- return (sumNodeSub(start->left) > sumNodeSub(start->right));
+// Krzysztof Rudnicki, 307585
+
+struct treeNode
+{
+ int key;
+ treeNode *left;
+ treeNode *right;
+};
+
+class Tree{
+
+ private:
+ treeNode *root;
+ public:
+ bool checkKeys(int givenKey);
+ bool findNode(treeNode*& nodeToFound, int givenKey);
+ static bool findNodeFrom(treeNode* curr, treeNode*& nodeToFound, int givenKey);
+ int sumNodeSub(const treeNode* start);
+};
+
+int Tree::sumNodeSub(const treeNode* start)
+{
+ // Without this the recursion has no base case and walks off the end of
+ // the tree; the declaration/definition const mismatch meant the body was
+ // never compiled, so it was never reached either.
+ if (start == nullptr) return 0;
+ return (sumNodeSub(start->right)
+ + sumNodeSub(start->left)
+ + start->key);
+}
+
+// As submitted this did not compile: it called findNode on a treeNode*, which
+// is a Tree method, and its while loop never advanced curr so it could not
+// have terminated either. Same intent -- search the tree for a key -- expressed
+// as the recursion the original was reaching for.
+bool Tree::findNodeFrom(treeNode* curr, treeNode*& nodeToFound, int givenKey)
+{
+ if(curr == nullptr) return 0;
+ if(curr -> key == givenKey)
+ {
+ nodeToFound = curr;
+ return 1;
+ }
+ if(findNodeFrom(curr -> left, nodeToFound, givenKey)) return 1;
+ return findNodeFrom(curr -> right, nodeToFound, givenKey);
+}
+
+bool Tree::findNode(treeNode*& nodeToFound, int givenKey)
+{
+ return findNodeFrom(root, nodeToFound, givenKey);
+}
+
+
+bool Tree::checkKeys(int key)
+{
+ treeNode* start;
+ if (!findNode(start, key)) return 0;
+ // if we did not find the node we cannot compare the keys of it's left and right subtrees
+ return (sumNodeSub(start->left) > sumNodeSub(start->right));
}
\ No newline at end of file
diff --git a/Programming/EADS/lab3.cpp b/Programming/EADS/lab3.cpp
index 9f607752..f007f769 100644
--- a/Programming/EADS/lab3.cpp
+++ b/Programming/EADS/lab3.cpp
@@ -24,7 +24,7 @@ public:
Node* getRoot() const;
void destroy(Node* &someNode);
~Dictionary();
- void copyDictionary(Dictionary *&copiedDictRoot, Dictionary *otherTreeRoot);
+ void copyDictionary(Node *&copiedDictRoot, Node *otherTreeRoot);
Dictionary(const Dictionary &other);
void insert(const Key &newKey, const Info &newInfo);
void remove(const Key &keyToRemove);
@@ -67,13 +67,15 @@ template
Dictionary::~Dictionary() { destroy(root); }
template
-void Dictionary::copyDictionary(Dictionary* &copiedDictRoot, Dictionary* otherDictRoot)
+void Dictionary::copyDictionary(Node* &copiedDictRoot, Node* otherDictRoot)
{
if(otherDictRoot == nullptr) copiedDictRoot = nullptr;
else
{
- copiedDictRoot = new Dictionary;
+ copiedDictRoot = new Node;
+ copiedDictRoot->key = otherDictRoot -> key;
copiedDictRoot->info = otherDictRoot -> info;
+ copiedDictRoot->balanceFactor = otherDictRoot -> balanceFactor;
copyDictionary(copiedDictRoot -> left, otherDictRoot -> left);
copyDictionary(copiedDictRoot -> right, otherDictRoot -> right);
}
@@ -83,7 +85,7 @@ template
Dictionary::Dictionary(const Dictionary &other)
{
if(other.root == nullptr) root = nullptr;
- else copyTree(root, other.root);
+ else copyDictionary(root, other.root);
}
template
diff --git a/Programming/EADS/run.sh b/Programming/EADS/run.sh
new file mode 100755
index 00000000..6c16673d
--- /dev/null
+++ b/Programming/EADS/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# Programming/EADS -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind cxx \
+ -- "$@"
diff --git a/Programming/EARIN/run.sh b/Programming/EARIN/run.sh
new file mode 100755
index 00000000..3d64a327
--- /dev/null
+++ b/Programming/EARIN/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# Programming/EARIN -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind python \
+ -- "$@"
diff --git a/Programming/ECOAR/run.sh b/Programming/ECOAR/run.sh
new file mode 100755
index 00000000..ad889548
--- /dev/null
+++ b/Programming/ECOAR/run.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Programming/ECOAR -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind make \
+ --entry "$(dirname "$(readlink -f "$0")")/C" \
+ -- "$@"
diff --git a/Programming/ECOTE/run.sh b/Programming/ECOTE/run.sh
new file mode 100755
index 00000000..2e0cb758
--- /dev/null
+++ b/Programming/ECOTE/run.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Programming/ECOTE -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind python \
+ --entry "$(dirname "$(readlink -f "$0")")/program/code/main.py" \
+ -- "$@"
diff --git a/Programming/ECRYPT/run.sh b/Programming/ECRYPT/run.sh
new file mode 100755
index 00000000..3cfd00f9
--- /dev/null
+++ b/Programming/ECRYPT/run.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Programming/ECRYPT -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind none \
+ --note "test1.py is a 0-byte stub; this course produced no submitted code" \
+ -- "$@"
diff --git a/Programming/ECRYPT_PROJECT/run.sh b/Programming/ECRYPT_PROJECT/run.sh
new file mode 100755
index 00000000..f441b9a5
--- /dev/null
+++ b/Programming/ECRYPT_PROJECT/run.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Programming/ECRYPT_PROJECT -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind python \
+ --entry "$(dirname "$(readlink -f "$0")")/code" \
+ -- "$@"
diff --git a/Programming/EGUI/run.sh b/Programming/EGUI/run.sh
new file mode 100755
index 00000000..e0bfcb5b
--- /dev/null
+++ b/Programming/EGUI/run.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Programming/EGUI -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind dotnet \
+ --entry "$(dirname "$(readlink -f "$0")")/lab2/lab2" \
+ -- "$@"
diff --git a/Programming/EOOP/run.sh b/Programming/EOOP/run.sh
new file mode 100755
index 00000000..caf5c8ea
--- /dev/null
+++ b/Programming/EOOP/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# Programming/EOOP -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind cxx \
+ -- "$@"
diff --git a/Programming/EOPSY/run.sh b/Programming/EOPSY/run.sh
new file mode 100755
index 00000000..93651780
--- /dev/null
+++ b/Programming/EOPSY/run.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Programming/EOPSY -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind make \
+ --entry "$(dirname "$(readlink -f "$0")")/lab3/task3/ftp" \
+ -- "$@"
diff --git a/Programming/EPFU/run.sh b/Programming/EPFU/run.sh
new file mode 100755
index 00000000..5f11d6fa
--- /dev/null
+++ b/Programming/EPFU/run.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Programming/EPFU -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind make \
+ --entry "$(dirname "$(readlink -f "$0")")/penguins/src" \
+ -- "$@"
diff --git a/Programming/ERSMS-project/run.sh b/Programming/ERSMS-project/run.sh
new file mode 100755
index 00000000..a322d5d5
--- /dev/null
+++ b/Programming/ERSMS-project/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# Programming/ERSMS-project -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind compose \
+ -- "$@"
diff --git a/Programming/NLP/run.sh b/Programming/NLP/run.sh
new file mode 100755
index 00000000..e685812e
--- /dev/null
+++ b/Programming/NLP/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# Programming/NLP -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind python \
+ -- "$@"
diff --git a/Programming/PBAD/run.sh b/Programming/PBAD/run.sh
new file mode 100755
index 00000000..8f1435af
--- /dev/null
+++ b/Programming/PBAD/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# Programming/PBAD -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind notebook \
+ -- "$@"
diff --git a/Programming/PORR/run.sh b/Programming/PORR/run.sh
new file mode 100755
index 00000000..eb6f060e
--- /dev/null
+++ b/Programming/PORR/run.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Programming/PORR -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind python \
+ --entry "$(dirname "$(readlink -f "$0")")/code" \
+ -- "$@"
diff --git a/Programming/PSD/run.sh b/Programming/PSD/run.sh
index 9bf28073..b4d69788 100755
--- a/Programming/PSD/run.sh
+++ b/Programming/PSD/run.sh
@@ -1,5 +1,21 @@
#!/bin/bash
+# --probe: report whether this can run here WITHOUT running it, per the
+# contract in ../../run.sh. This script is the original coursework runner and
+# is deliberately left otherwise untouched, so the probe is a early exit
+# rather than a rewrite -- it installs packages with sudo and starts Kafka,
+# which is exactly what a probe must never trigger.
+if [[ "${1:-}" == "--probe" ]]; then
+ for tool in python mvn java; do
+ if ! command -v "$tool" > /dev/null 2>&1; then
+ echo "needs $tool"
+ exit 78
+ fi
+ done
+ echo "starts the Kafka/Flink stack (installs packages with sudo)"
+ exit 0
+fi
+
# Function to run a script in the background and save its PID
run_script() {
python "$1" &
diff --git a/Programming/TRAK/run.sh b/Programming/TRAK/run.sh
new file mode 100755
index 00000000..4ae9e700
--- /dev/null
+++ b/Programming/TRAK/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# Programming/TRAK -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind python \
+ -- "$@"
diff --git a/Programming/USD/run.sh b/Programming/USD/run.sh
new file mode 100755
index 00000000..6638d9d7
--- /dev/null
+++ b/Programming/USD/run.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Programming/USD -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind none \
+ --note "README describes a MetaDrive RL assignment; no implementation was submitted" \
+ -- "$@"
diff --git a/Programming/WDWR/run.sh b/Programming/WDWR/run.sh
new file mode 100755
index 00000000..6a12a776
--- /dev/null
+++ b/Programming/WDWR/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# Programming/WDWR -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind python \
+ -- "$@"
diff --git a/Programming/ZPOB/run.sh b/Programming/ZPOB/run.sh
new file mode 100755
index 00000000..0ad11059
--- /dev/null
+++ b/Programming/ZPOB/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# Programming/ZPOB -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind tex \
+ -- "$@"
diff --git a/Programming/psd_project/run.sh b/Programming/psd_project/run.sh
new file mode 100755
index 00000000..5e54ee33
--- /dev/null
+++ b/Programming/psd_project/run.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+#
+# Programming/psd_project -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind maven \
+ --entry "$(dirname "$(readlink -f "$0")")/anomaly-detector" \
+ -- "$@"
diff --git a/Programming/twm_4/run.sh b/Programming/twm_4/run.sh
new file mode 100755
index 00000000..a6280ffb
--- /dev/null
+++ b/Programming/twm_4/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# Programming/twm_4 -- see ../../run.sh for the probe contract.
+set -euo pipefail
+
+# shellcheck source-path=SCRIPTDIR
+# shellcheck source=../../scripts/course_lib.sh
+. "$(dirname "$(readlink -f "$0")")/../../scripts/course_lib.sh"
+
+course_main --kind notebook \
+ -- "$@"
diff --git a/run.sh b/run.sh
index b24d0cdd..2e26a599 100755
--- a/run.sh
+++ b/run.sh
@@ -45,6 +45,13 @@ cmd_check() {
cmd_install() { exec "${SCRIPT_DIR}/install.sh" "$@"; }
+# A runner opts in either by handling --probe itself, or by delegating to the
+# shared library (which does). Anything else predates this harness and is left
+# strictly alone.
+_supports_probe() {
+ grep -qF -- '--probe' "$1" || grep -qF 'course_lib.sh' "$1"
+}
+
# Probe one directory. Prints "\t"; never executes a project.
#
# The guard below is load-bearing. Some course dirs already ship their own
@@ -56,7 +63,7 @@ cmd_install() { exec "${SCRIPT_DIR}/install.sh" "$@"; }
# blocks on input anyway.
probe_one() {
local dir="$1" runner="$1/run.sh"
- if [[ -x "$runner" ]] && grep -qF -- '--probe' "$runner"; then
+ if [[ -x "$runner" ]] && _supports_probe "$runner"; then
local out status
out="$(timeout 20s "$runner" --probe 2> /dev/null)" && status=0 || status=$?
case "$status" in
diff --git a/scripts/course_lib.sh b/scripts/course_lib.sh
new file mode 100644
index 00000000..8b238f81
--- /dev/null
+++ b/scripts/course_lib.sh
@@ -0,0 +1,225 @@
+# shellcheck shell=bash
+# Shared behaviour for every per-course run.sh. Sourced, never executed.
+#
+# A course run.sh is a stub: it declares what kind of project it is, then calls
+# course_main. The logic lives here once, because 44 hand-copied runners is
+# precisely how gates and scripts in this fleet have drifted apart before.
+#
+# Contract (see ../../run.sh):
+# --probe report whether this could run HERE, without running it
+# exit 0 runnable | 78 blocked | 79 nothing to run
+# (no args) actually run it
+#
+# A stub calls:
+# course_main --kind [--entry ] [--note ] -- "$@"
+# Values are passed as arguments rather than set as globals so that a stub has
+# no assignments a linter has to be told to ignore.
+
+readonly COURSE_EXIT_BLOCKED=78
+readonly COURSE_EXIT_NO_CODE=79
+
+COURSE_ROOT="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[1]}")")" && pwd)"
+readonly COURSE_ROOT
+
+# --- helpers -----------------------------------------------------------------
+
+_have() { command -v "$1" > /dev/null 2>&1; }
+
+# Report "blocked" with the reason, which is the whole point of the status
+# table: "needs X" is useful, "failed" is not.
+_blocked() {
+ printf '%s\n' "$1"
+ exit "$COURSE_EXIT_BLOCKED"
+}
+
+_no_code() {
+ printf '%s\n' "$1"
+ exit "$COURSE_EXIT_NO_CODE"
+}
+
+_runnable() { printf '%s\n' "$1"; exit 0; }
+
+# First tool present wins; otherwise blocked naming all the candidates.
+_require_any() {
+ local label="$1"; shift
+ local tool
+ for tool in "$@"; do
+ if _have "$tool"; then
+ printf '%s' "$tool"
+ return 0
+ fi
+ done
+ _blocked "needs $label (none of: $*)"
+}
+
+# Probe-side variant. _require_any prints the tool it found, so probes have to
+# redirect its stdout -- which also swallowed the failure REASON and turned
+# every blocked probe into "unknown". This one returns a status and prints
+# only on failure.
+_probe_any() {
+ local label="$1"; shift
+ local tool
+ for tool in "$@"; do
+ _have "$tool" && return 0
+ done
+ _blocked "needs $label (none of: $*)"
+}
+
+_first_file() {
+ find "$COURSE_ROOT" -name "$1" -not -path '*/node_modules/*' -print -quit 2>/dev/null
+}
+
+# --- probes ------------------------------------------------------------------
+
+_probe() {
+ case "$COURSE_KIND" in
+ none) _no_code "${COURSE_NOTE:-no executable code in this directory}" ;;
+ tex)
+ _probe_any "a LaTeX toolchain" latexmk pdflatex xelatex
+ _runnable "builds ${COURSE_ENTRY:-the report} with latexmk"
+ ;;
+ python)
+ _have python3 || _blocked "needs python3"
+ [[ -n "$(_first_file '*.py')" ]] || _no_code "no .py files found"
+ _runnable "python3 ${COURSE_ENTRY:-the entry script}"
+ ;;
+ node)
+ _have node || _blocked "needs node"
+ _have npm || _blocked "needs npm"
+ _runnable "npm install && npm start"
+ ;;
+ maven)
+ _have mvn || _blocked "needs maven"
+ _have java || _blocked "needs a JDK"
+ _runnable "mvn package"
+ ;;
+ make)
+ _have make || _blocked "needs make"
+ _probe_any "a C compiler" gcc cc clang
+ _runnable "make"
+ ;;
+ dotnet)
+ _have dotnet || _blocked "needs the .NET SDK"
+ _runnable "dotnet build"
+ ;;
+ unity)
+ _have unity-editor || _have unityhub \
+ || _blocked "needs the Unity editor (GUI, not installable on a CI runner)"
+ _runnable "opens in the Unity editor"
+ ;;
+ matlab)
+ _probe_any "MATLAB or Octave" matlab octave
+ _runnable "runs the .m scripts"
+ ;;
+ cxx)
+ _probe_any "a C++ compiler" g++ clang++
+ [[ -n "$(_first_file '*.cpp')" ]] || _no_code "no .cpp files found"
+ _runnable "compiles each standalone .cpp"
+ ;;
+ notebook)
+ _have jupyter || _blocked "needs jupyter"
+ _runnable "jupyter nbconvert --execute"
+ ;;
+ compose)
+ _have docker || _blocked "needs docker"
+ docker info > /dev/null 2>&1 || _blocked "docker is installed but the daemon is not running"
+ _runnable "docker compose up"
+ ;;
+ *) _blocked "unknown COURSE_KIND '${COURSE_KIND:-unset}'" ;;
+ esac
+}
+
+# --- runners -----------------------------------------------------------------
+
+_run() {
+ case "$COURSE_KIND" in
+ none) _no_code "${COURSE_NOTE:-nothing to run}" ;;
+ tex)
+ local tex="${COURSE_ENTRY:-}"
+ [[ -n "$tex" ]] || tex="$(_first_file '*.tex')"
+ [[ -n "$tex" ]] || _no_code "no .tex found"
+ local tool
+ tool="$(_require_any 'a LaTeX toolchain' latexmk pdflatex xelatex)"
+ if [[ $tool == latexmk ]]; then
+ ( cd "$(dirname "$tex")" && latexmk -pdf -interaction=nonstopmode "$(basename "$tex")" )
+ else
+ ( cd "$(dirname "$tex")" && "$tool" -interaction=nonstopmode "$(basename "$tex")" )
+ fi
+ ;;
+ python)
+ local entry="${COURSE_ENTRY:-}"
+ [[ -n "$entry" ]] || entry="$(_first_file 'main.py')"
+ [[ -n "$entry" ]] || _no_code "no entry point; see this directory's README"
+ ( cd "$(dirname "$entry")" && python3 "$(basename "$entry")" )
+ ;;
+ node) ( cd "${COURSE_ENTRY:-$COURSE_ROOT}" && npm install && npm start ) ;;
+ maven) ( cd "${COURSE_ENTRY:-$COURSE_ROOT}" && mvn -q package ) ;;
+ make) ( cd "${COURSE_ENTRY:-$COURSE_ROOT}" && make ) ;;
+ dotnet) ( cd "${COURSE_ENTRY:-$COURSE_ROOT}" && dotnet build ) ;;
+ compose) ( cd "${COURSE_ENTRY:-$COURSE_ROOT}" && docker compose up ) ;;
+ unity) _blocked "open ${COURSE_ROOT} in the Unity editor; there is no headless path" ;;
+ cxx)
+ # Each .cpp here is a self-contained assignment with its own main(),
+ # so they are compiled individually rather than linked together.
+ local cxx src rel out
+ cxx="$(_require_any 'a C++ compiler' g++ clang++)"
+ # Binaries go to build/, which is gitignored -- compiling in place
+ # drops untracked executables next to the sources, and an
+ # extensionless binary is not something .gitignore catches by name.
+ mkdir -p "$COURSE_ROOT/build"
+ while IFS= read -r src; do
+ rel="${src#"$COURSE_ROOT"/}"
+ out="$COURSE_ROOT/build/$(basename "${rel%.cpp}")"
+ # Some files here are exam-answer fragments with no main(), so
+ # they cannot be linked. Compile those to an object instead --
+ # that still type-checks them, which is the point.
+ if grep -qE '\bint[[:space:]]+main[[:space:]]*\(' "$src"; then
+ printf 'compiling %s\n' "$rel"
+ "$cxx" -O2 -o "$out" "$src"
+ else
+ printf 'checking %s (no main; -c only)\n' "$rel"
+ "$cxx" -O2 -c -o "${out}.o" "$src"
+ fi
+ done < <(find "$COURSE_ROOT" -name '*.cpp' -not -path '*/node_modules/*' -not -path '*/build/*' | sort)
+ printf 'binaries in %s/build\n' "$COURSE_ROOT"
+ ;;
+ notebook)
+ _have jupyter || _blocked "needs jupyter"
+ local nb
+ while IFS= read -r nb; do
+ jupyter nbconvert --to notebook --execute --inplace "$nb"
+ done < <(find "$COURSE_ROOT" -name '*.ipynb' -not -path '*/.ipynb_checkpoints/*' | sort)
+ ;;
+ matlab)
+ local tool
+ tool="$(_require_any 'MATLAB or Octave' matlab octave)"
+ printf 'run the .m files in %s with %s\n' "$COURSE_ROOT" "$tool"
+ ;;
+ *) printf 'unknown COURSE_KIND: %s\n' "${COURSE_KIND:-unset}" >&2; return 1 ;;
+ esac
+}
+
+course_main() {
+ COURSE_KIND=""
+ COURSE_ENTRY=""
+ COURSE_NOTE=""
+ while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --kind) COURSE_KIND="$2"; shift 2 ;;
+ --entry) COURSE_ENTRY="$2"; shift 2 ;;
+ --note) COURSE_NOTE="$2"; shift 2 ;;
+ --) shift; break ;;
+ *) break ;;
+ esac
+ done
+
+ case "${1:-}" in
+ --probe) _probe ;;
+ -h | --help)
+ printf 'usage: %s [--probe]\n' "$(basename "$0")"
+ printf ' --probe report runnable/blocked/no-code without running\n'
+ ;;
+ "") _run ;;
+ *) printf 'unknown option: %s\n' "$1" >&2; return 1 ;;
+ esac
+}