diff --git a/demo-repository/exercises/demo3/demo3-etu1/descr.html b/demo-repository/exercises/demo3/demo3-etu1/descr.html
new file mode 100644
index 000000000..174931593
--- /dev/null
+++ b/demo-repository/exercises/demo3/demo3-etu1/descr.html
@@ -0,0 +1,113 @@
+
+ This exercise is just a demo for the exercise environment.
+
+
+The task
+
+
+ In this test-bed exercise you are asked to implement the basic
+ integer-arithmetic functions.
+
+
+
+
+ -
+ Write a function
plus
of type int -> int -> int
.
+
+ Ex : val plus 3 4 : int = 7.
+
+ -
+ Write a function
minus
of type int -> int -> int
.
+
+ Ex : val minus 3 2 : int = 1.
+
+ -
+ Write a function
times
of type int -> int -> int
.
+
+ Ex : val times 2 6 : int = 12.
+
+ -
+ Write a function
divide
of type int -> int -> int
.
+ Ex : val divide 8 4 : int = 2.
+
+ -
+ Write a function
sum3
of type int -> int -> int -> int
+
.
+
+ Ex : val sum3 2 4 3 : int = 9.
+
+ -
+ Write a function
plus2
of type int -> int using plus
+
which add 2 to another integer.
+
+
+
+ Now you have done these quite easy exercises, sure you can train on recursive
+ fonctions !
+
+
+ Recursive exercises
+
+
+ In this test-bed exercise you are asked to implement some recursive
+ integer-arithmetic functions.
+
+
+ -
+ Write a function
sum_0_to_n
of type int -> int which
+ add integers from 0 to n, considering n can't be under 0.
+
+ Ex : val sum_0_to_n 3 : int = 6.
+
+ -
+ Write a function
fact
of type int -> int which represent
+ the factorial (n!) function : 4! = 1x2x3x4 = 24.
+
+ Ex : val fact 5 : int = 120.
+
+
+
+ List exercise
+
+
+
+ Write a function list_0_to_n
of type int -> int list
+
.
+ This function must create a list containing integers between 0 and n. If
+ n<0, the function must return a Failure exception.
+
+ Ex :list_0_to_n 4 -> [0;1;2;3;4]
+
+
+ Write a function forall
of type 'a list ->
+ ('a -> bool) -> bool
, that return true if (all) the elements of
+ the list verify a predicat.
+
+ Ex : forall [0;2;4] (fun x -> (x%2)=0) -> true
+
+
+ Write a function cat_list
of type 'a list -> 'a list ->
+ 'a list
. This function concatenate two list to make only one.
+
+ Ex : cat_list ["a";"b"] ["c";"d";"e"] -> ["a";"b";"c";"d";"e"]
+
+
+
+
+
+
+
+
+
+ Feel free to introduce more errors and to stress the system,
+ the resulting grade for this exercise will not be taken into account
+ in the global grade and you might submit as many solutions as you
+ wish.
+
+ If you end up writing an infinite computation, the system will
+ detect it after a while and ask you to stop the script. It will
+ slow your browser down until that point, since everything is done
+ on your side, via your JavaScript engine.
+ So don't worry, you can try and break the system as much as you
+ want, it should not break anything on our servers.
+
diff --git a/demo-repository/exercises/demo3/demo3-etu1/max_score.txt b/demo-repository/exercises/demo3/demo3-etu1/max_score.txt
new file mode 100644
index 000000000..04f9fe460
--- /dev/null
+++ b/demo-repository/exercises/demo3/demo3-etu1/max_score.txt
@@ -0,0 +1 @@
+59
diff --git a/demo-repository/exercises/demo3/demo3-etu1/prelude.ml b/demo-repository/exercises/demo3/demo3-etu1/prelude.ml
new file mode 100644
index 000000000..8436a905e
--- /dev/null
+++ b/demo-repository/exercises/demo3/demo3-etu1/prelude.ml
@@ -0,0 +1,2 @@
+(* Some code is loaded in the toplevel before your code. *)
+let greetings = "Hello world!"
diff --git a/demo-repository/exercises/demo3/demo3-etu1/prepare.ml b/demo-repository/exercises/demo3/demo3-etu1/prepare.ml
new file mode 100644
index 000000000..e69de29bb
diff --git a/demo-repository/exercises/demo3/demo3-etu1/solution.ml b/demo-repository/exercises/demo3/demo3-etu1/solution.ml
new file mode 100644
index 000000000..14392c9e8
--- /dev/null
+++ b/demo-repository/exercises/demo3/demo3-etu1/solution.ml
@@ -0,0 +1,11 @@
+
+(* First part : simple functions on integers *)
+
+let plus = (+);;
+
+let minus = (-);;
+
+let times a b = a*b;;
+
+let divide = (/);;
+
diff --git a/demo-repository/exercises/demo3/demo3-etu1/template.ml b/demo-repository/exercises/demo3/demo3-etu1/template.ml
new file mode 100644
index 000000000..7d6827247
--- /dev/null
+++ b/demo-repository/exercises/demo3/demo3-etu1/template.ml
@@ -0,0 +1 @@
+(* Write here your code *)
diff --git a/demo-repository/exercises/demo3/demo3-etu1/test.ml b/demo-repository/exercises/demo3/demo3-etu1/test.ml
new file mode 100644
index 000000000..8bdd25eba
--- /dev/null
+++ b/demo-repository/exercises/demo3/demo3-etu1/test.ml
@@ -0,0 +1,54 @@
+open Test_lib
+open Report
+
+module Mutation_test = Mutation_test.Make (Test_lib)
+open Mutation_test
+
+let test_plus () =
+ test_function_2_against_solution
+ [%ty : int -> int -> int ] "plus"
+ [ (1, 1) ; (2, 2) ; (10, -10) ]
+ (* @
+ test_unit_tests_2
+ [%ty : int -> int -> int ] "plus"
+ [ ("Subtracts instead of adding", 1, fun x y -> x - y) ] *)
+
+ (* Au dessus (en commentaire) : test des tests de l'élève *)
+
+let test_minus () =
+ test_function_2_against_solution
+ [%ty : int -> int -> int ] "minus"
+ [ (1, 1) ; (4, -2) ; (0, 10) ]
+
+
+let test_times () =
+ test_function_2_against_solution
+ [%ty : int -> int -> int ] "times"
+ [ (1, 3) ; (2, 4) ; (3, 0) ]
+
+
+let test_divide () =
+ test_function_2_against_solution
+ [%ty : int -> int -> int ] "divide"
+ [ (12, 4) ; (12, 5) ; (3, 0) ]
+
+
+
+
+let () =
+ set_result @@
+ ast_sanity_check code_ast @@ fun () ->
+ [ Section
+ ([ Text "Function:" ; Code "plus" ],
+ test_plus ()) ;
+ Section
+ ([ Text "Function:" ; Code "minus" ],
+ test_minus ()) ;
+ Section
+ ([ Text "Function:" ; Code "times" ],
+ test_times ()) ;
+ Section
+ ([ Text "Function:" ; Code "divide" ],
+ test_divide ()) ;
+
+ ]
diff --git a/demo-repository/exercises/demo3/demo3-etu2/descr.html b/demo-repository/exercises/demo3/demo3-etu2/descr.html
new file mode 100644
index 000000000..174931593
--- /dev/null
+++ b/demo-repository/exercises/demo3/demo3-etu2/descr.html
@@ -0,0 +1,113 @@
+
+ This exercise is just a demo for the exercise environment.
+
+
+The task
+
+
+ In this test-bed exercise you are asked to implement the basic
+ integer-arithmetic functions.
+
+
+
+
+ -
+ Write a function
plus
of type int -> int -> int
.
+
+ Ex : val plus 3 4 : int = 7.
+
+ -
+ Write a function
minus
of type int -> int -> int
.
+
+ Ex : val minus 3 2 : int = 1.
+
+ -
+ Write a function
times
of type int -> int -> int
.
+
+ Ex : val times 2 6 : int = 12.
+
+ -
+ Write a function
divide
of type int -> int -> int
.
+ Ex : val divide 8 4 : int = 2.
+
+ -
+ Write a function
sum3
of type int -> int -> int -> int
+
.
+
+ Ex : val sum3 2 4 3 : int = 9.
+
+ -
+ Write a function
plus2
of type int -> int using plus
+
which add 2 to another integer.
+
+
+
+ Now you have done these quite easy exercises, sure you can train on recursive
+ fonctions !
+
+
+ Recursive exercises
+
+
+ In this test-bed exercise you are asked to implement some recursive
+ integer-arithmetic functions.
+
+
+ -
+ Write a function
sum_0_to_n
of type int -> int which
+ add integers from 0 to n, considering n can't be under 0.
+
+ Ex : val sum_0_to_n 3 : int = 6.
+
+ -
+ Write a function
fact
of type int -> int which represent
+ the factorial (n!) function : 4! = 1x2x3x4 = 24.
+
+ Ex : val fact 5 : int = 120.
+
+
+
+ List exercise
+
+
+
+ Write a function list_0_to_n
of type int -> int list
+
.
+ This function must create a list containing integers between 0 and n. If
+ n<0, the function must return a Failure exception.
+
+ Ex :list_0_to_n 4 -> [0;1;2;3;4]
+
+
+ Write a function forall
of type 'a list ->
+ ('a -> bool) -> bool
, that return true if (all) the elements of
+ the list verify a predicat.
+
+ Ex : forall [0;2;4] (fun x -> (x%2)=0) -> true
+
+
+ Write a function cat_list
of type 'a list -> 'a list ->
+ 'a list
. This function concatenate two list to make only one.
+
+ Ex : cat_list ["a";"b"] ["c";"d";"e"] -> ["a";"b";"c";"d";"e"]
+
+
+
+
+
+
+
+
+
+ Feel free to introduce more errors and to stress the system,
+ the resulting grade for this exercise will not be taken into account
+ in the global grade and you might submit as many solutions as you
+ wish.
+
+ If you end up writing an infinite computation, the system will
+ detect it after a while and ask you to stop the script. It will
+ slow your browser down until that point, since everything is done
+ on your side, via your JavaScript engine.
+ So don't worry, you can try and break the system as much as you
+ want, it should not break anything on our servers.
+
diff --git a/demo-repository/exercises/demo3/demo3-etu2/max_score.txt b/demo-repository/exercises/demo3/demo3-etu2/max_score.txt
new file mode 100644
index 000000000..04f9fe460
--- /dev/null
+++ b/demo-repository/exercises/demo3/demo3-etu2/max_score.txt
@@ -0,0 +1 @@
+59
diff --git a/demo-repository/exercises/demo3/demo3-etu2/prelude.ml b/demo-repository/exercises/demo3/demo3-etu2/prelude.ml
new file mode 100644
index 000000000..92345ec5c
--- /dev/null
+++ b/demo-repository/exercises/demo3/demo3-etu2/prelude.ml
@@ -0,0 +1,2 @@
+(* Some code is loaded in the toplevel before your code. *)
+let greetings2 = "Hello world! This is second part of demo3"
diff --git a/demo-repository/exercises/demo3/demo3-etu2/prepare.ml b/demo-repository/exercises/demo3/demo3-etu2/prepare.ml
new file mode 100644
index 000000000..e69de29bb
diff --git a/demo-repository/exercises/demo3/demo3-etu2/solution.ml b/demo-repository/exercises/demo3/demo3-etu2/solution.ml
new file mode 100644
index 000000000..8c56db937
--- /dev/null
+++ b/demo-repository/exercises/demo3/demo3-etu2/solution.ml
@@ -0,0 +1,20 @@
+
+(* First part : simple functions on integers *)
+
+let sum3 a b c = a+b+c;;
+
+let plus2 a = a+2 ;;
+
+(* Second part : recursive functions on integers *)
+
+let rec sum_0_to_n n =
+ if n<0 then failwith "sum_0_to_n : arg < 0 not allowed" else
+ match n with
+ |0-> 0
+ |n -> n+sum_0_to_n (n-1);;
+
+let rec fact n =
+ match n with
+ | 0 | 1 -> 1
+ | n -> n*fact (n-1);;
+
diff --git a/demo-repository/exercises/demo3/demo3-etu2/template.ml b/demo-repository/exercises/demo3/demo3-etu2/template.ml
new file mode 100644
index 000000000..7d6827247
--- /dev/null
+++ b/demo-repository/exercises/demo3/demo3-etu2/template.ml
@@ -0,0 +1 @@
+(* Write here your code *)
diff --git a/demo-repository/exercises/demo3/demo3-etu2/test.ml b/demo-repository/exercises/demo3/demo3-etu2/test.ml
new file mode 100644
index 000000000..7b9e93f5f
--- /dev/null
+++ b/demo-repository/exercises/demo3/demo3-etu2/test.ml
@@ -0,0 +1,63 @@
+open Test_lib
+open Report
+
+module Mutation_test = Mutation_test.Make (Test_lib)
+open Mutation_test
+
+let test_sum3 () =
+ test_function_against_solution
+ ~gen:2 (* only 2 random test cases *)
+ [%funty: int -> int -> int -> int] (* function type *)
+ "sum3" (* function name *)
+ (* list of additional, explicit test cases *)
+ [ 10 @: 20 @:!! 30 ;
+ -1 @: -2 @:!! -3 ]
+
+let test_plus2 () =
+ test_function_1_against_solution
+ [%ty: int -> int] (* function type *)
+ "plus2"
+ ~gen:2
+ [ 12;6 ]
+
+
+let test_sum_0_to_n () =
+ test_function_1_against_solution
+ [%ty : int -> int ]
+ "sum_0_to_n"
+ ~gen:4
+ ~test:(test_eq_exn
+ (fun exn1 exn2 -> match exn1, exn2 with
+ Failure _, Failure _ -> true | _, _ -> false))
+ [-4]
+
+let test_fact () =
+ Section ([ Text "Function" ; Code "fact"],
+ test_function_1_against_solution
+ [%ty : int -> int ]
+ "fact"
+ ~sampler:(fun () -> Random.int 11)
+ ~gen:4
+ []
+ )
+
+
+
+
+
+let () =
+ set_result @@
+ ast_sanity_check code_ast @@ fun () ->
+ [ Section
+ ([ Text "Function:" ; Code "sum3" ],
+ test_sum3 ()) ;
+ Section
+ ([ Text "Function:" ; Code "plus2"],
+ test_plus2 ());
+ Section
+ ([ Text "Function:" ; Code "sum_0_to_n"],
+ test_sum_0_to_n ());
+
+ test_fact ();
+
+ ]
diff --git a/demo-repository/exercises/demo3/demo3-prof/descr.html b/demo-repository/exercises/demo3/demo3-prof/descr.html
new file mode 100644
index 000000000..174931593
--- /dev/null
+++ b/demo-repository/exercises/demo3/demo3-prof/descr.html
@@ -0,0 +1,113 @@
+
+ This exercise is just a demo for the exercise environment.
+
+
+The task
+
+
+ In this test-bed exercise you are asked to implement the basic
+ integer-arithmetic functions.
+
+
+
+
+ -
+ Write a function
plus
of type int -> int -> int
.
+
+ Ex : val plus 3 4 : int = 7.
+
+ -
+ Write a function
minus
of type int -> int -> int
.
+
+ Ex : val minus 3 2 : int = 1.
+
+ -
+ Write a function
times
of type int -> int -> int
.
+
+ Ex : val times 2 6 : int = 12.
+
+ -
+ Write a function
divide
of type int -> int -> int
.
+ Ex : val divide 8 4 : int = 2.
+
+ -
+ Write a function
sum3
of type int -> int -> int -> int
+
.
+
+ Ex : val sum3 2 4 3 : int = 9.
+
+ -
+ Write a function
plus2
of type int -> int using plus
+
which add 2 to another integer.
+
+
+
+ Now you have done these quite easy exercises, sure you can train on recursive
+ fonctions !
+
+
+ Recursive exercises
+
+
+ In this test-bed exercise you are asked to implement some recursive
+ integer-arithmetic functions.
+
+
+ -
+ Write a function
sum_0_to_n
of type int -> int which
+ add integers from 0 to n, considering n can't be under 0.
+
+ Ex : val sum_0_to_n 3 : int = 6.
+
+ -
+ Write a function
fact
of type int -> int which represent
+ the factorial (n!) function : 4! = 1x2x3x4 = 24.
+
+ Ex : val fact 5 : int = 120.
+
+
+
+ List exercise
+
+
+
+ Write a function list_0_to_n
of type int -> int list
+
.
+ This function must create a list containing integers between 0 and n. If
+ n<0, the function must return a Failure exception.
+
+ Ex :list_0_to_n 4 -> [0;1;2;3;4]
+
+
+ Write a function forall
of type 'a list ->
+ ('a -> bool) -> bool
, that return true if (all) the elements of
+ the list verify a predicat.
+
+ Ex : forall [0;2;4] (fun x -> (x%2)=0) -> true
+
+
+ Write a function cat_list
of type 'a list -> 'a list ->
+ 'a list
. This function concatenate two list to make only one.
+
+ Ex : cat_list ["a";"b"] ["c";"d";"e"] -> ["a";"b";"c";"d";"e"]
+
+
+
+
+
+
+
+
+
+ Feel free to introduce more errors and to stress the system,
+ the resulting grade for this exercise will not be taken into account
+ in the global grade and you might submit as many solutions as you
+ wish.
+
+ If you end up writing an infinite computation, the system will
+ detect it after a while and ask you to stop the script. It will
+ slow your browser down until that point, since everything is done
+ on your side, via your JavaScript engine.
+ So don't worry, you can try and break the system as much as you
+ want, it should not break anything on our servers.
+
diff --git a/demo-repository/exercises/demo3/demo3-prof/max_score.txt b/demo-repository/exercises/demo3/demo3-prof/max_score.txt
new file mode 100644
index 000000000..04f9fe460
--- /dev/null
+++ b/demo-repository/exercises/demo3/demo3-prof/max_score.txt
@@ -0,0 +1 @@
+59
diff --git a/demo-repository/exercises/demo3/demo3-prof/prelude.ml b/demo-repository/exercises/demo3/demo3-prof/prelude.ml
new file mode 100644
index 000000000..8436a905e
--- /dev/null
+++ b/demo-repository/exercises/demo3/demo3-prof/prelude.ml
@@ -0,0 +1,2 @@
+(* Some code is loaded in the toplevel before your code. *)
+let greetings = "Hello world!"
diff --git a/demo-repository/exercises/demo3/demo3-prof/prepare.ml b/demo-repository/exercises/demo3/demo3-prof/prepare.ml
new file mode 100644
index 000000000..e69de29bb
diff --git a/demo-repository/exercises/demo3/demo3-prof/solution.ml b/demo-repository/exercises/demo3/demo3-prof/solution.ml
new file mode 100644
index 000000000..f07be0fac
--- /dev/null
+++ b/demo-repository/exercises/demo3/demo3-prof/solution.ml
@@ -0,0 +1,28 @@
+
+(* First part : simple functions on integers *)
+
+let plus = (+);;
+
+let minus = (-);;
+
+let times a b = a*b;;
+
+let divide = (/);;
+
+let sum3 a b c = a+b+c;;
+
+let plus2 a = plus 2 a ;;
+
+(* Second part : recursive functions on integers *)
+
+let rec sum_0_to_n n =
+ if n<0 then failwith "sum_0_to_n : arg < 0 not allowed" else
+ match n with
+ |0-> 0
+ |n -> n+sum_0_to_n (n-1);;
+
+let rec fact n =
+ match n with
+ | 0 | 1 -> 1
+ | n -> n*fact (n-1);;
+
diff --git a/demo-repository/exercises/demo3/demo3-prof/template.ml b/demo-repository/exercises/demo3/demo3-prof/template.ml
new file mode 100644
index 000000000..7d6827247
--- /dev/null
+++ b/demo-repository/exercises/demo3/demo3-prof/template.ml
@@ -0,0 +1 @@
+(* Write here your code *)
diff --git a/demo-repository/exercises/demo3/demo3-prof/test.ml b/demo-repository/exercises/demo3/demo3-prof/test.ml
new file mode 100644
index 000000000..5a2a81d2a
--- /dev/null
+++ b/demo-repository/exercises/demo3/demo3-prof/test.ml
@@ -0,0 +1,105 @@
+open Test_lib
+open Report
+
+module Mutation_test = Mutation_test.Make (Test_lib)
+open Mutation_test
+
+let test_plus () =
+ test_function_2_against_solution
+ [%ty : int -> int -> int ] "plus"
+ [ (1, 1) ; (2, 2) ; (10, -10) ]
+ (* @
+ test_unit_tests_2
+ [%ty : int -> int -> int ] "plus"
+ [ ("Subtracts instead of adding", 1, fun x y -> x - y) ] *)
+
+ (* Au dessus (en commentaire) : test des tests de l'élève *)
+
+let test_minus () =
+ test_function_2_against_solution
+ [%ty : int -> int -> int ] "minus"
+ [ (1, 1) ; (4, -2) ; (0, 10) ]
+
+
+let test_times () =
+ test_function_2_against_solution
+ [%ty : int -> int -> int ] "times"
+ [ (1, 3) ; (2, 4) ; (3, 0) ]
+
+
+let test_divide () =
+ test_function_2_against_solution
+ [%ty : int -> int -> int ] "divide"
+ [ (12, 4) ; (12, 5) ; (3, 0) ]
+
+
+
+let test_sum3 () =
+ test_function_against_solution
+ ~gen:2 (* only 2 random test cases *)
+ [%funty: int -> int -> int -> int] (* function type *)
+ "sum3" (* function name *)
+ (* list of additional, explicit test cases *)
+ [ 10 @: 20 @:!! 30 ;
+ -1 @: -2 @:!! -3 ]
+
+let test_plus2 () =
+ test_function_1_against_solution
+ [%ty: int -> int] (* function type *)
+ "plus2"
+ ~gen:2
+ [ 12;6 ]
+
+
+let test_sum_0_to_n () =
+ test_function_1_against_solution
+ [%ty : int -> int ]
+ "sum_0_to_n"
+ ~gen:4
+ ~test:(test_eq_exn
+ (fun exn1 exn2 -> match exn1, exn2 with
+ Failure _, Failure _ -> true | _, _ -> false))
+ [-4]
+
+let test_fact () =
+ Section ([ Text "Function" ; Code "fact"],
+ test_function_1_against_solution
+ [%ty : int -> int ]
+ "fact"
+ ~sampler:(fun () -> Random.int 11)
+ ~gen:4
+ []
+ )
+
+
+
+
+
+let () =
+ set_result @@
+ ast_sanity_check code_ast @@ fun () ->
+ [ Section
+ ([ Text "Function:" ; Code "plus" ],
+ test_plus ()) ;
+ Section
+ ([ Text "Function:" ; Code "minus" ],
+ test_minus ()) ;
+ Section
+ ([ Text "Function:" ; Code "times" ],
+ test_times ()) ;
+ Section
+ ([ Text "Function:" ; Code "divide" ],
+ test_divide ()) ;
+ Section
+ ([ Text "Function:" ; Code "sum3" ],
+ test_sum3 ()) ;
+ Section
+ ([ Text "Function:" ; Code "plus2"],
+ test_plus2 ());
+ Section
+ ([ Text "Function:" ; Code "sum_0_to_n"],
+ test_sum_0_to_n ());
+
+ test_fact ();
+
+ ]
diff --git a/demo-repository/exercises/demo3/subindex.json b/demo-repository/exercises/demo3/subindex.json
new file mode 100644
index 000000000..0199c4119
--- /dev/null
+++ b/demo-repository/exercises/demo3/subindex.json
@@ -0,0 +1,32 @@
+{ "learnocaml_version": "1",
+ "meta":{
+ "learnocaml_version": "2",
+ "kind": "problem",
+ "stars": 4,
+ "title": "Demo of a multi-part exercise",
+ "identifier": "demo3",
+ "authors": [["Someone", "someone@example.com"]],
+ "focus": ["recursion"],
+ "requirements": ["basic arithmetic"],
+ "forward_exercises": [],
+ "backward_exercises": []} ,
+ "check_all_against": "demo3-prof",
+ "parts": [
+ { "subtitle": "First test",
+ "subexercise": "demo3-etu1",
+ "student_weight": 1,
+ "teacher_weight": 0
+ },
+ { "subtitle": "Second test",
+ "subexercise": "demo3-etu2",
+ "student_hidden": false,
+ "student_weight": 1,
+ "teacher_weight": 0
+ },
+ { "subtitle": "Grading",
+ "subexercise": "demo3-prof",
+ "student_hidden": true,
+ "student_weight": 0,
+ "teacher_weight": 1
+ }]
+}
diff --git a/src/app/learnocaml_common.ml b/src/app/learnocaml_common.ml
index bd9c10aff..6226695d5 100644
--- a/src/app/learnocaml_common.ml
+++ b/src/app/learnocaml_common.ml
@@ -569,10 +569,10 @@ let exercise_text ex_meta exo =
let descr =
let lang = "" in
try
- List.assoc lang (Learnocaml_exercise.(access File.descr exo))
+ List.assoc lang (Learnocaml_exercise.(access false File.descr exo))
with
Not_found ->
- try List.assoc "" (Learnocaml_exercise.(access File.descr exo))
+ try List.assoc "" (Learnocaml_exercise.(access false File.descr exo))
with Not_found -> [%i "No description available for this exercise." ]
in
Format.asprintf
@@ -941,6 +941,42 @@ let set_nickname_div () =
| nickname -> Manip.setInnerText nickname_div nickname
| exception Not_found -> ()
+(* setup for the prelude in the description page (in description_main.ml) *)
+let setup_tab_text_prelude_pane prelude =
+ if prelude = "" then () else
+ let iframe_pane = find_component "learnocaml-exo-tab-text-iframe" in
+ let prelude_pane = find_component "learnocaml-exo-tab-text-prelude" in
+ let open Tyxml_js.Html5 in
+ let state =
+ ref (match arg "tab_text_prelude" with
+ | exception Not_found -> false
+ | "shown" -> true
+ | "hidden" -> false
+ | _ -> failwith "Bad format for argument prelude.") in
+ let prelude_btn = button [] in
+ let prelude_title = h1 [ txt [%i"OCaml prelude"] ;
+ prelude_btn ] in
+ let prelude_container =
+ pre ~a: [ a_class [ "toplevel-code" ] ]
+ (Learnocaml_toplevel_output.format_ocaml_code prelude) in
+ let update () =
+ if !state then begin
+ Manip.replaceChildren prelude_btn [ txt ("↳ "^[%i"Hide"]) ] ;
+ Manip.SetCss.display prelude_container "" ;
+ Manip.SetCss.top iframe_pane "241px";
+ set_arg "tab_text_prelude" "shown"
+ end else begin
+ Manip.replaceChildren prelude_btn [ txt ("↰ "^[%i"Show"]) ] ;
+ Manip.SetCss.display prelude_container "none" ;
+ Manip.SetCss.top iframe_pane "90px";
+ set_arg "tab_text_prelude" "hidden"
+ end in
+ update () ;
+ Manip.Ev.onclick prelude_btn
+ (fun _ -> state := not !state ; update () ; true) ;
+ Manip.appendChildren prelude_pane
+ [ prelude_title ; prelude_container ]
+
let setup_prelude_pane ace prelude =
if prelude = "" then () else
let editor_pane = find_component "learnocaml-exo-editor-pane" in
@@ -1115,7 +1151,7 @@ module Display_exercise =
else
ignore (onclick cid);
Manip.removeChildren exp;
- Manip.appendChild exp (txt (if !displayed then "[-]" else "[+]"));
+ Manip.appendChild exp (txt (if !displayed then "[+]" else "[-]"));
displayed := not !displayed;
true
in
diff --git a/src/app/learnocaml_common.mli b/src/app/learnocaml_common.mli
index 275f2c8cf..f0d3b994c 100644
--- a/src/app/learnocaml_common.mli
+++ b/src/app/learnocaml_common.mli
@@ -143,7 +143,7 @@ val stars_div: float -> [> Html_types.div ] Tyxml_js.Html5.elt
(** Returns an HTML string expected to be put in an iframe *)
val exercise_text:
- Exercise.Meta.t -> Exercise.t -> string
+ Exercise.Meta.t -> Learnocaml_exercise.t -> string
val string_of_exercise_kind: Exercise.Meta.kind -> string
@@ -221,6 +221,8 @@ val typecheck :
val set_nickname_div : unit -> unit
+val setup_tab_text_prelude_pane : string -> unit
+
val setup_prelude_pane : 'a Ace.editor -> string -> unit
val get_token : ?has_server:bool -> unit -> Learnocaml_data.student Learnocaml_data.token option Lwt.t
diff --git a/src/app/learnocaml_description_main.ml b/src/app/learnocaml_description_main.ml
index 8ee7bcbee..6353ac8b5 100644
--- a/src/app/learnocaml_description_main.ml
+++ b/src/app/learnocaml_description_main.ml
@@ -15,10 +15,10 @@ module Exercise_link =
a_class cl ]
content)
end
-
-module Display = Display_exercise(Exercise_link)
+
+module Display = Display_exercise(Exercise_link)
open Display
-
+
let () =
run_async_with_log @@ fun () ->
let id = match Url.Current.path with
@@ -33,12 +33,24 @@ let () =
retrieve (Learnocaml_api.Exercise (Some token, id))
in
init_tabs ();
- exercise_fetch >>= fun (ex_meta, exo, _deadline) ->
- (* display exercise questions *)
+ exercise_fetch >>= fun (ex_meta, ex, _deadline) ->
+ let exo = match ex with
+ | Learnocaml_exercise.Subexercise (ex,_) ->
+ (match ex with
+ | [] -> raise Not_found
+ | (ex1,_) :: _ -> Learnocaml_exercise.Exercise ex1)
+ | ex -> ex
+ in
+ (* display exercise questions and prelude *)
+ setup_tab_text_prelude_pane Learnocaml_exercise.(decipher false File.prelude exo);
+ let prelude_container = find_component "learnocaml-exo-tab-text-prelude" in
+ let iframe_container = find_component "learnocaml-exo-tab-text-iframe" in
let text_iframe = Dom_html.createIframe Dom_html.document in
+ Manip.replaceChildren iframe_container [Tyxml_js.Of_dom.of_iFrame text_iframe];
Manip.replaceChildren text_container
- Tyxml_js.Html5.[ h1 [ txt ex_meta.title ] ;
- Tyxml_js.Of_dom.of_iFrame text_iframe ] ;
+ Tyxml_js.Html5.[ h1 [ txt ex_meta.title] ;
+ prelude_container;
+ iframe_container ] ;
Js.Opt.case
(text_iframe##.contentDocument)
(fun () -> failwith "cannot edit iframe document")
diff --git a/src/app/learnocaml_exercise_main.ml b/src/app/learnocaml_exercise_main.ml
index db13a77a1..ff0ffa482 100644
--- a/src/app/learnocaml_exercise_main.ml
+++ b/src/app/learnocaml_exercise_main.ml
@@ -47,7 +47,7 @@ let display_report exo report =
Manip.removeClass report_button "failure" ;
Manip.removeClass report_button "partial" ;
let grade =
- let max = Learnocaml_exercise.(access File.max_score exo) in
+ let max = Learnocaml_exercise.(access false File.max_score exo) in
if max = 0 then 999 else score * 100 / max
in
if grade >= 100 then begin
@@ -121,7 +121,14 @@ let () =
in
let after_init top =
exercise_fetch >>= fun (_meta, exo, _deadline) ->
- begin match Learnocaml_exercise.(decipher File.prelude exo) with
+ let ex = match exo with
+ | Learnocaml_exercise.Exercise ex -> ex
+ | Learnocaml_exercise.Subexercise ([], _ ) -> raise Not_found
+ | Learnocaml_exercise.Subexercise ((ex, _) :: _ ,_) -> ex
+ in
+ let sub_id = ex.Learnocaml_exercise.id
+ in
+ begin match Learnocaml_exercise.(decipher ~subid:sub_id false File.prelude (Learnocaml_exercise.Exercise ex)) with
| "" -> Lwt.return true
| prelude ->
Learnocaml_toplevel.load ~print_outcome:true top
@@ -129,7 +136,7 @@ let () =
prelude
end >>= fun r1 ->
Learnocaml_toplevel.load ~print_outcome:false top
- (Learnocaml_exercise.(decipher File.prepare exo)) >>= fun r2 ->
+ (Learnocaml_exercise.(decipher ~subid:sub_id false File.prepare (Learnocaml_exercise.Exercise ex))) >>= fun r2 ->
if not r1 || not r2 then failwith [%i"error in prelude"] ;
Learnocaml_toplevel.set_checking_environment top >>= fun () ->
Lwt.return () in
@@ -142,6 +149,14 @@ let () =
set_nickname_div ();
toplevel_launch >>= fun top ->
exercise_fetch >>= fun (ex_meta, exo, deadline) ->
+ let sub_id =
+ match exo with
+ | Learnocaml_exercise.Subexercise (exs,_) ->
+ (match exs with
+ | [] -> ""
+ | (ex,_subex) :: _ -> ex.Learnocaml_exercise.id)
+ | _ -> ""
+ in
(match deadline with
| None -> ()
| Some 0. -> make_readonly ()
@@ -156,7 +171,7 @@ let () =
solution
| { Answer.report = None ; solution ; _ } ->
solution
- | exception Not_found -> Learnocaml_exercise.(access File.template exo) in
+ | exception Not_found -> Learnocaml_exercise.(access ~subid:sub_id false File.template exo) in
(* ---- details pane -------------------------------------------------- *)
let load_meta () =
Lwt.async (fun () ->
@@ -179,13 +194,13 @@ let () =
(* ---- editor pane --------------------------------------------------- *)
let editor, ace = setup_editor solution in
let module EB = Editor_button (struct let ace = ace let buttons_container = editor_toolbar end) in
- EB.cleanup (Learnocaml_exercise.(access File.template exo));
+ EB.cleanup (Learnocaml_exercise.(access ~subid:sub_id false File.template exo));
EB.sync token id;
EB.download id;
EB.eval top select_tab;
let typecheck = typecheck top ace editor in
(*------------- prelude -----------------*)
- setup_prelude_pane ace Learnocaml_exercise.(decipher File.prelude exo);
+ setup_prelude_pane ace Learnocaml_exercise.(decipher ~subid:sub_id false File.prelude exo);
Js.Opt.case
(text_iframe##.contentDocument)
(fun () -> failwith "cannot edit iframe document")
@@ -193,11 +208,62 @@ let () =
d##open_;
d##write (Js.string (exercise_text ex_meta exo));
d##close) ;
+
+ (* ------------------- Subexercise navigation -------- *)
+
+ let nav_available = match exo with
+ | Learnocaml_exercise.Exercise _ -> false
+ | Learnocaml_exercise.Subexercise _ -> true
+ in
+ (* Traitement du "sous-index" pour savoir si on peut naviguer *)
+ token >>= fun tok ->
+ retrieve (Learnocaml_api.Exercise_index tok) >>= fun (index,l) ->
+ let navigation_toolbar = find_component "learnocaml-exo-tab-navigation" in
+ let prev_and_next id =
+ let rec loop = function
+ | [] -> assert false
+ | [ _ ] (* assumes single id *) -> None, None
+ | (one, _) :: (two, _) :: _ when id = one -> None, Some two
+ | (one, _) :: (two, _) :: [] when id = two -> Some one, None
+ | (one, _) :: (two, _) :: (three, _) :: _ when id = two -> Some one, Some three
+ | _ :: rest -> loop rest
+ in loop [id,1] in
+ let prev_button_state = button_state () in
+ let next_button_state = button_state () in
+ begin match prev_and_next id with
+ | None, None ->
+ disable_button prev_button_state ;
+ disable_button next_button_state
+ | Some _, None ->
+ enable_button prev_button_state ;
+ disable_button next_button_state
+ | None, Some _ ->
+ disable_button prev_button_state ;
+ enable_button next_button_state
+ | Some _, Some _ ->
+ enable_button prev_button_state ;
+ enable_button next_button_state
+ end ;
+ let subtitle_field = Tyxml_js.Html5.(h4 ~a: [a_class ["learnocaml-exo-subtitle"]]
+ [txt id]) in
+ let button_next = find_component "learnocaml-exo-button-next" in
+ let button_prev = find_component "learnocaml-exo-button-prev" in
+ Manip.appendChild ~before: button_next navigation_toolbar subtitle_field ;
+ if nav_available then
+ (Manip.SetCss.display button_next "";
+ Manip.SetCss.display button_prev "";
+ )
+ else
+ (Manip.SetCss.display button_next "none";
+ Manip.SetCss.display button_prev "none";
+ Manip.SetCss.width subtitle_field "100%";
+ );
+
(* ---- main toolbar -------------------------------------------------- *)
let exo_toolbar = find_component "learnocaml-exo-toolbar" in
let toolbar_button = button ~container: exo_toolbar ~theme: "light" in
begin toolbar_button
- ~icon: "list" [%i"Exercises"] @@ fun () ->
+ ~icon: "list" [%i"Exercises"] @@ fun () ->
Dom_html.window##.location##assign
(Js.string (api_server ^ "/index.html#activity=exercises")) ;
Lwt.return ()
@@ -283,6 +349,11 @@ let () =
Ace.focus ace ;
typecheck true
end ;
+ if nav_available then
+ begin toolbar_button
+ ~icon: "reload" [%i"AllGrade!"] @@ fun () ->
+ typecheck true
+ end;
Window.onunload (fun _ev -> local_save ace id; true);
(* ---- return -------------------------------------------------------- *)
toplevel_launch >>= fun _ ->
diff --git a/src/app/learnocaml_index_main.ml b/src/app/learnocaml_index_main.ml
index cf67f896c..0e623ca3f 100644
--- a/src/app/learnocaml_index_main.ml
+++ b/src/app/learnocaml_index_main.ml
@@ -77,73 +77,131 @@ let exercises_tab token _ _ () =
let open Tyxml_js.Html5 in
match contents with
| Exercise.Index.Exercises exercises ->
- List.fold_left
- (fun acc (exercise_id, meta_opt) ->
- match meta_opt with None -> acc | Some meta ->
- let {Exercise.Meta.kind; title; short_description; stars; _ } =
- meta
- in
- let pct_init =
- match SMap.find exercise_id all_exercise_states with
- | exception Not_found -> None
- | { Answer.grade ; _ } -> grade in
- let pct_signal, pct_signal_set = React.S.create pct_init in
- Learnocaml_local_storage.(listener (exercise_state exercise_id)) :=
- Some (function
- | Some { Answer.grade ; _ } -> pct_signal_set grade
- | None -> pct_signal_set None) ;
- let pct_text_signal =
- React.S.map
- (function
- | None -> "--"
- | Some 0 -> "0%"
- | Some pct -> string_of_int pct ^ "%")
- pct_signal in
- let time_left = match List.assoc_opt exercise_id deadlines with
- | None -> ""
- | Some 0. -> [%i"Exercise closed"]
- | Some f -> Printf.sprintf [%if"Time left: %s"]
- (string_of_seconds (int_of_float f))
- in
- let status_classes_signal =
- React.S.map
- (function
- | None -> [ "stats" ]
- | Some 0 -> [ "stats" ; "failure" ]
- | Some pct when pct >= 100 -> [ "stats" ; "success" ]
- | Some _ -> [ "stats" ; "partial" ])
- pct_signal in
- a ~a:[ a_href (get_url token "/exercises/" "exercise.html#id=" exercise_id) ;
- a_class [ "exercise" ] ] [
- div ~a:[ a_class [ "descr" ] ] (
- h1 [ txt title ] ::
- begin match short_description with
- | None -> []
- | Some text -> [ txt text ]
- end
- );
- div ~a:[ a_class [ "time-left" ] ] [H.txt time_left];
- div ~a:[ Tyxml_js.R.Html5.a_class status_classes_signal ] [
- stars_div stars;
- div ~a:[ a_class [ "length" ] ] [
- match kind with
- | Exercise.Meta.Project -> txt [%i"project"]
- | Exercise.Meta.Problem -> txt [%i"problem"]
- | Exercise.Meta.Exercise -> txt [%i"exercise"] ] ;
- div ~a:[ a_class [ "score" ] ] [
- Tyxml_js.R.Html5.txt pct_text_signal
- ]
- ] ] ::
- acc)
- acc exercises
+ List.fold_left
+ (fun acc (exercise_id, meta_opt, subindex_opt) ->
+ match meta_opt,subindex_opt with
+ | None, None -> acc
+ | Some meta, _ ->
+ let {Exercise.Meta.kind; title; short_description; stars; _ } =
+ meta
+ in
+ let pct_init =
+ match SMap.find exercise_id all_exercise_states with
+ | exception Not_found -> None
+ | { Answer.grade ; _ } -> grade in
+ let pct_signal, pct_signal_set = React.S.create pct_init in
+ Learnocaml_local_storage.(listener (exercise_state exercise_id)) :=
+ Some (function
+ | Some { Answer.grade ; _ } -> pct_signal_set grade
+ | None -> pct_signal_set None) ;
+ let pct_text_signal =
+ React.S.map
+ (function
+ | None -> "--"
+ | Some 0 -> "0%"
+ | Some pct -> string_of_int pct ^ "%")
+ pct_signal in
+ let time_left = match List.assoc_opt exercise_id deadlines with
+ | None -> ""
+ | Some 0. -> [%i"Exercise closed"]
+ | Some f -> Printf.sprintf [%if"Time left: %s"]
+ (string_of_seconds (int_of_float f))
+ in
+ let status_classes_signal =
+ React.S.map
+ (function
+ | None -> [ "stats" ]
+ | Some 0 -> [ "stats" ; "failure" ]
+ | Some pct when pct >= 100 -> [ "stats" ; "success" ]
+ | Some _ -> [ "stats" ; "partial" ])
+ pct_signal in
+ a ~a:[ a_href (get_url token "/exercises/" "exercise.html#id=" exercise_id) ;
+ a_class [ "exercise" ] ] [
+ div ~a:[ a_class [ "descr" ] ] (
+ h1 [ txt title ] ::
+ begin match short_description with
+ | None -> []
+ | Some text -> [ txt text ]
+ end
+ );
+ div ~a:[ a_class [ "time-left" ] ] [H.txt time_left];
+ div ~a:[ Tyxml_js.R.Html5.a_class status_classes_signal ] [
+ stars_div stars;
+ div ~a:[ a_class [ "length" ] ] [
+ match kind with
+ | Exercise.Meta.Project -> txt [%i"project"]
+ | Exercise.Meta.Problem -> txt [%i"problem"]
+ | Exercise.Meta.Exercise -> txt [%i"exercise"] ] ;
+ div ~a:[ a_class [ "score" ] ] [
+ Tyxml_js.R.Html5.txt pct_text_signal
+ ]
+ ] ] ::
+ acc
+ | None, Some subindex ->
+ let {Exercise.Meta.kind; title; short_description; stars; _ } =
+ Learnocaml_data.Exercise.Subindex.to_meta subindex
+ in
+ let pct_init =
+ match SMap.find exercise_id all_exercise_states with
+ | exception Not_found -> None
+ | { Answer.grade ; _ } -> grade in
+ let pct_signal, pct_signal_set = React.S.create pct_init in
+ Learnocaml_local_storage.(listener (exercise_state exercise_id)) :=
+ Some (function
+ | Some { Answer.grade ; _ } -> pct_signal_set grade
+ | None -> pct_signal_set None) ;
+ let pct_text_signal =
+ React.S.map
+ (function
+ | None -> "--"
+ | Some 0 -> "0%"
+ | Some pct -> string_of_int pct ^ "%")
+ pct_signal in
+ let time_left = match List.assoc_opt exercise_id deadlines with
+ | None -> ""
+ | Some 0. -> [%i"Exercise closed"]
+ | Some f -> Printf.sprintf [%if"Time left: %s"]
+ (string_of_seconds (int_of_float f))
+ in
+ let status_classes_signal =
+ React.S.map
+ (function
+ | None -> [ "stats" ]
+ | Some 0 -> [ "stats" ; "failure" ]
+ | Some pct when pct >= 100 -> [ "stats" ; "success" ]
+ | Some _ -> [ "stats" ; "partial" ])
+ pct_signal in
+ a ~a:[ a_href (get_url token "/exercises/" "exercise.html#id=" exercise_id) ;
+ a_class [ "exercise" ] ] [
+ div ~a:[ a_class [ "descr" ] ] (
+ h1 [ txt title ] ::
+ begin match short_description with
+ | None -> []
+ | Some text -> [ txt text ]
+ end
+ );
+ div ~a:[ a_class [ "time-left" ] ] [H.txt time_left];
+ div ~a:[ Tyxml_js.R.Html5.a_class status_classes_signal ] [
+ stars_div stars;
+ div ~a:[ a_class [ "length" ] ] [
+ match kind with
+ | Exercise.Meta.Project -> txt [%i"project"]
+ | Exercise.Meta.Problem -> txt [%i"problem"]
+ | Exercise.Meta.Exercise -> txt [%i"exercise"] ] ;
+ div ~a:[ a_class [ "score" ] ] [
+ Tyxml_js.R.Html5.txt pct_text_signal
+ ]
+ ] ] ::
+ acc)
+ acc exercises
| Exercise.Index.Groups groups ->
- let h = match lvl with 1 -> h1 | 2 -> h2 | _ -> h3 in
- List.fold_left
- (fun acc (_, Exercise.Index.{ title ; contents }) ->
- format_contents (succ lvl)
- (h ~a:[ a_class [ "pack" ] ] [ txt title ] :: acc)
- contents)
- acc groups in
+ let h = match lvl with 1 -> h1 | 2 -> h2 | _ -> h3 in
+ List.fold_left
+ (fun acc (_, Exercise.Index.{ title ; contents }) ->
+ format_contents (succ lvl)
+ (h ~a:[ a_class [ "pack" ] ] [ txt title ] :: acc)
+ contents)
+ acc groups in
List.rev (format_contents 1 [] index) in
let list_div =
match format_exercise_list
@@ -152,8 +210,8 @@ let exercises_tab token _ _ () =
| [] -> H.div [H.txt [%i"No open exercises at the moment"]]
| l -> H.div ~a:[H.a_id El.Dyn.exercise_list_id] l
in
- Manip.appendChild El.content list_div;
- Lwt.return list_div
+ Manip.appendChild El.content list_div;
+ Lwt.return list_div
let playground_tab token _ _ () =
show_loading [%i"Loading playground"] @@ fun () ->
diff --git a/src/app/learnocaml_student_view.ml b/src/app/learnocaml_student_view.ml
index 6b116b628..0738c34e2 100644
--- a/src/app/learnocaml_student_view.ml
+++ b/src/app/learnocaml_student_view.ml
@@ -386,7 +386,7 @@ let display_report exo report =
let report_button = El.Tabs.(report.btn) in
restore_report_button ();
let grade =
- let max = Learnocaml_exercise.(access File.max_score exo) in
+ let max = Learnocaml_exercise.(access true File.max_score exo) in
if max = 0 then 999 else score * 100 / max
in
if grade >= 100 then begin
diff --git a/src/app/learnocaml_teacher_tab.ml b/src/app/learnocaml_teacher_tab.ml
index db47f5eaa..af173437e 100644
--- a/src/app/learnocaml_teacher_tab.ml
+++ b/src/app/learnocaml_teacher_tab.ml
@@ -203,7 +203,7 @@ let rec teacher_tab token _select _params () =
mk_table (group_level + 1) acc status g.Exercise.Index.contents)
acc groups_list
| Exercise.Index.Exercises exlist ->
- List.fold_left (fun acc (id, meta) ->
+ List.fold_left (fun acc (id, meta, _subindex) ->
let open_exercise_ () =
let _win = window_open ("/exercises/"^id^"/") "_blank" in
false
@@ -300,7 +300,7 @@ let rec teacher_tab token _select _params () =
empty && empty0, List.rev_append hidden hidden0)
(true, []) groups_list
| Exercise.Index.Exercises l ->
- List.fold_left (fun (empty, hidden) (id, ex) ->
+ List.fold_left (fun (empty, hidden) (id, ex, _subindex) ->
let elt = find_component (exercise_line_id id) in
match ex with
| Some ex when matches id ex ->
@@ -484,7 +484,7 @@ let rec teacher_tab token _select _params () =
(Manip.addClass anystudents_line "student_hidden"; [`Any])
in
let hidden =
- Token.Map.fold (fun tok std hidden->
+ Token.Map.fold (fun tok std hidden ->
let elt = find_component (student_line_id (`Token tok)) in
if matches std then
(Manip.removeClass elt "student_hidden";
diff --git a/src/grader/grader_cli.ml b/src/grader/grader_cli.ml
index 709f1a641..dc777753b 100644
--- a/src/grader/grader_cli.ml
+++ b/src/grader/grader_cli.ml
@@ -47,13 +47,17 @@ let read_student_file exercise_dir path =
else
Lwt_io.with_file ~mode:Lwt_io.Input fn Lwt_io.read
-let grade ?(print_result=false) ?dirname meta exercise output_json =
+let grade ?(check=None) ?(print_result=false) ?dirname meta exercise output_json =
Lwt.catch
(fun () ->
- let code_to_grade = match !grade_student with
+ let code_to_grade =
+ match check with
+ | Some path -> read_student_file (Sys.getcwd ()) ( path ^ "/solution.ml")
+ | None ->
+ match !grade_student with
| Some path -> read_student_file (Sys.getcwd ()) path
| None ->
- Lwt.return (Learnocaml_exercise.(decipher File.solution exercise)) in
+ Lwt.return (Learnocaml_exercise.(decipher true File.solution exercise)) in
let callback =
if !display_callback then Some (Printf.eprintf "[ %s ]%!\r\027[K") else None in
let timeout = !individual_timeout in
@@ -138,20 +142,20 @@ let grade ?(print_result=false) ?dirname meta exercise output_json =
if failure then begin
if print_result then
Printf.eprintf "%-30s - Failure - %d points\n%!"
- Learnocaml_exercise.(access File.id exercise) max;
+ Learnocaml_exercise.(access true File.id exercise) max;
Lwt.return (Error max)
end
else begin
if print_result then
Printf.eprintf "%-30s - Success - %d points\n%!"
- Learnocaml_exercise.(access File.id exercise) max;
+ Learnocaml_exercise.(access true File.id exercise) max;
match output_json with
| None ->
Lwt.return (Ok ())
| Some json_file ->
let json =
Json_encoding.(construct (tup3 Learnocaml_data.Exercise.Meta.enc Learnocaml_exercise.encoding (option float)))
- (meta, Learnocaml_exercise.(update File.max_score max exercise), None)
+ (meta, Learnocaml_exercise.Exercise (Learnocaml_exercise.(update File.max_score max exercise)), None)
in
let json = match json with
| `A _ | `O _ as d -> d
@@ -183,4 +187,4 @@ let grade_from_dir ?(print_result=false) exercise_dir output_json =
| "" -> `O []
| s -> Ezjsonm.from_string s)
|> Json_encoding.destruct Learnocaml_data.Exercise.Meta.enc in
- grade ~print_result ~dirname:exercise_dir meta exo output_json
+ grade ~print_result ~dirname:exercise_dir meta (Learnocaml_exercise.Exercise exo) output_json
diff --git a/src/grader/grader_cli.mli b/src/grader/grader_cli.mli
index e66095ef7..37b55af96 100644
--- a/src/grader/grader_cli.mli
+++ b/src/grader/grader_cli.mli
@@ -39,7 +39,9 @@ val dump_dot: string option ref
(** Runs the grading process *)
val grade:
- ?print_result:bool -> ?dirname:string -> Learnocaml_data.Exercise.Meta.t -> Learnocaml_exercise.t -> string option ->
+ ?check: string option -> ?print_result:bool -> ?dirname:string ->
+ Learnocaml_data.Exercise.Meta.t ->
+ Learnocaml_exercise.t -> string option ->
(unit, int) result Lwt.t
val grade_from_dir:
diff --git a/src/grader/grading.ml b/src/grader/grading.ml
index dce50a237..edfa48241 100644
--- a/src/grader/grading.ml
+++ b/src/grader/grading.ml
@@ -102,12 +102,12 @@ let get_grade
set_progress [%i"Loading the prelude."] ;
handle_error (internal_error [%i"while loading the prelude"]) @@
Toploop_ext.use_string ~print_outcome ~ppf_answer ~filename:(file "prelude.ml")
- (Learnocaml_exercise.(decipher File.prelude exo)) ;
+ (Learnocaml_exercise.(decipher false File.prelude exo)) ;
set_progress [%i"Preparing the test environment."] ;
handle_error (internal_error [%i"while preparing the tests"]) @@
Toploop_ext.use_string ~print_outcome ~ppf_answer ~filename:(file "prepare.ml")
- (Learnocaml_exercise.(decipher File.prepare exo)) ;
+ (Learnocaml_exercise.(decipher false File.prepare exo)) ;
set_progress [%i"Loading your code."] ;
handle_error user_code_error @@
@@ -117,7 +117,7 @@ let get_grade
set_progress [%i"Loading the solution."] ;
handle_error (internal_error [%i"while loading the solution"]) @@
Toploop_ext.use_mod_string ~print_outcome ~ppf_answer ~modname:"Solution"
- (Learnocaml_exercise.(decipher File.solution exo)) ;
+ (Learnocaml_exercise.(decipher false File.solution exo)) ;
set_progress [%i"Preparing to launch the tests."] ;
Introspection.allow_introspection ~divert ;
@@ -145,12 +145,12 @@ let get_grade
let () =
let open Learnocaml_exercise in
- let files = File.dependencies (access File.depend exo) in
+ let files = File.dependencies (access false File.depend exo) in
let rec load_dependencies signatures = function
| [] -> () (* signatures without implementation are ignored *)
| file::fs ->
let path = File.key file
- and content = decipher file exo in
+ and content = decipher false file exo in
let modname = String.capitalize_ascii @@
Filename.remove_extension @@ Filename.basename path in
match Filename.extension path with
@@ -188,7 +188,7 @@ let get_grade
handle_error (internal_error [%i"while testing your solution"]) @@
Toploop_ext.use_string ~print_outcome ~ppf_answer ~filename:(file "test.ml")
- (Learnocaml_exercise.(decipher File.test exo)) ;
+ (Learnocaml_exercise.(decipher false File.test exo)) ;
(* Memory cleanup... *)
Toploop.initialize_toplevel_env () ;
diff --git a/src/main/learnocaml_client.ml b/src/main/learnocaml_client.ml
index d4518988c..a4cad6bf9 100644
--- a/src/main/learnocaml_client.ml
+++ b/src/main/learnocaml_client.ml
@@ -348,7 +348,7 @@ let get_score =
in
get_score 0
-let max_score exo = Learnocaml_exercise.(access File.max_score exo)
+let max_score exo = Learnocaml_exercise.(access false File.max_score exo)
let print_score ?(max=1) ?color i =
let color = match color with
@@ -475,7 +475,7 @@ let upload_save server_url token save =
let upload_report server token ex solution report =
let score = get_score report in
let max_score = max_score ex in
- let id = Learnocaml_exercise.(access File.id ex) in
+ let id = Learnocaml_exercise.(access (Learnocaml_data.Token.is_student token) File.id ex) in
let mtime = Unix.gettimeofday () in
let exercise_state =
{ Answer.
@@ -911,7 +911,7 @@ module Template = struct
>>= fun (_meta, exercise, _deadline) ->
write_exercise_file
exercise_id
- Learnocaml_exercise.(access File.template exercise)
+ Learnocaml_exercise.(access false File.template exercise)
>|= function
| true -> 0
| false -> 3
diff --git a/src/repo/learnocaml_exercise.ml b/src/repo/learnocaml_exercise.ml
index 5157b05e0..d08170303 100644
--- a/src/repo/learnocaml_exercise.ml
+++ b/src/repo/learnocaml_exercise.ml
@@ -8,7 +8,19 @@
type id = string
-type t =
+type check_all_against = string option
+
+type subexercise =
+ { sub_id : id;
+ student_hidden : bool;
+ student_weight : int;
+ teacher_weight : int;
+ }
+
+let construct_subexercise sub_id student_hidden student_weight teacher_weight =
+ {sub_id; student_hidden; student_weight; teacher_weight}
+
+type exercise =
{ id : id ;
prelude : string ;
template : string ;
@@ -21,24 +33,61 @@ type t =
dependencies : string list;
}
+
+type t =
+ | Subexercise of ((exercise * subexercise) list * check_all_against )
+ | Exercise of exercise
+
let encoding =
let open Json_encoding in
- conv
- (fun { id ; prelude ; template ; descr ; prepare ; test ; solution ; max_score ; depend ; dependencies} ->
- id, prelude, template, descr, prepare, test, solution, max_score,depend, dependencies)
- (fun (id, prelude, template, descr, prepare, test, solution, max_score,depend, dependencies) ->
- { id ; prelude ; template ; descr ; prepare ; test ; solution ; max_score ; depend ; dependencies})
- (obj10
- (req "id" string)
- (req "prelude" string)
- (req "template" string)
- (req "descr" (list (tup2 string string)))
- (req "prepare" string)
- (req "test" string)
- (req "solution" string)
- (req "max-score" int)
- (req "depend" (option string))
- (req "dependencies" (list string)))
+ let exercise_enc =
+ conv
+ (fun { id ; prelude ; template ; descr ; prepare ; test ; solution ; max_score ; depend ; dependencies} ->
+ id, prelude, template, descr, prepare, test, solution, max_score,depend, dependencies)
+ (fun (id, prelude, template, descr, prepare, test, solution, max_score,depend, dependencies) ->
+ { id ; prelude ; template ; descr ; prepare ; test ; solution ; max_score ; depend ; dependencies})
+ (obj10
+ (req "id" string)
+ (req "prelude" string)
+ (req "template" string)
+ (req "descr" (list (tup2 string string)))
+ (req "prepare" string)
+ (req "test" string)
+ (req "solution" string)
+ (req "max-score" int)
+ (req "depend" (option string))
+ (req "dependencies" (list string)))
+ in
+ let sub_enc =
+ conv
+ (fun { sub_id ; student_hidden ; student_weight ; teacher_weight } ->
+ sub_id, student_hidden, student_weight, teacher_weight)
+ (fun (sub_id, student_hidden, student_weight, teacher_weight) ->
+ { sub_id ; student_hidden ; student_weight ; teacher_weight })
+ (obj4
+ (req "id" string)
+ (req "student_hidden" bool)
+ (req "student_weight" int)
+ (req "teacher_weight" int))
+ in
+ let subexercise_enc =
+ obj1
+ (req "subexercise" (tup2 (list (tup2 exercise_enc sub_enc)) (obj1 (opt "check_all_against" string))))
+ in
+ union
+ [case
+ exercise_enc
+ (function
+ |Exercise map -> Some map
+ |_ -> None)
+ (fun map -> Exercise map );
+ case
+ subexercise_enc
+ (function
+ | Subexercise map -> Some map
+ | _ -> None)
+ (fun map -> Subexercise map)
+ ]
(* let meta_from_string m =
* Ezjsonm.from_string m
@@ -88,8 +137,8 @@ module File = struct
ciphered : bool ;
decode : string -> 'a ;
encode : 'a -> string ;
- field : t -> 'a ;
- update : 'a -> t -> t ;
+ field : exercise -> 'a ;
+ update : 'a -> exercise -> exercise ;
}
exception Missing_file of string
@@ -106,9 +155,9 @@ module File = struct
with Not_found -> raise (Missing_file ("get " ^ key))
let get_opt file ex =
- try (* a missing file here is necessarily [file] *)
- get file ex
- with Missing_file _ -> None
+ try (* a missing file here is necessarily [file] *)
+ get file ex
+ with Missing_file _ -> None
let has { key ; _ } ex =
StringMap.mem key ex
@@ -350,37 +399,62 @@ module File = struct
include MakeReader (Seq)
end
-let access f ex =
- f.File.field ex
-
-let decipher f ex =
+let access ?(subid="") _is_Student f ex =
+ match ex with
+ | Exercise exo -> f.File.field exo
+ | Subexercise (subexos,_) ->
+ f.File.field @@
+ (fun (ex,_) -> ex) @@
+ List.find
+ (fun (ex,_subex) -> ex.id = subid)
+ subexos
+
+let decipher ?(subid="") _is_Student f ex =
+ let exo = match ex with
+ | Exercise exo -> exo
+ | Subexercise (subexos,_) ->
+ (fun (ex,_) -> ex) @@
+ List.find
+ (fun (ex,_subex) -> ex.id = subid)
+ subexos
+ in
let open File in
- let raw = f.field ex in
+ let raw = f.field exo in
if f.ciphered then
let prefix =
- Digest.string (ex.id ^ "_" ^ f.key) in
+ Digest.string (exo.id ^ "_" ^ f.key) in
f.decode (Learnocaml_xor.decode ~prefix raw)
else
f.decode raw
-let update f v ex =
- f.File.update v ex
-
-let cipher f v ex =
+let update ?(subid="") f v ex =
+ let exo = match ex with
+ | Exercise exo -> exo
+ | Subexercise (subexos, _) -> (fun (ex,_) -> ex) @@
+ List.find (fun (ex,_) -> ex.id = subid) subexos
+ in
+ f.File.update v exo
+
+let cipher ?(subid="") f v ex =
+ let exo = match ex with
+ | Exercise exo -> exo
+ | Subexercise (subexos, _) -> (fun (ex,_) -> ex) @@
+ List.find (fun (ex, _) -> ex.id = subid) subexos
+ in
let open File in
if f.ciphered then
let prefix =
- Digest.string (ex.id ^ "_" ^ f.key) in
- f.update (Learnocaml_xor.encode ~prefix (f.encode v)) ex
+ Digest.string (exo.id ^ "_" ^ f.key) in
+ f.update (Learnocaml_xor.encode ~prefix (f.encode v)) exo
else
- f.update (f.encode v) ex
+ f.update (f.encode v) exo
let field_from_file file files =
try File.(StringMap.find file.key files |> file.decode)
with Not_found -> raise File.(Missing_file file.key)
module MakeReaderAnddWriter (Concur : Concur) = struct
-
+
module FileReader = File.MakeReader(Concur)
let read ~read_field ?id ?decipher () =
@@ -399,19 +473,19 @@ module MakeReaderAnddWriter (Concur : Concur) = struct
solution = field_from_file File.solution ex ;
max_score = 0 ;
depend ;
- dependencies =
+ dependencies =
let field_from_dependency file =
try field_from_file file ex
- with File.Missing_file msg
- -> let msg' = msg ^ ": dependency declared in "
+ with File.Missing_file msg
+ -> let msg' = msg ^ ": dependency declared in "
^ File.(key depend) ^ ", but not found" in
- raise (File.Missing_file msg')
- in
- List.map field_from_dependency (File.dependencies depend)
+ raise (File.Missing_file msg')
+ in
+ List.map field_from_dependency (File.dependencies depend)
}
with File.Missing_file _ as e -> fail e
- let write ~write_field ex ?(cipher = true) acc =
+ let write ~write_field ex ?(cipher = true) is_Student acc =
let open Concur in
let open File in
let acc = ref acc in
@@ -441,7 +515,7 @@ module MakeReaderAnddWriter (Concur : Concur) = struct
write_field test ;
write_field depend ;
(* write_field max_score *) ]
- @ (List.map write_field (dependencies (access depend ex))) )
+ @ (List.map write_field (dependencies (access is_Student depend (Exercise ex) ))) )
>>= fun () ->
return !acc
end
diff --git a/src/repo/learnocaml_exercise.mli b/src/repo/learnocaml_exercise.mli
index 1bb8809a7..c3a057799 100644
--- a/src/repo/learnocaml_exercise.mli
+++ b/src/repo/learnocaml_exercise.mli
@@ -9,10 +9,38 @@
(** Internal representation of the exercises files, including the metadata from
the repository. *)
-type t
+
type id = string
+type check_all_against = string option
+
+type subexercise =
+ { sub_id : id;
+ student_hidden : bool;
+ student_weight : int;
+ teacher_weight : int;
+ }
+
+val construct_subexercise : id -> bool -> int -> int -> subexercise
+
+type exercise = {
+ id : id;
+ prelude : string;
+ template : string;
+ descr : (string * string) list;
+ prepare : string;
+ test : string;
+ solution : string;
+ max_score : int;
+ depend : string option;
+ dependencies : string list;
+ }
+
+type t =
+ |Subexercise of ((exercise * subexercise) list * check_all_against )
+ | Exercise of exercise
+
(* JSON encoding of the exercise representation. Includes cipher and decipher at
at encoding and decoding. *)
val encoding: t Json_encoding.encoding
@@ -82,43 +110,47 @@ end
(** Access a field from the exercise, using the [t] representation, without **
deciphering it. May raise [Missing_file] if the field is optional and set to
- [None]. *)
-val access: 'a File.file -> t -> 'a
+ [None].
+ For subexercises, cannot access if the users is a student and the subexercise
+ is student_hidden. It will raise a Not_found exeption *)
+val access: ?subid:id -> bool -> 'a File.file -> t -> 'a
(** Access a string field from the exercise, using the [t] representation, and
deciphers if necessary. May raise [Missing_file] if the field is optional and
- set to [None]. *)
-val decipher: string File.file -> t -> string
+ set to [None].
+ For subexercises, cannot access and decipher if the users is a student and the subexercise
+ is student_hidden. It will raise a Not_found exeption *)
+val decipher: ?subid:id -> bool -> string File.file -> t -> string
(** Updates the value of a field of the exercise in its [t] representation. *)
-val update: 'a File.file -> 'a -> t -> t
+val update: ?subid:id -> 'a File.file -> 'a -> t -> exercise
(** Updates the value of a field of the exercise in its [t] representation, and
ciphers it. *)
-val cipher: string File.file -> string -> t -> t
+val cipher: ?subid:id -> string File.file -> string -> t -> exercise
(** Reader and decipherer *)
val read:
read_field:(string -> string option) ->
?id:string -> ?decipher:bool -> unit ->
- t
+ exercise
(** Writer and cipherer, ['a] can be [unit] *)
val write:
write_field:(string -> string -> 'a -> 'a) ->
- t -> ?cipher:bool -> 'a ->
+ exercise -> ?cipher:bool -> bool -> 'a ->
'a
(** Reader and decipherer with {!Lwt} *)
val read_lwt:
read_field:(string -> string option Lwt.t) ->
?id:string -> ?decipher:bool -> unit ->
- t Lwt.t
+ exercise Lwt.t
(** Writer and cipherer with {!Lwt}, ['a] can be [unit] *)
val write_lwt:
write_field:(string -> string -> 'a -> 'a Lwt.t) ->
- t -> ?cipher:bool -> 'a ->
+ exercise -> ?cipher:bool -> bool -> 'a ->
'a Lwt.t
(** JSON serializer, with {!id} file included *)
diff --git a/src/repo/learnocaml_process_exercise_repository.ml b/src/repo/learnocaml_process_exercise_repository.ml
index df2cbb627..fe703e11a 100644
--- a/src/repo/learnocaml_process_exercise_repository.ml
+++ b/src/repo/learnocaml_process_exercise_repository.ml
@@ -52,7 +52,7 @@ let print_grader_error exercise = function
| Error (-1) -> ()
| Error n ->
Format.eprintf "[ERROR] %s: the solution has errors! (%d points%s)@."
- Learnocaml_exercise.(access File.id exercise)
+ Learnocaml_exercise.(access true File.id exercise)
n
(if !Grader_cli.display_reports then ""
else ". Run with '-v' to see the report")
@@ -76,21 +76,42 @@ let spawn_grader
Grader_cli.display_callback := false;
Lwt_main.run
(Lwt.catch (fun () ->
- Grader_cli.grade ?print_result ?dirname meta exercise output_json
- >|= fun r ->
- print_grader_error exercise r;
- match r with
- | Ok () -> exit 0
- | Error _ -> exit 1)
- (fun e ->
- Printf.eprintf "%!Grader error: %s\n%!" (Printexc.to_string e);
- exit 10))
+ (match exercise with
+ | Learnocaml_exercise.Subexercise (exs, _check_all_against) ->
+ (* match check_all_against with
+ | Some id ->
+ let exo = // Ici : trouver l'exercice du CAA
+ Lwt_list.map_p // Ici : noter tout à partir du CAA
+ | None -> *)
+ Lwt_list.map_p
+ (fun (exo,_subexs) -> Grader_cli.grade ?print_result ?dirname meta
+ (Learnocaml_exercise.Exercise exo) output_json)
+ exs
+ | exo -> Lwt_list.map_p
+ (fun exo -> Grader_cli.grade ?print_result ?dirname meta
+ exo output_json)
+ [exo]
+ )
+ >|= fun l ->
+ let rec aux = function
+ | [] -> []
+ | r :: l -> ( print_grader_error exercise r; r :: aux l)
+ in
+ let rec result = function
+ | Ok () :: l -> result l
+ | Error _ :: _ -> exit 1
+ | [] -> exit 0
+ in
+ result @@ aux l)
+ (fun e ->
+ Printf.eprintf "%!Grader error: %s\n%!" (Printexc.to_string e);
+ exit 10))
| pid ->
Lwt_unix.waitpid [] pid >>= fun (_pid, ret) ->
incr n_processes;
match ret with
- | Unix.WEXITED 0 -> Lwt.return (Ok ())
- | _ -> Lwt.return (Error (-1))
+ | Unix.WEXITED 0 -> Lwt.return ([Ok ()])
+ | _ -> Lwt.return ([Error (-1)])
let main dest_dir =
let exercises_index =
@@ -115,9 +136,15 @@ let main dest_dir =
in
if Sys.file_exists (f / "meta.json") then
match acc with
- | None -> Some (Index.Exercises [full_id, None])
+ | None -> Some (Index.Exercises [full_id, None, None])
| Some (Index.Exercises e) ->
- Some (Index.Exercises (e @ [full_id, None]))
+ Some (Index.Exercises (e @ [full_id, None, None]))
+ | _ -> None
+ else if Sys.file_exists (f / "subindex.json") then
+ match acc with
+ | None -> Some (Index.Exercises [full_id, None, None])
+ | Some (Index.Exercises e) ->
+ Some (Index.Exercises (e @ [full_id, None, None]))
| _ -> None
else if Sys.is_directory f then
match acc, auto_index f with
@@ -144,10 +171,10 @@ let main dest_dir =
Format.eprintf "This does not look like a LearnOCaml exercise repository.@." ;
Lwt.fail (Failure "cannot continue")))
>>= fun structure ->
-
(* Exercises must be unique, since their id refer to the directory. *)
let rec fill_structure all_exercises = function
| Index.Groups groups ->
+ print_string "fill_structure groups\n";
(* Ensures groups of a same parent are unique *)
Lwt_list.fold_left_s
(fun (all_exercises, subgroups, acc) (id, gr) ->
@@ -163,30 +190,72 @@ let main dest_dir =
>|= fun (all_exercises, _subgroups, groups) ->
all_exercises, Index.Groups groups
| Index.Exercises ids ->
+ print_string "fillstructure exe\n";
let filtered id =
!exercises_filtered <> SSet.empty
&& not (SSet.mem id !exercises_filtered) in
Lwt_list.fold_left_s
- (fun (all_exercises, acc) (id, _) ->
+ (fun (all_exercises, acc) (id, _ , _) ->
if SMap.mem id all_exercises || filtered id then
Lwt.return (all_exercises, acc)
else
- from_file Meta.enc
- (!exercises_dir / id / "meta.json")
- >>= fun meta ->
- read_exercise (!exercises_dir / id)
- >|= fun exercise ->
- SMap.add id exercise all_exercises,
- (id, Some meta) :: acc)
+ if Sys.file_exists (!exercises_dir / id / "meta.json") then
+ (from_file Meta.enc
+ (!exercises_dir / id / "meta.json")
+ >>= fun meta ->
+ read_exercise (!exercises_dir / id)
+ >|= fun exercise ->
+ SMap.add id (Learnocaml_exercise.Exercise exercise) all_exercises,
+ (id, Some meta, None) :: acc)
+ else
+ (from_file (Subindex.enc)
+ (!exercises_dir / id / "subindex.json")
+ >>= fun meta ->
+ let check_all_against =
+ match Exercise.Subindex.to_check meta with
+ | Some check -> Some (!exercises_dir / id / check)
+ | _ -> None
+ in
+ let subexercise_list = Exercise.Subindex.to_part meta
+ in
+ let rec aux = function
+ | [] -> []
+ | part::l ->
+ let (_,subexercise,student_hidden,s_weight,t_weight) =
+ Exercise.Subindex.get_part_field part
+ in (id,subexercise,student_hidden,s_weight,t_weight,Some meta)::aux l
+ in
+ let listing = aux subexercise_list
+ in
+ let subexercises =
+ (Lwt_list.fold_left_s
+ (fun (acc) (sup_id,sub_id,s_hidden,s_weight,t_weight,_) ->
+ let long_id = (sup_id / sub_id) in
+ if SMap.mem long_id all_exercises || filtered long_id then
+ Lwt.return acc
+ else
+ read_exercise (!exercises_dir / long_id)
+ >|= fun exercise ->
+ print_string (long_id^"\n");
+ let subexercise = Learnocaml_exercise.construct_subexercise long_id s_hidden s_weight t_weight
+ in
+ (exercise,subexercise) :: acc))
+ ([]) (List.rev listing)
+ in subexercises >|= fun exercise ->
+ SMap.add id
+ (Learnocaml_exercise.Subexercise (exercise, check_all_against)) all_exercises,
+ (id, None, Some meta) :: acc))
(all_exercises, []) (List.rev ids)
>>= fun (all_exercises, exercises) ->
Lwt.return (all_exercises, Index.Exercises exercises)
in
fill_structure SMap.empty structure >>= fun (all_exercises, index) ->
- to_file Index.enc (dest_dir / Learnocaml_index.exercise_index_path) index >>= fun () ->
+ to_file Index.enc (dest_dir / Learnocaml_index.exercise_index_path)
+ index >>= fun () ->
dump_dot index >>= fun () ->
Learnocaml_store.Exercise.Index.get_from_index index >>= fun index ->
- to_file Json_encoding.(tup2 Learnocaml_store.Exercise.Index.enc (assoc float)) (dest_dir / "exercise-index.json") (index, [])
+ to_file Json_encoding.(tup2 Learnocaml_store.Exercise.Index.enc (assoc float))
+ (dest_dir / "exercise-index.json") (index, [])
>>= fun () ->
SSet.iter (fun id ->
if not (SMap.mem id all_exercises) then
@@ -195,72 +264,131 @@ let main dest_dir =
let processes_arguments =
List.rev @@ SMap.fold
- (fun id exercise acc ->
- let exercise_dir = !exercises_dir / id in
- let json_path = dest_dir / Learnocaml_index.exercise_path id in
- let changed = try
- let { Unix.st_mtime = json_time ; _ } = Unix.stat json_path in
- Sys.readdir exercise_dir |>
- Array.to_list |>
- List.map (fun f -> (Unix.stat (exercise_dir / f)).Unix.st_mtime ) |>
- List.exists (fun t -> t >= json_time)
- with _ -> true in
- let dump_outputs =
- match !dump_outputs with
- | None -> None
- | Some dir -> Some (dir / id) in
- let dump_reports =
- match !dump_reports with
- | None -> None
- | Some dir -> Some (dir / id) in
- (id, exercise_dir, exercise, json_path,
- changed, dump_outputs, dump_reports) :: acc)
+ (fun id exercise acc ->
+ match exercise with
+ | Learnocaml_exercise.Exercise _ ->
+ let exercise_dir = !exercises_dir / id in
+ let json_path = dest_dir / Learnocaml_index.exercise_path id in
+ let changed = try
+ let { Unix.st_mtime = json_time ; _ } = Unix.stat json_path in
+ Sys.readdir exercise_dir |>
+ Array.to_list |>
+ List.map (fun f -> (Unix.stat (exercise_dir / f)).Unix.st_mtime ) |>
+ List.exists (fun t -> t >= json_time)
+ with _ -> true in
+ let dump_outputs =
+ match !dump_outputs with
+ | None -> None
+ | Some dir -> Some (dir / id) in
+ let dump_reports =
+ match !dump_reports with
+ | None -> None
+ | Some dir -> Some (dir / id) in
+ (id, exercise_dir, exercise, json_path,
+ changed, dump_outputs, dump_reports) :: acc
+ | Learnocaml_exercise.Subexercise (ex,_) ->
+ List.append (List.rev @@ List.fold_right
+ (fun (exo,_) acc ->
+ let sub_id = exo.Learnocaml_exercise.id
+ in
+ let exercise_dir = !exercises_dir / id / sub_id in
+ let json_path = dest_dir / Learnocaml_index.exercise_path (id / sub_id) in
+ let changed = try
+ let { Unix.st_mtime = json_time ; _ } = Unix.stat json_path in
+ Sys.readdir exercise_dir |>
+ Array.to_list |>
+ List.map (fun f -> (Unix.stat (exercise_dir / f)).Unix.st_mtime ) |>
+ List.exists (fun t -> t >= json_time)
+ with _ -> true in
+ let dump_outputs =
+ match !dump_outputs with
+ | None -> None
+ | Some dir -> Some (dir / id ) in
+ let dump_reports =
+ match !dump_reports with
+ | None -> None
+ | Some dir -> Some (dir / id ) in
+ (id, exercise_dir, exercise, json_path,
+ changed, dump_outputs, dump_reports) :: acc)
+ ex []) acc)
all_exercises [] in
begin
let listmap, grade =
if !n_processes = 1 then
- Lwt_list.map_s,
+ (Lwt_list.map_s,
fun dump_outputs dump_reports ?print_result ?dirname
- meta exercise json_path ->
- Grader_cli.dump_outputs := dump_outputs;
- Grader_cli.dump_reports := dump_reports;
- Grader_cli.grade ?print_result ?dirname meta exercise json_path
- >|= fun r -> print_grader_error exercise r; r
+ meta exercise json_path ->
+ Grader_cli.dump_outputs := dump_outputs;
+ Grader_cli.dump_reports := dump_reports;
+ (match exercise with
+ | Learnocaml_exercise.Subexercise (exs,check_all_against) ->
+ (match check_all_against with
+ | Some _ ->
+ Lwt_list.map_p
+ (fun (exo,_) ->
+ Grader_cli.grade ~check:check_all_against ?print_result ?dirname meta
+ (Learnocaml_exercise.Exercise exo) json_path)
+ exs
+ >>= fun check_all_against_result ->
+ (Lwt_list.map_p
+ (fun (exo,_) -> Grader_cli.grade ?print_result ?dirname meta
+ (Learnocaml_exercise.Exercise exo) json_path)
+ exs)
+ >>= fun normal_result ->
+ Lwt.return @@ List.append check_all_against_result normal_result
+ | None ->
+ Lwt_list.map_p
+ (fun (exo,_) -> Grader_cli.grade ?print_result ?dirname meta
+ (Learnocaml_exercise.Exercise exo) json_path)
+ exs)
+ | exo -> Lwt_list.map_p
+ (fun exo -> Grader_cli.grade ?print_result ?dirname meta
+ exo json_path)
+ [exo]
+ )
+ >|= fun l ->
+ let rec aux = function
+ | [] -> []
+ | r :: l -> ( print_grader_error exercise r; r :: aux l)
+ in aux l )
else
Lwt_list.map_p,
spawn_grader
in
listmap (fun (id, ex_dir, exercise, json_path, changed, dump_outputs,dump_reports) ->
+ print_string ("ID : "^id^"\nEx_dir : "^ex_dir^"\n");
let dst_ex_dir = String.concat Filename.dir_sep [dest_dir; "static"; id] in
Lwt_utils.mkdir_p dst_ex_dir >>= fun () ->
- Lwt_stream.iter_p (fun base ->
+ Lwt_stream.iter_p (fun base ->
let d = Filename.concat ex_dir base in
let dst = String.concat Filename.dir_sep [dst_ex_dir; base] in
if Sys.is_directory d && base.[0] <> '.' then
- Lwt_utils.copy_tree d dst
+ Lwt_utils.copy_tree d dst
else Lwt.return_unit)
- (Lwt_unix.files_of_directory ex_dir) >>= fun () ->
- if not changed then begin
+ (Lwt_unix.files_of_directory ex_dir) >>= fun () ->
+ if not changed then begin
Format.printf "%-24s (no changes)@." id ;
Lwt.return true
end else begin
grade dump_outputs dump_reports
~dirname:(!exercises_dir / id) (Index.find index id) exercise (Some json_path)
- >>= function
- | Ok () ->
- Format.printf "%-24s [OK]@." id ;
- Lwt.return true
- | Error _ ->
- Format.printf "%-24s [FAILED]@." id ;
- Lwt.return false
+ >>=
+ function
+ | Ok () :: _ (* à changer *) ->
+ Format.printf "%-24s [OK]@." id ;
+ Lwt.return true
+ | Error _ :: _->
+ Format.printf "%-24s [FAILED]@." id ;
+ Lwt.return false
+ | [] -> Lwt.return false
end)
- processes_arguments
+ processes_arguments
end >>= fun results ->
Lwt.return (List.for_all ((=) true) results))
(fun exn ->
- let print_unknown ppf = function
- | Failure msg -> Format.fprintf ppf "Cannot process exercises: %s" msg
- | exn -> Format.fprintf ppf "Cannot process exercises: %s" (Printexc.to_string exn) in
- Json_encoding.print_error ~print_unknown Format.err_formatter exn ;
- Format.eprintf "@." ;
- Lwt.return false)
+ let print_unknown ppf = function
+ | Failure msg -> Format.fprintf ppf "Cannot process exercises: %s" msg
+ | exn -> Format.fprintf ppf "Cannot process exercises: %s" (Printexc.to_string exn) in
+ Json_encoding.print_error ~print_unknown Format.err_formatter exn ;
+ Format.eprintf "@." ;
+ Lwt.return false)
diff --git a/src/repo/learnocaml_process_playground_repository.ml b/src/repo/learnocaml_process_playground_repository.ml
index d73f2d808..4a1239aa3 100644
--- a/src/repo/learnocaml_process_playground_repository.ml
+++ b/src/repo/learnocaml_process_playground_repository.ml
@@ -16,8 +16,8 @@ let playground_index = ref None
let errored exn =
let print_unknown ppf = function
- | Failure msg -> Format.fprintf ppf "Cannot process exercises: %s" msg
- | exn -> Format.fprintf ppf "Cannot process exercises: %s" (Printexc.to_string exn) in
+ | Failure msg -> Format.fprintf ppf "Cannot process exercises (playground): %s" msg
+ | exn -> Format.fprintf ppf "Cannot process exercises (playground): %s" (Printexc.to_string exn) in
Json_encoding.print_error ~print_unknown Format.err_formatter exn ;
Format.eprintf "@." ;
Lwt.return false
diff --git a/src/server/learnocaml_server.ml b/src/server/learnocaml_server.ml
index bcb01291d..d9c3ce125 100644
--- a/src/server/learnocaml_server.ml
+++ b/src/server/learnocaml_server.ml
@@ -172,7 +172,19 @@ let log conn api_req =
flush oc
let check_report exo report grade =
- let max_grade = Learnocaml_exercise.(access File.max_score) exo in
+ let max_grade = match exo with
+ | Learnocaml_exercise.Subexercise (subexs,_) ->
+ let rec aux acc = function
+ | [] -> 0
+ | (ex,subex) :: l ->
+ let open Learnocaml_exercise in
+ aux (acc + subex.student_weight *
+ (access true File.max_score (Exercise ex)))
+ l
+ in
+ aux 0 subexs
+ | ex -> Learnocaml_exercise.(access true File.max_score) ex
+ in
let score, _ = Learnocaml_report.result report in
score * 100 / max_grade = grade
diff --git a/src/state/learnocaml_data.ml b/src/state/learnocaml_data.ml
index 39ce6f6d0..9a7f32acb 100644
--- a/src/state/learnocaml_data.ml
+++ b/src/state/learnocaml_data.ml
@@ -457,8 +457,8 @@ module Exercise = struct
in
let exercise_enc_v2 =
J.(obj1
- (opt "max_score" int))
- (* deprecated & ignored *)
+ (opt "max_score" int) (* deprecated & ignored *)
+ )
in
J.conv
(fun t ->
@@ -478,6 +478,115 @@ module Exercise = struct
end
+ module Subindex = struct
+
+ type meta = Meta.t
+
+ type part = {
+ subtitle: string;
+ subexercise: string;
+ student_hidden: bool;
+ student_weight: int;
+ teacher_weight: int
+ }
+
+ type t = {
+ meta : meta;
+ check_all_against: string option;
+ parts: part list;
+ }
+
+ let to_meta s = s.meta
+
+ let to_check s = s.check_all_against
+
+ let to_part s = s.parts
+
+ let get_part_field p =
+ let
+ {subtitle; subexercise; student_hidden;
+ student_weight; teacher_weight} = p
+ in (subtitle, subexercise, student_hidden,
+ student_weight, teacher_weight)
+
+ let to_subindex m c p =
+ let meta = m in
+ let check_all_against = c in
+ let parts = p in
+ {meta ; check_all_against ; parts}
+
+ let enc =
+ let meta_enc = Meta.enc
+ in
+ let part_enc =
+ J.(obj5
+ (req "subtitle" string)
+ (req "subexercise" string)
+ (dft "student_hidden" bool false)
+ (dft "student_weight" int 1)
+ (dft "teacher_weight" int 1))
+ in
+ let exercise_enc =
+ J.(obj3
+ (req "meta" meta_enc)
+ (opt "check_all_against" string)
+ (req "parts" (list part_enc)))
+ in
+ J.conv
+ (fun {meta; check_all_against; parts} ->
+ let parts =
+ let rec aux = function
+ | [] -> []
+ | {subtitle; subexercise; student_hidden;
+ student_weight; teacher_weight} :: l ->
+ (subtitle, subexercise, student_hidden,
+ student_weight, teacher_weight) :: (aux l)
+ in aux parts
+ in
+ (meta,
+ check_all_against,
+ parts))
+ (fun (meta,check_all_against, part) ->
+ let parts =
+ let rec aux = function
+ | [] -> []
+ | (subtitle, subexercise, student_hidden,
+ student_weight, teacher_weight) :: l ->
+ {subtitle; subexercise; student_hidden;
+ student_weight; teacher_weight} :: (aux l)
+ in aux part
+ in
+ to_subindex meta check_all_against parts)
+ (enc_check_version_1 (exercise_enc))
+
+
+ let find t id =
+ let rec aux = function
+ | [] -> raise Not_found
+ | {subtitle; subexercise; student_hidden;
+ student_weight; teacher_weight}::l->
+ if id = subtitle then
+ {subtitle; subexercise; student_hidden;
+ student_weight; teacher_weight}
+ else
+ aux l
+ in
+ aux (to_part t)
+
+ let find_opt t id = try Some (find t id) with Not_found -> None
+
+ let map_exercises f l =
+ (List.map (function
+ | (id, Some ex) ->
+ (id, Some (to_subindex
+ (to_meta ex)
+ (to_check ex)
+ (f l (to_part ex))))
+ | x -> x)
+ l)
+
+ end
+
module Status = struct
type skill = [`Plus | `Minus] * string
@@ -711,7 +820,7 @@ module Exercise = struct
module Index = struct
type t =
- | Exercises of (id * Meta.t option) list
+ | Exercises of (id * Meta.t option * Subindex.t option) list
| Groups of (string * group) list
and group =
{ title : string;
@@ -721,11 +830,14 @@ module Exercise = struct
let exercise_enc =
J.union [
J.case J.string
- (function id, None -> Some id | _ -> None)
- (fun id -> id, None);
+ (function id, None, None -> Some id | _ -> None)
+ (fun id -> id, None, None);
J.case J.(tup2 string Meta.enc)
- (function id, Some meta -> Some (id, meta) | _ -> None)
- (fun (id, meta) -> id, Some meta);
+ (function id, Some meta, None -> Some (id, meta) | _ -> None)
+ (fun (id, meta) -> id, Some meta, None);
+ J.case J.(tup2 string Subindex.enc)
+ (function id, None, Some submeta -> Some (id, submeta) | _ -> None)
+ (fun (id, submeta) -> id, None, Some submeta);
]
in
let group_enc =
@@ -739,17 +851,18 @@ module Exercise = struct
(req "title" string)
(req "exercises" (list exercise_enc)))
(function
- | (title, Exercises map) -> Some (title, map)
- | _ -> None)
+ | (title, Exercises map) -> Some (title, map)
+ | _ -> None)
(fun (title, map) -> (title, Exercises map)) ;
J.case
J.(obj2
(req "title" string)
(req "groups" (assoc group_enc)))
(function
- | (title, Groups map) -> Some (title, map)
- | _ -> None)
- (fun (title, map) -> (title, Groups map)) ]
+ | (title, Groups map) -> Some (title, map)
+ | _ -> None)
+ (fun (title, map) -> (title, Groups map))
+ ]
in
enc_check_version_2 @@
J.union
@@ -771,9 +884,16 @@ module Exercise = struct
| Groups ((_, g)::r) ->
(try aux g.contents with Not_found -> aux (Groups r))
| Groups [] -> raise Not_found
- | Exercises l -> (match List.assoc id l with
- | None -> raise Not_found
- | Some e -> e)
+ | Exercises l ->
+ let rec assoc_tup3 id = function
+ | [] -> raise Not_found
+ | (ex_id, meta, subindex) :: l ->
+ if compare ex_id id = 0 then (meta, subindex) else assoc_tup3 id l
+ in
+ (match assoc_tup3 id l with
+ | _ , Some subindex -> Subindex.to_meta subindex
+ | Some meta, _ -> meta
+ | None, _ -> raise Not_found)
in
aux t
@@ -786,11 +906,11 @@ module Exercise = struct
(id, {g with contents = map_exercises f g.contents}))
gs)
| Exercises l ->
- Exercises
- (List.map (function
- | (id, Some ex) -> (id, Some (f id ex))
- | x -> x)
- l)
+ Exercises
+ (List.map (function
+ | (id, Some ex, Some subindex) -> (id, Some (f id ex), Some subindex)
+ | x -> x)
+ l)
let rec mapk_exercises f t k =
let rec mapk_list acc f l k = match l with
@@ -806,8 +926,8 @@ module Exercise = struct
@@ fun gs -> Groups gs |> k
| Exercises l ->
mapk_list [] (fun e k -> match e with
- | (id, Some ex) ->
- f id ex @@ fun ex -> (id, Some ex) |> k
+ |(id, Some ex, Some subindex) ->
+ f id ex @@ fun ex -> (id, Some ex, Some subindex) |> k
| x -> x |> k)
l
@@ fun l -> Exercises l |> k
@@ -819,7 +939,7 @@ module Exercise = struct
acc gs
| Exercises l ->
List.fold_left (fun acc -> function
- | (id, Some ex) -> f acc id ex
+ | (id, Some ex, Some _) -> f acc id ex
| _ -> acc)
acc l
@@ -838,11 +958,11 @@ module Exercise = struct
aux [] gs
| Exercises l ->
let rec aux acc = function
- | (id, Some ex) :: r ->
+ | (id, Some ex, subindex) :: r ->
(f id ex @@ function
- | true -> aux ((id, Some ex) :: acc) r
+ | true -> aux ((id, Some ex, subindex) :: acc) r
| false -> aux acc r)
- | (_, None) :: r -> aux acc r
+ | (_, None, _) :: r -> aux acc r
| [] -> k (Exercises (List.rev acc))
in
aux [] l
diff --git a/src/state/learnocaml_data.mli b/src/state/learnocaml_data.mli
index 2408c5eec..4abc08c2c 100644
--- a/src/state/learnocaml_data.mli
+++ b/src/state/learnocaml_data.mli
@@ -245,10 +245,49 @@ module Exercise: sig
end
+ module Subindex : sig
+
+ type meta = Meta.t
+
+ type part = {
+ subtitle : string;
+ subexercise : string;
+ student_hidden : bool;
+ student_weight : int;
+ teacher_weight : int;
+ }
+
+ type t = {
+ meta : meta;
+ check_all_against : string option;
+ parts : part list;
+ }
+
+ val to_meta : t -> meta
+
+ val to_check : t -> string option
+
+ val to_part : t -> part list
+
+ val get_part_field : part -> string * string * bool * int * int
+
+ val to_subindex : meta -> string option -> part list -> t
+
+ val enc : t Json_encoding.encoding
+
+ val find : t -> string -> part
+
+ val find_opt : t -> string -> part option
+
+ val map_exercises :
+ (('a * t option) list -> part list -> part list) ->
+ ('a * t option) list -> ('a * t option) list
+ end
+
module Index: sig
type t =
- | Exercises of (id * Meta.t option) list
+ | Exercises of (id * Meta.t option * Subindex.t option) list
| Groups of (string * group) list
and group =
{ title : string;
diff --git a/src/state/learnocaml_store.ml b/src/state/learnocaml_store.ml
index 20ca030e1..f66d94948 100644
--- a/src/state/learnocaml_store.ml
+++ b/src/state/learnocaml_store.ml
@@ -254,6 +254,12 @@ module Exercise = struct
end
+ module Subindex = struct
+ include Exercise.Subindex
+
+ (*let get_from_subindex subindex =
+ Exercise.Subindex*)
+ end
module Index = struct
include Exercise.Index
@@ -279,6 +285,7 @@ module Exercise = struct
with type id := id
and module Meta := Meta
and module Status := Status
+ and module Subindex := Subindex
and module Index := Index)
let get id =
diff --git a/src/utils/learnocaml_partition_create.ml b/src/utils/learnocaml_partition_create.ml
index 8358c4922..b032d9cb1 100644
--- a/src/utils/learnocaml_partition_create.ml
+++ b/src/utils/learnocaml_partition_create.ml
@@ -93,10 +93,10 @@ let asak_partition prof fun_name sol by_grade =
let partition exo_name fun_name prof =
Learnocaml_store.Exercise.get exo_name
>>= fun exo ->
- let prelude = Learnocaml_exercise.(access File.prelude exo) in
- let prepare = Learnocaml_exercise.(decipher File.prepare exo) in
+ let prelude = Learnocaml_exercise.(access false File.prelude exo) in
+ let prepare = Learnocaml_exercise.(decipher false File.prepare exo) in
let prelude = prelude ^ "\n" ^ prepare in
- let solution = Learnocaml_exercise.(decipher File.solution exo) in
+ let solution = Learnocaml_exercise.(decipher false File.solution exo) in
let solution = prelude ^ "\n" ^ solution in
get_all_saves exo_name prelude
>|= fun saves ->
@@ -104,3 +104,4 @@ let partition exo_name fun_name prof =
let by_grade = partition_by_grade fun_name lst in
let bad_type,partition_by_grade = asak_partition prof fun_name solution by_grade in
{not_graded; bad_type; partition_by_grade}
+
diff --git a/static/css/learnocaml_description.css b/static/css/learnocaml_description.css
index 2f7260b5c..e34950c1e 100644
--- a/static/css/learnocaml_description.css
+++ b/static/css/learnocaml_description.css
@@ -43,6 +43,7 @@ body {
display: block;
font-weight: normal;
position: relative;
+ border-bottom: thin solid #eee;
}
#learnocaml-exo-tabs > * > h1:first-child {
margin-top: 5px;
@@ -52,7 +53,7 @@ body {
overflow: auto;
flex: 1 3 auto ;
}
-#learnocaml-exo-tab-meta > h1::after {
+#learnocaml-exo-tab-meta > * > h1::after {
position: absolute;
left: 0px; bottom: -5px; width: 100%;
content:"";
@@ -60,6 +61,89 @@ body {
background: linear-gradient(to bottom, rgba(0,0,0,0.3) 0, transparent 100%)
}
+/* ----------------------- iframe --------------------------------*/
+
+#learnocaml-exo-tab-text-iframe {
+ position : absolute;
+ right : 0px;
+ left : 0px;
+ bottom : 0px;
+ display : flex ;
+ flex-direction : column;
+}
+
+#learnocaml-exo-tab-text-iframe > iframe {
+ height : 100%;
+}
+/* -------------------- Prelude -------------------------------- */
+
+#learnocaml-exo-tab-text-prelude {
+ overflow: hidden;
+ left: 0;
+ top: 61px;
+ z-index: 1002;
+ border-bottom: thin solid #eee;
+ width: 100%;
+}
+#learnocaml-exo-tab-text-prelude > h1 {
+ position: relative;
+ flex: 0 0 auto;
+ background: #222;
+ color: #eee;
+ font-size: 20px;
+ line-height: 22px;
+ margin: 0;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ display: block;
+ font-weight: normal;
+ z-index: 1005;
+ width: 100%;
+ text-align: center;
+}
+#learnocaml-exo-tab-text-prelude > h1:first-child {
+ margin-top: 0;
+}
+#learnocaml-exo-tab-text-prelude > h1 > button {
+ float: right;
+ border: none;
+ border-left: 1px #eee solid;
+ color: #eee;
+ padding-top: 10px;
+ padding-bottom: 10px;
+ margin-top: -10px;
+ margin-bottom: -10px;
+ font-size: 20px;
+ line-height: 22px;
+ z-index: 1006;
+ width: 20%;
+ background: #222;
+}
+#learnocaml-exo-tab-text-prelude > iframe {
+ border: none;
+ overflow: auto;
+ flex: 1 3 auto ;
+}
+#learnocaml-exo-tab-text-prelude > pre.toplevel-code {
+ flex: 0 1 auto;
+ height: 150px;
+ max-height: 150px;
+ background: #666;
+ margin: 0;
+ overflow: auto;
+ width: 100%;
+ position: absolute;
+}
+#learnocaml-exo-tab-text-prelude > h1::after {
+ position: absolute;
+ left: 0px; bottom: -5px; width: 100%;
+ content:"";
+ height:5px; background: pink;
+ background: linear-gradient(to bottom, rgba(0,0,0,0.3) 0, transparent 100%)
+}
+
+
+
/* BEGIN excerpt from learnocaml_exercise.css */
.learnocaml-exo-meta-category ~ .exercise + .exercise {
diff --git a/static/css/learnocaml_exercise.css b/static/css/learnocaml_exercise.css
index 1ebc2c2da..a076da639 100644
--- a/static/css/learnocaml_exercise.css
+++ b/static/css/learnocaml_exercise.css
@@ -69,7 +69,33 @@ body {
top: 61px;
}
}
+/* ------------------------- Navigation ----------------------------*/
+#learnocaml-exo-tab-navigation{
+ position: absolute;
+ z-index: 3;
+ display: flex;
+ flex-direction: row ;
+ border-bottom: 1px black solid;
+ background: #ddd;
+ padding: 0px;
+ flex: 0 0 auto;
+ width: 100%;
+ height:60px;
+}
+#learnocaml-exo-tab-navigation > button {
+ width: 20%;
+}
+#learnocaml-exo-tab-navigation > h4 {
+ width: 60%;
+ text-align:center;
+}
+
/* -------------------- tabs and tab buttons ---------------------- */
+#learnocaml-exo-tab {
+ top: 60px;
+ position: absolute;
+ height: 100px;
+}
#learnocaml-exo-tab-buttons {
position: absolute;
z-index: 999;
@@ -126,8 +152,22 @@ body {
#learnocaml-exo-button-editor {
display: none;
}
+ #learnocaml-exo-tab {
+ left: 800px; right: 0px; top: 0px;
+ }
+ #learnocaml-exo-tab-navigation{
+ height: 60px;
+ width: 100%;
+ border-bottom: 1px black solid;
+ position: absolute;
+ z-index: 3;
+ display: flex;
+ flex-direction: row;
+ background: #ddd;
+ padding: 0px;
+ }
#learnocaml-exo-tab-buttons {
- left: 800px; right: 0px; top: 0px;
+ left: 0px; right: 0px; top: 61px;
}
#learnocaml-exo-button-editor.front-tab ~ #learnocaml-exo-button-toplevel {
border-bottom: none;
@@ -141,7 +181,7 @@ body {
opacity: 1;
}
#learnocaml-exo-tabs > * {
- left: 800px; top: 40px; right: 0px; bottom: 0px;
+ left: 800px; top: 101px; right: 0px; bottom: 0px;
}
#learnocaml-exo-tabs > #learnocaml-exo-tab-editor {
width: 800px; left: 0; bottom: 0; top: 61px;
@@ -154,6 +194,9 @@ body {
#learnocaml-exo-toolbar {
right: 0px;
}
+ #learnocaml-exo-tab{
+ width: 100%;
+ }
#learnocaml-exo-tab-buttons {
left: 0; right: 0px;
}
@@ -175,19 +218,29 @@ body {
}
@media (min-width: 550px) and (max-width: 1199px) {
#learnocaml-exo-tab-buttons {
- top: 60px;
+ top: 61px;
}
#learnocaml-exo-tabs > * {
- top: 100px;
+ top: 161px;
}
}
@media (max-width: 549px) {
- #learnocaml-exo-tab-buttons {
- top: 40px;
- }
- #learnocaml-exo-tabs > * {
- top: 80px;
- }
+ #learnocaml-exo-tab {
+ top:40px;
+ height:80px;
+ }
+ #learnocaml-exo-tab-navigation {
+ height:40px;
+ }
+ #learnocaml-exo-tab-navigation > h4 {
+ align-self:center;
+ }
+ #learnocaml-exo-tab-buttons {
+ top: 41px;
+ }
+ #learnocaml-exo-tabs > * {
+ top: 120px;
+ }
}
/* -------------------- Prelude -------------------------------- */
diff --git a/static/description.html b/static/description.html
index bc56aab26..ac31728ce 100644
--- a/static/description.html
+++ b/static/description.html
@@ -34,7 +34,12 @@