diff --git a/README.md b/README.md
index 937a0d7e939fc262881440752ae06f46cccd63ff..2dffcf7352724d9653bd491b65bfd180570fdf1a 100644
--- a/README.md
+++ b/README.md
@@ -228,7 +228,7 @@ When this is run, the titles are shown as follows:
 | | | |_ __  _| |_| |  \/_ __ __ _  __| | ___ 
 | | | | '_ \| | __| | __| '__/ _` |/ _` |/ _ \
 | |_| | | | | | |_| |_\ \ | | (_| | (_| |  __/
- \___/|_| |_|_|\__|\____/_|  \__,_|\__,_|\___| v0.1.17, started: 21/09/2021 11:56:22
+ \___/|_| |_|_|\__|\____/_|  \__,_|\__,_|\___| v0.1.17, started: 21/09/2021 11:57:05
 
 CS 102 Report 2 
 Question 1: Week1                                                                                                       
@@ -242,7 +242,7 @@ Question 2: The same problem as before with nicer titles
  * q2.2) Checking if reverse_list([1, 2, 3]) = [3, 2, 1]............................................................PASS
  * q2)   Total...................................................................................................... 6/6
  
-Total points at 11:56:22 (0 minutes, 0 seconds)....................................................................16/16
+Total points at 11:57:05 (0 minutes, 0 seconds)....................................................................16/16
 
 Including files in upload...
  * cs102
@@ -540,12 +540,12 @@ and TAs can choose to annotate the students code directly in Autolab -- we are h
 # Citing
 ```bibtex
 @online{unitgrade_devel,
-	title={Unitgrade-devel (0.1.27): \texttt{pip install unitgrade-devel}},
+	title={Unitgrade-devel (0.1.35): \texttt{pip install unitgrade-devel}},
 	url={https://lab.compute.dtu.dk/tuhe/unitgrade_private},
-	urldate = {2021-09-21}, 
+	urldate = {2022-05-19}, 
 	month={9},
 	publisher={Technical University of Denmark (DTU)},
 	author={Tue Herlau},
-	year={2021},
+	year={2022},
 }
 ```
