<commit_before>package beachplug

import (
	"fmt"
	"html/template"
	"log"
	"net/http"
	"net/url"
	"sort"
	"time"
	"unicode/utf8"

	"github.com/vincent-petithory/kraken/fileserver"
)

// Server defines the beachplug server constructor.
var Server fileserver.Constructor = func(root string, params fileserver.Params) fileserver.Server {
	return &server{
		root: root,
		fs:   http.Dir(root),
	}
}

type server struct {
	fs   http.FileSystem
	root string
}

func (s server) Root() string {
	return s.root
}

func (s server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path[0] != '/' {
		r.URL.Path = "/" + r.URL.Path
	}
	f, err := s.fs.Open(r.URL.Path)
	if err != nil {
		http.NotFound(w, r)
		return
	}
	defer f.Close()

	fi, err := f.Stat()
	if err != nil {
		http.NotFound(w, r)
		return
	}

	if !fi.IsDir() {
		http.ServeContent(w, r, fi.Name(), fi.ModTime(), f)
		return
	}
	if r.URL.Path[len(r.URL.Path)-1] != '/' {
		w.Header().Set("Location", r.URL.Path+"/")
		w.WriteHeader(http.StatusMovedPermanently)
		return
	}

	// Dir listing
	ctx := tplCtx{
		Root:        r.URL.Path,
		Style:       css,
		Files:       make(filelist, 0),
		Directories: make(dirlist, 0),
	}
	for {
		fis, err := f.Readdir(100)
		if err == io.EOF {
			break
		}
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		for _, fi := range fis {
			if fi.IsDir() {
				ctx.Directories = append(ctx.Directories, fi.Name())
			} else {
				f := file{fi.Name(), fi.Size(), fi.ModTime().Truncate(time.Second)}
				ctx.Files = append(ctx.Files, f)
			}
		}
	}
	if r.URL.Path != "/" {
		ctx.Directories = append(dirlist{".."}, ctx.Directories...)
	}
	ctx.NumFiles = len(ctx.Files)
	ctx.NumDirectories = len(ctx.Directories)

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.WriteHeader(http.StatusOK)
	if err := tpl.Execute(w, ctx); err != nil {
		log.Print(err)
	}
}

type tplCtx struct {
	Root           string
	Style          template.CSS
	Files          filelist
	Directories    dirlist
	NumFiles       int
	NumDirectories int
}

type file struct {
	Name    string
	Size    int64
	ModTime time.Time
}

type dirlist []string

func (l dirlist) Less(i int, j int) bool {
	return l[i] < l[j]
}
func (l dirlist) Swap(i int, j int) { l[i], l[j] = l[j], l[i] }
func (l dirlist) Len() int          { return len(l) }

type filelist []file

func (l filelist) Less(i int, j int) bool {
	if l[i].Name == l[j].Name {
		return l[i].ModTime.Before(l[j].ModTime)
	}
	return l[i].Name < l[j].Name
}
func (l filelist) Swap(i int, j int) { l[i], l[j] = l[j], l[i] }
func (l filelist) Len() int          { return len(l) }

const (
	kib = 1024
	mib = 1024 * 1024
	gib = 1024 * 1024 * 1024
)

var fm = map[string]interface{}{
	"urlpath": func(path string) string {
		return (&url.URL{Path: path}).String()
	},
	"sorted": func(v interface{}) interface{} {
		if sv, ok := v.(sort.Interface); ok {
			sort.Sort(sv)<commit_msg>beachplug: sort files/dirs case insensitive<commit_after>package beachplug

import (
	"fmt"
	"html/template"
	"io"
	"log"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/vincent-petithory/kraken/fileserver"
)

// Server defines the beachplug server constructor.
var Server fileserver.Constructor = func(root string, params fileserver.Params) fileserver.Server {
	return &server{
		root: root,
		fs:   http.Dir(root),
	}
}

type server struct {
	fs   http.FileSystem
	root string
}

func (s server) Root() string {
	return s.root
}

func (s server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path[0] != '/' {
		r.URL.Path = "/" + r.URL.Path
	}
	f, err := s.fs.Open(r.URL.Path)
	if err != nil {
		http.NotFound(w, r)
		return
	}
	defer f.Close()

	fi, err := f.Stat()
	if err != nil {
		http.NotFound(w, r)
		return
	}

	if !fi.IsDir() {
		http.ServeContent(w, r, fi.Name(), fi.ModTime(), f)
		return
	}
	if r.URL.Path[len(r.URL.Path)-1] != '/' {
		w.Header().Set("Location", r.URL.Path+"/")
		w.WriteHeader(http.StatusMovedPermanently)
		return
	}

	// Dir listing
	ctx := tplCtx{
		Root:        r.URL.Path,
		Style:       css,
		Files:       make(filelist, 0),
		Directories: make(dirlist, 0),
	}
	for {
		fis, err := f.Readdir(100)
		if err == io.EOF {
			break
		}
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		for _, fi := range fis {
			if fi.IsDir() {
				ctx.Directories = append(ctx.Directories, fi.Name())
			} else {
				f := file{fi.Name(), fi.Size(), fi.ModTime().Truncate(time.Second)}
				ctx.Files = append(ctx.Files, f)
			}
		}
	}
	if r.URL.Path != "/" {
		ctx.Directories = append(dirlist{".."}, ctx.Directories...)
	}
	ctx.NumFiles = len(ctx.Files)
	ctx.NumDirectories = len(ctx.Directories)

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.WriteHeader(http.StatusOK)
	if err := tpl.Execute(w, ctx); err != nil {
		log.Print(err)
	}
}

type tplCtx struct {
	Root           string
	Style          template.CSS
	Files          filelist
	Directories    dirlist
	NumFiles       int
	NumDirectories int
}

type file struct {
	Name    string
	Size    int64
	ModTime time.Time
}

type dirlist []string

func (l dirlist) Less(i int, j int) bool {
	return strings.ToLower(l[i]) < strings.ToLower(l[j])
}
func (l dirlist) Swap(i int, j int) { l[i], l[j] = l[j], l[i] }
func (l dirlist) Len() int          { return len(l) }

type filelist []file

func (l filelist) Less(i int, j int) bool {
	if l[i].Name == l[j].Name {
		return l[i].ModTime.Before(l[j].ModTime)
	}
	return strings.ToLower(l[i].Name) < strings.ToLower(l[j].Name)
}
func (l filelist) Swap(i int, j int) { l[i], l[j] = l[j], l[i] }
func (l filelist) Len() int          { return len(l) }

const (
	kib = 1024
	mib = 1024 * 1024
	gib = 1024 * 1024 * 1024
)

var fm = map[string]interface{}{
	"urlpath": func(path string) string {
		return (&url.URL{Path: path}).String()
	},
	"sorted": func(v interface{}) interface{} {
		if sv, ok := v.(sort.Interface); ok {
			sort.Sort(sv)
{'fraction_non_alphanumeric': 0.17107671825594942, 'fraction_numerical': 0.011649192877350641, 'mean_word_length': 3.0013315579227697, 'pattern_counts': {'":': 4, '<': 7, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2205196', 'n_tokens_mistral': 2580, 'n_tokens_neox': 2411, 'n_words': 565}