kthutils 1.40__tar.gz → 1.41__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. {kthutils-1.40 → kthutils-1.41}/PKG-INFO +1 -1
  2. {kthutils-1.40 → kthutils-1.41}/pyproject.toml +1 -1
  3. {kthutils-1.40 → kthutils-1.41}/src/kthutils/forms.nw +108 -24
  4. {kthutils-1.40 → kthutils-1.41}/LICENSE +0 -0
  5. {kthutils-1.40 → kthutils-1.41}/README.md +0 -0
  6. {kthutils-1.40 → kthutils-1.41}/makefiles/.circleci/config.yml +0 -0
  7. {kthutils-1.40 → kthutils-1.41}/makefiles/.git +0 -0
  8. {kthutils-1.40 → kthutils-1.41}/makefiles/.gitignore +0 -0
  9. {kthutils-1.40 → kthutils-1.41}/makefiles/CONTRIBUTING.md +0 -0
  10. {kthutils-1.40 → kthutils-1.41}/makefiles/Dockerfile +0 -0
  11. {kthutils-1.40 → kthutils-1.41}/makefiles/Dockerfile.nw +0 -0
  12. {kthutils-1.40 → kthutils-1.41}/makefiles/LICENSE +0 -0
  13. {kthutils-1.40 → kthutils-1.41}/makefiles/Makefile +0 -0
  14. {kthutils-1.40 → kthutils-1.41}/makefiles/Makefile.nw +0 -0
  15. {kthutils-1.40 → kthutils-1.41}/makefiles/README.md +0 -0
  16. {kthutils-1.40 → kthutils-1.41}/makefiles/doc.mk +0 -0
  17. {kthutils-1.40 → kthutils-1.41}/makefiles/doc.mk.nw +0 -0
  18. {kthutils-1.40 → kthutils-1.41}/makefiles/exam.bib +0 -0
  19. {kthutils-1.40 → kthutils-1.41}/makefiles/exam.mk +0 -0
  20. {kthutils-1.40 → kthutils-1.41}/makefiles/exam.mk.nw +0 -0
  21. {kthutils-1.40 → kthutils-1.41}/makefiles/haskell.mk +0 -0
  22. {kthutils-1.40 → kthutils-1.41}/makefiles/haskell.mk.nw +0 -0
  23. {kthutils-1.40 → kthutils-1.41}/makefiles/intro.tex +0 -0
  24. {kthutils-1.40 → kthutils-1.41}/makefiles/latexmkrc +0 -0
  25. {kthutils-1.40 → kthutils-1.41}/makefiles/makefiles.bib +0 -0
  26. {kthutils-1.40 → kthutils-1.41}/makefiles/makefiles.tex +0 -0
  27. {kthutils-1.40 → kthutils-1.41}/makefiles/miun.course.mk +0 -0
  28. {kthutils-1.40 → kthutils-1.41}/makefiles/miun.depend.mk +0 -0
  29. {kthutils-1.40 → kthutils-1.41}/makefiles/miun.docs.mk +0 -0
  30. {kthutils-1.40 → kthutils-1.41}/makefiles/miun.port.mk +0 -0
  31. {kthutils-1.40 → kthutils-1.41}/makefiles/miun.pub.mk +0 -0
  32. {kthutils-1.40 → kthutils-1.41}/makefiles/noweb.mk +0 -0
  33. {kthutils-1.40 → kthutils-1.41}/makefiles/noweb.mk.nw +0 -0
  34. {kthutils-1.40 → kthutils-1.41}/makefiles/pkg.mk +0 -0
  35. {kthutils-1.40 → kthutils-1.41}/makefiles/pkg.mk.nw +0 -0
  36. {kthutils-1.40 → kthutils-1.41}/makefiles/portability.mk +0 -0
  37. {kthutils-1.40 → kthutils-1.41}/makefiles/portability.mk.nw +0 -0
  38. {kthutils-1.40 → kthutils-1.41}/makefiles/preamble.tex +0 -0
  39. {kthutils-1.40 → kthutils-1.41}/makefiles/pub.mk +0 -0
  40. {kthutils-1.40 → kthutils-1.41}/makefiles/pub.mk.nw +0 -0
  41. {kthutils-1.40 → kthutils-1.41}/makefiles/results.mk +0 -0
  42. {kthutils-1.40 → kthutils-1.41}/makefiles/results.mk.nw +0 -0
  43. {kthutils-1.40 → kthutils-1.41}/makefiles/subdir.mk +0 -0
  44. {kthutils-1.40 → kthutils-1.41}/makefiles/subdir.mk.nw +0 -0
  45. {kthutils-1.40 → kthutils-1.41}/makefiles/tex.bib +0 -0
  46. {kthutils-1.40 → kthutils-1.41}/makefiles/tex.mk +0 -0
  47. {kthutils-1.40 → kthutils-1.41}/makefiles/tex.mk.nw +0 -0
  48. {kthutils-1.40 → kthutils-1.41}/makefiles/transform.bib +0 -0
  49. {kthutils-1.40 → kthutils-1.41}/makefiles/transform.mk +0 -0
  50. {kthutils-1.40 → kthutils-1.41}/makefiles/transform.mk.nw +0 -0
  51. {kthutils-1.40 → kthutils-1.41}/src/kthutils/CLAUDE.md +0 -0
  52. {kthutils-1.40 → kthutils-1.41}/src/kthutils/Makefile +0 -0
  53. {kthutils-1.40 → kthutils-1.41}/src/kthutils/__init__.py +0 -0
  54. {kthutils-1.40 → kthutils-1.41}/src/kthutils/cli.nw +0 -0
  55. {kthutils-1.40 → kthutils-1.41}/src/kthutils/iprange.nw +0 -0
  56. {kthutils-1.40 → kthutils-1.41}/src/kthutils/onedrive.nw +0 -0
  57. {kthutils-1.40 → kthutils-1.41}/src/kthutils/participants.nw +0 -0
  58. {kthutils-1.40 → kthutils-1.41}/src/kthutils/stayawhile.nw +0 -0
  59. {kthutils-1.40 → kthutils-1.41}/src/kthutils/ug.nw +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kthutils