\ No newline at end of file
diff --git a/devel/WIN-CA01.crt b/devel/WIN-CA01.crt
new file mode 100644
index 0000000000000000000000000000000000000000..b1f8f19988df4b91d8ced1a0896b543544771440
--- /dev/null
+++ b/devel/WIN-CA01.crt
@@ -0,0 +1,37 @@
+-----BEGIN CERTIFICATE-----
+MIIGdzCCBF+gAwIBAgIKYQ00SQABAAAACDANBgkqhkiG9w0BAQsFADBOMQswCQYD
+VQQGEwJESzEmMCQGA1UEChMdRGFubWFya3MgVGVrbmlza2UgVW5pdmVyc2l0ZXQx
+FzAVBgNVBAMTDkRUVSBST09UIENBIDAxMB4XDTE1MTIwMjExMTUzMFoXDTI3MTIw
+MjExMjUzMFowcDESMBAGCgmSJomT8ixkARkWAmRrMRMwEQYKCZImiZPyLGQBGRYD
+ZHR1MRMwEQYKCZImiZPyLGQBGRYDd2luMTAwLgYDVQQDEydBZmRlbGluZ2VuIGZv
+ciBJVCBTZXJ2aWNlIElzc3VpbmcgQ0EgMDEwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQCl/6Yh1HdY8LoufwnRn9yv9Hz9XVb/WNDXIKspbE445PkChBO/
+tAW8LkeJ0BL946uA7i6xLbIgo4QFyDsBXNZxlyS5lPZQmof2fFYOa2dZVbn/FeTA
++dXGCnMUBD47OICTpCwnoaDcl6AfZQ07g5lRKsKY5ccW7BKdjmCtN5/X5zB5Ch5c
+L6NZRpVVNyTTv6iT3syaZJ/W9yNjBamCYFwmIV1CwBVEOK9D3Bz9qmVBeHx+P0h+
+H/oAQ76z0AIlyar/Qm6ssQ5hmaoq/Z/ENpAtQb5+vA7eOxz0KWDe2HCBspxHiv8U
+HaDeKAZHGdY7th23rVUtMvwRv+8XcEdUoz6RAgMBAAGjggIzMIICLzASBgkrBgEE
+AYI3FQEEBQIDAgACMCMGCSsGAQQBgjcVAgQWBBT6pUIFoFyPh3ZYZvYZweN5FXU/
+ljAdBgNVHQ4EFgQUqeIC70e4rIw+GLPlKWwVl3wPoDMwgdoGA1UdIASB0jCBzzCB
+xAYLKwYBBAHYXIN9AwEwgbQwgYYGCCsGAQUFBwICMHoeeABEAGEAbgBtAGEAcgBr
+AHMAIABUAGUAawBuAGkAcwBrAGUAIABVAG4AaQB2AGUAcgBzAGkAdABlAHQAIABD
+AGUAcgB0AGkAZgBpAGMAYQB0AGUAIABQAHIAYQBjAHQAaQBjAGUAIABTAHQAYQB0
+AGUAbQBlAG4AdDApBggrBgEFBQcCARYdaHR0cDovL3BraS53aW4uZHR1LmRrL3Bv
+bGljeS8wBgYEVR0gADAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8E
+BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRBhxqyba/R+PbUjmNc
+jIHYuz5OxTBCBgNVHR8EOzA5MDegNaAzhjFodHRwOi8vcGtpLndpbi5kdHUuZGsv
+RFRVJTIwUk9PVCUyMENBJTIwMDEoMSkuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggr
+BgEFBQcwAoY+aHR0cDovL3BraS53aW4uZHR1LmRrL1dJTi1ST09UQ0EwMV9EVFUl
+MjBST09UJTIwQ0ElMjAwMSgxKS5jcnQwDQYJKoZIhvcNAQELBQADggIBAKd3WjBY
+y2ajwiqfI/GKsIgwJBehkP+4T2TTNDTnbU56IJECFySMQgQk3Qm488w3+EDMU6L5
+RVKBkqUHV/4Xsee3a4r/j+EDbp9EKJIzU8yDV55m0ccnJfaFIWL8RBYE3cjo+D0T
+PxMPzTorNgVU3OhCgyURh8CWoCBXUqyUrF+7qyO/Gqw50ZE5bJnY865AqWmTf37X
+hiDoVOxlcF/cf9DWquU6GgOITxPZj7af8Mssem4SnsE4IGL4H+Qgtfeh+4TE0CZz
+x3ferL88ILU+rdcfvq60OJs5VfyX6HEXNFM8cr2Dt40/Po3D0tca1KEI5LPbkVsp
+w6e1jQzQJ/XV4aPGm153A7tICqM/MbAQO3M57ia+2i5In00JBl1aZd1TRSAnUl5C
+/APaMIuEQ/VVwJ+VcX0/DXAv7i+pAqhmvrrpyBCI73WfBT4EVYuOpmmucnXNXWBy
+kotoBuGuqIbfdb83OaqzAcF2M7ZwzNkGNkZFab+tV4oaOOcuilw1NTRzbHdDTsr9
+lXRcUjdsX5AUnrDQLCsVWtevqJ6CprBwjHszZTGYcj4v26dKPytR8Wg0gRp6gmIi
+MHa5iCMeDuSf0NPTCfNQ+DKjkyUZj07obbQtnvqVU78lvU/KwMIm3ISfN6dD496G
+W5YzizfONrbRtsvQZYlcs0avcW/5G8yOYQeF
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/devel/WIN-CA02.crt b/devel/WIN-CA02.crt
new file mode 100644
index 0000000000000000000000000000000000000000..da6e6331750a5b9e3d91c3a6c042fc8a6c1dbec2
--- /dev/null
+++ b/devel/WIN-CA02.crt
@@ -0,0 +1,37 @@
+-----BEGIN CERTIFICATE-----
+MIIGdzCCBF+gAwIBAgIKYRCS3AABAAAACTANBgkqhkiG9w0BAQsFADBOMQswCQYD
+VQQGEwJESzEmMCQGA1UEChMdRGFubWFya3MgVGVrbmlza2UgVW5pdmVyc2l0ZXQx
+FzAVBgNVBAMTDkRUVSBST09UIENBIDAxMB4XDTE1MTIwMjExMTkxMVoXDTI3MTIw
+MjExMjkxMVowcDESMBAGCgmSJomT8ixkARkWAmRrMRMwEQYKCZImiZPyLGQBGRYD
+ZHR1MRMwEQYKCZImiZPyLGQBGRYDd2luMTAwLgYDVQQDEydBZmRlbGluZ2VuIGZv
+ciBJVCBTZXJ2aWNlIElzc3VpbmcgQ0EgMDIwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQCjfR055ycQHow0JsvgrywYMFnrf0ETzBQ3qhyW4R87m/KOQgBv
+Mn/q3lFGMpFabSxv2auTBe4ZKwOyVbIW1dLNtwBDUZ0Ix1LUUdOlwi83YqmGBObe
+rT7hUmNFvaykDjnizszjLpHIxydsdK368u4oclCTPS2Lb5eMMhanwRNVpDtyeoPB
+TA3hw/yq9yaDqv49D7diqCPxAC6rwTkjTirs4On8y6WSqiRSDP656XMo6NhTk8f1
+dy+8zCvHih7tgzvrAReReR3bbPVx8v3ZIRcRSoKXLXP3wU3bPjHBuOJgSZoI7U+b
+tFq9XIwxWG77PDe7OyGx11297d995CL8CrU7AgMBAAGjggIzMIICLzASBgkrBgEE
+AYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBTMZ8ENgxEXj672axmHA73ZdGc6
+vTAdBgNVHQ4EFgQUBhPbV1NxrI24r7VdZ487d3Ld3D8wgdoGA1UdIASB0jCBzzCB
+xAYLKwYBBAHYXIN9AwEwgbQwgYYGCCsGAQUFBwICMHoeeABEAGEAbgBtAGEAcgBr
+AHMAIABUAGUAawBuAGkAcwBrAGUAIABVAG4AaQB2AGUAcgBzAGkAdABlAHQAIABD
+AGUAcgB0AGkAZgBpAGMAYQB0AGUAIABQAHIAYQBjAHQAaQBjAGUAIABTAHQAYQB0
+AGUAbQBlAG4AdDApBggrBgEFBQcCARYdaHR0cDovL3BraS53aW4uZHR1LmRrL3Bv
+bGljeS8wBgYEVR0gADAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8E
+BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRBhxqyba/R+PbUjmNc
+jIHYuz5OxTBCBgNVHR8EOzA5MDegNaAzhjFodHRwOi8vcGtpLndpbi5kdHUuZGsv
+RFRVJTIwUk9PVCUyMENBJTIwMDEoMSkuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggr
+BgEFBQcwAoY+aHR0cDovL3BraS53aW4uZHR1LmRrL1dJTi1ST09UQ0EwMV9EVFUl
+MjBST09UJTIwQ0ElMjAwMSgxKS5jcnQwDQYJKoZIhvcNAQELBQADggIBAK62o90Z
+QCDB4hsFRi9IoyrgL8fTJS3PTTXSsdnyRoXAQJzzAsWvvg4iTIMjJmpnYffB07Ax
+mAmfJ7mueWVqZ7S0TwZjqgIZJmzzYV44eLn6CUq5Ua5UwaLCv+gsVnz/lR43BWCT
+/heKHq6W64ST2whi4f/uhlaQj5zgsMXPtBgLDRsEvXUlrVHilaU7/4PtheeRGdbY
+hAXnN6qCJlOeZIrgVtvBqG8hoe4f5pqXsJ4hPRKYxBcA1RI1tb6Z20L3f5+ppqNM
+MbOqBTbtRL1IZl0ktLouiOo9/s9rTnDxaFotWp370mGbTqaOuNIxHfhuJC/koaTf
+Z3MyMBduQKRh8UzTrM+vkkYww8kG2+ZvAvUl3v6Co27kl37MGleJtxjNsejLx9A5
+XKSU29pMG/dHtPWRjlBOZXKuGzcs6TzY1i/HPxmGXn2xmXe4Zxt3akJTZJStZ5xu
+4afLprlCYR9Wc7w5FUG6WkrvWBZD9r6UYuQQSknK5KqdL2rymI/4Dp0IYE1ykZXX
+P6DFULwVXIypQVwRY2L+JxBJ8EeUEc8LciJjhKFHf2zYwh2B27zDTIcEMXZPvZ42
+JaWb94x0JkaiKwPGwTO/Qf//yLhpkhTTat1HmfpsQsd8GQosAdG7DmGT2b84Ps5T
+mj11TwBgoKu/qe7tW3wijRQABbjO7EUCtRYq
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/devel/ldap.py b/devel/ldap.py
new file mode 100644
index 0000000000000000000000000000000000000000..fe7ff5b21b91372cca649b3d99eeeb99512fa4dc
--- /dev/null
+++ b/devel/ldap.py
@@ -0,0 +1,52 @@
+# https://wiki.fysik.dtu.dk/it/Exchange_IMAP_Email#exchange-ldap-address-book
+"""
+https://serverfault.com/questions/153526/how-can-i-find-the-ldap-server-in-the-dns-on-windows
+
+
+"""
+from ldap3 import Server, Connection, ALL, NTLM, SIMPLE, SASL
+from ldap3 import ObjectDef
+from pathlib import Path
+
+if __name__ == "__main__":
+
+    home = str(Path.home())
+    server = Server('ait-padfdc20.win.dtu.dk', use_ssl=True)
+    with open(home + "/.passw", 'r') as f:
+        passw = f.read()
+
+    conn = Connection(server, user="win\\tuhe", password=passw, authentication=SIMPLE)
+    bound = conn.bind()
+    print("Did bind?", bound)
+    who = conn.extend.standard.who_am_i()
+    print("Who?", who)
+
+    # person = ObjectDef('inetOrgPerson')
+    # import ldap3
+    # ldap3.Reader(conn, person, 'member=CN=tuhe,OU=xxx,OU=xxxxxx,DC=mydomain,DC=com', 'DC=win,DC=dtu,DC=dk').search()
+    # conn.extend.standard.paged_search
+
+    # con = ldap.initialize('ldap://192.168.16.12:38')
+
+    # user_dn = r"Administrator@foo.com"
+    # password = "bar"
+
+    criteria = "(&(objectClass=user)(sAMAccountName=tuhe))"
+    # attributes = ['displayName', 'company']
+    base = 'DC=win,DC=dtu,DC=dk'
+
+    sr = conn.extend.standard.paged_search(base, search_filter=criteria)
+    sr = list(sr)
+    for r in sr:
+        print(r)
+    print(sr)
+    print( list(conn.search(base, search_filter=criteria)) )
+
+
+    sr = list(sr)
+
+
+
+    # try:
+    #     con.simple_bind_s(user_dn, password)
+    #     res = con.search_s("CN=Users,DC=foo,DC=com", ldap.SCOPE_SUBTREE, '(objectClass=User)')
diff --git a/docker_images/docker_tango_python/Dockerfile b/docker_images/docker_tango_python/Dockerfile
index e081c74465e0b3f3a0e933f16f5a1f180c8740c3..bc05f666f81280d1aac298f1da2d3ac03a37a080 100644
--- a/docker_images/docker_tango_python/Dockerfile
+++ b/docker_images/docker_tango_python/Dockerfile
@@ -29,7 +29,7 @@ RUN chmod +s /usr/bin/autodriver
 
 # Do the python stuff.
 COPY requirements.txt requirements.txt
