From e29990a29adaf52f9b532adc0f65f1b08deb5395 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Wed, 12 Oct 2016 00:18:36 +0200 Subject: [PATCH] Display main page with all students and all projects --- README.md | 70 +++++++++++++++++++++++++++++ main.go | 112 ++++++++++++++++++++++++++++++++++++++++++++++ nginx-sample.conf | 17 +++++++ 3 files changed, 199 insertions(+) create mode 100644 main.go create mode 100644 nginx-sample.conf diff --git a/README.md b/README.md index e69de29..fe69605 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,70 @@ +Shemu +===== + +When harvest comes, Shemu tells you if you have correctly submitted your work. + +Shemu is a HTML interface looking for an extracted tarball for each students in +each projects. + + +Compilation +----------- + +This program is written in Go. It has no dependancy, you can compile it with +the following command: + +```shell +go build +``` + + +Projects +-------- + +Shemu takes as argument a path to a directory containing projects. + +The tree exptected looks like: + + rendu/ + TP1/ + login_x/ + ... + mercie_d/ + ... + TP2/ + login_x/ + ... + mercie_d/ + ... + +In the above tree, 2 projects are defined: named `TP1` and `TP2`, from their +directory name. + + +Students +-------- + +A file with students list is expected, in CSV format. + +A line looks like: + +```csv +Mercier,Pierre-Olivier,mercie_d,64170,github@nemunai.re,0123456789 +``` + +The third column is used has student identifier. It is this identifier that is +search in each project directory. + + +Usage +----- + +```shell +./shemu -students ~/projects/students.csv -path ~/projects/ +``` + +Another convinient way to use it is to launch and forget: + +```shell +nohup ./shemu -bind :8079 -title "Submissions title" -students ~/projects/students.csv -path ~/projects/ & +``` diff --git a/main.go b/main.go new file mode 100644 index 0000000..8584007 --- /dev/null +++ b/main.go @@ -0,0 +1,112 @@ +package main + +import ( + "bufio" + "encoding/csv" + "flag" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "path" + "path/filepath" + "time" +) + +var students [][]string +var rendusDir string +var title string + +func Serve(w http.ResponseWriter, r *http.Request) { + log.Printf("Handling %s request from %s: %s [%s]\n", r.Method, r.RemoteAddr, r.URL.Path, r.UserAgent()) + + w.Header().Set("Content-Type", "text/html") + + w.Write([]byte(` + + + ` + title + ` + + + + +

` + title + `

+ `)) + if dirs, err := ioutil.ReadDir(rendusDir); err == nil { + w.Write([]byte(``)) + for _, dir := range dirs { + if dir.IsDir() { + w.Write([]byte(``)) + } + } + w.Write([]byte(``)) + for _, student := range students { + login := student[2] + w.Write([]byte(``)) + for _, dir := range dirs { + if dir.IsDir() { + if fi, err := os.Stat(path.Join(rendusDir, dir.Name(), login)); err == nil { + w.Write([]byte(``)) + } else { + w.Write([]byte(``)) + } + } + } + w.Write([]byte(``)) + } + } + w.Write([]byte(`
` + dir.Name() + `
` + login + `` + fi.ModTime().Format(time.UnixDate) + `
+ + +`)) +} + +func main() { + var studentsFile string + + var bind = flag.String("bind", "0.0.0.0:8081", "Bind port/socket") + flag.StringVar(&studentsFile, "students", "./students.csv", "Path to a CSV file containing in the third column the name of the directory to look for") + flag.StringVar(&rendusDir, "path", "./rendu/", "Path to the submissions directory (each subdirectory is a project)") + flag.StringVar(&title, "title", "Rendus VIRLI", "Title of the page") + flag.Parse() + + var err error + + if rendusDir, err = filepath.Abs(rendusDir); err != nil { + log.Fatal(err) + } else if dirs, err := ioutil.ReadDir(rendusDir); err != nil { + log.Fatal(err) + } else { + var projects []string + nbProj := 0 + for _, dir := range dirs { + if dir.IsDir() { + projects = append(projects, dir.Name()) + nbProj++ + } + } + log.Println(nbProj, "projects found:", projects) + } + + // Read and parse students list + log.Println("Reading students files...") + if studentsFile, err = filepath.Abs(studentsFile); err != nil { + log.Fatal(err) + } else if fi, err := os.Open(studentsFile); err != nil { + log.Fatal(err) + } else { + r := csv.NewReader(bufio.NewReader(fi)) + if students, err = r.ReadAll(); err != nil { + log.Fatal(err) + } + } + log.Println(len(students), "students loaded.") + + log.Println("Registering handlers...") + http.HandleFunc("/", Serve) + log.Println(fmt.Sprintf("Ready, listening on %s", *bind)) + if err := http.ListenAndServe(*bind, nil); err != nil { + log.Fatal("Unable to listen and serve: ", err) + } +} diff --git a/nginx-sample.conf b/nginx-sample.conf new file mode 100644 index 0000000..0beff3e --- /dev/null +++ b/nginx-sample.conf @@ -0,0 +1,17 @@ +proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=STATIC:10m inactive=24h max_size=1g; + +server { + listen 80; + listen [::]:80; + + location / + { + proxy_pass http://localhost:8080; + proxy_set_header X-Real-IP $remote_addr; + proxy_cache STATIC; + proxy_cache_valid 200 10s; + proxy_redirect off; + proxy_cache_use_stale error timeout invalid_header updating + http_500 http_502 http_503 http_504; + } +}