3
- Version: 1.40
3
+ Version: 1.41
4
4
  Summary: Various tools for automation at KTH
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kthutils"
3
- version = "1.40"
3
+ version = "1.41"
4
4
  description = "Various tools for automation at KTH"
5
5
  authors = [
6
6
  {name="Daniel Bosk", email="dbosk@kth.se"}
@@ -819,46 +819,90 @@ courses in the same way; only the columns the fields live in differ.
819
819
  We therefore lift the body of the format string into its own chunk,
820
820
  [[<<rewriter format string body>>]], so both rewriters share a single
821
821
  definition and cannot drift apart when we tweak the reporting commands.
822
+
823
+ The body passes through two interpreters before it runs:
824
+ first the shell executing this setup script, then Python's [[str.format]]
825
+ when a row is rewritten.
826
+ Each has its own escaping (backslashes and [[$]] for the shell, doubled
827
+ braces for [[format]]), and the body is a shell script itself, full of
828
+ [[$]], quotes and line continuations.
829
+ Escaping all of that for the setup-time shell would make the chunk
830
+ unreadable and easy to break.
831
+ We therefore feed the body through a quoted heredoc: with the delimiter
832
+ quoted, the shell copies the text verbatim into the configuration, so the
833
+ chunk below is exactly what ends up in the config and only the [[format]]
834
+ braces remain to think about.
822
835
  <<[[restlabbsetup.sh]]>>=
823
- kthutils config ${RESTLABB_REWRITER}.format_string -s "
836
+ kthutils config ${RESTLABB_REWRITER}.format_string -s "$(cat <<'_EOF'
824
837
  <<rewriter format string body>>
825
- "
838
+ _EOF
839
+ )"
826
840
  @
827
841
 
828
- The body echoes the parsed fields for the log, then---unless the grader
842
+ The body echoes the fields for the log, then---unless the grader
829
843
  already reported the result in Canvas---runs [[canvaslms grade]], and
830
844
  finally reports the aggregated result to Ladok.
845
+
846
+ The fields that go through a regex (the course nick and year, the student,
847
+ the grader, and the grade, assignment and assignment group extracted from
848
+ the module column) can be mis-parsed when a TA writes something we did not
849
+ anticipate.
850
+ When that happens we fix the value by hand before running the commands, and
851
+ we want to fix it in one place, not at every use.
852
+ So each such field is assigned to a shell variable once and the commands
853
+ only use the variable.
854
+ Fields copied verbatim from a column, such as [[in_canvas]] (\enquote{Ja}
855
+ or \enquote{Nej}) and [[passed]], cannot be mis-parsed, so they stay inline.
856
+
857
+ We place each assignment directly after the echo of the raw column it was
858
+ parsed from: the reader then sees the input and the parsed result next to
859
+ each other and can check the parse on the spot.
860
+ The module column feeds three parsed fields, so all three follow the
861
+ [[Module:]] line.
862
+ The student and grader columns are only used through their regexes, so
863
+ there the assignment comes first and the echo prints the variable.
864
+ Assigned values are single-quoted since a parsed value can contain shell
865
+ metacharacters (an assignment reads [[Laboration .(1|2).]]), and every use
866
+ is double-quoted for the same reason.
867
+ Since all rows are printed into one stream, each block re-assigns every
868
+ variable before use, so the blocks stay independent when pasted together.
831
869
  <<rewriter format string body>>=