-RUN pip3 install -r requirements.txt
+RUN pip3 install -r requirements_pip.txt
 
 # Clean up
 WORKDIR /home
diff --git a/docker_images/unitgrade-docker/Dockerfile b/docker_images/unitgrade-docker/Dockerfile
index 98a40077104bfe3274ee00062beac6769934e1a4..c52ab6f4ea333affedd36b0fb78dbeeeac206f1d 100644
--- a/docker_images/unitgrade-docker/Dockerfile
+++ b/docker_images/unitgrade-docker/Dockerfile
@@ -9,7 +9,7 @@ WORKDIR /home
 
 # Remember to include requirements.
 COPY requirements.txt requirements.txt
-RUN pip3 install -r requirements.txt
+RUN pip3 install -r requirements_pip.txt
 
 # Not required.
 # RUN pip install git+https://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git
@@ -17,5 +17,3 @@ RUN pip3 install -r requirements.txt
 COPY . .
 
 ADD . /home
-
-# CMD [ "python3", "app.py"]
diff --git a/docs/build_docs.py b/docs/build_docs.py
index 057ace96f77e36f3d0ff31a90a0b30d50d7c1042..5d048587f3ff74a2625953424caf923b8ac7d341 100644
--- a/docs/build_docs.py
+++ b/docs/build_docs.py
@@ -39,19 +39,9 @@ if __name__ == "__main__":
         f.write(out)
     # os.system("cd ../examples/example_framework/instructor && python -m cs102.report2_grade")
 
