Skip to content

Commit

Permalink
Merge pull request #3 from takenX10/responsive-addition
Browse files Browse the repository at this point in the history
added automatic embed and minify of style.css and made website respon…
  • Loading branch information
lucat1 authored May 21, 2022
2 parents 792692f + a4fc229 commit 24d2ce7
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 89 deletions.
5 changes: 5 additions & 0 deletions footer.gohtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
</div>
<hr>
<p>Generated by <a href="https://github.com/lucat1/statik">statik</a> on {{ .Date.Format "02 Jan 06 15:04 MST" }}</p>
</body>
</html>
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
module github.com/lucat1/statik

go 1.17

require (
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/tdewolff/minify/v2 v2.11.2 // indirect
github.com/tdewolff/parse/v2 v2.5.29 // indirect
)
13 changes: 13 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fsnotify/fsnotify v1.5.3/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/tdewolff/minify/v2 v2.11.2 h1:PpaPWhNlMVjkAKaOj0bbPv6KCVnrm8jbVwG7OtSdAqw=
github.com/tdewolff/minify/v2 v2.11.2/go.mod h1:NxozhBtgUVypPLzQdV96wkIu9J9vAiVmBcKhfC2zMfg=
github.com/tdewolff/parse/v2 v2.5.29 h1:Uf0OtZL9YaUXTuHEOitdo9lD90P0XTwCjZi+KbGChuM=
github.com/tdewolff/parse/v2 v2.5.29/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
14 changes: 14 additions & 0 deletions header.gohtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang='en'>
<head>
<meta name='viewport' content='width=device-width'>
<style>{{ .Stylesheet }}</style>
<title>Index of {{ .FullPath }}</title>
</head>
<body>
<h1>
Index of
{{ if not (eq (len .Root.Name) 0) }}/<a href={{ .Root.URL }}>{{ .Root.Name }}</a>{{ end }}/{{ range $i,$a := .Parts }}<a href="{{ .URL }}">{{ .Name }}</a>/{{ end }}
</h1>
<hr>
<div class="g">
9 changes: 9 additions & 0 deletions line.gohtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{{ if and .IsDir (eq .Name "..") }}
<a href="{{ .URL }}" class="d">{{ .Name }}</a>
<p>{{ .Date.Format "02 Jan 06 15:04 MST" }}</p>
<p>{{ .Size }}</p>
{{ else }}
<a href="{{ .URL }}" {{ if .IsDir }}class="d"{{ end }}>{{ .Name }}</a>
<p>{{ .Date.Format "02 Jan 06 15:04 MST" }}</p>
<p>{{ .Size }}</p>
{{ end }}
224 changes: 145 additions & 79 deletions statik.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package main

import (
"bytes"
_ "embed"
"flag"
"fmt"
"html/template"
"io"
"io/fs"
"io/ioutil"
"log"
Expand All @@ -14,68 +17,72 @@ import (
"sort"
"strings"
"time"
)

const (
formatLayout = time.RFC822
minNameSpace, minNameSpacing, dateSpace, sizeSpace = 50, 10, "30", "8"
linkSuffix = ".link"
"github.com/dustin/go-humanize"
"github.com/tdewolff/minify/v2"
"github.com/tdewolff/minify/v2/css"
"github.com/tdewolff/minify/v2/html"
"github.com/tdewolff/minify/v2/js"
)

type Dir struct {
Name string
URL string
}

type Header struct {
Root Dir
Parts []Dir
FullPath string
Stylesheet template.CSS
}

type Footer struct {
Date time.Time
}

type Line struct {
IsDir bool
Name string
URL string
Size string
Date time.Time
}

const linkSuffix = ".link"

var (
baseDir, outDir string
baseUrl *url.URL = nil
baseURL *url.URL = nil

include, exclude *regexp.Regexp = nil, nil
empty, recursive, sortEntries, converLinks bool
)

func bytes(b int64) string {
const unit = 1000
if b < unit {
return fmt.Sprintf("%d B", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
}
//go:embed "style.css"
style string
//go:embed "header.gohtml"
rawHeader string
//go:embed "line.gohtml"
rawLine string
//go:embed "footer.gohtml"
rawFooter string

header, footer, line *template.Template
)

// joins the baseUrl path with the given relative path and returns the url as a string
func join(rel string) string {
cpy := baseUrl.Path
baseUrl.Path = path.Join(baseUrl.Path, rel)
res := baseUrl.String()
baseUrl.Path = cpy
func withBaseURL(rel string) string {
cpy := baseURL.Path
baseURL.Path = path.Join(baseURL.Path, rel)
res := baseURL.String()
baseURL.Path = cpy
return res
}

func header(rel string) string {
path := path.Join(baseUrl.Path + rel)
str := "<html><head><meta name='viewport' content='width=device-width'><style>:root{--b:#fbf1c7;--f:#282828;--d:#af3a03}@media (prefers-color-scheme:dark){:root{--b:#282828;--f:#fbf1c7;--d:#fe8019}}*{color:var(--f);background:var(--b)}body{font-size:16px;font-family:monospace;margin:0;padding:1.5rem;line-height:1.8}a{text-decoration:none;border-bottom:1px solid}.d{color:var(--d)}</style><title>Index of " + path + "</title></head><body><h1>Index of " + path + "</h1><hr><pre>"
if rel != "/" {
str += "<a href=\"" + join(rel+"/..") + "\" class=\"d\">..</a>\n"
func gen(tmpl *template.Template, data interface{}, out io.Writer) {
if err := tmpl.Execute(out, data); err != nil {
log.Fatalf("could not generate template for the %s section:\n%s\n", tmpl.Name(), err)
}
return str
}

func line(name string, nameSpace int, path string, modTime time.Time, isDir bool, size int64, link bool) string {
space := strings.Repeat(" ", nameSpace-len(name))
url := path
if !link {
url = join(path)
}
extra := ""
if isDir {
extra = " class=\"d\""
}
return fmt.Sprintf("<a href=\"%s\"%s>%s</a>%s %-"+dateSpace+"s %-"+sizeSpace+"s\n", url, extra, name, space, modTime.Format(formatLayout), bytes(size))
}

func footer(date time.Time) string {
return "</pre><hr><p>Generated by <a href=\"https://github.com/lucat1/statik\">statik</a> on " + date.Format(formatLayout) + "</p></body></html>"
}

func copy(src, dest string) {
Expand All @@ -99,7 +106,7 @@ func filter(entries []fs.FileInfo) []fs.FileInfo {
return filtered
}

func generate(dir string) bool {
func generate(m *minify.M, dir string, parts []string) bool {
entries, err := ioutil.ReadDir(dir)
if err != nil {
log.Fatalf("Could not read input directory: %s\n%s\n", dir, err)
Expand All @@ -118,62 +125,81 @@ func generate(dir string) bool {
})
}

if !strings.HasSuffix(dir, "/") {
dir += "/"
}
rel := strings.Replace(dir, baseDir, "", 1)
out := path.Join(outDir, rel)
if err := os.Mkdir(out, os.ModePerm); err != nil {
log.Fatalf("Could not create output *sub*directory: %s\n%s\n", out, err)
rel := path.Join(parts...)
outDir := path.Join(outDir, rel)
if err := os.Mkdir(outDir, os.ModePerm); err != nil {
log.Fatalf("Could not create output *sub*directory: %s\n%s\n", outDir, err)
}
htmlPath := path.Join(out, "index.html")
htmlPath := path.Join(outDir, "index.html")
html, err := os.OpenFile(htmlPath, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
log.Fatalf("Could not create output index.html: %s\n%s\n", htmlPath, err)
}

nameSpace := minNameSpace
for _, entry := range entries {
l := len(entry.Name())
if strings.HasSuffix(entry.Name(), linkSuffix) {
l -= len(linkSuffix)
}
l += minNameSpacing

if l > nameSpace {
nameSpace = l
}
out := new(bytes.Buffer)
// Generate the header and the double dots back anchor when appropriate
p, url := []Dir{}, ""
for _, part := range parts {
url = path.Join(url, part)
p = append(p, Dir{Name: part, URL: withBaseURL(url)})
}
gen(header, Header{
Root: Dir{
Name: strings.TrimPrefix(strings.TrimSuffix(baseURL.Path, "/"), "/"),
URL: baseURL.String(),
},
Parts: p,
FullPath: path.Join(baseURL.Path+rel) + "/",
Stylesheet: template.CSS(style),
}, out)
if len(parts) != 0 {
gen(line, Line{
IsDir: true,
Name: "..",
URL: withBaseURL(path.Join(rel, "..")),
Size: humanize.Bytes(0),
}, out)
}

content := header(rel)
for _, entry := range entries {
pth := path.Join(dir, entry.Name())
// Avoid recursive infinite loop
if pth == outDir {
continue
}

data := Line{
IsDir: entry.IsDir(),
Name: entry.Name(),
URL: withBaseURL(path.Join(rel, entry.Name())),
Size: humanize.Bytes(uint64(entry.Size())),
Date: entry.ModTime(),
}
if strings.HasSuffix(pth, linkSuffix) {
data.Name = data.Name[:len(data.Name)-len(linkSuffix)]
data.Size = humanize.Bytes(0)

url, err := ioutil.ReadFile(pth)
if err != nil {
log.Fatalf("Could not read link file: %s\n%s\n", pth, err)
}
content += line(entry.Name()[:len(entry.Name())-len(linkSuffix)], nameSpace, string(url), entry.ModTime(), entry.IsDir(), 0, true)
data.URL = string(url)
gen(line, data, out)
continue
}

// Only list directories when recursing and only those which are not empty
if !entry.IsDir() || recursive && generate(pth) {
content += line(entry.Name(), nameSpace, path.Join(rel, entry.Name()), entry.ModTime(), entry.IsDir(), entry.Size(), false)
if !entry.IsDir() || recursive && generate(m, pth, append(parts, entry.Name())) {
gen(line, data, out)
}

// Copy all files over to the web root
if !entry.IsDir() {
copy(pth, path.Join(out, entry.Name()))
copy(pth, path.Join(outDir, entry.Name()))
}
}
content += footer(time.Now())
if n, err := html.Write([]byte(content)); err != nil || n != len(content) {
gen(footer, Footer{Date: time.Now()}, out)
if err := m.Minify("text/html", html, out); err != nil {
log.Fatalf("Could not write to index.html: %s\n%s\n", htmlPath, err)
}
if err := html.Close(); err != nil {
Expand All @@ -184,6 +210,22 @@ func generate(dir string) bool {
return !empty
}

func loadTemplate(name string, path string, def *string, dest **template.Template) {
var (
content []byte
err error
)
if path != "" {
if content, err = ioutil.ReadFile(path); err != nil {
log.Fatalf("Could not read %s template file %s:\n%s\n", name, path, err)
}
*def = string(content)
}
if *dest, err = template.New(name).Parse(*def); err != nil {
log.Fatalf("Could not parse %s template:\n%s\n", name, path, err)
}
}

func main() {
i := flag.String("i", ".*", "A regex pattern to include files into the listing")
e := flag.String("e", "\\.git(hub)?", "A regex pattern to exclude files from the listing")
Expand All @@ -192,6 +234,10 @@ func main() {
s := flag.Bool("sort", true, "Sort files A-z and by type")
b := flag.String("b", "http://localhost", "The base URL")
l := flag.Bool("l", false, "Convert .link files to anchor tags")
argstyle := flag.String("style", "", "Use a custom stylesheet file")
argfooter := flag.String("footer", "", "Use a custom footer template")
argheader := flag.String("header", "", "Use a custom header template")
argline := flag.String("line", "", "Use a custom line template")
flag.Parse()

args := flag.Args()
Expand All @@ -205,16 +251,19 @@ func main() {
src = args[0]
dest = args[1]
}

log.Println("Running with parameters:")
log.Println("\tInclude:\t", *i)
log.Println("\tExclude:\t", *e)
log.Println("\tRecursive:\t", *r)
log.Println("\tEmpty:\t\t", *emp)
log.Println("\tConvert links:\t\t", *l)
log.Println("\tConvert links:\t", *l)
log.Println("\tSource:\t\t", src)
log.Println("\tDestination:\t", dest)
log.Println("\tBase URL:\t", *b)
log.Println("\tStyle:\t\t", *argstyle)
log.Println("\tFooter:\t\t", *argfooter)
log.Println("\tHeader:\t\t", *argheader)
log.Println("\tline:\t\t", *argline)

var err error
if include, err = regexp.Compile(*i); err != nil {
Expand Down Expand Up @@ -246,8 +295,25 @@ func main() {
log.Fatalf("Could not remove output directory previous contents: %s\n%s\n", outDir, err)
}
}
if baseUrl, err = url.Parse(*b); err != nil {
if baseURL, err = url.Parse(*b); err != nil {
log.Fatalf("Could not parse base URL: %s\n%s\n", *b, err)
}
generate(baseDir)

loadTemplate("header", *argheader, &rawHeader, &header)
loadTemplate("line", *argline, &rawLine, &line)
loadTemplate("footer", *argfooter, &rawFooter, &footer)

if *argstyle != "" {
var content []byte
if content, err = ioutil.ReadFile(*argstyle); err != nil {
log.Fatalf("Could not read stylesheet file %s:\n%s\n", *argstyle, err)
}
style = string(content)
}

m := minify.New()
m.AddFunc("text/css", css.Minify)
m.AddFunc("text/html", html.Minify)
m.AddFunc("application/javascript", js.Minify)
generate(m, baseDir, []string{})
}
Loading

0 comments on commit 24d2ce7

Please sign in to comment.