832
870
  echo 'Time: {time}'
833
871
  echo 'Course: {course}'
872
+ course='{course_nick}{year}'
834
873
  echo 'Module: {module}'
835
- echo 'Student: {student}'
874
+ assignment='{assignment}'
875
+ assignment_group='{assignment_group}'
876
+ grade='{grade}'
877
+ student='{student}'
878
+ echo "Student: $student"
836
879
  echo 'Passed: {passed}'
837
880
  echo 'Comments: {comments}'
838
- echo 'Grader: {grader}'
881
+ grader='{grader}'
882
+ echo "Grader: $grader"
839
883
  echo 'In Canvas: {in_canvas}'
840
884
  if [ '{in_canvas}' = 'Ja' ]; then
841
885
  echo 'Already in Canvas.'
842
886
  elif [ '{passed}' = 'Godkänt' ]; then
843
- canvaslms grade \\
844
- -c '{course_nick}{year}' \\
845
- -a '{assignment}' \\
846
- -u '{student}' \\
847
- -g '{grade}' \\
848
- -m 'OK {grader}'
887
+ canvaslms grade \
888
+ -c "$course" \
889
+ -a "$assignment" \
890
+ -u "$student" \
891
+ -g "$grade" \
892
+ -m "OK $grader"
849
893
  fi
850
894
  echo 'Canvas:'
851
- canvaslms submissions \\
852
- -c '{course_nick}{year}' \\
853
- -a '{assignment}' \\
854
- -u '{student}'
895
+ canvaslms submissions \
896
+ -c "$course" \
897
+ -a "$assignment" \
898
+ -u "$student"
855
899
  echo
856
- canvaslms results \\
857
- -c '{course_nick}{year}' \\
858
- -A '{assignment_group}' \\
859
- -u '{student}' \\
860
- -S canvaslms.grades.conjunctavgsurvey \\
861
- | sed -E 's/ ?[HV]T[0-9]{{2}}( \(.*\))?//' \\
900
+ canvaslms results \
901
+ -c "$course" \
902
+ -A "$assignment_group" \
903
+ -u "$student" \
904
+ -S canvaslms.grades.conjunctavgsurvey \
905
+ | sed -E 's/ ?[HV]T[0-9]{{2}}( \(.*\))?//' \
862
906
  | ladok -v report -f
863
907
  echo
864
908
  @
@@ -1154,7 +1198,7 @@ Each June we also run a \emph{lab week}, recording results in a separate KTH
1154
1198
  Form that we always name [[labweek<YEAR>]] (e.g.\ [[labweek2026]]).
1155
1199
  It reports results for the same courses in the same way as Restlabb, so it
1156
1200
  reuses the same format string and the same regexes.
1157
- Only two things differ, and both follow from the form's layout.
1201
+ Only three things differ, and all follow from the form's layout.
1158
1202
 
1159
1203
  First, the columns sit in different positions.
1160
1204
  The lab week form lets a pair of students redovisa together, so on top of the
@@ -1175,13 +1219,20 @@ regexes---one extracting the first address, one the second---is joined into a
1175
1219
  parenthesised alternation when both match (and left as a bare address when
1176
1220
  only the first does).
1177
1221
 
1222
+ Third, the lab week form has no \enquote{passed?} question:
1223
+ the TAs only record results that passed, so every row is a pass.
1224
+ The shared format string still tests [[{passed}]] against \enquote{Godkänt}
1225
+ before grading in Canvas, so we supply that as a fixed [[value]] rather than
1226
+ from a column (see the [[value]] substitution in \cref{RewriteRow}).
1227
+
1178
1228
  We clear any previous configuration first, in case we re-run these lines after
1179
1229
  an update.
1180
1230
  <<[[restlabbsetup.sh]]>>=
1181
1231
  kthutils config ${LABWEEK_REWRITER} -s ""