-
     data['bibtex'] = bib
     data = {**data, **dump_data("../examples")}
 
-    # import glob
-    # fls = [f for f in glob.glob("../examples/**/*.*", recursive=True) if f.split(".")[-1] in ["py", "txt", "shell"] ]
-    # import os
-    # # fls = [(f, ) for f in fls]
-    # for file in fls:
-    #     with open(file, 'r') as f:
-    #         k = os.path.relpath(file, "../examples").replace(os.sep, "_").replace(".", "_")
-    #         data[k] = f.read()
-
     data['resources'] = "https://gitlab.compute.dtu.dk/tuhe/unitgrade_private/-/raw/master"
 
     with open("README.jinja.md", 'r') as f:
diff --git a/docs/legacy/unitgrade_private_v1/example/report0.py b/docs/legacy/unitgrade_private_v1/example/report0.py
index 863d17094b2826da9baaa217792bb1cd6bc53a4e..9d59d490442555053a2e27fbce948d256bc2dffc 100644
--- a/docs/legacy/unitgrade_private_v1/example/report0.py
+++ b/docs/legacy/unitgrade_private_v1/example/report0.py
@@ -33,7 +33,7 @@ class Week1QuestionGroup(QuestionGroup):
 
     class GraphTraversalQuestion(QPrintItem):
         def compute_answer_print(self):
-            from irlc.ex01 import graph_traversal
+            from irlc.ex02 import graph_traversal
             graph_traversal.main()
 
 class Report0(Report):
diff --git a/docs/snips/deploy.txt b/docs/snips/deploy.txt
index 0d01cfbe6e356a10ddc1c7628e37b668676744d5..75ee795725390c0c21c4a512509326283f9be95b 100644
--- a/docs/snips/deploy.txt
+++ b/docs/snips/deploy.txt
@@ -3,7 +3,7 @@
 | | | |_ __  _| |_| |  \/_ __ __ _  __| | ___ 
 | | | | '_ \| | __| | __| '__/ _` |/ _` |/ _ \
 | |_| | | | | | |_| |_\ \ | | (_| | (_| |  __/
- \___/|_| |_|_|\__|\____/_|  \__,_|\__,_|\___| v0.1.17, started: 21/09/2021 11:57:05
+ \___/|_| |_|_|\__|\____/_|  \__,_|\__,_|\___| v0.1.17, started: 19/05/2022 15:14:09
 
 CS 102 Report 2 
 Question 1: Week1                                                                                                       
@@ -17,7 +17,7 @@ Question 2: The same problem as before with nicer titles
  * q2.2) Checking if reverse_list([1, 2, 3]) = [3, 2, 1]............................................................PASS
  * q2)   Total...................................................................................................... 6/6
  
-Total points at 11:57:05 (0 minutes, 0 seconds)....................................................................16/16
+Total points at 15:14:09 (0 minutes, 0 seconds)....................................................................16/16
 
 Including files in upload...
  * cs102
diff --git a/docs/unitgrade_devel.bib b/docs/unitgrade_devel.bib
index f3941047d59ed8aa396027808679f10edb675467..80336770757d705c517e5a04e319b015909559fc 100644
--- a/docs/unitgrade_devel.bib
+++ b/docs/unitgrade_devel.bib
@@ -1,9 +1,9 @@
 @online{unitgrade_devel,
-	title={Unitgrade-devel (0.1.27): \texttt{pip install unitgrade-devel}},
+	title={Unitgrade-devel (0.1.35): \texttt{pip install unitgrade-devel}},
 	url={https://lab.compute.dtu.dk/tuhe/unitgrade_private},
-	urldate = {2021-09-21}, 
+	urldate = {2022-05-19}, 
 	month={9},
 	publisher={Technical University of Denmark (DTU)},
 	author={Tue Herlau},
-	year={2021},
+	year={2022},
 }
\ No newline at end of file
diff --git a/examples/autolab_example/docker/docker_tango_python/Dockerfile b/examples/autolab_example/docker/docker_tango_python/Dockerfile
index e081c74465e0b3f3a0e933f16f5a1f180c8740c3..bc05f666f81280d1aac298f1da2d3ac03a37a080 100644
--- a/examples/autolab_example/docker/docker_tango_python/Dockerfile
+++ b/examples/autolab_example/docker/docker_tango_python/Dockerfile
@@ -29,7 +29,7 @@ RUN chmod +s /usr/bin/autodriver
 
 # Do the python stuff.
 COPY requirements.txt requirements.txt
-RUN pip3 install -r requirements.txt
+RUN pip3 install -r requirements_pip.txt
 
 # Clean up
 WORKDIR /home
diff --git a/examples/autolab_example/docker/unitgrade-docker/Dockerfile b/examples/autolab_example/docker/unitgrade-docker/Dockerfile
index 98a40077104bfe3274ee00062beac6769934e1a4..aad55aecf971805af81098925cf0f526446d1037 100644
--- a/examples/autolab_example/docker/unitgrade-docker/Dockerfile
+++ b/examples/autolab_example/docker/unitgrade-docker/Dockerfile
@@ -9,7 +9,7 @@ WORKDIR /home
 
 # Remember to include requirements.
 COPY requirements.txt requirements.txt
-RUN pip3 install -r requirements.txt
+RUN pip3 install -r requirements_pip.txt
 
 # Not required.
 # RUN pip install git+https://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git
diff --git a/examples/example_docker/instructor/docker/docker_tango_python/Dockerfile b/examples/example_docker/instructor/docker/docker_tango_python/Dockerfile
index e081c74465e0b3f3a0e933f16f5a1f180c8740c3..bc05f666f81280d1aac298f1da2d3ac03a37a080 100644
--- a/examples/example_docker/instructor/docker/docker_tango_python/Dockerfile
+++ b/examples/example_docker/instructor/docker/docker_tango_python/Dockerfile
@@ -29,7 +29,7 @@ RUN chmod +s /usr/bin/autodriver
 
 # Do the python stuff.
 COPY requirements.txt requirements.txt
-RUN pip3 install -r requirements.txt
+RUN pip3 install -r requirements_pip.txt
 
 # Clean up
 WORKDIR /home
diff --git a/examples/example_docker/instructor/docker/unitgrade-docker/Dockerfile b/examples/example_docker/instructor/docker/unitgrade-docker/Dockerfile
index 98a40077104bfe3274ee00062beac6769934e1a4..aad55aecf971805af81098925cf0f526446d1037 100644
--- a/examples/example_docker/instructor/docker/unitgrade-docker/Dockerfile
+++ b/examples/example_docker/instructor/docker/unitgrade-docker/Dockerfile
@@ -9,7 +9,7 @@ WORKDIR /home
 
 # Remember to include requirements.
 COPY requirements.txt requirements.txt
-RUN pip3 install -r requirements.txt
+RUN pip3 install -r requirements_pip.txt
 
 # Not required.
 # RUN pip install git+https://git@gitlab.compute.dtu.dk/tuhe/unitgrade.git
diff --git a/examples/mat1.py b/examples/mat1.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/examples/mat1/instructor/mat1/homework1.py b/examples/mat1/instructor/mat1/homework1.py
new file mode 100644
index 0000000000000000000000000000000000000000..125b6787fd8c9fe7ab427bc1b29f858b97573ed2
--- /dev/null
+++ b/examples/mat1/instructor/mat1/homework1.py
@@ -0,0 +1,21 @@
+def derivative(f, x, epsilon):
+    '''
+    Given a function f, a point x, and a (small) number epsilon, this function should approximately compute the derivative
+    $$
+    df/dx \approx (f(x + \epsilon) - f(x))/\epsilon
+    $$
+    :param f:
+    :param x:
+    :param epsilon:
+    :return:
+    '''
+    return (f(x+epsilon)-f(x))/epsilon
+
+def riemann_integrate_xs(a, b, N):
+
+    pass
+
+
+def riemann_integrate(f, a, b, N):
+
+    pass
diff --git a/src/unitgrade_devel.egg-info/PKG-INFO b/src/unitgrade_devel.egg-info/PKG-INFO
index c24c14555aa8fdd79cede48d7bd49653ffaca96d..2cbaf6ab99e73c7e0222c60ce812d7b47be34a62 100644
--- a/src/unitgrade_devel.egg-info/PKG-INFO
+++ b/src/unitgrade_devel.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: unitgrade-devel
-Version: 0.1.28
+Version: 0.1.35
 Summary: A set of tools to develop unitgrade tests and reports and later evaluate them
 Home-page: https://lab.compute.dtu.dk/tuhe/unitgrade_private
 Author: Tue Herlau
diff --git a/src/unitgrade_private/docker_helpers.py b/src/unitgrade_private/docker_helpers.py
index 1e77a6aa1061c2ded4df8efed4af4b9df5779c78..4c53c4d6cdcd5f28f1f53de004c6e39a3de252ba 100644
--- a/src/unitgrade_private/docker_helpers.py
+++ b/src/unitgrade_private/docker_helpers.py
@@ -101,8 +101,14 @@ def student_token_file_runner(host_tmp_dir, student_token_file, instructor_grade
     return pycom, token_location
 
 
-def docker_run_token_file(Dockerfile_location, host_tmp_dir, student_token_file, tag=None, instructor_grade_script=None, fix_user=None):
+
+def docker_run_token_file(Dockerfile_location, host_tmp_dir, student_token_file, tag=None, instructor_grade_script=None,
+                          fix_user=None,
+                          # grade_script_relative_destination_dir=None, # The relative location relative to the top-dir containing the package. Example: irlc/project1
+                          xvfb=True):
     """
+    xvfb: Control whether to use X-windows. Works on linux. This seems like a good idea when using e.g. gym.
+
     This thingy works:
 
     To build the image, run:
@@ -113,6 +119,10 @@ def docker_run_token_file(Dockerfile_location, host_tmp_dir, student_token_file,
     docker run -v c:/Users/tuhe/Documents/2021/python-docker/tmp:/app python-docker > output.log
 
     """
+    Dockerfile_location = Dockerfile_location.replace("\\", "/")
+    host_tmp_dir = host_tmp_dir.replace("\\", "/")
+    student_token_file = student_token_file.replace("\\", "/")
+
     # A bunch of tests. This is going to be great!
     Dockerfile_location = os.path.abspath(Dockerfile_location)
     assert os.path.exists(Dockerfile_location)
@@ -138,8 +148,19 @@ def docker_run_token_file(Dockerfile_location, host_tmp_dir, student_token_file,
     # Done extracting the zip file! Now time to move the (good) report test class into the location.
     gscript = instructor_grade_script
 
-    student_grade_script = host_tmp_dir + "/" + sources['report_relative_location']
-    instructor_grade_script = os.path.dirname(student_grade_script) + "/"+os.path.basename(gscript)
+    # if grade_script_relative_destination_dir is None:
+    #     student_grade_script = host_tmp_dir + "/" + sources['report_relative_location']
+    # else:
+    #     student_grade_script = host_tmp_dir + "/" + grade_script_relative_destination_dir
+    # Get relative location from first line of the grade script.
+    with open(instructor_grade_script, 'r') as f:
+        student_grade_script_dir = os.path.dirname( host_tmp_dir + "/" + f.read().splitlines()[0][1:].strip() )
+    print("student_grade_script", student_grade_script_dir)
+
+
+
+    student_grade_script_dir = student_grade_script_dir.replace("\\", "/")
+    instructor_grade_script = student_grade_script_dir + "/"+os.path.basename(gscript)
     shutil.copy(gscript, instructor_grade_script)
 
     """
@@ -151,14 +172,20 @@ def docker_run_token_file(Dockerfile_location, host_tmp_dir, student_token_file,
         dockname = tag
 
     tmp_grade_file =  sources['name'] + "/" + sources['report_relative_location']
+    tmp_grade_file = tmp_grade_file.replace("\\", "/")
 
-    pycom = ".".join( sources['report_module_specification'][:-1] + [os.path.basename(gscript)[:-3],] )
+    # pycom = ".".join( sources['report_module_specification'][:-1] + [os.path.basename(gscript)[:-3],] )
+    pycom = ".".join(os.path.relpath(instructor_grade_script, host_tmp_dir)[:-3].split("/"))
     pycom = "python3 -m " + pycom
 
     if fix_user:
         user_cmd = ' --user "$(id -u):$(id -g)" '
     else:
         user_cmd = ''
+
+    if xvfb:
+        user_cmd = " -e DISPLAY=:0 -v /tmp/.X11-unix:/tmp/.X11-unix " + user_cmd
+
     tmp_path = os.path.abspath(host_tmp_dir).replace("\\", "/")
     dcom = f"docker run {user_cmd} -v {tmp_path}:/home {dockname} {pycom}"
     cdcom = f"cd {os.path.dirname(Dockerfile_location)}"
@@ -167,11 +194,14 @@ def docker_run_token_file(Dockerfile_location, host_tmp_dir, student_token_file,
     print(fcom)
     init = time.time() - start
     # thtools.execute_command(fcom.split())
-    subprocess.check_output(fcom, shell=True).decode("utf-8")
+    out = subprocess.check_output(fcom, shell=True).decode("utf-8")
     host_tmp_dir +"/" + os.path.dirname(tmp_grade_file) + "/"
     tokens = glob.glob( os.path.dirname(instructor_grade_script) + "/*.token" )
     for t in tokens:
         print("Source image produced token", t)
     elapsed = time.time() - start
     print("Elapsed time is", elapsed, f"({init=} seconds)")
+    if len(tokens) != 1:
+        print("Wrong number of tokens produced:", len(tokens))
+        print(out)
     return tokens[0]
diff --git a/src/unitgrade_private/hidden_create_files.py b/src/unitgrade_private/hidden_create_files.py
index eb5f894db31cb8b5465e2bbf3a69977ae16ced2e..96a74400340ce056d7684b8da80e9b0071f640e8 100644
--- a/src/unitgrade_private/hidden_create_files.py
+++ b/src/unitgrade_private/hidden_create_files.py
@@ -92,6 +92,12 @@ def setup_grade_file_report(ReportClass, execute=False, obfuscate=False, minify=
                                                        'head': pyhead})
     output = fn[:-3] + "_grade.py"
     print("> Writing student script to", output, "(this script may be shared)")
+    # Add the relative location string:
+
+    # Add relative location to first line of file. Important for evaluation/sanity-checking.
+    report_relative_dir = report._import_base_relative()[1]
+    s = "# " + report_relative_dir + "\n" + s
+
     with open(output, 'w', encoding="utf-8") as f:
         f.write(s)
 
@@ -114,6 +120,7 @@ def setup_grade_file_report(ReportClass, execute=False, obfuscate=False, minify=
         wa = """ WARNING: Modifying, decompiling or otherwise tampering with this script, it's data or the resulting .token file will be investigated as a cheating attempt. """
         sauce = ["'''" + wa + "'''"] + sauce[:-1]
         sauce = "\n".join(sauce)
+        sauce = "# " + report_relative_dir + "\n" + sauce
         with open(output, 'w') as f:
             f.write(sauce)
 
@@ -124,3 +131,4 @@ def setup_grade_file_report(ReportClass, execute=False, obfuscate=False, minify=
         s = os.path.basename(fn)[:-3] + "_grade"
         exec("import " + s)
     a = 234
+    print("====== EXECUTION AND PACKING OF REPORT IS COMPLETE ======")
diff --git a/src/unitgrade_private/hidden_gather_upload.py b/src/unitgrade_private/hidden_gather_upload.py
index 835227ac1f899f7d7f800eb501a87cd705d1deda..cdfd77a89f9b1a61287fc39934b6eeb644c95c56 100644
--- a/src/unitgrade_private/hidden_gather_upload.py
+++ b/src/unitgrade_private/hidden_gather_upload.py
@@ -23,7 +23,14 @@ def gather_imports(imp):
         top_package = os.path.dirname(m.__file__)
         module_import = True
     else:
-        top_package = __import__(m.__name__.split('.')[0]).__path__._path[0]
+        im = __import__(m.__name__.split('.')[0])
+        if isinstance(im, list):
+            print("im is a list")
+            print(im)
+        # the __path__ attribute *may* be a string in some cases. I had to fix this.
+        print("path.:",  __import__(m.__name__.split('.')[0]).__path__)
+        # top_package = __import__(m.__name__.split('.')[0]).__path__._path[0]
+        top_package = __import__(m.__name__.split('.')[0]).__path__[0]
         module_import = False
 
     found_hashes = {}
@@ -89,13 +96,24 @@ def gather_report_source_include(report):
             print(f" * {m.__name__}")
     return sources
 
-
-def gather_upload_to_campusnet(report, output_dir=None):
+# def report_script_relative_location(report):
+#     """
+#     Given the grade script corresponding to the 'report', work out it's relative location either compared to the
+#     package it is in or directory.
+#     """
+#     if len(report.individual_imports) == 0:
+#         return "./"
+#     else:
+#
+#     pass
+
+def gather_upload_to_campusnet(report, output_dir=None, token_include_plaintext_source=False):
     # n = report.nL
     args = parser.parse_args()
     results, table_data = evaluate_report(report, show_help_flag=False, show_expected=False, show_computed=False, silent=True,
                                           show_progress_bar=not args.noprogress,
-                                          big_header=not args.autolab)
+                                          big_header=not args.autolab,
+                                          )
     print("")
     sources = {}
     if not args.autolab:
@@ -123,10 +141,11 @@ def gather_upload_to_campusnet(report, output_dir=None):
             if hash in known_hashes and f_rel not in cov_files and use_coverage:
                 print("Skipping", f_rel)
             else:
-                s_include.append("#"*3 +" Content of " + f_rel +" " + "#"*3)
-                s_include.append("")
-                s_include.append(s['pycode'][f_rel])
-                s_include.append("")
+                if token_include_plaintext_source:
+                    s_include.append("#"*3 +" Content of " + f_rel +" " + "#"*3)
+                    s_include.append("")
+                    s_include.append(s['pycode'][f_rel])
+                    s_include.append("")
 
     if output_dir is None:
         output_dir = os.getcwd()
diff --git a/src/unitgrade_private/plagiarism/mossit.py b/src/unitgrade_private/plagiarism/mossit.py
index 17845d622a8f275d7ba28e0fba28e58af73f2e99..86af6c7feebde9760b969f9565b051595f033706 100644
--- a/src/unitgrade_private/plagiarism/mossit.py
+++ b/src/unitgrade_private/plagiarism/mossit.py
@@ -4,10 +4,12 @@ import shutil
 import glob
 from unitgrade_private.token_loader import unpack_sources_from_token
 import mosspy
+import fnmatch
 
-
-def moss_prepare(whitelist_dir, submission_dir):
+def moss_prepare(whitelist_dir, submission_dir, blacklist=None):
     # Get all whitelist hashes.
+    if blacklist == None:
+        blacklist = []
     moss_tmp_dir = os.path.dirname(os.path.abspath(whitelist_dir)) + "/tmp"
     if os.path.isdir(moss_tmp_dir):
         shutil.rmtree(moss_tmp_dir, ignore_errors=True)
@@ -19,10 +21,13 @@ def moss_prepare(whitelist_dir, submission_dir):
     white_hashes = set()
     for k, py in enumerate(pys):
         id = file_id(py)
-        print("> Whitelisting", py)
+
         if id not in white_hashes:
             white_hashes.add(id)
-            shutil.copy(py, tmp_base + f"/{k}_" + os.path.basename(py))
+            if not fnmatch.fnmatch(py, "*_grade.py"):
+                # if fnmatch.fnmatch(py, "*fruit_homework.py"):
+                print("> Whitelisting", py)
+                shutil.copy(py, tmp_base + f"/{k}_" + os.path.basename(py))
 
 
     tmp_submission_dir = moss_tmp_dir + "/submissions"
@@ -33,11 +38,10 @@ def moss_prepare(whitelist_dir, submission_dir):
 
         pys = glob.glob(student_dir + "/**/*.py", recursive=True)
         for k, py in enumerate(pys):
-            if file_id(py) in white_hashes:
+            if file_id(py) in white_hashes or any([fnmatch.fnmatch(py, b) for b in blacklist]):
                 continue
             print("> Including", py)
             shutil.copy(py, tmp_student_dir + f"/{k}_" + os.path.basename(py))
-
     return tmp_base, tmp_submission_dir
 
 
@@ -53,11 +57,12 @@ def get_id(moss_pl):
     return pl.split("=")[1][:-1]
 
 
-def moss_it(whitelist_dir="", submissions_dir="", moss_id=None):
+def moss_it(whitelist_dir="", submissions_dir="", moss_id=None, blacklist=None):
     whitelist_dir = os.path.abspath(whitelist_dir)
     ensure_tokens_unpacked(whitelist_dir)
     ensure_tokens_unpacked(submissions_dir)
-    tmp_base, tmp_submission_dir = moss_prepare(whitelist_dir, submissions_dir)
+    print("> moss_prepare", whitelist_dir, submissions_dir)
+    tmp_base, tmp_submission_dir = moss_prepare(whitelist_dir, submissions_dir, blacklist=blacklist)
 
     userid = int(moss_id)
     m = mosspy.Moss(userid, "python")
@@ -65,6 +70,7 @@ def moss_it(whitelist_dir="", submissions_dir="", moss_id=None):
         m.addBaseFile(f)
 
     m.addFilesByWildcard(tmp_submission_dir + "/*/*.py")
+    print("> Calling moss")
     url = m.send(lambda file_path, display_name: print('*', end='', flush=True))
     print()
     print("Report Url: " + url)
diff --git a/src/unitgrade_private/version.py b/src/unitgrade_private/version.py
index 32eedaa05654cc78c0ec385c0529a2a4a668d89d..ea21bff46bc3df21060c3e5ef352215638af595e 100644
--- a/src/unitgrade_private/version.py
+++ b/src/unitgrade_private/version.py
@@ -1 +1 @@
-version = "0.1.28"
+version = "0.1.35"