Skip to content

Commit a6038fe

Browse files
committed
fix(teacher_tab): Fix Open/Close handling w.r.t. Assignments
* Refactor and fix "Open"/"Closed"/… display code * Add function `is_open_or_assigned_globally` in `Learnocaml_data` that returns ( GloballyOpen | GloballyOpenOrAssigned | GloballyClosedOrAssigned | GloballyClosed ) * Make invariants explicit in OCaml comments * Add `check…` and `fix…` functions in `Learnocaml_data.Exercise.Status` to assert if the (assignments map, default) comply with the invariants (a false result means that there is at least one Open and one Closed) Close ocaml-sf#534 Close ocaml-sf#558
1 parent 48583ba commit a6038fe

File tree

4 files changed

+146
-25
lines changed

4 files changed

+146
-25
lines changed

src/app/learnocaml_teacher_tab.ml

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -299,13 +299,12 @@ let rec teacher_tab token _select _params () =
299299
H.td [stars_div meta.Exercise.Meta.stars];
300300
H.td [
301301
let cls, text =
302-
match Token.Map.is_empty ES.(st.assignments.token_map),
303-
ES.(st.assignments.default) with
304-
| true, ES.Open -> "exo_open", [%i"Open"]
305-
| true, ES.Closed -> "exo_closed", [%i"Closed"]
306-
| _, (ES.Assigned _ | ES.Closed) ->
307-
"exo_assigned", [%i"Assigned"]
308-
| false, ES.Open -> "exo_assigned", [%i"Open/Assg"]
302+
let open ES in
303+
match is_open_or_assigned_globally st.assignments with
304+
| GloballyOpen -> "exo_open", [%i"Open"]
305+
| GloballyOpenOrAssigned -> "exo_assigned", [%i"Open/Assigned"]
306+
| GloballyClosedOrAssigned -> "exo_assigned", [%i"Assigned"]
307+
| GloballyClosed -> "exo_closed", [%i"Closed"]
309308
in
310309
H.span ~a:[H.a_class [cls]] [H.txt text]
311310
];
@@ -856,17 +855,12 @@ let rec teacher_tab token _select _params () =
856855
let ids = htbl_keys selected_exercises in
857856
let fstat =
858857
if List.exists (fun id ->
859-
let st = get_status id in
860-
ES.(default_assignment st.assignments = Open))
858+
let st = get_status id in
859+
let open_assg = ES.is_open_or_assigned_globally st.ES.assignments in
860+
open_assg = ES.GloballyOpen || open_assg = ES.GloballyOpenOrAssigned)
861861
ids
862-
then ES.(fun assg ->
863-
match default_assignment assg with
864-
| Open -> set_default_assignment assg Closed
865-
| _ -> assg)
866-
else ES.(fun assg ->
867-
match default_assignment assg with
868-
| Closed -> set_default_assignment assg Open
869-
| _ -> assg)
862+
then ES.set_close_or_assigned_globally
863+
else ES.set_open_or_assigned_globally
870864
in
871865
!exercise_status_change (htbl_keys selected_exercises) fstat;
872866
true)
@@ -1330,7 +1324,11 @@ let rec teacher_tab token _select _params () =
13301324
in
13311325
let open_exercises =
13321326
SMap.fold (fun ex st acc ->
1333-
if ES.(st.assignments.default = Open) then ex::acc else acc)
1327+
let open ES in
1328+
let global_st = is_open_or_assigned_globally st.assignments in
1329+
if global_st = GloballyOpen
1330+
|| global_st = GloballyOpenOrAssigned
1331+
then ex :: acc else acc)
13341332
!status_map []
13351333
|> List.rev
13361334
in

src/state/learnocaml_data.ml

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -508,11 +508,96 @@ module Exercise = struct
508508

509509
let set_default_assignment a default = {a with default}
510510

511+
let make_assignments token_map default =
512+
{ token_map; default }
513+
511514
let get_status token a =
512515
match Token.Map.find_opt token a.token_map with
513516
| Some a -> a
514517
| None -> a.default
515518