1182
- kthutils config ${LABWEEK_REWRITER}.format_string -s "
1232
+ kthutils config ${LABWEEK_REWRITER}.format_string -s "$(cat <<'_EOF'
1183
1233
  <<rewriter format string body>>
1184
- "
1234
+ _EOF
1235
+ )"
1185
1236
  @
1186
1237
 
1187
1238
  The verbatim columns and the shared regexes only differ from Restlabb in their
@@ -1204,6 +1255,7 @@ kthutils config ${LABWEEK_REWRITER}.substitutions.year.column -s 8
1204
1255
  kthutils config ${LABWEEK_REWRITER}.substitutions.year.regex \
1205
1256
  -s "<<year regex>>"
1206
1257
  kthutils config ${LABWEEK_REWRITER}.substitutions.in_canvas.column -s 12
1258
+ kthutils config ${LABWEEK_REWRITER}.substitutions.passed.value -s Godkänt
1207
1259
  kthutils config ${LABWEEK_REWRITER}.substitutions.grade.column -s 10
1208
1260
  kthutils config ${LABWEEK_REWRITER}.substitutions.grade.regex \
1209
1261
  <<grade regexes>>
@@ -1279,6 +1331,13 @@ else
1279
1331
  fi
1280
1332
  @
1281
1333
 
1334
+ When we extract the restlabb results, we must be logged into KTH.
1335
+ We can trigger that log in with [[ladok]].
1336
+ <<[[restlabb.sh]]>>=
1337
+ ME=de709f81-a867-11e7-8dbf-78e86dc2470c
1338
+ until ladok student $ME 2>&1 >/dev/null; do true; done
1339
+ @ This way we will not proceed until the MFA login has succeeded.
1340
+
1282
1341
  Next we extract the new values since the last run (using the [[next]] command)
1283
1342
  and [[grep]] for the relevant lines:
1284
1343
  namely those mentioning me or any of my courses.
@@ -1456,6 +1515,7 @@ delimiter: Annotated[str, delimiter_arg] = "\t",
1456
1515
  @
1457
1516
 
1458
1517
  \subsubsection{Rewriting a row}
1518
+ \label{RewriteRow}
1459
1519
 
1460
1520
  Now we want to do the actual rewriting.
1461
1521
  <<API for KTH Forms>>=
@@ -1539,6 +1599,18 @@ def test_rewrite_row_column_slice():
1539
1599
  }
1540
1600
  result = kthutils.forms.rewrite_row(row, rewriter)
1541
1601
  assert result == "Range: col1\tcol2"
1602
+
1603
+ def test_rewrite_row_constant_value():
1604
+ # A value substitution ignores the row entirely.
1605
+ row = ["data", "Godkänt"]
1606
+ rewriter = {
1607
+ "format_string": "Passed: {passed}",
1608
+ "substitutions": {
1609
+ "passed": {"value": "Nej"}
1610
+ }
1611
+ }
1612
+ result = kthutils.forms.rewrite_row(row, rewriter)
1613
+ assert result == "Passed: Nej"
1542
1614
  @
1543
1615
 
1544
1616
  The lab week rewriter (\cref{LabWeekRewriter}) relies on a stepped slice and a
@@ -1605,10 +1677,22 @@ character.
1605
1677
  \item[regex] The regex to apply to the data in the column.
1606
1678
  This should have the form [[s/regex/replacement/]].
1607
1679
  The [[/]] can be any character, same as in [[sed]].
1680
+ \item[value] A fixed string to use as the substitution, ignoring the row.
1681
+ Format strings are shared between forms (\cref{LabWeekRewriter}), and a form
1682
+ may lack a column that the format string still refers to.
1683
+ Rather than dropping the field from the shared format string, or faking it
1684
+ with a regex that rewrites anything to a constant, the rewriter for that
1685
+ form supplies the value directly.
1608
1686
  \end{description}
1687
+ A [[value]] substitution needs neither column nor regex, so we handle it
1688
+ first and skip the rest of the loop body.
1609
1689
  <<create [[substitutions]] from [[row]] and [[rewriter]]>>=
1610
1690
  substitutions = {}
1611
1691
  for variable, var_subst in rewriter["substitutions"].items():
1692
+ if "value" in var_subst:
1693
+ substitutions[variable] = var_subst["value"]
1694
+ continue
1695
+
1612
1696
  if "column" in var_subst:
1613
1697
  <<make [[data]] contain the columns from [[row]]>>
1614
1698
  else:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes