Skip to content

Commit 31e42f1

Browse files
authored
Update to java8 and make use of $common folder (#13)
* Update to java8 * Make use of $common folder * Refactor folder structure
1 parent 39432f7 commit 31e42f1

File tree

2,060 files changed

+700060
-750801
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

2,060 files changed

+700060
-750801
lines changed

$common/runfile.py

+218
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
#!/usr/bin/python3
2+
# -*- coding: utf-8 -*-
3+
4+
#
5+
# Copyright (c) 2016 François Michel, edited by Alexandre Dubray 2017
6+
# This program is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU Affero General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU Affero General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU Affero General Public License
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
#
18+
19+
import os
20+
import json
21+
import subprocess
22+
import shlex
23+
import sys
24+
import re
25+
from json import JSONDecodeError
26+
27+
from inginious import feedback
28+
from inginious import input
29+
30+
def initTranslations():
31+
""" Initialise la fonction _() qui permettra de traduire les chaines de caractère.
32+
33+
Crée un fichier contenant le code de langue afin que le code Java puisse le récupérer plus tard
34+
"""
35+
import gettext
36+
current_locale = input.get_input("@lang")
37+
with open("student/lang","w+") as f: # This file will be used by Java to get the correct lang
38+
f.write(current_locale)
39+
try: # Try to load translations.
40+
language = gettext.translation ('run', 'student/Translations/translations_run', [current_locale])
41+
_ = language.gettext
42+
except OSError:
43+
_ = gettext.gettext # This will use String id if an error occurs
44+
return _
45+
46+
_ = initTranslations()
47+
48+
def getfilename(file):
49+
""" Retourne le nom de fichier (sans l'extension) du fichier nommé file """
50+
return os.path.splitext(file)[0]
51+
52+
def add_indentation_level(to_indent):
53+
""" Ajoute l'indentation au message to_indent pour l'insérer dans un code-bloc """
54+
return ' ' + ' '.join(to_indent.splitlines(keepends=True))
55+
56+
def parsetemplate():
57+
""" Parse les réponse de l'étudiant
58+
59+
Cette fonction s'occupe de mettre les réponse de l'étudiant aux endroit appropriés.
60+
Pour chaque fichier dans le dossiers /task/Templates , si il possède une ou plusieurs
61+
fois le pattern @@<id-question>@@, la réponse de la question avec l'id <id-question>
62+
sera placée à la place du pattern et le fichier résultant sera copier dans le dossier
63+
/task/StudentCode (créer au début de la fonction) avec une extension .java
64+
"""
65+
os.mkdir('./StudentCode')
66+
for file in os.listdir('./Templates'):
67+
filename = getfilename(file)
68+
input.parse_template('./Templates/' + file,'./StudentCode/' + filename + '.java');
69+
70+
def librairies():
71+
"""Définit l'ensemble des pathfile qui seront utilisé via l'option -cp de javac et java """
72+
lib = '.'
73+
lib += ':/usr/share/java/junit.jar'
74+
lib += ':/usr/share/java/hamcrest/core.jar'
75+
lib += ':/usr/share/java/mockito/mockito-core.jar'
76+
lib += ':/usr/share/java/cglib/cglib.jar'
77+
lib += ':/usr/share/java/objenesis/objenesis.jar'
78+
lib += ':/usr/share/java/objectweb-asm/asm-all.jar'
79+
lib += ':./student'
80+
lib += ':./src'
81+
lib += ':./StudentCode'
82+
return lib
83+
84+
def compile_files(test_file):
85+
""" Compile l'ensemble des fichier .java nécessaire pour les tests
86+
87+
Compile l'ensemble des fichiers de test. Seul la compilation de ces fichier
88+
doit être faite car le compilateur de java compilera les classes dont ils
89+
ont besoin pour fonctionner
90+
91+
Keyword arguments:
92+
test_file -- La liste des fichier de tests
93+
"""
94+
Log = ""
95+
javac_cmd = "javac -d ./student -encoding UTF8 -cp " + librairies()
96+
with open('LogCompile.log','w+') as f:
97+
subprocess.call(shlex.split(javac_cmd) + test_file, universal_newlines=True,stderr=f)
98+
if os.path.getsize('LogCompile.log') > 0:
99+
f.seek(0)
100+
Log = f.read()
101+
return Log
102+
103+
def get_test_files(runner):
104+
"""Retourne la liste de l'ensemble des nom fichiers de test trouvé dans /task/src
105+
106+
Cette méthode est appelée uniquement si aucune liste n'est fournie dans le fichier
107+
config.json. Si c'est le cas, un fichier de test est un fichier tel que
108+
- Il se trouve dans le dossiers src
109+
- Il est différent du fichier runner
110+
- Il n'est pas le fichier de correction (qui s'appelle d'office Correction.java)
111+
- C'est un fichier .java
112+
113+
La liste retournée contient uniquement les noms récupérer via getfilename
114+
115+
Keyword arguments:
116+
runner -- le nom du fichier runner
117+
"""
118+
files = []
119+
for file in os.listdir('./src'):
120+
if getfilename(file) != runner and not file.startswith('Correction') and not os.path.isdir("./src/"+file) and file.endswith('.java'):
121+
files.append(getfilename(file))
122+
return files
123+
124+
def run(customscript,execcustom,nexercices,tests=[],runner='Runner'):
125+
""" Parse les réponse des étudiant, compile et lance les tests et donne le feedback aux étudiant
126+
127+
Keyword arguments:
128+
customscript -- nom d'un script personnalisé
129+
execcustom -- Si oui (valeur != 0) ou non (0) il faut exécuter le script personnalisé customscript
130+
nexercices -- la nombre d'exercice dans la tâche
131+
tests -- Fichiers de test à lancer
132+
runner -- Fichier runner (default 'Runner')
133+
"""
134+
#Récupération des fichiers de tests si jamais il ne sont pas fournis à l'appel de la méthode
135+
if not tests:
136+
tests = get_test_files(runner)
137+
code_litteral = ".. code-block::\n\n"
138+
parsetemplate() # Parse les réponses de l'étudiant
139+
if execcustom != 0: # On doit exécuter le script personnalsé
140+
# If this is a python script we call the main() method with the _() function to transmit the translation mechanism
141+
if (customscript == "custom_translatable.py"):
142+
from custom_translatable import main
143+
outcustom = main(_)
144+
else:
145+
outcustom = subprocess.call(['./' + customscript],universal_newlines=True)
146+
if outcustom != 0: # Le script a renvoyé une erreur
147+
exit()
148+
149+
# On compile les fichier. La fonction map applique une fonction (argument 1) sur une liste (argument 2)
150+
# L'expression lambda définit une fonction anonyme qui ajoute le dossiers src et l'extension .java aux nom de fichier tests
151+
anonymous_fun = lambda file : './src/' + file + '.java' # Create anonymous funcntion
152+
anonymous_fun_2 = lambda file : '/course/common/src/' + file + '.java'
153+
Log = compile_files([anonymous_fun(file) for file in tests] )
154+
Log += compile_files([anonymous_fun_2(file) for file in [runner]] )
155+
if Log == "": # La compilation a réussie
156+
with open('err.txt', 'w+', encoding="utf-8") as f:
157+
# On lance le runner
158+
os.chdir('./student')
159+
java_cmd = "run_student java -ea -cp " + librairies()
160+
# On passe comme argument au fichier runner les fichier de tests (Voir documentation runner)
161+
resproc = subprocess.Popen( shlex.split(java_cmd) + ['src/' + runner] + tests, universal_newlines=True, stderr=f, stdout=subprocess.PIPE)
162+
resproc.communicate()
163+
resultat = resproc.returncode
164+
f.flush()
165+
f.seek(0)
166+
outerr = f.read()
167+
print(outerr) # On affiche la sortie de stderr dans les informations de debug
168+
if resultat == 127: # Les tests ont réussis
169+
feedback.set_global_result('success')
170+
elif resultat == 252: # Limite de mémoire dépassée
171+
feedback.set_global_result('failed')
172+
feedback.set_global_feedback(_("La limite de mémoire de votre programme est dépassée"))
173+
elif resultat == 253: # timeout
174+
feedback.set_global_result('failed')
175+
feedback.set_global_feedback(_("La limite de temps d'exécution de votre programme est dépassée"))
176+
else: # Les tests ont échouées
177+
if nexercices == 1:
178+
outerr = add_indentation_level(outerr) # On ajoute de l'indentation pour que ça s'affiche dans un cadre gris pour les étudiants
179+
feedback.set_global_result('failed')
180+
feedback.set_global_feedback(_("Il semble que vous ayez fait des erreurs dans votre code…\n\n") + code_litteral + outerr + "\n")
181+
else:
182+
i = 1
183+
while i <= nexercices:
184+
"""
185+
Cette regex va matcher tout ce qui se trouve entre
186+
- @i (avec i le numéro du sous-problème que l'on considère
187+
- @<un ou plusieurs chiffre>
188+
189+
et donc matcher tout les feedback associés au sous-problème que l'on considère
190+
"""
191+
regex = '@' + str(i) + ' :\n(.*?)(?=@\d+ :|$)'
192+
regex_question = re.findall(regex, outerr, re.DOTALL)
193+
if len(regex_question) == 0 and outerr == "": # Il n'y a pas de match pour la regex, le sous-problème est bien répondu
194+
feedback.set_problem_feedback(_("Vous avez bien répondu à cette question"), "q" + str(i))
195+
feedback.set_problem_result("success", "q"+str(i))
196+
elif outerr != "":
197+
feedback.set_global_feedback(_(outerr))
198+
feedback.set_global_result("failed")
199+
else:
200+
outerr_question = ''.join(regex_question) # On remet tout les feedback trouvé en un seul
201+
outerr_question = add_indentation_level(outerr_question) # on l'indente
202+
feed = _("Il semble que vous ayez fait des erreurs dans votre code…\n\n") + code_litteral + outerr_question + "\n"
203+
feedback.set_problem_feedback(feed,"q"+str(i))
204+
i += 1
205+
else: # La compilation a raté
206+
Log = add_indentation_level(Log)
207+
feed = _("Le programme ne compile pas : \n\n") + code_litteral + Log + "\n"
208+
feedback.set_global_result('failed')
209+
feedback.set_global_feedback(feed)
210+
211+
if __name__ == '__main__':
212+
try:
213+
task_options = json.load(open('config.json', 'r', encoding="utf-8"))
214+
except JSONDecodeError as e:
215+
print(_("Impossible de décoder le config.json"))
216+
print(e)
217+
exit()
218+
run(**task_options)
File renamed without changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* Copyright (c) 2017 Olivier Martin
3+
* This program is free software: you can redistribute it and/or modify
4+
* it under the terms of the GNU Affero General Public License as published by
5+
* the Free Software Foundation, either version 3 of the License, or
6+
* (at your option) any later version.
7+
* This program is distributed in the hope that it will be useful,
8+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+
* GNU Affero General Public License for more details.
11+
*
12+
* You should have received a copy of the GNU Affero General Public License
13+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
14+
*/
15+
16+
package student.Translations;
17+
import java.util.ResourceBundle;
18+
import java.util.Locale;
19+
import java.io.FileReader;
20+
import java.io.BufferedReader;
21+
22+
public class Translator {
23+
24+
// Localisation des fichiers .properties contenant les traduction
25+
private static String bundleLocation = "MessagesBundle";
26+
27+
private static ResourceBundle myResources;
28+
private static boolean ok = true;
29+
30+
// Si une erreur suivient en essayant de récupérer la langue définie par le script run, on utilise le français.
31+
private static String lang_error = "fr";
32+
33+
// Fichier crée par le script run qui permet à Translator.java de savoir quelle langue prendre.
34+
private static String lang_file = "./lang";
35+
36+
/*
37+
* @pre -
38+
* @post Retourne la traduction du String {s} en fonction de la langue définie dans le fichier "./lang".
39+
* Retourne la chaine non traduite si le fichier de traduction pour la langue définie n'existe pas ou si une erreur est survenue.
40+
*/
41+
public static String translate(String s) {
42+
43+
// Try to load once the bundle. If no bundle found, we use the String id instead of the translation.
44+
if(ok == true){
45+
try {
46+
if (myResources == null) {
47+
myResources = ResourceBundle.getBundle(bundleLocation, new Locale(get_lang()), ResourceBundle.Control.getNoFallbackControl(ResourceBundle.Control.FORMAT_PROPERTIES));
48+
}
49+
} catch (Exception e){
50+
// No bundle found
51+
} finally {
52+
ok = false;
53+
}
54+
}
55+
56+
if (myResources != null){
57+
try {
58+
return myResources.getString(s);
59+
} catch (Exception e) { // Can happen if translation is invalid).
60+
return s;
61+
}
62+
} else
63+
return s;
64+
}
65+
66+
/*
67+
* Read the code lang ("en_US" for example) in the file "./lang" that run script should have created.
68+
* Retourne {lang_error} si un problème survient.
69+
*/
70+
private static String get_lang(){
71+
BufferedReader br = null;
72+
try {
73+
br = new BufferedReader(new FileReader(lang_file));
74+
return br.readLine().replace("\n\r", "").replace("\n", "").replace(" ", ""); //Delete evantual unwanted characters
75+
} catch(Exception e){
76+
return lang_error;
77+
} finally {
78+
try{
79+
br.close();
80+
}catch(Exception e){
81+
return lang_error;
82+
}
83+
}
84+
}
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/bin/python3
2+
import importlib.util
3+
import sys
4+
import json
5+
from json import JSONDecodeError
6+
from distutils.file_util import copy_file
7+
import os
8+
9+
# Dynamically load modules we need
10+
# Credits to https://stackoverflow.com/a/67692/6149867
11+
# And for the explanation : http://www.blog.pythonlibrary.org/2016/05/27/python-201-an-intro-to-importlib/
12+
def dynamically_load_module(module, path):
13+
spec = importlib.util.spec_from_file_location(module, path)
14+
mod = importlib.util.module_from_spec(spec)
15+
spec.loader.exec_module(mod)
16+
return mod
17+
18+
19+
#####################################
20+
# Our import for common run file #
21+
#####################################
22+
sys.path.append("/course/common")
23+
24+
runfile = dynamically_load_module("runfile", "/course/common/runfile.py")
25+
26+
isExist = os.path.exists("./student/Translations")
27+
28+
if not isExist:
29+
os.makedirs("./student/Translations")
30+
31+
copy_file("/course/common/student/Translations/Translator.java", "./student/Translations/Translator.java")
32+
33+
try:
34+
task_options = json.load(open('config.json', 'r', encoding="utf-8"))
35+
except JSONDecodeError as e:
36+
print(_("Impossible de décoder le config.json"))
37+
print(e)
38+
exit()
39+
40+
runfile.run(**task_options)

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.save*

0 commit comments

Comments
 (0)