519+
(* Global assignment status, w.r.t. all students as a whole
520+
521+
Invariants: forall exo_status : t,
522+
523+
1.(REQUIRED):
524+
(exo_status.assignments.default <> Open && Token.Map.for_all (fun _ st -> st <> Open) exo_status.assignments.token_map)
525+
|| (exo_status.assignments.default <> Closed && Token.Map.for_all (fun _ st -> st <> Closed) exo_status.assignments.token_map)
526+
527+
2.(IfNormalized):
528+
is_open_assigned_globally exo_status.assignments \in \{GloballyOpen, GloballyClosed\} ->
529+
exo_status.assignments.token_map = Token.Map.empty *)
530+
type global_status =
531+
| GloballyOpen (** "Open" *)
532+
| GloballyClosed (** "Closed" *)
533+
| GloballyOpenOrAssigned (** "Open/Assigned" *)
534+
| GloballyClosedOrAssigned (** "Assigned" *)
535+
536+
let check_open_close a =
537+
match a.default with
538+
| Open ->
539+
Token.Map.for_all (fun _tok st -> st <> Closed) a.token_map
540+
| Closed ->
541+
Token.Map.for_all (fun _tok st -> st <> Open) a.token_map
542+
| Assigned _ ->
543+
let o, c =
544+
Token.Map.fold (fun _tok st (o, c) ->
545+
(o && st <> Closed,
546+
c && st <> Open)) a.token_map (true, true) in
547+
o || c
548+
549+
let fix_open_close a =
550+
let mp =
551+
Token.Map.map (function Open -> Closed | st -> st) a.token_map in
552+
match a.default with
553+
| Open | Closed ->
554+
make_assignments mp Closed
555+
| assg ->
556+
make_assignments mp assg
557+
558+
let check_and_fix_open_close a =
559+
if check_open_close a then a
560+
else fix_open_close a
561+
562+
let is_open_or_assigned_globally a =
563+
match a.default with
564+
| Open ->
565+
if Token.Map.exists (fun _tok -> function Assigned _ -> true | _ -> false) a.token_map
566+
then GloballyOpenOrAssigned
567+
else GloballyOpen
568+
| Closed ->
569+
if Token.Map.exists (fun _tok -> function Assigned _ -> true | _ -> false) a.token_map
570+
then GloballyClosedOrAssigned
571+
else GloballyClosed
572+
| Assigned _ ->
573+
if Token.Map.exists (fun _tok -> (=) Open) a.token_map
574+
then GloballyOpenOrAssigned
575+
else GloballyClosedOrAssigned
576+
577+
let set_close_or_assigned_globally a =
578+
match is_open_or_assigned_globally a with
579+
| GloballyOpen -> make_assignments Token.Map.empty Closed
580+
| GloballyOpenOrAssigned ->
581+
make_assignments
582+
(Token.Map.map (function Open -> Closed | st -> st) a.token_map)
583+
(match a.default with Open -> Closed | a -> a)
584+
(* otherwise, maybe: forget the map and re-add all tokens ? *)
585+
| GloballyClosedOrAssigned -> a
586+
| GloballyClosed -> a
587+
588+
let set_open_or_assigned_globally a =
589+
match is_open_or_assigned_globally a with
590+
| GloballyClosed -> make_assignments Token.Map.empty Open
591+
| GloballyClosedOrAssigned ->
592+
make_assignments
593+
(Token.Map.map (function Closed -> Open | st -> st) a.token_map)
594+
(match a.default with Closed -> Open | a -> a)
595+
(* otherwise, maybe: forget the map and re-add all tokens ? *)
596+
| GloballyOpenOrAssigned -> a
597+
| GloballyOpen -> a
598+
599+
(* Note/Erik: we may also want to implement set_assigned_globally *)
600+
516601
let is_open_assignment token a =
517602
match get_status token a with
518603
| Assigned a ->
@@ -640,9 +725,6 @@ module Exercise = struct
640725
skills_focus;
641726
assignments = { default; token_map } }
642727

643-
let make_assignments token_map default =
644-
{ token_map; default }
645-
646728
let enc =
647729
let status_enc =
648730
J.union [

src/state/learnocaml_data.mli

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,53 @@ module Exercise: sig
203203

204204
val set_default_assignment: assignments -> status -> assignments
205205

206+
val make_assignments:
207+
status Token.Map.t -> status -> assignments
208+
206209
val get_status:
207210
Token.t -> assignments -> status
208211

212+
(** Global assignment status, w.r.t. all students as a whole
213+
214+
Invariants: forall exo_status : t,
215+
216+
1.(REQUIRED):
217+
(exo_status.assignments.default <> Open && Token.Map.for_all (fun _ st -> st <> Open) exo_status.assignments.token_map)
218+
|| (exo_status.assignments.default <> Closed && Token.Map.for_all (fun _ st -> st <> Closed) exo_status.assignments.token_map)
219+
220+
2.(IfNormalized):
221+
is_open_assigned_globally exo_status.assignments \in \{GloballyOpen, GloballyClosed\} ->
222+
exo_status.assignments.token_map = Token.Map.empty *)
223+
type global_status =
224+
| GloballyOpen (** "Open" *)
225+
| GloballyClosed (** "Closed" *)
226+
| GloballyOpenOrAssigned (** "Open/Assigned" *)
227+
| GloballyClosedOrAssigned (** "Assigned" *)
228+
229+
val is_open_or_assigned_globally: assignments -> global_status
230+
231+
(** Close assignments status globally (for all unassigned students), namely:
232+
- GloballyOpen -> GloballyClosed
233+
- GloballyOpenOrAssigned -> GloballyClosedOrAssigned
234+
- other -> no-op *)
235+
val set_close_or_assigned_globally: assignments -> assignments
236+
237+
(** Open assignments status globally (for all unassigned students), namely:
238+
- GloballyClosed -> GloballyOpen
239+
- GloballyClosedOrAssigned -> GloballyOpenOrAssigned
240+
- other -> no-op *)
241+
val set_open_or_assigned_globally: assignments -> assignments
242+
243+
(** Check if the assignments map and default comply with the invariants.
244+
Return false if there are at least one Open and at least one Closed. *)
245+
val check_open_close: assignments -> bool
246+
247+
(** Replace all Open with Closed. *)
248+
val fix_open_close: assignments -> assignments
249+
250+
(** Call [check_open_close] then (if need be) [fix_open_close] *)
251+
val check_and_fix_open_close: assignments -> assignments
252+
209253
val is_open_assignment:
210254
Token.t -> assignments -> [> `Open | `Closed | `Deadline of float]
211255

@@ -241,9 +285,6 @@ module Exercise: sig
241285
val three_way_merge:
242286
ancestor:t -> theirs:t -> ours:t -> t
243287

244-
val make_assignments:
245-
status Token.Map.t -> status -> assignments
246-
247288
val enc: t Json_encoding.encoding
248289

249290
end

translations/fr.po

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,7 @@ msgid "Assigned"
633633
msgstr "Devoir"
634634

635635
#: File "src/app/learnocaml_teacher_tab.ml", line 308, characters 57-68
636-
msgid "Open/Assg"
636+
msgid "Open/Assigned"
637637
msgstr "Ouvert/Devoir"
638638

639639
#: File "src/app/learnocaml_teacher_tab.ml", line 368, characters 49-61 391,

0 commit comments

Comments
 (0)