diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9eab7de
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+local/epfl/cache/*.txt
diff --git a/bin/bamc b/bin/bamc
new file mode 100755
index 0000000..eb7d9f7
--- /dev/null
+++ b/bin/bamc
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+#!/bin/bash
+
+# Set SNAME, SDIR and SPATH
+if [ -h $0 ]; then SNAME=$(readlink $0); else SNAME=$0; fi
+SDIR=$(dirname $SNAME)
+if [[ "$SDIR" =~ ^/ ]]; then SPATH=$SDIR; else SPATH=$(realpath $(pwd)/$SDIR); fi
+
+# Set DIR_LIB
+DIR_LIB=$(realpath $SPATH/../lib/bash)
+
+# Source libs
+source $DIR_LIB/colors.sh
+source $DIR_LIB/io.sh
+source $DIR_LIB/cmdline_parser.sh
+source $DIR_LIB/bamc_configuration.sh
+source $DIR_LIB/bamc_help.sh
+source $DIR_LIB/bamc_workspace.sh
+
+DEFAULT_ITEMS=""
+DEFAULT_ACTIONS="help"
+CALLBACK_PREFIX="action_"
+
+run $@
+RC=$?
+debug "Return code: $RC"
+
+exit $RC
diff --git a/conf/bamc.conf b/conf/bamc.conf
new file mode 100644
index 0000000..5a616f9
--- /dev/null
+++ b/conf/bamc.conf
@@ -0,0 +1,49 @@
+#!/bin/bash
+
+# WORKSPACE
+DIR_WORKSPACE="."
+DIR_QUESTIONS='questions'
+DIR_EXAMS='exams'
+DIR_PROJECTS='projects'
+FILE_WORKSPACE_ROOT=".workspace_root"
+
+# CONFIGURATION DIRS
+DIR_DATA=$(realpath $SPATH/../data)
+DIR_SKEL=$DIR_DATA/skel
+
+return 0
+
+# INTPUT FILES
+FILE_LANG='lang.conf'
+FILE_SECTIONS='sections.conf'
+FILE_PROF='prof.conf'
+FILE_STUDENTS='students.csv'
+FILE_SAMPLE_CSV='sample.csv'
+FILE_EXTRA_CSV='extra.csv'
+FILE_BLANK_CSV='blank.csv'
+FILE_AMC_OPTIONS='options.xml'
+FILE_AMC_MAILING='mailing.xml'
+FILE_LIST_MAIN='list.tex'
+FILE_LIST_TSTART='start_tab.tex'
+FILE_LIST_TEND='end_tab.tex'
+
+# OUTPUT DIRS
+DIR_EXAMS='exams'
+DIR_SAMPLES='pdf-samples'
+DIR_BLANKS='pdf-blanks'
+DIR_PDF='pdf-exams'
+DIR_LIST_OUT='pdf-lists'
+
+# OUTPUT FILES
+FILE_PROF_TEX='professor.tex'
+FILE_EXAM_BASENAME='exam'
+FILE_RESULTS='raw'
+FILE_EMAILS_SCRIPT='send_emails.sh'
+FILE_LIST_ROWS='rows.tex'
+
+# VARIABLES
+PDFLATEX_RUNS=1
+EXTRA_COPIES=9
+DEFAULT_EMAIL='pierre-olivier.valles@epfl.ch'
+LIST_ROWS_PER_PAGE=50
+LIST_MAX_CHAR=30
diff --git a/data/CSV/blank.csv b/data/CSV/blank.csv
new file mode 100644
index 0000000..ef45c47
--- /dev/null
+++ b/data/CSV/blank.csv
@@ -0,0 +1,3 @@
+ID,NOM,SCIPER,SECTION,EMAIL,TAILLE_NOM,NOM_COMPLET,SEMESTRE
+n/a,n/a,999999,(n/a),do_not_reply@epfl.ch,3,n/a,1
+
diff --git a/data/CSV/extra.csv b/data/CSV/extra.csv
new file mode 100644
index 0000000..d5f9cbd
--- /dev/null
+++ b/data/CSV/extra.csv
@@ -0,0 +1 @@
+#ID#,XXX-#NB#,#SCIPER#,XXX,#EMAIL#
diff --git a/data/CSV/sample.csv b/data/CSV/sample.csv
new file mode 100644
index 0000000..4d39d92
--- /dev/null
+++ b/data/CSV/sample.csv
@@ -0,0 +1,5 @@
+ID,NOM,SCIPER,SECTION,EMAIL,TAILLE_NOM,NOM_COMPLET,SEMESTRE
+1,Lennon John,XXXXX1,XYZ,pierre-Olivier.valles@epfl.ch,11,Lennon John,1
+2,McCartney Paul,XXXXX2,XYZ,pierre-Olivier.valles@epfl.ch,14,McCartney Paul,1
+3,Harrisson George,XXXXX3,XYZ,pierre-Olivier.valles@epfl.ch,16,Harrisson George,1
+4,Starr Ringo,XXXXX4,XYZ,pierre-Olivier.valles@epfl.ch,11,Starr Ringo,1
diff --git a/data/base/exam.tex b/data/base/exam.tex
new file mode 100644
index 0000000..3ec31bc
--- /dev/null
+++ b/data/base/exam.tex
@@ -0,0 +1,48 @@
+\documentclass[a4paper]{article}
+
+\usepackage[utf8]{inputenc} %Unicode
+\usepackage[T1]{fontenc}
+\usepackage{fix-cm}
+
+\usepackage[lang=#LANG#,bloc,completemulti]{automultiplechoice}
+
+%%% Default packages
+\input{./packages.tex}
+
+%%% Formating specific to EPFL
+\input{./style_extra.tex}
+\input{./true-false.tex} % Language dependent !
+\input{./question-text.tex} % Language dependent !
+
+%%% Professor specific imports
+\input{./professor.tex}
+\input{./style_professor.tex}
+
+%%% Randomseed
+\input{./random-seed.tex}
+
+\begin{document}
+
+%%% Import sections
+\input{./sections.tex}
+
+\newcommand{\sujet}{
+ \onecopy{1}{
+ %%% debut de l'en-tête des copies :
+ \input{./first_page.tex}
+
+ \input{./random-sections.tex}
+
+ % #SPECIFIC#
+
+ %\clearpage ~
+ % Make sure that all the exams have the same number of pages (otherwise the repro will not be able to print!)
+ \loop \ifnum \thepage < \totalPages \clearpage ~ \repeat
+
+ \AMCassociation{\ID}
+ }
+}
+
+\csvreader[head to column names]{./students.csv}{}{\sujet}
+
+\end{document}
diff --git a/data/base/packages.tex b/data/base/packages.tex
new file mode 100644
index 0000000..11fbbe8
--- /dev/null
+++ b/data/base/packages.tex
@@ -0,0 +1,24 @@
+%% Extra packages
+
+%% CSV file access
+\usepackage{csvsimple}
+
+%% Multicolumn formatting
+\usepackage{multicol}
+
+%% Math packages
+\usepackage{graphicx}
+\usepackage{amsmath}
+\usepackage{mathrsfs}
+\usepackage{euscript}
+\usepackage{epsfig}
+\usepackage{ifthen}
+\usepackage{amsfonts}
+\usepackage{amssymb}
+
+%% Graphics
+\usepackage{graphicx}
+\graphicspath{ {media/} }
+
+%% Drawing
+\usepackage{tikz}
diff --git a/data/base/professor.tex b/data/base/professor.tex
new file mode 100644
index 0000000..d46a652
--- /dev/null
+++ b/data/base/professor.tex
@@ -0,0 +1,6 @@
+\newcommand{\prof}{#PROF_NAME#}
+\newcommand{\totalPages}{#TOTAL_PAGES#}
+\newcommand{\StudentsPath}{./students.csv}
+\newcommand{\Time}{#DURATION#}
+\newcommand{\ExamDate}{#DATE#}
+\newcommand{\ExamName}{#EXAM#}
diff --git a/data/base/random-seed.tex b/data/base/random-seed.tex
new file mode 100644
index 0000000..fb5b35b
--- /dev/null
+++ b/data/base/random-seed.tex
@@ -0,0 +1 @@
+\AMCrandomseed{11051975}
\ No newline at end of file
diff --git a/data/base/style_extra.tex b/data/base/style_extra.tex
new file mode 100644
index 0000000..00e1d16
--- /dev/null
+++ b/data/base/style_extra.tex
@@ -0,0 +1,87 @@
+%% Default values for open questions:
+\AMCopenOpts{lines=10,lineheight=2ex,framerulecol=black,dots=false}
+
+\newcommand{\letter}{A}
+
+\makeatletter
+\renewcommand{\theenumi}{\alph{enumi}}
+\renewcommand{\labelenumi}{(\theenumi)}
+\makeatother
+
+%% OpenBox commands ============================
+\newcommand{\OpenBox}[1]{
+\noindent
+ \fbox{
+ \centering
+ \begin{minipage}{1.0\textwidth}
+~
+ \vspace{#1}
+~
+ \end{minipage}
+ }
+}
+
+\newcommand{\FullPageOpenBox}[0]{
+ \clearpage
+ \OpenBox{23cm}
+}
+
+%% OpenGrid commands ============================
+\newcommand{\OpenGrid}[1]{
+\noindent
+\begin{tikzpicture}
+\draw[step=.5cm,lightgray]
+(0cm,0cm) grid (15.5cm,#1);
+\end{tikzpicture}
+}
+
+\newcommand{\FullPageOpenGrid}[1]{
+ \clearpage
+ \OpenGrid{23cm}
+}
+
+%% ==============================================
+% Single points
+
+\newcommand{\correctorChoices}{\correctchoice[0]{}\scoring{b=0}\correctchoice[Y]{}\scoring{b=0.25}\correctchoice[X]{}\scoring{b=0.5}\correctchoice[W]{}\scoring{b=0.75}\correctchoice[V]{\qquad}\scoring{b=1}}
+
+\newcommand{\correctorThree}[3][0]{
+\par
+\begin{questionmultx}{#2}
+#3
+
+\noindent
+ \AMCOpen{lines=#1,framerulecol=white}{
+ \correctorChoices\correctorChoices\correctorChoices
+}
+\end{questionmultx}
+\vspace{-55pt}~
+}
+
+\newcommand{\correctorOne}[3][0]{
+\par
+\noindent
+\begin{questionmultx}{#2}
+#3
+
+\noindent
+ \AMCOpen{lines=#1,framerulecol=white}{
+ \correctorChoices\makebox[220.3pt][c]{}
+}
+\end{questionmultx}
+\vspace{-55pt}~
+}
+
+\newcommand{\correctorTwo}[3][0]{
+\par
+\noindent
+\begin{questionmultx}{#2}
+#3
+
+\noindent
+ \AMCOpen{lines=#1,framerulecol=white}{
+ \correctorChoices\correctorChoices\makebox[108.5pt][c]{}
+}
+\end{questionmultx}
+\vspace{-55pt}~
+}
diff --git a/data/base/style_professor.tex b/data/base/style_professor.tex
new file mode 100644
index 0000000..bac967f
--- /dev/null
+++ b/data/base/style_professor.tex
@@ -0,0 +1,50 @@
+%% New commands, professor specific
+
+\newcommand\RR{\mathbb{R}}
+\newcommand\PP{\mathbb{P}}
+\newcommand{\Col}{\mathop{\rm Col}\nolimits}
+\newcommand{\Nul}{\mathop{\rm Ker}\nolimits}
+\newcommand{\Lign}{\mathop{\rm Lig}\nolimits}
+
+\newcommand{\Span}{\mathop{\rm Vect}\nolimits}
+\renewcommand{\vec}[1]{\mathbf{#1}}
+
+\newcommand{\Vector}[1]{\left[
+ \begin{array}{c}
+ #1
+ \end{array}
+\right]
+}
+
+\newenvironment{Matrix}[1]{\left[
+ \begin{array}[r]{#1}}{
+ \end{array}
+\right]
+}
+
+% Some namenclature used:
+\newcommand{\dimension}{taille} % used for size of a matrix. usually dimension ou taille
+
+%% Questions %%%
+\baremeDefautS{b=3,m=-1}
+\baremeDefautM{b=2,m=0}
+
+\newcommand{\VERO}{
+ \bareme{b=1,m=-1}
+ \begin{choiceshoriz}[o]
+ \bonne{VRAI}
+ \mauvaise{FAUX}
+ \end{choiceshoriz}
+
+\medskip
+}
+
+\newcommand{\FALSO}{
+ \bareme{b=1,m=-1}
+ \begin{choiceshoriz}[o]
+ \mauvaise{VRAI}
+ \bonne{FAUX}
+ \end{choiceshoriz}
+
+\medskip
+}
diff --git a/data/config/de-mailing.xml b/data/config/de-mailing.xml
new file mode 100644
index 0000000..f648bd0
--- /dev/null
+++ b/data/config/de-mailing.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+ --senderauto-multiple-choice <do-not-reply@epfl.ch>
+ --textHallo,
+
+Im Anhang finden Sie das korrigierte Prüfung.
+Sollten Sie Fragen in Bezug auf Ihre Prüfung, kontaktieren Sie bitte Ihren Lehrer direkt.
+
+Mit freundlichen Grüßen.
+ --subjectIhre "Analyse 1" behoben Prüfung
+
+
+ --project./
+
+
+ --students-list%FILE_STUDENTS%
+ --list-encodingutf-8
+ --email-columnEMAIL
+
+
+ --transportsendmail
+ --sendmail-path/usr/bin/sendmail
+
+
\ No newline at end of file
diff --git a/data/config/en-mailing.xml b/data/config/en-mailing.xml
new file mode 100644
index 0000000..063a7a9
--- /dev/null
+++ b/data/config/en-mailing.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+ --senderauto-multiple-choice <do-not-reply@epfl.ch>
+ --textHello,
+
+Please find attached your corrected exam.
+Should you have any questions regarding your exam, please contact your teacher directly.
+
+Sincerely
+ --subjectYour Analyse 1 corrected exam
+
+
+ --project./
+
+
+ --students-list%FILE_STUDENTS%
+ --list-encodingutf-8
+ --email-columnEMAIL
+
+
+ --transportsendmail
+ --sendmail-path/usr/bin/sendmail
+
+
\ No newline at end of file
diff --git a/data/config/fr-mailing.xml b/data/config/fr-mailing.xml
new file mode 100644
index 0000000..fbf124a
--- /dev/null
+++ b/data/config/fr-mailing.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+ --senderauto-multiple-choice <do-not-reply@epfl.ch>
+ --textBonjour,
+
+Veuillez trouvez ci-jointe la correction de votre examen.
+Pour toute question relative à votre copie, n'hésitez pas à contacter votre enseignant.
+
+Bonne réception.
+ --subjectCorrection de votre examen blanc d'Analyse 1
+
+
+ --project./
+
+
+ --students-list%FILE_STUDENTS%
+ --list-encodingutf-8
+ --email-columnEMAIL
+
+
+ --transportsendmail
+ --sendmail-path/usr/bin/sendmail
+
+
\ No newline at end of file
diff --git a/data/config/options.xml b/data/config/options.xml
new file mode 100644
index 0000000..86a7a2a
--- /dev/null
+++ b/data/config/options.xml
@@ -0,0 +1,61 @@
+
+
+ <_modifie>
+ <_modifie_ok>1
+
+
+ marges
+
+ <preassoc>
+ 0
+
+ cr
+ data
+ EXAM-catalog.pdf
+ EXAM-sujet.pdf
+ EXAM-calage.xy
+ EXAM-corrige.pdf
+ EMAIL
+ Linear Algebra: test result
+ Please find enclosed your annotated completed answer sheet.
+Best Regards.
+ UTF-8
+ UTF-8
+ ID,NOM,SCIPER,SECTION,EMAIL,TAILLE_NOM,NOM_COMPLET,SEMESTRE
+ ;
+
+
+ 2
+ student.copy,student.key,student.name
+
+
+ 1
+ a4
+ n
+ latex
+ EXAM-filtered.tex
+ CSV
+ SCIPER
+ %PROJET/students.csv
+ 1
+
+ pdflatex
+
+ 0
+ normal
+ 0.5
+ 6
+ 1
+ 1
+ notes-common.xml
+ 0
+ 0
+
+ ALL
+ STUDENTS
+ 0.007
+ %PROJET/exam.tex
+ %(ID)
+Score: %S/%M
+ "%s/%m"
+
diff --git a/data/de/first_page.tex b/data/de/first_page.tex
new file mode 100644
index 0000000..fc2aa56
--- /dev/null
+++ b/data/de/first_page.tex
@@ -0,0 +1,54 @@
+% First page - GERMAN
+
+\noindent
+\begin{minipage}[c]{350pt}
+ \bf \prof~-~\ExamName~-~\SECTION \\
+ ~ \\
+ \bf \ExamDate~-~Dauer: \Time
+\end{minipage}
+\begin{minipage}[c]{100pt}
+ \includegraphics[scale=0.24]{Logo_EPFL}
+\end{minipage}
+
+\vfill
+\begin{minipage}[c]{\textwidth}
+\noindent
+\hfill\fontsize{100}{120}{\selectfont{\ID{}}}\hfill
+\vspace{1cm}
+\par
+\noindent
+\hfill\fontsize{40}{48}{\selectfont{\NOM{}}}\hfill~
+\end{minipage}
+\vfill
+
+\noindent
+\namefield{SCIPER: {\Large \bf \SCIPER{} }} \\
+~ \\
+\noindent
+\textbf{Drehen Sie diese Seite nicht um, bevor Sie dazu aufgefordert werden.}
+\textbf{Jedes Blatt hat eine Vorder- und eine R\"uckseite. Es gibt \totalPages\ Seiten, die letzten sind m\"oglicherweise leer.}
+\textbf{L\"osen Sie nicht die Heftklammer.}
+
+\begin{itemize}
+\item[-] Legen Sie Ihren Studentenausweis auf den Tisch.
+\item[-] Es sind \textbf{keine} Unterlagen zugelassen.
+\item[-] Die Nutzung eines \textbf{Taschenrechners} oder jedes anderen elektronischen Hilfsmittels ist w\"ahrend der Pr\"ufung nicht gestattet.
+\item[-] F\"ur die \textbf{Multiple Choice} Fragen erh\"alt man:
+ \begin{itemize}
+ \item [$+3$] Punkte, wenn die Antwort korrekt ist,
+ \item [0] Punkte, wenn die Frage nicht beantwortet ist oder mehrere M\"oglichkeiten angekreuzt sind, und
+ \item [$-1$] Punkt, wenn die Antwort falsch ist.
+ \end{itemize}
+\item [-] F\"ur die \textbf{Wahr/Falsch }Fragen erh\"alt man:
+ \begin{itemize}
+ \item [$+1$] Punkt, wenn die Antwort korrekt ist,
+ \item [0] Punkte, wenn die Frage nicht beantwortet ist oder mehrere M\"oglichkeiten angekreuzt sind, und
+ \item [$-1$] Punkt, wenn die Antwort falsch ist.
+ \end{itemize}
+\item[-] Benutzen Sie einen \textbf{Bleistift} und radieren Sie, falls n\"otig, sorgf\"altig aus.
+\item[-] Beachten Sie bitte diese Richtlinien bei der Markierung \textbf{der Antworten}:
+\end{itemize}
+
+\hfill \includegraphics[scale=0.2]{good_bad_square} \hfill
+
+% End of first page
diff --git a/data/de/header_1.tex b/data/de/header_1.tex
new file mode 100644
index 0000000..9a41685
--- /dev/null
+++ b/data/de/header_1.tex
@@ -0,0 +1,12 @@
+
+\newpage
+
+\subsection*{Erster Teil, Multiple-Choice-Fragen}
+
+\noindent
+Kreuze bitte für jede Frage die Box an, die zu der richtigen Lösung gehört. Streiche nichts durch, sondern benutze stattdessen einen Radiergummi.
+Es gibt \textbf{genau eine} richtige Antwort pro Frage.
+\bigskip
+
+
+%% Leave at least 2 empty lines after the \bigskip
\ No newline at end of file
diff --git a/data/de/header_2.tex b/data/de/header_2.tex
new file mode 100644
index 0000000..7e98374
--- /dev/null
+++ b/data/de/header_2.tex
@@ -0,0 +1,12 @@
+
+\newpage
+
+\subsection*{Zweiter Teil, Wahr/Falsch-Fragen}
+
+\noindent
+Kreuze bitte für jede der folgenden Fragen die Box WAHR an, wenn die Aussage \textbf{immer korrekt} ist, oder die Box FALSCH, wenn sie \textbf{nicht immer korrekt} ist, d.h. wenn die Aussage
+manchmal falsch ist. Streiche nichts durch, sondern benutze stattdessen einen Radiergummi.
+\bigskip
+
+
+%% Leave at least 2 empty lines after the \bigskip
\ No newline at end of file
diff --git a/data/de/question-text.tex b/data/de/question-text.tex
new file mode 100644
index 0000000..ee7380b
--- /dev/null
+++ b/data/de/question-text.tex
@@ -0,0 +1,11 @@
+%% Change Question text - FRENCH
+
+\renewcommand{\AMCbeginQuestion}[2]{\QuestionText{#1}}
+\def\QuestionText{\TEXT}
+\def\TEXT#1{\vspace{\AMCformVSpace}\par {\bf Frage #1 :} }
+\def\NOTEXT#1{ }
+
+%% little text can be written in the marking area to tell the students not to tick these boxes,
+\def\AMCotextReserved{\emph{Hier nicht schreiben.}}
+
+%% End of file
\ No newline at end of file
diff --git a/data/de/true-false.tex b/data/de/true-false.tex
new file mode 100644
index 0000000..c06d683
--- /dev/null
+++ b/data/de/true-false.tex
@@ -0,0 +1,23 @@
+%% TRUE / FALSE commands - GERMAN
+
+\AMCtext{draft}{ENTWURF}
+
+\newcommand{\TRUE}{
+ \bareme{b=1,m=-1}
+ \begin{choiceshoriz}[o]
+ \bonne{WAHR}
+ \mauvaise{FALSCH}
+ \end{choiceshoriz}
+\medskip
+}
+
+\newcommand{\FALSE}{
+ \bareme{b=1,m=-1}
+ \begin{choiceshoriz}[o]
+ \mauvaise{WAHR}
+ \bonne{FALSCH}
+ \end{choiceshoriz}
+\medskip
+}
+
+%% End of file
\ No newline at end of file
diff --git a/data/en/first_page.tex b/data/en/first_page.tex
new file mode 100644
index 0000000..4779243
--- /dev/null
+++ b/data/en/first_page.tex
@@ -0,0 +1,55 @@
+% First page - ENGLISH
+
+\noindent
+\begin{minipage}[c]{350pt}
+ \bf \prof~-~\ExamName~-~\SECTION \\
+ ~ \\
+ \bf \ExamDate~-~duration : \Time
+\end{minipage}
+\begin{minipage}[c]{100pt}
+ \includegraphics[scale=0.24]{Logo_EPFL}
+\end{minipage}
+
+\vfill
+\begin{minipage}[c]{\textwidth}
+\noindent
+\hfill\fontsize{100}{120}{\selectfont{\ID{}}}\hfill
+\vspace{1cm}
+\par
+\noindent
+\hfill\fontsize{40}{48}{\selectfont{\NOM{}}}\hfill~
+\end{minipage}
+\vfill
+
+\noindent
+\namefield{SCIPER: {\Large \bf \SCIPER{} }} \\
+~ \\
+\noindent
+\textbf{Do not turn the page before the start of the exam.}
+\textbf{This document is double-sided, has \totalPages\ pages, the last ones possibly blank.}
+\textbf{Do not unstaple.}
+
+\medskip
+\begin{itemize}
+\item[-] Place your student card on your table.
+\item[-] \textbf{No other paper materials} are allowed to be used during the exam.
+\item[-] Using a \textbf{calculator} or any electronic device is not permitted during the exam.
+\item[-] For the \textbf{multiple choice} questions, we give
+ \begin{itemize}
+ \item [$+3$] points if your answer is correct,
+ \item [$0$ ] points if you give no answer or more than one,
+ \item [$-1$] points if your answer is incorrect.
+ \end{itemize}
+\item[-] For the \textbf{true/false} questions, we give
+ \begin{itemize}
+ \item [$+1$] points if your answer is correct,
+ \item [$0$ ] points if you give no answer or more than one,
+ \item [$-1$] points if your answer is incorrect.
+ \end{itemize}
+\item[-] \textbf{Use a pencil} and clearly erase with an eraser if necessary.
+\item[-] Observe these guidelines when \textbf{recording your answers}:
+\end{itemize}
+
+\hfill \includegraphics[scale=0.2]{good_bad_square} \hfill
+
+% End of first page
diff --git a/data/en/header_1.tex b/data/en/header_1.tex
new file mode 100644
index 0000000..a70fd0a
--- /dev/null
+++ b/data/en/header_1.tex
@@ -0,0 +1,11 @@
+
+\newpage
+
+\subsection*{First part: multiple choice questions}
+
+\noindent
+For each question, cross the box corresponding to the correct answer. Each question has \textbf{exactly one} correct answer.
+\bigskip
+
+
+%% Leave at least 2 empty lines after the \bigskip
\ No newline at end of file
diff --git a/data/en/header_2.tex b/data/en/header_2.tex
new file mode 100644
index 0000000..4874c14
--- /dev/null
+++ b/data/en/header_2.tex
@@ -0,0 +1,11 @@
+
+\newpage
+
+\subsection*{Second part, true/false questions}
+
+\noindent
+For each question, cross the box (without erasing) TRUE if the statement is \textbf{always true} and the box FALSE if it is \textbf{not always true} (i.e., it is sometimes false).
+\bigskip
+
+
+%% Leave at least 2 empty lines after the \bigskip
\ No newline at end of file
diff --git a/data/en/question-text.tex b/data/en/question-text.tex
new file mode 100644
index 0000000..7f453c2
--- /dev/null
+++ b/data/en/question-text.tex
@@ -0,0 +1,12 @@
+%% Change Question text - FRENCH
+
+\renewcommand{\AMCbeginQuestion}[2]{\QuestionText{#1}}
+\def\QuestionText{\TEXT}
+\def\TEXT#1{\vspace{\AMCformVSpace}\par {\bf Question #1 :} }
+\def\NOTEXT#1{ }
+
+
+%% little text can be written in the marking area to tell the students not to tick these boxes,
+\def\AMCotextReserved{\emph{Do not write here.}}
+
+%% End of file
\ No newline at end of file
diff --git a/data/en/true-false.tex b/data/en/true-false.tex
new file mode 100644
index 0000000..9dbb04a
--- /dev/null
+++ b/data/en/true-false.tex
@@ -0,0 +1,21 @@
+%% TRUE / FALSE commands - ENGLISH
+
+\newcommand{\TRUE}{
+ \bareme{b=1,m=-1}
+ \begin{choiceshoriz}[o]
+ \bonne{TRUE}
+ \mauvaise{FALSE}
+ \end{choiceshoriz}
+\medskip
+}
+
+\newcommand{\FALSE}{
+ \bareme{b=1,m=-1}
+ \begin{choiceshoriz}[o]
+ \mauvaise{TRUE}
+ \bonne{FALSE}
+ \end{choiceshoriz}
+\medskip
+}
+
+%% End of file
\ No newline at end of file
diff --git a/data/fr/first_page.tex b/data/fr/first_page.tex
new file mode 100644
index 0000000..d4880f8
--- /dev/null
+++ b/data/fr/first_page.tex
@@ -0,0 +1,55 @@
+% First page - FRENCH
+
+\noindent
+\begin{minipage}[c]{350pt}
+ \bf \prof~-~\ExamName~-~\SECTION \\
+ ~ \\
+ \bf \ExamDate~-~durée : \Time
+\end{minipage}
+\begin{minipage}[c]{100pt}
+ \includegraphics[scale=0.24]{Logo_EPFL}
+\end{minipage}
+
+\vfill
+\begin{minipage}[c]{\textwidth}
+\noindent
+\hfill\fontsize{100}{120}{\selectfont{\ID{}}}\hfill
+\vspace{1cm}
+\par
+\noindent
+\hfill\fontsize{40}{48}{\selectfont{\NOM{}}}\hfill~
+\end{minipage}
+\vfill
+
+\noindent
+\namefield{SCIPER: {\Large \bf \SCIPER{} }} \\
+~ \\
+\noindent
+\textbf{Attendez le d\'ebut de l'\'epreuve avant de tourner la page.}
+\textbf{Ce document est imprim\'e recto-verso, il contient \totalPages\ pages, les derni\`eres pouvant \^etre vides.}
+\textbf{Ne pas d\'egrafer.}
+
+\medskip
+\begin{itemize}
+\item[-] Posez votre carte d'\'etudiant sur la table.
+\item[-] \textbf{Aucun} document n'est autoris\'{e}.
+\item[-] L'utilisation d'une \textbf{calculatrice} et de tout outil \'electronique est interdite pendant l'\'epreuve.
+\item[-] Pour les questions \`a \textbf{choix multiple}, on comptera:
+ \begin{itemize}
+ \item [$+3$] points si la r\'eponse est correcte,
+ \item [0] point si la question n'est pas r\'epondue ou s'il y a plusieurs croix,
+ \item [$-1$] point si la r\'eponse est incorrecte.
+ \end{itemize}
+\item [-]Pour les questions de type \textbf{vrai-faux}, on comptera:
+ \begin{itemize}
+ \item [$+1$] point si la r\'eponse est correcte,
+ \item [0] point si la question n'est pas r\'epondue ou s'il y a plusieurs croix,
+ \item [$-1$] point si la r\'eponse est incorrecte.
+ \end{itemize}
+\item[-] Utilisez un \textbf{crayon} et effacez proprement avec une \textbf{gomme} si n\'ecessaire.
+\item[-] Respectez les consignes suivantes pour \textbf{marquer vos réponses} :
+\end{itemize}
+
+\hfill \includegraphics[scale=0.2]{good_bad_square} \hfill
+
+%% End of first page
diff --git a/data/fr/header_1.tex b/data/fr/header_1.tex
new file mode 100644
index 0000000..3a543f4
--- /dev/null
+++ b/data/fr/header_1.tex
@@ -0,0 +1,11 @@
+
+\newpage
+
+\subsection*{Premi\`ere partie, questions \`a choix multiple}
+
+\noindent
+Pour chaque question mettre une croix dans la case correspondante \`a la r\'eponse correcte sans faire de ratures. Il n'y a qu'\textbf{une seule} r\'{e}ponse correcte par question.
+\bigskip
+
+
+%% Leave at least 2 empty lines after the \bigskip
\ No newline at end of file
diff --git a/data/fr/header_2.tex b/data/fr/header_2.tex
new file mode 100644
index 0000000..b20cd05
--- /dev/null
+++ b/data/fr/header_2.tex
@@ -0,0 +1,11 @@
+
+\newpage
+
+\subsection*{Deuxi\`eme partie, questions du type Vrai ou Faux}
+
+\noindent
+Pour chaque question, mettre une croix (sans faire de ratures) dans la case VRAI si l'affirmation est \textbf{toujours vraie} ou dans la case FAUX si elle \textbf{n'est pas toujours vraie} (c'est-\`a-dire, si elle est parfois fausse).
+\bigskip
+
+
+%% Leave at least 2 empty lines after the \bigskip
\ No newline at end of file
diff --git a/data/fr/question-text.tex b/data/fr/question-text.tex
new file mode 100644
index 0000000..124d80a
--- /dev/null
+++ b/data/fr/question-text.tex
@@ -0,0 +1,12 @@
+%% Change Question text - FRENCH
+
+\renewcommand{\AMCbeginQuestion}[2]{\QuestionText{#1}}
+\def\QuestionText{\TEXT}
+\def\TEXT#1{\vspace{\AMCformVSpace}\par {\bf Question #1 :} }
+\def\NOTEXT#1{ }
+
+
+%% little text can be written in the marking area to tell the students not to tick these boxes,
+\def\AMCotextReserved{\emph{R\'eserv\'e au correcteur}}
+
+%% End of file
\ No newline at end of file
diff --git a/data/fr/true-false.tex b/data/fr/true-false.tex
new file mode 100644
index 0000000..753487d
--- /dev/null
+++ b/data/fr/true-false.tex
@@ -0,0 +1,21 @@
+%% TRUE / FALSE commands - FRENCH
+
+\newcommand{\TRUE}{
+ \bareme{b=1,m=-1}
+ \begin{choiceshoriz}[o]
+ \bonne{VRAI}
+ \mauvaise{FAUX}
+ \end{choiceshoriz}
+\medskip
+}
+
+\newcommand{\FALSE}{
+ \bareme{b=1,m=-1}
+ \begin{choiceshoriz}[o]
+ \mauvaise{VRAI}
+ \bonne{FAUX}
+ \end{choiceshoriz}
+\medskip
+}
+
+%% End of file
\ No newline at end of file
diff --git a/data/list/de/end_tab.tex b/data/list/de/end_tab.tex
new file mode 100644
index 0000000..dc2e4dd
--- /dev/null
+++ b/data/list/de/end_tab.tex
@@ -0,0 +1,2 @@
+\hline
+\end{tabularx}
diff --git a/data/list/de/list.tex b/data/list/de/list.tex
new file mode 100644
index 0000000..b7bd967
--- /dev/null
+++ b/data/list/de/list.tex
@@ -0,0 +1,24 @@
+\documentclass[a4paper,10pt]{report}
+\usepackage[utf8]{inputenc}
+\usepackage[table,xcdraw]{xcolor}
+\usepackage{tabularx}
+\usepackage{lastpage}
+\usepackage{fancyhdr}
+\usepackage[textwidth=17cm,textheight=22cm]{geometry}
+
+\pagestyle{fancy}
+\renewcommand{\headrulewidth}{0pt}
+\chead{#PROF_NAME# - #EXAM# - #DATE#}
+\cfoot{\textit{\small{Page \thepage\ of \pageref{LastPage}}}}
+
+% Title Page
+\title{Liste der zur Pr\"ufung registrierten Sch\"uler \\ #PROF_NAME#}
+\author{#EXAM#}
+\date{#DATE#}
+
+\begin{document}
+\maketitle
+
+\input{./rows.tex}
+
+\end{document}
diff --git a/data/list/de/start_tab.tex b/data/list/de/start_tab.tex
new file mode 100644
index 0000000..4dd28b1
--- /dev/null
+++ b/data/list/de/start_tab.tex
@@ -0,0 +1,12 @@
+\noindent
+\begin{tabularx}{\textwidth}{|r|r|l|l|l|l|X|}
+\hline
+ \rowcolor[HTML]{E2001A}
+ {\color[HTML]{FFFFFF} \texttt{\#}} &
+ {\color[HTML]{FFFFFF} \texttt{SCIPER}} &
+ {\color[HTML]{FFFFFF} \texttt{Section}} &
+ {\color[HTML]{FFFFFF} \texttt{Name}} &
+ {\color[HTML]{FFFFFF} \texttt{~1~}}&
+ {\color[HTML]{FFFFFF} \texttt{~2~}}& \\
+\hline
+\hline
\ No newline at end of file
diff --git a/data/list/en/end_tab.tex b/data/list/en/end_tab.tex
new file mode 100644
index 0000000..dc2e4dd
--- /dev/null
+++ b/data/list/en/end_tab.tex
@@ -0,0 +1,2 @@
+\hline
+\end{tabularx}
diff --git a/data/list/en/list.tex b/data/list/en/list.tex
new file mode 100644
index 0000000..9ddfa02
--- /dev/null
+++ b/data/list/en/list.tex
@@ -0,0 +1,24 @@
+\documentclass[a4paper,10pt]{report}
+\usepackage[utf8]{inputenc}
+\usepackage[table,xcdraw]{xcolor}
+\usepackage{tabularx}
+\usepackage{lastpage}
+\usepackage{fancyhdr}
+\usepackage[textwidth=17cm,textheight=22cm]{geometry}
+
+\pagestyle{fancy}
+\renewcommand{\headrulewidth}{0pt}
+\chead{#PROF_NAME# - #EXAM# - #DATE#}
+\cfoot{\textit{\small{Page \thepage\ of \pageref{LastPage}}}}
+
+% Title Page
+\title{Students list \\ #PROF_NAME#}
+\author{#EXAM#}
+\date{#DATE#}
+
+\begin{document}
+\maketitle
+
+\input{./rows.tex}
+
+\end{document}
\ No newline at end of file
diff --git a/data/list/en/start_tab.tex b/data/list/en/start_tab.tex
new file mode 100644
index 0000000..4dd28b1
--- /dev/null
+++ b/data/list/en/start_tab.tex
@@ -0,0 +1,12 @@
+\noindent
+\begin{tabularx}{\textwidth}{|r|r|l|l|l|l|X|}
+\hline
+ \rowcolor[HTML]{E2001A}
+ {\color[HTML]{FFFFFF} \texttt{\#}} &
+ {\color[HTML]{FFFFFF} \texttt{SCIPER}} &
+ {\color[HTML]{FFFFFF} \texttt{Section}} &
+ {\color[HTML]{FFFFFF} \texttt{Name}} &
+ {\color[HTML]{FFFFFF} \texttt{~1~}}&
+ {\color[HTML]{FFFFFF} \texttt{~2~}}& \\
+\hline
+\hline
\ No newline at end of file
diff --git a/data/list/fr/end_tab.tex b/data/list/fr/end_tab.tex
new file mode 100644
index 0000000..dc2e4dd
--- /dev/null
+++ b/data/list/fr/end_tab.tex
@@ -0,0 +1,2 @@
+\hline
+\end{tabularx}
diff --git a/data/list/fr/list.tex b/data/list/fr/list.tex
new file mode 100644
index 0000000..e06b0e0
--- /dev/null
+++ b/data/list/fr/list.tex
@@ -0,0 +1,24 @@
+\documentclass[a4paper,10pt]{report}
+\usepackage[utf8]{inputenc}
+\usepackage[table,xcdraw]{xcolor}
+\usepackage{tabularx}
+\usepackage{lastpage}
+\usepackage{fancyhdr}
+\usepackage[textwidth=17cm,textheight=22cm]{geometry}
+
+\pagestyle{fancy}
+\renewcommand{\headrulewidth}{0pt}
+\chead{#PROF_NAME# - #EXAM# - #DATE#}
+\cfoot{\textit{\small{Page \thepage\ of \pageref{LastPage}}}}
+
+% Title Page
+\title{Liste des \'etudiants inscrits \\ #PROF_NAME#}
+\author{#EXAM#}
+\date{#DATE#}
+
+\begin{document}
+\maketitle
+
+\input{./rows.tex}
+
+\end{document}
\ No newline at end of file
diff --git a/data/list/fr/start_tab.tex b/data/list/fr/start_tab.tex
new file mode 100644
index 0000000..8679860
--- /dev/null
+++ b/data/list/fr/start_tab.tex
@@ -0,0 +1,12 @@
+\noindent
+\begin{tabularx}{\textwidth}{|r|r|l|l|l|l|X|}
+\hline
+ \rowcolor[HTML]{E2001A}
+ {\color[HTML]{FFFFFF} \texttt{\#}} &
+ {\color[HTML]{FFFFFF} \texttt{SCIPER}} &
+ {\color[HTML]{FFFFFF} \texttt{Section}} &
+ {\color[HTML]{FFFFFF} \texttt{Nom}} &
+ {\color[HTML]{FFFFFF} \texttt{~1~}}&
+ {\color[HTML]{FFFFFF} \texttt{~2~}}& \\
+\hline
+\hline
\ No newline at end of file
diff --git a/data/media/Logo_EPFL.png b/data/media/Logo_EPFL.png
new file mode 100644
index 0000000..929e1cd
Binary files /dev/null and b/data/media/Logo_EPFL.png differ
diff --git a/data/media/good_bad_square.png b/data/media/good_bad_square.png
new file mode 100644
index 0000000..6d272b2
Binary files /dev/null and b/data/media/good_bad_square.png differ
diff --git a/data/skel/workspace/exams/skel/exam.conf b/data/skel/workspace/exams/skel/exam.conf
new file mode 100644
index 0000000..cbbc2d6
--- /dev/null
+++ b/data/skel/workspace/exams/skel/exam.conf
@@ -0,0 +1,6 @@
+PROF_NAME:Ens: M. Skeleton
+EXAM:Analyse I
+DURATION:3 heures
+DATE:12 janvier 2015
+LANG:fr
+TOTAL_PAGES:12
diff --git a/data/skel/workspace/exams/skel/extra_packages.tex b/data/skel/workspace/exams/skel/extra_packages.tex
new file mode 100644
index 0000000..7350215
--- /dev/null
+++ b/data/skel/workspace/exams/skel/extra_packages.tex
@@ -0,0 +1,27 @@
+%% Extra packages
+
+%% CSV file access
+\usepackage{csvsimple}
+
+%% Multicolumn formatting
+\usepackage{multicol}
+
+%% Math packages
+\usepackage{graphicx}
+\usepackage{amsmath}
+\usepackage{mathrsfs}
+\usepackage{euscript}
+\usepackage{epsfig}
+\usepackage{ifthen}
+\usepackage{amsfonts}
+\usepackage{amssymb}
+
+%% Graphics
+\usepackage{graphicx}
+\graphicspath{ {media/} }
+
+%% Specific commands for Skel teacher (EXAMPLE)
+\newcommand{\R}{\mathbb{R}}
+\newcommand{\Q}{\mathbb{Q}}
+\newcommand{\N}{\mathbb{N}}
+\newcommand{\Z}{\mathbb{Z}}
diff --git a/data/skel/workspace/exams/skel/extra_section.tex b/data/skel/workspace/exams/skel/extra_section.tex
new file mode 100644
index 0000000..15cf964
--- /dev/null
+++ b/data/skel/workspace/exams/skel/extra_section.tex
@@ -0,0 +1,168 @@
+
+\newpage
+
+\subsection*{Troisi\`eme partie, questions de type ouvert}
+
+\noindent
+\begin{itemize}
+\item Ceci est la plus petite partie de l'examen (elle compte {\bf 16 points}).
+\item En g\'en\'eral, il ne s'agit pas de questions \`a choix multiple ; toutefois, il faut imp\'erativement que vous mettiez vos r\'eponses dans les cases pr\'evues \`a cet effet !
+\item Vous pouvez utiliser les feuilles (recto-verso) comme brouillon. Seules les r\'eponses dans les cases sont valides !
+\item Il est possible que parfois plusieurs r\'eponses soient valables !
+\item Laisser libre les cases \`a cocher : elles sont r\'eserv\'ees au correcteur.
+\end{itemize}
+
+\bigskip
+
+
+%% Leave at least 2 empty lines after the \bigskip
+%% ===============================
+\renewcommand{\correctorChoices}{
+ \correctchoice[0]{}\scoring{b=0}
+ \correctchoice[Y]{}\scoring{b=0.5}
+ \correctchoice[X]{}\scoring{b=1}
+ \correctchoice[W]{}\scoring{b=1.5}
+ \correctchoice[V]{\qquad}\scoring{b=2}
+}
+\correctorTwo{q-sem-01}{ \textit{Cette question est notée sur 4 points. (2 fois 2 points)} }
+%\renewcommand{\AMCbeginQuestion}[2]{\QuestionText{#1}}
+%\def\QuestionText{\TEXT}
+%\def\TEXT#1{}
+%\def\NOTEXT#1{}
+\bigskip
+
+
+%% Leave at least 2 empty lines after the \bigskip
+\noindent
+\begin{enumerate}
+\item Existe-t-il des fonctions surjectives $f: A\to B$ ?
+\begin{table}[h]
+\Large
+\centering
+\begin{tabular}{|c|c|c|c|}
+\hline
+$A$ & $B$ & Oui & Non \\
+\hline
+$\{1,2\}$ & $\{1,2,3\}$ && \\
+\hline
+$\{1,2,3\}$ & $\{1,2\}$ && \\
+\hline
+$\N$ & $\Z$ && \\
+\hline
+$]0,1[$ & $\R$ && \\
+\hline
+\end{tabular}
+\end{table}
+
+%% ===============================
+\item Existe-t-il des fonctions surjectives \emph{et continues} $f: A \to B$ ?
+\begin{table}[h]
+\Large
+\centering
+\begin{tabular}{|c|c|c|c|}
+\hline
+$A$ & $B$ & Oui & Non \\
+\hline
+$[0,1]$ & $[-1,2]$ && \\
+\hline
+$[-1,1]$ & $\R$ && \\
+\hline
+$]-1,1[$ & $\R\backslash \{0\}$ && \\
+\hline
+$[-2,0] \cup [1,4]$ & $[-1,0[ \cup [1, 2]$ && \\
+\hline
+\end{tabular}
+\end{table}
+\end{enumerate}
+%% ===============================
+\newpage
+\correctorTwo{q-sem-02}{ \textit{Cette question est notée sur 4 points.} }
+\bigskip
+
+
+%% Leave at least 2 empty lines after the \bigskip
+\noindent
+Soit $A$ un ensemble ouvert et born\'e et $B$ un ensemble ferm\'{e} dans $\R$. On suppose que $A \cap B \neq \emptyset$.
+Les sous-ensembles de $\R$ suivants sont-ils ouverts, ferm\'es, born\'es ? Justifier la r\'eponse !
+\begin{table}[h]
+\Large
+\centering
+\begin{tabular}{|c|c|c|c|}
+\hline
+Ensemble & ouvert & ferm\'e & born\'e \\
+\hline
+$A\setminus B$ &&& \\
+\hline
+$B\setminus A$ &&& \\
+\hline
+\end{tabular}
+\end{table}
+\\
+\noindent
+\textit{Justification :}
+\\
+\OpenBox{8cm}
+\bigskip
+
+
+%% ===============================
+\correctorTwo{q-sem-03}{ \textit{Cette question est notée sur 4 points.} }
+\bigskip
+
+
+%% Leave at least 2 empty lines after the \bigskip
+\noindent
+Calculer les rayons de convergence des s\'eries enti\`eres suivantes.
+\begin{align*}
+i)\quad \sum_{k=0}^\infty k (x-1)^k && ii)\quad \sum_{k=0}^\infty \frac1{2^k}\, x^k && iii)\quad \sum_{k=0}^\infty k! x^k
+\end{align*}
+\begin{table}[h]
+\Large \centering
+\begin{tabular}{|c|c|}
+\hline
+S\'erie & Rayon de convergence \\
+\hline
+$i)$ & \\
+\hline
+$ii)$ & \\
+\hline
+$iii)$ & \\
+\hline
+\end{tabular}
+\end{table}
+%% ===============================
+\newpage
+\correctorTwo{q-sem-04}{ \textit{Cette question est notée sur 4 points. (2 fois 2 points)} }
+\bigskip
+
+
+%% Leave at least 2 empty lines after the \bigskip
+\noindent
+\begin{enumerate}
+\item L'assertion suivante est-elle vraie ou fausse ?
+\newline Soient $f: [a,b]\to \R$ et $g: [a,b]\to \R$ deux fonctions escaliers relatives aux
+partitions $P, Q\subset [a,b]$ respectivement. Alors, le produit $fg$ est encore une fonction escalier.
+\begin{table}[h]
+\Large
+\centering
+\begin{tabular}{|c|c|}
+\hline
+Vraie & Fausse \\
+\hline
+& \\
+\hline
+\end{tabular}
+\end{table}
+
+\item Si l'assertion est vraie, la fonction $fg$ est une fonction escalier relative \`a quelle partition?
+\newline Si l'assertion est fausse, justifier la r\'eponse !
+\end{enumerate}
+\bigskip
+
+
+%% Leave at least 2 empty lines after the \bigskip
+\noindent
+\textit{Partition ou justification :}
+\\
+\OpenBox{14cm}
+
diff --git a/data/skel/workspace/exams/skel/sections.conf b/data/skel/workspace/exams/skel/sections.conf
new file mode 100644
index 0000000..9b81a5c
--- /dev/null
+++ b/data/skel/workspace/exams/skel/sections.conf
@@ -0,0 +1,2 @@
+mc-common_01.tex,mc-common_02.tex
+tf-common_01.tex,tf-common_02.tex
diff --git a/data/skel/workspace/exams/skel/students.csv b/data/skel/workspace/exams/skel/students.csv
new file mode 100644
index 0000000..0c536c1
--- /dev/null
+++ b/data/skel/workspace/exams/skel/students.csv
@@ -0,0 +1,5 @@
+ID,NOM,SCIPER,SECTION,EMAIL
+1,Mick Jagger,XXXXX1,XYZ,mick.jagger@epfl.ch
+2,Keith Richards,XXXXX2,XYZ,keith.richards@epfl.ch
+3,Charlie Watts,XXXXX3,XYZ,charlie.watts@epfl.ch
+4,Ronnie Wood,XXXXX4,XYZ,ronnie.wood@epfl.ch
diff --git a/data/skel/workspace/questions/fr/mc-common_01.tex b/data/skel/workspace/questions/fr/mc-common_01.tex
new file mode 100644
index 0000000..e016c9c
--- /dev/null
+++ b/data/skel/workspace/questions/fr/mc-common_01.tex
@@ -0,0 +1,12 @@
+\begin{question}{q:mc-01}
+Soit le sous-ensemble $E\subset \mathbb{R}$ d\'efini par
+$\displaystyle E=\Bigg\{2\left(1+\frac 1 n\right)
+^{\hspace{-1mm}n}: n\in \mathbb{N}\setminus\{0\}\Bigg\}$.
+\\Alors
+\begin{reponses}
+\bonne{$10$ est un majorant de $E$}
+\mauvaise{le minimum de $E$ est $2$}
+\mauvaise{$E$ est ferm\'e}
+\mauvaise{le supremum de $E$ appartient \`a $E$}
+\end{reponses}
+\end{question}
diff --git a/data/skel/workspace/questions/fr/mc-common_02.tex b/data/skel/workspace/questions/fr/mc-common_02.tex
new file mode 100644
index 0000000..b9ebf01
--- /dev/null
+++ b/data/skel/workspace/questions/fr/mc-common_02.tex
@@ -0,0 +1,10 @@
+\begin{question}{q:mc-02}
+L'\'equation $~z^{-1}=\overline z\,$, o\`u $\overline z $
+est le complexe conjugu\'e de $z$, admet
+\begin{reponses}
+ \mauvaise{exactement une solution dans $\mathbb{C}$}
+ \mauvaise{exactement deux solutions dans $\mathbb{C}$}
+ \mauvaise{aucune solution dans $\mathbb{C}$}
+ \bonne{une infinit\'e de solutions dans $\mathbb{C}$}
+\end{reponses}
+\end{question}
diff --git a/data/skel/workspace/questions/fr/tf-common_01.tex b/data/skel/workspace/questions/fr/tf-common_01.tex
new file mode 100644
index 0000000..d5c395c
--- /dev/null
+++ b/data/skel/workspace/questions/fr/tf-common_01.tex
@@ -0,0 +1,5 @@
+\begin{question}{q:tf-01}
+Soit $A$ un sous-ensemble born\'e et non vide de $\mathbb{R}$.\\
+Alors $\,\inf A\in A\,$ et $\,\sup A \in A\,$.
+\FALSE
+\end{question}
diff --git a/data/skel/workspace/questions/fr/tf-common_02.tex b/data/skel/workspace/questions/fr/tf-common_02.tex
new file mode 100644
index 0000000..7746a0a
--- /dev/null
+++ b/data/skel/workspace/questions/fr/tf-common_02.tex
@@ -0,0 +1,8 @@
+\begin{question}{q:tf-02}
+Soient $f\colon \mathbb{R}\rightarrow \mathbb{R}$ et
+$g\colon\mathbb{R}\rightarrow \mathbb{R}$ deux fonctions d\'efinies sur tout
+$\mathbb{R}$. Si
+% $f$ et
+$f\circ g$ est injective, alors $g$ est injective.
+\TRUE
+\end{question}
diff --git a/install/install_bins.sh b/install/install_bins.sh
new file mode 100755
index 0000000..6fd2357
--- /dev/null
+++ b/install/install_bins.sh
@@ -0,0 +1,67 @@
+#!/bin/bash
+
+# Set SNAME, SDIR and SPATH
+if [ -h $0 ]; then SNAME=$(readlink $0); else SNAME=$0; fi
+SDIR=$(dirname $SNAME)
+if [[ "$SDIR" =~ ^/ ]]; then SPATH=$SDIR; else SPATH=$(realpath $(pwd)/$SDIR); fi
+
+# Set DIR_LIB
+DIR_LIB=$(realpath $SPATH/../lib/bash)
+
+# Source libs
+source $DIR_LIB/colors.sh
+source $DIR_LIB/io.sh
+
+# Set DESTINATION
+
+if [ -n "$1" ]; then
+ DESTINATION="$(realpath $1)"
+else
+ DESTINATION=$(realpath ${HOME}/bin)
+fi
+if [ ! -d $DESTINATION/ ]; then
+ error_echo "'$DESTINATION' is not a valid directory."
+ exit 1
+fi
+info_echo "Using '$DESTINATION' as binary directory."
+
+function link_to_binaries() {
+ for bin in $(echo $BINS); do
+ if [ -r $DESTINATION/$bin ]; then
+ if [ -h $DESTINATION/$bin ]; then
+ OLD=$(realpath $DESTINATION/$bin)
+ if [ "$OLD" == "$DIR_BIN/$bin" ]; then
+ color_echo_n "Command '$bin' already exists... "
+ else
+ color_echo_n "Moving command '$bin' from '$OLD' to '$DIR_BIN/$bin'... "
+ rm $DESTINATION/$bin
+ ln -s $DIR_BIN/$bin $DESTINATION/$bin
+
+
+ fi
+ else
+ error_echo "A similar command already exists: '$OLD'"
+ exit 2
+ fi
+ else
+ color_echo_n "Installing command '$bin' to '$DESTINATION/'... "
+ ln -s $DIR_BIN/$bin $DESTINATION/$bin
+ fi
+ [ -h $DESTINATION/$bin ]
+ check_rc_echo $?
+ done
+}
+
+DIR_BIN=$(realpath $SPATH/../bin)
+BINS='bamc'
+link_to_binaries
+
+DIR_BIN=$(realpath $SPATH/../local/epfl)
+BINS='search-epfl'
+link_to_binaries
+
+# Done
+warning_echo "Make sure '$DESTINATION' is in your \$PATH variable.";
+
+# RIP
+exit 0
diff --git a/lib/bash/bamc_actions.sh b/lib/bash/bamc_actions.sh
new file mode 100644
index 0000000..168278b
--- /dev/null
+++ b/lib/bash/bamc_actions.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+function tell_if_verbose() {
+ if [ "$(get_param_value 'mode')" = 'verbose' ]; then echo "$@"; fi
+}
+
+function do_list() {
+}
+
+function check_project_exists() {
+}
+
+function get_teachers() {
+}
+
+function do_test() {
+}
+
+function check_file_exists() {
+}
+
+function get_lang() {
+}
+
+function import_student_file() {
+}
+
+function get_expected_page_per_exam() {
+}
+
+function customize_tex_files() {
+}
+
+function build_sections() {
+}
+
+function do_sample() {
+}
+
+function do_check() {
+}
+
+function do_blank() {
+}
+
+function do_pdf() {
+}
+
+function build_amc_project() {
+}
+
+function add_media_files() {
+}
+
+function do_project() {
+}
+
+function do_clean() {
+}
diff --git a/lib/bash/bamc_configuration.sh b/lib/bash/bamc_configuration.sh
new file mode 100644
index 0000000..1e45e4f
--- /dev/null
+++ b/lib/bash/bamc_configuration.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+DIR_CONF=$SPATH/../conf/
+FILE_CONF=$DIR_CONF/$(basename $0).conf
+
+if [ ! -r $FILE_CONF ]; then
+ echo "Could not read configuration file ($FILE_CONF)"
+ exit 1;
+fi
+source $FILE_CONF
+
+# EOF
diff --git a/lib/bash/bamc_help.sh b/lib/bash/bamc_help.sh
new file mode 100644
index 0000000..bb651a0
--- /dev/null
+++ b/lib/bash/bamc_help.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+function action_help() {
+ local item=$1 # Useless...
+ echo -e "${DarkGreen}USAGE:${NoColor} "$(basename $0)" [-p|--params p1=v1,p2-v2,...] [-o|--only item1,item2,...] action1 [action2 ...]
+
+e.g.: $(basename $0) --params workspace=~/AMC_Workspace init
+e.g.: $(basename $0) show
+
+Parameters:
+-----------
+${DarkGreen}debug${NoColor} (GENERAL) print debugging messages
+${DarkGreen}force${NoColor} (GENERAL) do not ask for user confirmation
+${DarkGreen}workspace|w${NoColor} (GENERAL) name a specific workspace
+${DarkGreen}exam|e${NoColor} (GENERAL) name a specific exam within the workspace
+
+Items:
+-----------
+${DarkGreen}item1${NoColor} (GENERAL) items are the existing exams (as printed by the 'list' action)
+
+Actions:
+--------
+${DarkGreen}init${NoColor} (WORKSPACE) create a new workspace in the current directory
+
+${DarkGreen}add-exam${NoColor} (EXAMS) add a new exam in the current workspace
+${DarkGreen}list${NoColor} (EXAMS) print list of exams in the current workspace
+
+${DarkGreen}clean${NoColor} (PROJECTS) remove project and output directories
+${DarkGreen}project${NoColor} (PROJECTS) (re)build the AMC project
+
+${DarkGreen}check-latex${NoColor} (PROJECTS) check LaTeX syntax
+${DarkGreen}check-cvs${NoColor} (PROJECTS) check CSV files syntax
+${DarkGreen}check-amc${NoColor} (PROJECTS) check AMC specific syntax
+${DarkGreen}check${NoColor} (PROJECTS) do all checks
+
+${DarkGreen}blank${NoColor} (PDF) build a blank (anonymous) exam
+${DarkGreen}catalog${NoColor} (PDF) build a catalog of questions
+${DarkGreen}sample${NoColor} (PDF) build a sample (4 exams) exam
+${DarkGreen}exam${NoColor} (PDF) build PDF exam"
+ return 0
+}
+
diff --git a/lib/bash/bamc_workspace.sh b/lib/bash/bamc_workspace.sh
new file mode 100644
index 0000000..3b6e013
--- /dev/null
+++ b/lib/bash/bamc_workspace.sh
@@ -0,0 +1,130 @@
+#/bin/bash
+
+function get_workspace() {
+ is_set 'workspace'
+ if [ $? -eq 1 ]; then DIR_WORKSPACE=$(get_parameter 'workspace'); fi
+ is_set 'w'
+ if [ $? -eq 1 ]; then DIR_WORKSPACE=$(get_parameter 'w'); fi
+
+ if [ -z $DIR_WORKSPACE ]; then DIR_WORKSPACE="."; fi
+
+ if [[ ! "$DIR_WORKSPACE" =~ ^/ ]]; then DIR_WORKSPACE=$(echo "$(pwd)/$DIR_WORKSPACE" | sed 's?/\.$??'); fi
+}
+
+function check_workspace() {
+ if [ -r "$1/$FILE_WORKSPACE_ROOT" ]; then
+ return 1
+ else
+ return 0
+ fi
+}
+
+function locate_nearest_workspace() {
+ local next_dir=$DIR_WORKSPACE
+ if [ -d $next_dir ]; then next_dir=$(realpath $next_dir); fi
+ for i in $(echo 1 2 3 4); do
+ check_workspace $next_dir
+ if [ $? -eq 1 ]; then
+ # Workspace found
+ echo $next_dir
+ return
+ else
+ next_dir=$next_dir/../
+ if [ -d $next_dir ]; then next_dir=$(realpath $next_dir); fi
+ fi
+ done
+ echo "~none~"
+ return
+}
+
+function assert_workspace() {
+ get_workspace
+ check_workspace $DIR_WORKSPACE
+ if [ $? -eq 1 ]; then return 0; fi
+ DIR_WORKSPACE=$(locate_nearest_workspace)
+ if [ "$DIR_WORKSPACE" == "~none~" ]; then
+ error_echo "No valid workspace found around here..."
+ return 1
+ fi
+ return 0
+}
+
+function action_list() {
+ # Check workspace
+ assert_workspace; if [ $? -eq 1 ]; then return 1; fi
+
+ # List exams
+ for exam in $(find $DIR_WORKSPACE/$DIR_EXAMS -mindepth 1 -maxdepth 1 -type d -exec basename {} \;); do
+ echo $exam
+ done
+
+ # Done
+ return 0
+}
+
+function action_add-exam() {
+ # Check workspace
+ assert_workspace; if [ $? -eq 1 ]; then return 1; fi
+
+ # Set source
+ DIR_EXAM=$DIR_SKEL/workspace/$DIR_EXAMS/skel
+
+ if [ ! -d $DIR_EXAM ]; then
+ error_echo "Directory '$DIR_EXAM' not found."
+ return 1
+ fi
+
+ is_set 'exam'
+ if [ $? -eq 1 ]; then DESTINATION=$(get_parameter 'exam'); fi
+ is_set 'e'
+ if [ $? -eq 1 ]; then DESTINATION=$(get_parameter 'e'); fi
+
+ if [ -z "$DESTINATION" ]; then error_echo "No truc provided."; fi
+ exit 1
+
+ # Set destination
+ DESTINATION=$DIR_WORKSPACE/$DIR_EXAMS/$DESTINATION
+
+ if [ -d $DESTINATION ]; then
+ confirm "Do you want to write in existing exam '$(basename $DESTINATION)' ?"
+ if [ $? -eq 0 ]; then return 1; fi
+ fi
+
+ # Copy
+ color_echo_n "Creating new exam '$(basename $DESTINATION)' from '$DIR_EXAM'... "
+ cp -rp $DIR_EXAM $DESTINATION
+ check_rc_echo $?
+ return $?
+}
+
+
+function action_init() {
+ get_workspace
+ EXISTING_WORKSPACE=$(locate_nearest_workspace)
+ if [ "$EXISTING_WORKSPACE" != "~none~" ]; then
+ error_echo "You cannot create a workspace within a workspace!"
+ error_echo "Detected workspace location: '$EXISTING_WORKSPACE'"
+ return 1
+ fi
+
+ confirm "Do you want to initialize workspace: '${DIR_WORKSPACE}'?"
+ if [ $? -eq 0 ]; then return 1; fi
+
+ # Create directory
+ color_echo_n "Creating directory '${DIR_WORKSPACE}'... "
+ mkdir -p $DIR_WORKSPACE
+ check_rc_echo $?
+ if [ $? -ne 0 ]; then return 1; fi
+
+ color_echo_n "Adding workspace flag... "
+ touch $DIR_WORKSPACE/$FILE_WORKSPACE_ROOT
+ check_rc_echo $?
+ if [ $? -ne 0 ]; then return 1; fi
+
+ color_echo_n "Importing skeleton files... "
+ cp -rp $DIR_SKEL/workspace/* $DIR_WORKSPACE/
+ check_rc_echo $?
+ if [ $? -ne 0 ]; then return 1; fi
+
+ return 0
+}
diff --git a/lib/bash/cmdline_parser.sh b/lib/bash/cmdline_parser.sh
new file mode 100644
index 0000000..e787d80
--- /dev/null
+++ b/lib/bash/cmdline_parser.sh
@@ -0,0 +1,121 @@
+#!/bin/bash
+
+# Command line parser
+# USAGE: [in the calling script]
+# 1) set the global variables: $DEFAULT_ITEMS and $DEFAULT_ACTIONS
+# 2) set the global variable: $CALLBACK_PREFIX (default: cb_ )
+# 3) implement each the callback: e.g. function cb_list() { ... } to implement the list action
+# 4) call: run $@
+
+ACTIONS=''
+PARAMS=''
+ITEMS=''
+CALLBACK_PREFIX=${CALLBACK_PREFIX:-cb_}
+
+function error() {
+ error_echo $@
+}
+
+function debug() {
+ is_set debug
+ if [ $? -eq 1 ]; then debug_echo $@; fi
+}
+
+function parse_args() {
+ while [ $# -gt 0 ]; do
+ case $1 in
+ "-o"|"--only")
+ shift
+ ITEMS=$1
+ shift
+ continue
+ ;;
+ "-p"|"--params")
+ shift
+ PARAMS=$1
+ shift
+ continue
+ ;;
+ *)
+ if [ -z "$ACTIONS" ]; then ACTIONS=$1; else ACTIONS="$ACTIONS $1"; fi
+ shift
+ ;;
+ esac
+ done
+
+ if [ -z "$ITEMS" ]; then ITEMS=$(get_default_items); fi
+ if [ -z "$ITEMS" ]; then ITEMS='~no~items~'; fi
+ if [ -z "$ACTIONS" ]; then ACTIONS=$(get_default_actions); fi
+
+ debug "actions: '$ACTIONS' | items: '$ITEMS' | parameters: '$PARAMS'"
+}
+
+function get_items() {
+ echo "$ITEMS" | sed "s/,/\n/g"
+}
+
+function get_actions() {
+ echo "$ACTIONS" | sed "s/ /\n/g"
+}
+
+function get_params() {
+ echo "$PARAMS" | sed "s/,/\n/g"
+}
+
+function get_parameter() {
+ key=$1
+ # Key alone
+ line=$(echo $PARAMS | sed "s/,/\n/g" | grep "^$key$")
+ if [ -n "$line" ]; then echo "set"; return; fi
+ # Key + value
+ line=$(echo $PARAMS | sed "s/,/\n/g" | grep "^$key=" | cut -d '=' -f 2-)
+ if [ -n "$line" ]; then echo $line; return; fi
+ # unset
+ echo "unset"
+}
+
+function is_set() {
+ param=$1
+ value=$(get_parameter $param)
+ if [ "$value" != "unset" ]; then return 1; else return 0; fi
+}
+
+function run() {
+ local action
+ local item
+ local rc=0
+ parse_args $@
+ for action in $(get_actions); do
+ for item in $(get_items); do
+ debug "Running action '$action' on item '$item'"
+ action_switch $action $item
+ rc=$((rc+$?))
+ done
+
+ done
+ return $rc
+}
+
+function get_default_items() {
+ echo $DEFAULT_ITEMS
+}
+
+function get_default_actions() {
+ echo $DEFAULT_ACTIONS
+}
+
+function action_switch() {
+ local action=$1
+ local item=$2
+ local function_to_call="${CALLBACK_PREFIX}${action}"
+ local function_exists=$(type "$function_to_call" 2>&1 | grep -c "$function_to_call"' is a function');
+ if [ $function_exists -eq 1 ]; then
+ $function_to_call $item
+ return $?
+ else
+ error "invalid action: $action (not implemented ?)"
+ return 1
+ fi
+}
+
+# EOF
diff --git a/lib/bash/colors.sh b/lib/bash/colors.sh
new file mode 100644
index 0000000..e27bce2
--- /dev/null
+++ b/lib/bash/colors.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+if [ ! -t 1 ]; then return; fi
+if [ ! -t 2 ]; then return; fi
+
+# Define colors
+Black="\e[0;30m"
+DarkGray="\e[1;30m"
+DarkBlue="\e[0;34m"
+Blue="\e[1;34m"
+DarkGreen="\e[0;32m"
+Green="\e[1;32m"
+Cyan="\e[0;36m"
+LightCyan="\e[1;36m"
+DarkRed="\e[0;31m"
+Red="\e[1;31m"
+Purple="\e[0;35m"
+Pink="\e[1;35m"
+Brown="\e[0;33m"
+Yellow="\e[1;33m"
+Gray="\e[0;37m"
+White="\e[1;37m"
+NoColor="\e[00m"
+
+COLORS=1;
+
+function colors() {
+ echo -e ${Black}Black$NoColor
+ echo -e ${DarkGray}DarkGray$NoColor
+ echo -e ${DarkBlue}DarkBlue$NoColor
+ echo -e ${LightBlue}Blue$NoColor
+ echo -e ${DarkGreen}DarkGreen$NoColor
+ echo -e ${Green}Green$NoColor
+ echo -e ${Cyan}Cyan$NoColor
+ echo -e ${LightCyan}LightCyan$NoColor
+ echo -e ${DarkRed}DarkRed$NoColor
+ echo -e ${Red}Red$NoColor
+ echo -e ${Purple}Purple$NoColor
+ echo -e ${Pink}Pink$NoColor
+ echo -e ${Brown}Brown$NoColor
+ echo -e ${Yellow}Yellow$NoColor
+ echo -e ${Gray}Gray$NoColor
+ echo -e ${White}White$NoColor
+ echo -e ${NoColor}NoColor$NoColor
+}
diff --git a/lib/bash/functions.sh b/lib/bash/functions.sh
new file mode 100644
index 0000000..b47ad28
--- /dev/null
+++ b/lib/bash/functions.sh
@@ -0,0 +1,775 @@
+#!/bin/bash
+
+function get_targets() {
+ # TARGETS
+ TARGETS="check:do_check|\
+blank:do_blank|\
+test:do_test|\
+project:do_project|\
+clean:do_clean|\
+sample:do_sample|\
+pdf:do_pdf|\
+all:do_check,do_blank,do_sample,do_pdf,do_list|\
+webdav_catalog:do_webdav_catalog|\
+webdav_sample:do_webdav_sample|\
+webdav_list:do_webdav_list|\
+webdav_pdf:do_webdav_pdf|\
+webdav_all:do_webdav_catalog,do_webdav_sample,do_webdav_list,do_webdav_pdf|\
+list:do_list|\
+help:do_help|\
+scans:do_scans|\
+analyse:do_analyse|\
+associate:do_associate|\
+export:do_export|\
+annotate:do_annotate|\
+mailing:do_mailing"
+ echo $TARGETS | tr '|' '\n' | grep "^$1:" | cut -d ':' -f 2- | tr ',' ' '
+}
+
+function parse_arguments() {
+ if [ $# -lt 1 ]; then set 'test'; fi
+
+ while [ $# -gt 0 ]; do
+ case "$1" in
+ '--only'|'-o')
+ shift
+ echo teacher_list:$1 | tr ',' ' '
+ shift
+ ;;
+ '--params'|'-p')
+ shift
+ echo parameters:$1 | tr ',' ' '
+ shift
+ ;;
+ *)
+ echo target:$(get_targets $1)
+ shift
+ ;;
+ esac
+ done
+}
+
+function get_param_value() {
+ local IFS=' '
+ for p in $(echo $PARAMETERS | tr ',' ' '); do
+ echo $p | grep "$1=" | cut -d '=' -f 2
+ done
+}
+
+function do_actions() {
+ ACTIONS=$(parse_arguments $@ | grep "^target:" | cut -d ':' -f 2-)
+ TEACHERS=$(parse_arguments $@ | grep "^teacher_list:" | cut -d ':' -f 2-)
+ PARAMETERS=$(parse_arguments $@ | grep "^parameters:" | cut -d ':' -f 2-)
+ local rc=0
+ if [ -z "$ACTIONS" ]; then echo "Nothing to do!"; exit 0; fi
+ for ACTION in $(echo "$ACTIONS");
+ do
+ tell_if_verbose "Processing : ($ACTION)"
+ process_action $ACTION
+ rc=$((rc+$?))
+ done
+ if [ $rc -gt 0 ]; then
+ echo "RC=$rc"
+ fi
+ exit $rc
+}
+
+function tell_if_verbose() {
+ if [ "$(get_param_value 'mode')" = 'verbose' ]; then echo "$@"; fi
+}
+
+function do_help() {
+ echo $(basename $0)" [--only teacher,teacher] [--params ask=no,lang=xx] action [action ...]
+
+Options:
+--only, -o: only process the following teachers (comma separated list)
+--params, -p: set/force some parameters (e.g. ask=no,mode=verbose,lang=fr)
+
+Actions:
+ help: print this help
+ test: do nothing - usefull for testing with --only ...
+
+ # Exam preparation
+ project: build AMC project
+ clean: clean output dirs, projects, etc
+ sample: build samples PDF (sample)
+ blank: build blank exam PDF (blank and catalog)
+ pdf: build final PDF (exam and correction)
+ list: build exam students list
+ all: build blank, sample, final and list PDF files
+
+ # Exam publication
+ webdav_catalog: publish catalog PDF files to \$DIR_WEBDAV_CATALOGS
+ webdav_sample: publish sample PDF files to \$DIR_WEBDAV_SAMPLES
+
+
+ [...]"
+ #echo $TARGETS | tr '|' '\n' | sed "s/:/ => /g" | sed "s/,/, /g"
+}
+
+function process_action() {
+ case "$1" in
+ "do_clean") do_clean;;
+ "do_project") do_project;;
+ "do_sample") do_sample;;
+ "do_pdf") do_pdf;;
+ "do_help") do_help;;
+ "do_scans") do_scans;;
+ "do_analyse") do_analyse;;
+ "do_associate") do_associate;;
+ "do_annotate") do_annotate;;
+ "do_export") do_export;;
+ "do_mailing") do_mailing;;
+ "do_test") do_test;;
+ "do_blank") do_blank;;
+ "do_check") do_check;;
+ "do_list") do_list;;
+ "do_webdav_catalog") do_webdav_catalog;;
+ "do_webdav_sample") do_webdav_sample;;
+ "do_webdav_list") do_webdav_list;;
+ "do_webdav_pdf") do_webdav_pdf;;
+ *)
+ echo "Unimplemented action: '$1'"
+ return 1
+ ;;
+ esac
+ return $?
+}
+
+function do_list() {
+ local rc=0
+ local lang line id sciper name section IFS i tab FILE list_file
+ for teacher in $(get_teachers); do
+ tell_if_verbose " - $teacher"
+ if [ ! -d $DIR_TEACHERS/$teacher ]; then
+ echo "ERROR: [$teacher] teacher not found."
+ ((rc++))
+ else
+ lang=$(get_lang $teacher)
+ FILE=$DIR_TEACHERS/$teacher/$FILE_PROF
+ IFS=$'\n'
+ cp $DIR_LIST/$lang/list.tex $DIR_EXAMS/$teacher/$FILE_LIST_MAIN
+ list_file=$DIR_EXAMS/$teacher/$FILE_LIST_MAIN
+ check_file_exists $list_file
+ for replace in $(cat $FILE); do
+ pattern="#$(echo $replace | cut -d ':' -f 1)#"
+ string=$(echo $replace | cut -d ':' -f 2-)
+ sed -i "s/$pattern/$string/g" $list_file
+ done
+ rm -f $DIR_EXAMS/$teacher/$FILE_LIST_ROWS
+ i=0
+ tab=1
+ prev_section=''
+ for line in $(tail -n +2 $DIR_EXAMS/$teacher/$FILE_STUDENTS); do
+ ((i++))
+ if [ $tab -eq 1 ]; then
+ cat $DIR_LIST/$lang/$FILE_LIST_TSTART >> $DIR_EXAMS/$teacher/$FILE_LIST_ROWS
+ tab=0
+ fi
+ # id field
+ id=$(echo $line | cut -d ',' -f 1)
+ id=$(printf "%3s" $id)
+ id=$(echo $id | sed "s/ /~/g")
+
+ name=$(echo $line | cut -d ',' -f 2)
+ if [ $(echo -n $name | wc -c) -gt $LIST_MAX_CHAR ]; then name=$(echo $name | cut -c 1-$LIST_MAX_CHAR)"…"; fi
+ sciper=$(echo $line | cut -d ',' -f 3)
+ section=$(echo $line | cut -d ',' -f 4)
+ comment=$(echo $line | cut -d ',' -f 10)
+ if [ "$comment" == "n/a" ]; then comment=''; fi
+ if [ "$section" == "XXX" ]; then section=''; name="SCIPER:"; fi
+ name=$(printf "%-${LIST_MAX_CHAR}s" $name)
+ name=$(echo $name | sed "s/ /~/g")
+
+ if [ "$section" != "$prev_section" ]; then
+ if [ $i -ne 1 ]; then
+ cat $DIR_LIST/$lang/$FILE_LIST_TEND >> $DIR_EXAMS/$teacher/$FILE_LIST_ROWS
+ cat $DIR_LIST/$lang/$FILE_LIST_TSTART >> $DIR_EXAMS/$teacher/$FILE_LIST_ROWS
+ i=1
+ fi
+ prev_section=$section
+ fi
+
+ echo "\texttt{$id} & \texttt{$sciper} & \texttt{$section} & \texttt{$name} & & & {\tiny \texttt{$comment}} "'\\ \hline' >> $DIR_EXAMS/$teacher/$FILE_LIST_ROWS
+ if [ $(($i % $LIST_ROWS_PER_PAGE)) -eq 0 ]; then
+ cat $DIR_LIST/$lang/$FILE_LIST_TEND >> $DIR_EXAMS/$teacher/$FILE_LIST_ROWS
+ tab=1
+ fi
+ done
+ if [ $(($i % $LIST_ROWS_PER_PAGE)) -ne 0 ]; then
+ cat $DIR_LIST/$lang/$FILE_LIST_TEND >> $DIR_EXAMS/$teacher/$FILE_LIST_ROWS
+ fi
+ pdf_list=$(echo $FILE_LIST_MAIN | cut -d '.' -f 1)'.pdf'
+ cd $DIR_EXAMS/$teacher/
+ # Run pdflatex twice to get total number of pages
+ pdflatex ./$FILE_LIST_MAIN 2>&1 > /dev/null
+ pdflatex ./$FILE_LIST_MAIN 2>&1 > /dev/null
+ cd - > /dev/null
+ mkdir -p $DIR_LIST_OUT
+ cp $DIR_EXAMS/$teacher/$pdf_list $DIR_LIST_OUT/List-$teacher.pdf
+ cp $DIR_TEACHERS/$teacher/$FILE_STUDENTS $DIR_LIST_OUT/List-$teacher.csv
+ fi
+ done
+ return $rc
+}
+
+function check_project_exists() {
+ local teacher=$1
+ if [ ! -d $DIR_EXAMS/$teacher ]; then
+ echo "No project found for teacher: $1"
+ echo "Did you run \"project\" action ?"
+ exit 1
+ fi
+}
+
+function get_teachers() {
+ if [ -n "$TEACHERS" ]; then echo "$TEACHERS"; return; fi
+ find $DIR_TEACHERS -maxdepth 1 -mindepth 1 -type d -exec basename {} \; | grep -v ".ignore$" | sort
+}
+
+function do_test() {
+ local rc=0
+ for teacher in $(get_teachers); do
+ tell_if_verbose " - $teacher"
+ if [ ! -d $DIR_TEACHERS/$teacher ]; then
+ echo "ERROR: [$teacher] teacher not found."
+ ((rc++))
+ else
+ echo " Nothing done. just testing."
+ fi
+ done
+ return $rc
+}
+
+function check_file_exists() {
+ if [ ! -r $1 ]; then
+ if [ $# -lt 2 ]; then echo "ERROR: File not found: $1"; fi
+ if [ -n "$2" ]; then return $2; else exit 1; fi
+ fi
+ return 0
+}
+
+function get_lang() {
+ local forced_lang=$(get_param_value 'lang')
+ if [ -n "$forced_lang" ]; then
+ echo $forced_lang;
+ else
+ local FILE=$DIR_TEACHERS/$1/$FILE_LANG
+ check_file_exists $FILE
+ cat $FILE
+ fi
+}
+
+function import_tex_files() {
+ local teacher=$1
+ local lang=$(get_lang $teacher)
+ cp $DIR_DATA/base/* $DIR_EXAMS/$teacher/
+ cp $DIR_DATA/$lang/* $DIR_EXAMS/$teacher/
+}
+
+function override_tex_files() {
+ local teacher=$1
+ find $DIR_TEACHERS/$teacher/ -name '*.tex' -exec cp {} $DIR_EXAMS/$teacher/ \;
+}
+
+function import_student_file() {
+ local teacher=$1
+ local lang=$(get_lang $teacher)
+ local FILE=$DIR_TEACHERS/$teacher/$FILE_STUDENTS
+ check_file_exists $FILE
+ cp $FILE $DIR_EXAMS/$teacher/$FILE_STUDENTS
+
+ # Add extra students
+ local nb=0
+ local IFS=$'\n'
+ local id=$(tail -n 1 $DIR_EXAMS/$teacher/$FILE_STUDENTS | cut -d ',' -f 1)
+ local sample=$(head -n 1 $DIR_CSV/extra.csv)
+ while [ $nb -lt $EXTRA_COPIES ]; do
+ ((nb++))
+ ((id++))
+ echo $sample | sed "s/#NB#/$nb/g" | sed "s/#ID#/$id/g" | sed "s/#SCIPER#/FAKE-$nb/g" | sed "s/#EMAIL#/$DEFAULT_EMAIL/g" | sed "s/#SEMESTER#/$SEMESTER/g" >> $DIR_EXAMS/$teacher/$FILE_STUDENTS
+ done
+}
+
+function get_expected_page_per_exam() {
+ local teacher=$1
+ local FILE=$DIR_TEACHERS/$teacher/$FILE_PROF
+ cat $FILE | grep '^TOTAL_PAGES:' | cut -d ':' -f 2
+}
+
+function customize_tex_files() {
+ local teacher=$1 replace
+ local lang=$(get_lang $teacher)
+ local FILE=$DIR_TEACHERS/$teacher/$FILE_PROF
+ check_file_exists $FILE
+
+ # professor.tex
+ local prof_file=$DIR_EXAMS/$teacher/$FILE_PROF_TEX IFS=$'\n'
+ check_file_exists $prof_file
+ for replace in $(cat $FILE); do
+ pattern="#$(echo $replace | cut -d ':' -f 1)#"
+ string=$(echo $replace | cut -d ':' -f 2-)
+ sed -i "s/$pattern/$string/g" $prof_file
+ done
+
+ # exam.tex
+ lang=$(echo $lang | tr '[a-z]' '[A-Z]')
+ sed -i "s/#LANG#/$lang/g" $DIR_EXAMS/$teacher/$FILE_EXAM_BASENAME.tex
+
+ # Add specific.tex file
+ if [ -r $DIR_EXAMS/$teacher/specific.tex ]; then
+ sed -i 's?% #SPECIFIC#?\\input{./specific.tex}?g' $DIR_EXAMS/$teacher/$FILE_EXAM_BASENAME.tex
+ fi
+
+}
+
+function build_sections() {
+ local question_file question section_file section teacher=$1
+ local lang=$(get_lang $teacher) IFS=$'\n' FILE=$DIR_TEACHERS/$teacher/$FILE_SECTIONS
+ mkdir -p $DIR_EXAMS/$teacher
+ check_file_exists $FILE
+ local section_num=0
+ rm -f $DIR_EXAMS/$teacher/sections.tex $DIR_EXAMS/$teacher/random-sections.tex
+ for section in $(cat $FILE); do
+ ((section_num++))
+ section_file=$DIR_EXAMS/$teacher/section_${section_num}.tex
+ rm -f $section_file
+ for question in $(echo $section | tr ',' '\n'); do
+ #echo "Adding $question to section $section_file...";
+ question_file=$DIR_QUESTIONS/$lang/$question
+ check_file_exists $question_file
+ echo "%% From $lang/$question =======================================" >> $section_file
+ echo '\element{section'$section_num'}{' >> $section_file
+ cat $question_file >> $section_file
+ echo '}' >> $section_file
+ done
+ #echo "Updating 'sections.tex' file"
+ echo "\input{./section_${section_num}.tex}" >> $DIR_EXAMS/$teacher/sections.tex
+ #echo "Updating 'random-sections.tex' file"
+ echo "\input{./header_${section_num}.tex}
+\melangegroupe{section${section_num}}
+\restituegroupe{section${section_num}}
+" >> $DIR_EXAMS/$teacher/random-sections.tex
+
+ done
+}
+
+function do_sample() {
+ local teacher
+ local lang
+ local runs
+ local ok
+ local rc=0
+ for teacher in $(get_teachers); do
+ ok=0
+ tell_if_verbose " - $teacher"
+ check_project_exists $teacher
+ if [ -r $DIR_EXAMS/$teacher/INVALID ]; then ((rc++)); break; fi
+ runs=$PDFLATEX_RUNS
+ mv $DIR_EXAMS/$teacher/$FILE_STUDENTS $DIR_EXAMS/$teacher/$FILE_STUDENTS.sav
+ cp $DIR_CSV/$FILE_SAMPLE_CSV $DIR_EXAMS/$teacher/$FILE_STUDENTS
+ cd $DIR_EXAMS/$teacher
+ rm -f EXAM-sujet.pdf EXAM-corrige.pdf EXAM-catalog.pdf catalog.pdf
+ while [ $runs -gt 0 ]; do
+ auto-multiple-choice prepare --mode s --with pdflatex --filter latex --prefix ./ ./$FILE_EXAM_BASENAME.tex --out-sujet EXAM-sujet.pdf --out-corrige EXAM-corrige.pdf 2>&1 > /dev/null | grep -v 'deprecated'
+ rm -f $DIR_EXAMS/$teacher/catalog.pdf
+ check_file_exists EXAM-sujet.pdf 1
+ if [ $? -ne 0 ]; then runs=0; continue; else ok=1; fi;
+ ((runs--))
+ done
+ cd - 2>&1 > /dev/null
+ mv $DIR_EXAMS/$teacher/$FILE_STUDENTS.sav $DIR_EXAMS/$teacher/$FILE_STUDENTS
+ if [ $ok -eq 1 ]; then
+ mkdir -p $DIR_SAMPLES
+ lang=$(get_lang $teacher)
+ mv $DIR_EXAMS/$teacher/EXAM-sujet.pdf $DIR_SAMPLES/Sample-$teacher-$lang.pdf
+ rm -f $DIR_EXAMS/$teacher/catalog.pdf $DIR_EXAMS/$teacher/EXAM-corrige.pdf
+ else
+ ((rc++))
+ echo "ERROR: [$teacher] Sample not generated."
+ fi
+ done;
+ return $rc
+}
+
+function do_check() {
+ local teacher
+ local runs
+ local ok
+ local rc=0
+ for teacher in $(get_teachers); do
+ ok=0
+ tell_if_verbose " - $teacher"
+ check_project_exists $teacher
+ runs=$PDFLATEX_RUNS
+ mv $DIR_EXAMS/$teacher/$FILE_STUDENTS $DIR_EXAMS/$teacher/$FILE_STUDENTS.sav
+ cp $DIR_CSV/$FILE_BLANK_CSV $DIR_EXAMS/$teacher/$FILE_STUDENTS
+ cd $DIR_EXAMS/$teacher
+ rm -f EXAM-sujet.pdf EXAM-corrige.pdf EXAM-catalog.pdf catalog.pdf
+ while [ $runs -gt 0 ]; do
+ auto-multiple-choice prepare --mode s --with pdflatex --filter latex --prefix ./ ./$FILE_EXAM_BASENAME.tex --out-sujet EXAM-sujet.pdf --out-corrige EXAM-corrige.pdf 2>&1 > /dev/null | grep -v 'deprecated'
+ rm -f catalog.pdf
+ if [ ! -r EXAM-sujet.pdf ]; then runs=0; continue; else ok=1; fi;
+ if [ ! -r EXAM-corrige.pdf ]; then runs=0; continue; else ok=1; fi;
+ ((runs--))
+ done
+ cd - 2>&1 > /dev/null
+ mv $DIR_EXAMS/$teacher/$FILE_STUDENTS.sav $DIR_EXAMS/$teacher/$FILE_STUDENTS
+ if [ $ok -eq 0 ]; then
+ echo "ERROR: [$teacher] exam.tex does not compile."
+ touch $DIR_EXAMS/$teacher/INVALID
+ ((rc++))
+ else
+ rm -f $DIR_EXAMS/$teacher/INVALID
+ fi
+ done;
+ return $rc
+}
+
+function do_blank() {
+ local teacher
+ local lang
+ local runs
+ local ok
+ local rc=0
+ for teacher in $(get_teachers); do
+ ok=0
+ tell_if_verbose " - $teacher"
+ check_project_exists $teacher
+ if [ -r $DIR_EXAMS/$teacher/INVALID ]; then ((rc++)); break; fi
+ runs=$PDFLATEX_RUNS
+ mv $DIR_EXAMS/$teacher/$FILE_STUDENTS $DIR_EXAMS/$teacher/$FILE_STUDENTS.sav
+ cp $DIR_CSV/$FILE_BLANK_CSV $DIR_EXAMS/$teacher/$FILE_STUDENTS
+ cd $DIR_EXAMS/$teacher
+ rm -f EXAM-sujet.pdf EXAM-corrige.pdf EXAM-catalog.pdf catalog.pdf
+ while [ $runs -gt 0 ]; do
+ auto-multiple-choice prepare --mode s --with pdflatex --filter latex --prefix ./ ./$FILE_EXAM_BASENAME.tex --out-sujet EXAM-sujet.pdf --out-corrige EXAM-corrige.pdf 2>&1 > /dev/null | grep -v 'deprecated'
+ check_file_exists EXAM-sujet.pdf 1
+ if [ $? -ne 0 ]; then runs=0; continue; else ok=1; fi;
+ check_file_exists EXAM-corrige.pdf 1
+ if [ $? -ne 0 ]; then runs=0; continue; else ok=1; fi;
+ check_file_exists catalog.pdf 1
+ if [ $? -ne 0 ]; then runs=0; continue ;else ok=1; fi;
+ ((runs--))
+ done
+ cd - 2>&1 > /dev/null
+ mv $DIR_EXAMS/$teacher/$FILE_STUDENTS.sav $DIR_EXAMS/$teacher/$FILE_STUDENTS
+ if [ $ok -eq 1 ]; then
+ mkdir -p $DIR_BLANKS
+ lang=$(get_lang $teacher)
+ mv $DIR_EXAMS/$teacher/EXAM-sujet.pdf $DIR_BLANKS/Blank-$teacher-$lang.pdf
+ mv $DIR_EXAMS/$teacher/EXAM-corrige.pdf $DIR_BLANKS/Correction-Blank-$teacher-$lang.pdf
+ mv $DIR_EXAMS/$teacher/catalog.pdf $DIR_BLANKS/Catalog-$teacher-$lang.pdf
+ else
+ ((rc++))
+ echo "ERROR: [$teacher] Blank not generated."
+ fi
+ done;
+ return $rc
+}
+
+function do_pdf() {
+ local teacher
+ local lang
+ local runs
+ local ok=0
+ local rc
+ for teacher in $(get_teachers); do
+ ok=0
+ tell_if_verbose " - $teacher"
+ check_project_exists $teacher
+ if [ -r $DIR_EXAMS/$teacher/INVALID ]; then ((rc++)); break; fi
+ runs=$PDFLATEX_RUNS
+ cd $DIR_EXAMS/$teacher
+ while [ $runs -gt 0 ]; do
+ tell_if_verbose " (step 1/3)"
+ auto-multiple-choice prepare --mode s --with pdflatex --filter latex --prefix ./ ./$FILE_EXAM_BASENAME.tex --out-sujet EXAM-sujet.pdf --out-corrige EXAM-corrige.pdf --out-calage DOC-calage.xy 2>&1 > /dev/null | grep -v 'deprecated'
+ ((runs--))
+ check_file_exists EXAM-sujet.pdf 1
+ if [ $? -ne 0 ]; then runs=0; continue; else ok=1; fi;
+ check_file_exists EXAM-corrige.pdf 1
+ if [ $? -ne 0 ]; then runs=0; continue; else ok=1; fi;
+ check_file_exists DOC-calage.xy 1
+ if [ $? -ne 0 ]; then runs=0; continue; else ok=1; fi;
+ done
+ if [ $ok -eq 1 ]; then
+ tell_if_verbose " (step 2/3)"
+ auto-multiple-choice prepare --mode b --with pdflatex --filter latex --prefix ./ ./$FILE_EXAM_BASENAME.tex --data ./data/ 2>&1 > /dev/null | grep -v 'deprecated'
+ tell_if_verbose " (step 3/3)"
+ auto-multiple-choice meptex --src ./DOC-calage.xy --data ./data --debug /dev/null 2>&1 > /dev/null
+ fi
+ cd - 2>&1 > /dev/null
+ if [ $ok -eq 1 ]; then
+ mkdir -p $DIR_PDF
+ lang=$(get_lang $teacher)
+ cp $DIR_EXAMS/$teacher/EXAM-sujet.pdf $DIR_PDF/Exam-$teacher-$lang.pdf
+ cp $DIR_EXAMS/$teacher/EXAM-corrige.pdf $DIR_PDF/Exam-$teacher-$lang-CORRECTION.pdf
+ mv $DIR_EXAMS/$teacher/catalog.pdf $DIR_EXAMS/$teacher/EXAM-catalog.pdf
+ # Validate page count
+ nb_of_students=$(tail -n 1 $DIR_EXAMS/$teacher/$FILE_STUDENTS | cut -d ',' -f 1)
+ ppe=$(get_expected_page_per_exam $teacher)
+ expected_page_count=$(echo "$ppe * $nb_of_students" | bc)
+ actual_page_count=$(pdfinfo $DIR_PDF/Exam-$teacher-$lang.pdf | grep '^Pages:' | rev | cut -d ' ' -f 1 | rev)
+ if [ $expected_page_count -ne $actual_page_count ]; then
+ ((rc++))
+ echo "Unexpected number of pages generated."
+ fi
+ echo "$actual_page_count page(s) generated for $nb_of_students students. $expected_page_count pag(s) were expected." > $DIR_PDF/Exam-$teacher-$lang.log
+ else
+ ((rc++))
+ echo "Exam not generated for $teacher."
+ fi
+ done
+ return $rc
+}
+
+function build_amc_project() {
+ local teacher=$1
+ mkdir -p $DIR_EXAMS/$teacher/cr/corrections/jpg
+ mkdir -p $DIR_EXAMS/$teacher/cr/corrections/pdf
+ mkdir -p $DIR_EXAMS/$teacher/cr/diagnostic
+ mkdir -p $DIR_EXAMS/$teacher/cr/zooms
+ mkdir -p $DIR_EXAMS/$teacher/data
+ mkdir -p $DIR_EXAMS/$teacher/exports
+ mkdir -p $DIR_EXAMS/$teacher/scans
+ mkdir -p $DIR_EXAMS/$teacher/copies
+ mkdir -p $DIR_EXAMS/$teacher/media
+ cp $DIR_CONFIG/$FILE_AMC_OPTIONS $DIR_EXAMS/$teacher/$FILE_AMC_OPTIONS
+}
+
+function add_media_files() {
+ local teacher=$1
+ cp $DIR_MEDIA/* $DIR_EXAMS/$teacher/media/
+}
+
+function do_project() {
+ mkdir -p $DIR_EXAMS
+ local teacher
+ for teacher in $(get_teachers); do
+ tell_if_verbose " - $teacher"
+ build_amc_project $teacher
+ build_sections $teacher
+ import_tex_files $teacher
+ override_tex_files $teacher
+ customize_tex_files $teacher
+ add_media_files $teacher
+ import_student_file $teacher
+ done
+}
+
+function do_scans() {
+ local teacher
+ for teacher in $(get_teachers); do
+ echo "Processing teacher $teacher"
+ if [ ! -d $DIR_SCANS/$teacher ]; then
+ echo "No scans found! (looked in $DIR_SCANS/$teacher)"
+ break;
+ fi
+ rm -rf $DIR_EXAMS/$teacher/scans/
+ mkdir $DIR_EXAMS/$teacher/scans/
+ cp $DIR_SCANS/$teacher/* $DIR_EXAMS/$teacher/scans/
+ local file_count=$(find $DIR_EXAMS/$teacher/scans/ -type f | wc -l)
+ echo "Number of files: $file_count"
+ local page_count=0
+ for tif in $(find $DIR_EXAMS/$teacher/scans/ -type f); do
+ if [ $(tiffinfo $tif 2> /dev/null | grep 'Page Number:' | cut -d '-' -f 2 ) -eq 0 ]; then
+ ((page_count++))
+ else
+ echo "Invalid page count for file: $tif";
+ fi
+ done
+ echo "Number of pages: $page_count"
+ local students_count=$(cat $DIR_EXAMS/$teacher/$FILE_STUDENTS | grep -vc '^ID,')
+ echo "Number of students: $students_count"
+ local ppc=$(grep 'TOTAL_PAGES:' $DIR_TEACHERS/$teacher/$FILE_PROF | cut -d ':' -f 2)
+ local expected=$((ppc*students_count))
+ echo "Expected number of pages: $expected"
+ if [ ! $expected -eq $page_count ]; then
+ echo "MISSING COPIES :"
+ cat $DIR_EXAMS/$teacher/$FILE_STUDENTS | grep -v '^ID,' | cut -d ',' -f 1 >> /tmp/$$
+ find $DIR_EXAMS/$teacher/scans/ -type f | rev | cut -d '_' -f 1 | rev | cut -d '-' -f 1 | sed 's/^0*//g' | sort -u >> /tmp/$$
+ for missing in $(sort -n /tmp/$$ | uniq -u); do
+ grep "^$missing," $DIR_EXAMS/$teacher/$FILE_STUDENTS
+ done
+ rm -f /tmp/$$
+ fi
+ done
+}
+
+function do_analyse() {
+ local teacher
+ for teacher in $(get_teachers); do
+ echo "Processing teacher $teacher"
+ cd $DIR_EXAMS/$teacher/
+ auto-multiple-choice analyse --projet ./ ./scans/*
+ # auto-multiple-choice note --data ./data --seuil 0.05 # ignored anyway... check "seuil" in options.xml
+ cd - > /dev/null 2>&1
+ done
+}
+
+function do_associate() {
+ local teacher
+ for teacher in $(get_teachers); do
+ echo "Processing teacher $teacher"
+ cd $DIR_EXAMS/$teacher/
+ for csv in $(grep -v '^ID,' $FILE_STUDENTS | cut -d ',' -f 1,3); do
+ local id=$(echo $csv | cut -d ',' -f 1)
+ local sciper=$(echo $csv | cut -d ',' -f 2)
+ auto-multiple-choice association --data ./data --set --student $id --id $sciper
+ done
+ cd - > /dev/null 2>&1
+ done
+}
+
+function do_annotate() {
+ local teacher
+ for teacher in $(get_teachers); do
+ echo "Processing teacher $teacher"
+ cd $DIR_EXAMS/$teacher/
+ auto-multiple-choice annote --projet ./ --data ./data --fich-noms $FILE_STUDENTS
+ auto-multiple-choice regroupe --projet ./ --sujet EXAM-sujet.pdf --fich-noms $FILE_STUDENTS --tex-src $FILE_EXAM_BASENAME.tex --compose
+ cd - > /dev/null 2>&1
+ done
+}
+
+function do_mailing() {
+ local teacher
+ for teacher in $(get_teachers); do
+ echo "Processing teacher $teacher"
+ local lang=$(get_lang $teacher)
+ check_file_exists $DIR_CONFIG/$lang-$FILE_AMC_MAILING
+ cp $DIR_CONFIG/$lang-$FILE_AMC_MAILING $DIR_EXAMS/$teacher/$FILE_AMC_MAILING
+ sed -i "s?%FILE_STUDENTS%?./$FILE_STUDENTS?g" $DIR_EXAMS/$teacher/$FILE_AMC_MAILING
+ echo "#!/bin/bash
+auto-multiple-choice mailing --xmlargs ./$FILE_AMC_MAILING
+exit 0" > $DIR_EXAMS/$teacher/$FILE_EMAILS_SCRIPT
+ chmod +x $DIR_EXAMS/$teacher/$FILE_EMAILS_SCRIPT
+ done
+}
+
+function do_export() {
+ local teacher
+ for teacher in $(get_teachers); do
+ tell_if_verbose " - $teacher"
+ cd $DIR_EXAMS/$teacher/
+ auto-multiple-choice export --data ./data --module CSV --fich-noms $FILE_STUDENTS --o $FILE_RESULTS.csv
+ cd - > /dev/null 2>&1
+ done
+}
+
+function check_webdav_mounted() {
+ if [ ! -d $DIR_WEBDAV_BASE ]; then echo "ERROR: webdav dir ($DIR_WEBDAV_BASE) is not accessible."; exit 1; fi
+}
+
+function do_webdav_catalog() {
+ local teacher
+ local rc=0
+ check_webdav_mounted
+ for teacher in $(get_teachers); do
+ tell_if_verbose " - $teacher"
+ local lang=$(get_lang $teacher)
+ mkdir -p $DIR_WEBDAV_CATALOGS
+ for f in $(echo $DIR_BLANKS/Catalog-$teacher-$lang.pdf); do
+ check_file_exists $f 1
+ if [ $? -eq 0 ]; then
+ cp $f $DIR_WEBDAV_CATALOGS/
+ else
+ echo "ERROR: [$teacher] "$f" not found. Did you run 'blank' action?"
+ ((rc++))
+ break;
+ fi
+ done
+ done
+ return $rc
+}
+
+function do_webdav_sample() {
+ local teacher
+ local rc=0
+ check_webdav_mounted
+ for teacher in $(get_teachers); do
+ tell_if_verbose " - $teacher"
+ local lang=$(get_lang $teacher)
+ mkdir -p $DIR_WEBDAV_SAMPLES
+ for f in $(echo $DIR_SAMPLES/Sample-$teacher-$lang.pdf); do
+ check_file_exists $f 1
+ if [ $? -eq 0 ]; then
+ cp $f $DIR_WEBDAV_SAMPLES/
+ else
+ echo "ERROR: [$teacher] "$f" not found. Did you run 'sample' action?"
+ ((rc++))
+ break;
+ fi
+ done
+ done
+ return $rc
+}
+
+function do_webdav_pdf() {
+ local teacher
+ local rc=0
+ check_webdav_mounted
+ for teacher in $(get_teachers); do
+ tell_if_verbose " - $teacher"
+ local lang=$(get_lang $teacher)
+ mkdir -p $DIR_WEBDAV_EXAMS
+ for f in $(echo $DIR_PDF/Exam-$teacher-$lang.log $DIR_PDF/Exam-$teacher-$lang.pdf $DIR_PDF/Exam-$teacher-$lang-CORRECTION.pdf); do
+ check_file_exists $f 1
+ if [ $? -eq 0 ]; then
+ cp $f $DIR_WEBDAV_EXAMS/
+ else
+ echo "ERROR: [$teacher] "$f" not found. Did you run 'list' action?"
+ ((rc++))
+ break;
+ fi
+ done
+ done
+ return $rc
+}
+
+function do_webdav_list() {
+ local teacher
+ local rc=0
+ check_webdav_mounted
+ for teacher in $(get_teachers); do
+ tell_if_verbose " - $teacher"
+ local lang=$(get_lang $teacher)
+ mkdir -p $DIR_WEBDAV_LISTS
+ for f in $(echo $DIR_LIST_OUT/List-$teacher.pdf $DIR_LIST_OUT/List-$teacher.csv); do
+ check_file_exists $f 1
+ if [ $? -eq 0 ]; then
+ cp $f $DIR_WEBDAV_LISTS/
+ else
+ echo "ERROR: [$teacher] "$f" not found. Did you run 'list' action?"
+ ((rc++))
+ break;
+ fi
+ done
+ done
+ return $rc
+}
+
+function do_clean() {
+ local teacher
+ local ask=$(get_param_value 'ask')
+ for teacher in $(get_teachers); do
+ tell_if_verbose " - $teacher"
+ lang=$(get_lang $teacher)
+ if [ "$ask" != "no" ]; then
+ echo "Cleaning all generated data for teacher '$teacher' ? CTRL-C to abort."
+ echo "(Use '--params ask=no' to force)"
+ read a
+ fi
+ # Projects
+ rm -rf $DIR_EXAMS/$teacher
+ # Blanks
+ rm -f $DIR_BLANKS/Blank-$teacher-$lang.pdf $DIR_BLANKS/Correction-Blank-$teacher-$lang.pdf $DIR_BLANKS/Catalog-$teacher-$lang.pdf
+ # Samples
+ rm -f $DIR_SAMPLES/Sample-$teacher-$lang.pdf
+ # PDF
+ rm -f $DIR_PDF/Exam-$teacher-$lang.log $DIR_PDF/Exam-$teacher-$lang.pdf $DIR_PDF/Exam-$teacher-$lang-CORRECTION.pdf
+ # Lists
+ rm -f $DIR_LIST_OUT/List-$teacher.pdf $DIR_LIST_OUT/List-$teacher.csv
+ done
+ for dir_to_clean in $(echo "$DIR_EXAMS $DIR_BLANKS $DIR_SAMPLES $DIR_PDF $DIR_LIST_OUT"); do if [ -d $dir_to_clean ]; then rmdir --ignore-fail-on-non-empty $dir_to_clean; fi; done
+}
diff --git a/lib/bash/io.sh b/lib/bash/io.sh
new file mode 100644
index 0000000..6556579
--- /dev/null
+++ b/lib/bash/io.sh
@@ -0,0 +1,70 @@
+#!/bin/bash
+
+function color_echo() {
+ echo -e "$@"
+}
+
+function color_echo_n() {
+ echo -en "$@"
+}
+
+function debug_echo() {
+ color_echo "${White}DEBUG:${NoColor} $@"
+}
+
+function warning_echo() {
+ color_echo "${Yellow}WARING:${NoColor} $@" 1>&2
+}
+
+function error_echo() {
+ color_echo "${Red}ERROR:${NoColor} $@" 1>&2
+}
+
+function info_echo() {
+ color_echo "${Green}INFO:${NoColor} $@"
+}
+
+function OK_echo() {
+ color_echo "${Green}OK${NoColor}"
+}
+
+function KO_echo() {
+ color_echo "${Red}KO${NoColor}"
+}
+
+function check_rc_echo {
+ if [ $1 -eq 0 ]; then OK_echo; return 0; else KO_echo; return 1; fi
+}
+
+function function_exists() {
+ function_to_test=$1
+ function_exists=$(type "$function_to_test" 2>&1 | grep -c "$function_to_test"' is a function');
+ if [ $function_exists -eq 1 ]; then return 1; else return 0; fi
+}
+
+function confirm() {
+ function_exists get_parameter
+ if [ $? -eq 1 ]; then
+ forced=$(get_parameter 'force');
+ if [ "$forced" == "set" ]; then
+ return 1
+ fi
+ fi
+ color_echo "${White}Confirmation requested:${NoColor}"
+ color_echo "$@"
+ color_echo_n "Press ${Green}Y${NoColor}(es) to confirm or ${Red}CTRL-z${NoColor} to cancel. >>> "
+ local ans
+ read ans
+ case $ans in
+ Y|y|Yes|YES|yes)
+ color_echo "${Green}Confirmed.${NoColor}"
+ return 1
+ break;;
+ *)
+ color_echo "${Red}Cancelled.${NoColor}"
+ return 0
+ break;;
+ esac
+}
+
+# EOF
diff --git a/local/epfl/cache/readme b/local/epfl/cache/readme
new file mode 100644
index 0000000..a4a2b14
--- /dev/null
+++ b/local/epfl/cache/readme
@@ -0,0 +1,2 @@
+Here is stored the cached data.
+Feel free to clean-up this directory from time to time.
diff --git a/local/epfl/check_list b/local/epfl/check_list
new file mode 100755
index 0000000..d5b50ad
--- /dev/null
+++ b/local/epfl/check_list
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+function USAGE() {
+ echo "USAGE: $0 csv_list.csv";
+ exit 1
+}
+
+SOURCE=$1
+if [ ! -r $SOURCE ]; then echo "File not found: $SOURCE" >2; USAGE; fi
+
+function fetch_data() {
+ local sciper=$1
+ local cache_file="./cache/${sciper}.txt"
+ if [ ! -f $cache_file ]; then
+ ./search-epfl -s $sciper > $cache_file
+ DATA=$(./search-epfl -s $sciper | tr '\n' '~');
+ fi
+ DATA=$(cat $cache_file | tr '\n' '~')
+}
+
+function get_field() {
+ local field=$1
+ echo $DATA | tr '~' '\n' | grep "^$field:" | cut -d ':' -f 2-
+}
+
+IFS=$'\n'
+id=0
+for l in $(cat $1); do
+ ((id++))
+ sciper=$(echo $l | cut -d ',' -f 1)
+ rest=$(echo $l | cut -d ',' -f 2-)
+ fetch_data $sciper
+ section=$(get_field 'section')
+ name=$(get_field 'cname_usual')
+ status="OK";
+ if [ -z "$section" ] ; then section=$(echo $l | cut -d ',' -f 3); status="NOT FOUND"; fi
+ if [ -z "$name" ] ; then name=$(echo $l | cut -d ',' -f 2); fi
+ echo "$id,$sciper,$name,$section,$status,$rest"
+done
diff --git a/local/epfl/lib/people_search.php b/local/epfl/lib/people_search.php
new file mode 100755
index 0000000..25d10a1
--- /dev/null
+++ b/local/epfl/lib/people_search.php
@@ -0,0 +1,147 @@
+Http = new HttpRequest();
+ $this->base_url = 'http://search.epfl.ch/psearch.action';
+ $this->base_params = array( 'request_locale' => 'en',
+ 'q' => '',
+ 'origin' => 'header' );
+ $headers = Array( 'Accept-Charset' => 'UTF-8' );
+ $this->Http->setHeaders($headers);
+
+ }
+
+ public function SetLocale($locale) {
+ if ($locale == 'fr' or $locale == 'en' ) {
+ $this->base_params['request_locale'] = $locale;
+ }
+ }
+
+ protected function SetStartUrl() {
+ $url = $this->base_url.'?'.http_build_str($this->base_params);
+ #echo $url."\n";
+ $this->Http->setUrl($url);
+ $this->results = null;
+ $this->body = null;
+ }
+
+ public function GetBody() {
+ return $this->body;
+ }
+
+ public function FindBySciper($sciper) {
+ if (! is_numeric($sciper)) throw new Exception('Provided SCIPER is not numeric.');
+ $this->base_params['q'] = $sciper;
+ $this->SetStartUrl();
+ $this->DoSearch();
+ return $this->results;
+ }
+
+ public function FindByName($name) {
+ $this->base_params['q'] = $name;
+ $this->SetStartUrl();
+ $this->DoSearch();
+ return $this->results;
+ }
+
+ protected function DoSearch() {
+ $final_url = null;
+ $redirected = false;
+ do {
+ $response = $this->Http->send();
+ if ($response->getResponseCode() != 301 && $response->getResponseCode() != 302) break;
+ $final_url = $response->getHeader("Location");
+ #echo $final_url."\n";
+ $redirected = true;
+ $this->Http->setUrl($final_url);
+ } while (1);
+ $this->body = $this->Http->getResponseBody();
+ $this->results = array('found' => (int)$redirected);
+
+ if ($this->results['found']) {
+
+ # Email
+ $matches = array();
+ preg_match("/msgto\('(.*)','(.*)'\)/", $this->body, $matches );
+ if (! empty($matches)) {
+ $this->results['email'] = $matches[1].'@'.$matches[2];
+ $matches = explode('.', $matches[1]);
+ $this->results['fname_email'] = $matches[0];
+ $this->results['lname_email'] = $matches[1];
+ } else {
+ $this->results['email'] = '';
+ $this->results['fname_email'] = '';
+ $this->results['lname_email'] = '';
+ }
+
+ # Full names
+ $matches = array();
+ preg_match("~\
(.*)\
~", $this->body, $matches );
+ $matches = explode(' ', $matches[1]);
+ $this->results['fname_full'] = $matches[0];
+ $this->results['lname_full'] = $matches[1];
+ if (empty($this->results['email'])) {
+ $this->results['fname_email'] = $this->results['fname_full'];
+ $this->results['lname_email'] = $this->results['lname_full'];
+ }
+
+ # Compute usual names
+ if (! empty($this->results['email'])) {
+ $fnames = explode(' ', $this->results['fname_full']);
+ $i = 0;
+ $this->results['fname_usual'] = '';
+ while (strlen($this->results['fname_usual']) < strlen($this->results['fname_email']) and (array_key_exists($i, $fnames))) {
+ if ($i > 0) $this->results['fname_usual'] .= ' ';
+ $this->results['fname_usual'] .= $fnames[$i];
+ $i++;
+ }
+ $lnames = explode(' ', $this->results['lname_full']);
+ $i = 0;
+ $this->results['lname_usual'] = '';
+ while (strlen($this->results['lname_usual']) < strlen($this->results['lname_email']) and (array_key_exists($i, $lnames))) {
+ if ($i > 0) $this->results['lname_usual'] .= ' ';
+ $this->results['lname_usual'] .= $lnames[$i];
+ $i++;
+ }
+ } else {
+ $this->results['fname_usual'] = $this->results['fname_full'];
+ $this->results['lname_usual'] = $this->results['lname_full'];
+ }
+
+ # Compute complete name
+ $this->results['cname_full'] = $this->results['fname_full'].' '.$this->results['lname_full'];
+ $this->results['cname_usual'] = $this->results['fname_usual'].' '.$this->results['lname_usual'];
+
+ # Compute complete reversed
+ $this->results['cname_full_reversed'] = $this->results['lname_full'].' '.$this->results['fname_full'];
+ $this->results['cname_usual_reversed'] = $this->results['lname_usual'].' '.$this->results['fname_usual'];
+
+ # Sciper
+ preg_match("/vCard\?id=([0-9]+)&/", $this->body, $matches);
+ $this->results['SCIPER'] = $matches[1];
+ # Section
+ preg_match('/a title="(.*)" href=.*>(.*)', $this->body, $matches);
+ $this->results['section_description'] = $matches[1];
+ $this->results['section_acronym_full'] = $matches[2];
+ $matches = explode('-', $matches[2]);
+ $this->results['section'] = $matches[0];
+ if (array_key_exists(1, $matches)) {
+ $this->results['section_extended'] = $matches[1];
+ }
+ }
+ }
+}
+
+class BashWriter {
+ public function __construct($array = null) { if (!is_null($array)) $this->format($array); }
+ public function format($array) { if (is_array($array)) foreach($array as $key => $value) echo "$key:$value\n"; }
+}
+
+?>
diff --git a/local/epfl/liste.csv b/local/epfl/liste.csv
new file mode 100644
index 0000000..64b308d
--- /dev/null
+++ b/local/epfl/liste.csv
@@ -0,0 +1,219 @@
+247893,Abboud Robert,GC
+240822,Albanese Leopoldo Costantino Ercole,GC
+235706,Albutra Nasseem,GC
+250646,Ansermet Robin Brian,GC
+247777,Azélart Henri Bernard Vincent,GC
+237979,Badarani Louise Simone Flore,GC
+249696,Badoux Sophia Claire,GC
+246138,Bahchouch Hamza,GC
+245913,Barre Pierre Stéphane Denis,GC
+236760,Baudry Inès Marie Anne Véronique,GC
+238346,Bellamlih Mamou Ali,GC
+236362,Bembarek Mohammed-Amine,GC
+234779,Bensaid Younes,GC
+224796,Benzakour Hamza,GC
+249902,Berquand Cecile Anne,GC
+247180,Bhouri Adem,GC
+237985,Borremans Noé,GC
+247453,Bösch Felix Samuel,GC
+249807,Brault Victoire Thérèse Marie,GC
+240894,Brunner Kim Tan Quang,GC
+237074,Buqaj Gentian,GC
+247815,Burckhardt Valérie Claudia,GC
+233683,Casanova Michela,GC
+246679,Cassina Lisa,GC
+246210,Charif Linah,GC
+246830,Couturier Alexia,GC
+237591,Curis Gabriel François Marie Laurent,GC
+250002,D'Annunzio Lorn Douglas,GC
+228280,De Franceschini Cesare,GC
+247244,Decoppet Jean-Baptiste Luc Hubert,GC
+236097,Delannoy Louis Marc Bruno,GC
+251757,Delfino Arnaud,GC
+237169,Delvaille Guillaume André,GC
+246687,Dépraz Mathieu,GC
+246077,Descombes Albane Bénédicte,GC
+237110,Dorrio Alves Gonzalo Paulo Enrique,GC
+250747,Dorthe Titouan,GC
+240604,Drouin Thomas Pierre Jacques,GC
+239398,Durussel Shad Ali,GC
+236135,El Kafil Youssef,GC
+246556,El Ouazzani Touhami Yassine,GC
+250790,Favre Simon,GC
+237126,Ferrari Paolo Angelo,GC
+247221,Fink Arthur Guillaume,GC
+239433,Fiocconi Clara Sylvie Bernadette Gisèle,GC
+247082,Florez Diego,GC
+247749,Fontugne Augustin Jean Hugo,GC
+237132,Fraigedo Marco,GC
+240605,Framery Luis-Angel Charles,GC
+247867,Freiburghaus Sylvain,GC
+236718,Fuchs Michael Hubert,GC
+247483,Fuselier Héloïse Manon,GC
+233834,Gandy Lucas Christophe Mathieu,GC
+235013,Gasparovic Sergej,GC
+239503,Gautier Louis Pierre Robert,GC
+231100,Gillet Maxime Loris,GC
+236407,Giovanola Vincent,GC
+246021,Glatz Tobias Bruno,GC
+249835,Gratier De Saint-Louis Romain Essy Théo,GC
+234738,Gros Julie Noémie Marie,GC
+247030,Haan Julien Johan,GC
+203117,Haddadi Sasan,GC
+234847,Halleux Arthur,GC
+250669,Hatem Nicolas Fernand Joseph,GC
+239440,Herbin Océane Elisa Victoire Ildegarde,GC
+244982,Heredia Rosa Diego Isidoro,GC
+246698,Hourse Laetitia Marie Frédérique,GC
+250855,Hoxha Hamid,GC
+250446,Imperatori Lisa,GC
+250895,Jaber Mehdi,GC
+236654,Jacot-Descombes Fabien Jérôme,GC
+238028,Jamet Gregoire Aurelien Marie,GC
+239626,Joseph Quentin,GC
+242532,Keller Paul Alexandre Mario,GC
+235333,Kitane Youssef,GC
+227442,Kohler David,GC
+247288,Labrosse Lucas,GC
+246884,Lafaye Chloé Gisèle Christiane,GC
+249909,Lafkihi Youssef,GC
+235210,Lamrani Alaoui Othmane,GC
+237830,Lao Kevin Okynawa,GC
+239394,Lemghari Salma,GC
+247803,Liechti Simon Jean Marie,GC
+236727,Mariller Nicolas Loic Dorian,GC
+241602,Matthews Salmon Scott David,GC
+246713,Matthey Valériane,GC
+244962,Maye Robin Daniel Etienne,GC
+250935,Mermod Paul Victor Augustin,GC
+241337,Meroni Gabriele Leandro,GC
+246972,Michel Stanislas Hugo Werner,GC
+234759,Minault Edgar Paul,GC
+235570,Mock Pascal Jérémie,GC
+247210,Mockers Antoine Valentin,GC
+234500,Mohr Mathieu,GC
+247494,Monetta Fiona,GC
+240666,Monneron Armand Quy Kim,GC
+246619,Morim Dany,GC
+247798,Mosena Dan,GC
+250956,Mudry Grégoire Samuel,GC
+246907,Némethy Nicolas Karol Doctoré,GC
+245814,Nicolet Adrien,GC
+247797,Olsen Nils Frédérik Heiden,GC
+250613,Oswald Zora,GC
+250587,Padovani Christopher Anthony Marius,GC
+238531,Perard Arthur Stéphane,GC
+246251,Perruchoud Valentin,GC
+245360,Pfister Mélanie,GC
+233770,Ponce De Leon Bezerra Amanda Cristina,GC
+250632,Pouzere Marie-Lys Ella Dawa,GC
+236070,Prébandier Christophe,GC
+246979,Pugin Madeline,GC
+238323,Rahmaty Abdu,GC
+240671,Ramalhoto Penela Joel António,GC
+225733,Ramusat Romain Patrick Stéphane,GC
+229194,Reyes Samuel Emmanuel,GC
+235832,Richard Nicolas Paul,GC
+228105,Sahibi Ahmed Wassim,GC
+247669,Saint-Supéry Santiago,GC
+250866,Salvadé Nicolas Jean,GC
+247792,Schneider Romain,GC
+223539,Schöpfer Olivier Henri,GC
+251192,Schucany Hervé Robert Eugène,GC
+237729,Schürch Ursula Esther,GC
+235750,Senglet Côme Roland Gérard,GC
+247400,Senser Loic,GC
+244989,Sessa Ludovica,GC
+245842,Siganos Ioannis,GC
+236194,Silva Brites Loris,GC
+246529,Storz Jordi,GC
+247911,Stuber Thomas,GC
+249856,Tedeschi Julien Brian,GC
+236330,Thillaye Du Boullay Cyril Marie Régis,GC
+247038,Valentin Axel Olivier,GC
+234512,Velasquez Emmanuel Pablo,GC
+235259,Vermot Maeva Tran Tien Lac,GC
+250392,Vernay François,GC
+245828,Vernet Arthur Maximilien,GC
+235606,Viviant Jean Lou Marin,GC
+250429,Vuilleumier Violaine,GC
+234761,Walid Mahmoud Helmy Nadeem Mohamed,GC
+235072,Weibel Amine,GC
+238719,Woehner Monia,GC
+228073,Woerle Nicolas Pierre Antoine,GC
+201838,Wojnarowicz Matthias,GC
+238920,Yassafi Amine-Reda,GC
+224808,Adel Sonia,SIE
+237657,Alaoui Hiba,SIE
+240569,Andreetti Alice,SIE
+234661,Arnaudon Lucie,SIE
+250155,Barbe Emile,SIE
+246453,Barbey Léa,SIE
+236496,Bergqvist Miriam Christine,SIE
+250158,Bernegger Arline Andrea,SIE
+250302,Berteaux Adrien Pierre-Alain Boris,SIE
+236741,Bielser Florian Adrien,SIE
+250598,Bissel Laura Emilie,SIE
+238271,Bodmer Timon,SIE
+235142,Bonzi Patrick,SIE
+250252,Bouche Léopold Antoine Joanny,SIE
+250264,Bouvresse Déborah Michèle Albertine,SIE
+236472,Brand Florian,SIE
+247108,Bretton Aude Marie Dorothée,SIE
+236296,Brouillet Constance,SIE
+250689,Brunner Lena,SIE
+238204,Buchwalder Xavier Louis Maurice,SIE
+247605,Burg David Joël,SIE
+247225,Caby Theodore Leopold Marie,SIE
+236711,Camplani Nathalie Teresina,SIE
+235221,Chalak Chirine,SIE
+247814,Chappelier Coralie,SIE
+246680,Chatelan Elsa Jöelle,SIE
+236714,Collins Fiona Claire,SIE
+247326,Décosterd Bertil,SIE
+239479,Delessert Pauline Charlotte,SIE
+250337,Ellero Alicia Mélanie,SIE
+250142,Fetzer Aline Sophie,SIE
+247021,Franziskakis Johann,SIE
+247727,Girardet Lina Elodie Klara,SIE
+237757,Hariki Maryam,SIE
+239763,Ismaïl Ayoub,SIE
+245773,Joris Guillaume Philippe Benoit,SIE
+247918,Jouberton Achille Pierre,SIE
+247232,Jullien Nicolas,SIE
+247701,Keller Cloé,SIE
+213505,Klein Louis Xavier,SIE
+238503,Kowalski Coralie Jazz,SIE
+249748,Krey Vassiliki,SIE
+234856,Krzyzostaniak Théo Marek,SIE
+249920,Labenets Elena,SIE
+235335,Laroui Yassine Klaus,SIE
+250546,Locatelli Maxence Valentin,SIE
+249236,Lugrin René,SIE
+250915,Maeder Ivan,SIE
+249690,Manetti Nicolas,SIE
+246712,Matthey-Junod Anaïs,SIE
+252025,Meskaldji Amir Sami,SIE
+236271,Morandini Léonard Elliot,SIE
+248153,Neuenschwander Philipp,SIE
+240888,Nyffeler Cécile,SIE
+249546,Obrecht Jolan César,SIE
+250612,Ott Lucas,SIE
+247430,Pellaton Louise Diane,SIE
+250967,Perez Loïc,SIE
+247863,Perna Michael,SIE
+236801,Perret Antoine,SIE
+250607,Petit Maximilien Jean André,SIE
+239467,Quilici Andrea Vittorio,SIE
+223888,Rabbath Laura,SIE
+235752,Reymond-Joubin Maric Yannick Xavier,SIE
+237732,Romero Grass Maëlle,SIE
+248127,Sanchez Del Rio Kandel David,SIE
+251722,Tahiri Reda,SIE
+250383,Tournefier Alan Lucas,SIE
+235649,Vallotton Amandine,SIE
+245912,Vaucher Nadège,SIE
+246644,Viennot-Bourgin Pauline Marie,SIE
+247563,Vogel Mégane,SIE
+249614,Vogel Michael,SIE
+234516,Vruggink Marc,SIE
diff --git a/local/epfl/search-epfl b/local/epfl/search-epfl
new file mode 100755
index 0000000..72bf5b0
--- /dev/null
+++ b/local/epfl/search-epfl
@@ -0,0 +1,46 @@
+#!/usr/bin/env php
+SetLocale($lang);
+}
+
+$details = null;
+
+if (!empty($options['s']) or !empty($options['sciper'])) {
+ $sciper = empty($options['s'])? $options['sciper'] : $options['s'] ;
+ $details = $PeopleSearch->FindBySciper($sciper);
+} elseif (!empty($options['n']) or !empty($options['name'])) {
+ $name = empty($options['n'])? $options['name'] : $options['n'] ;
+ $details = $PeopleSearch->FindByName($name);
+}
+
+# Print results
+new BashWriter($details);
+
+?>