-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
338 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
Simple tool to generate a static listing of a directory, similarly to what web | ||
servers like Apache and lighthttpd offer. Can be used to host directories on | ||
static hosting sites such as Github Pages or Netlify. | ||
|
||
You can obtain the binary version of this software via: | ||
$ curl -sf https://goblin.reaper.im/github.com/lucat1/statik | sh | ||
Or any other compliant service such as https://gobinaries.com/. | ||
|
||
A list of customization opitons can be found by looking at the program's help. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module github.com/lucat1/statik | ||
|
||
go 1.17 | ||
|
||
require ( | ||
github.com/tdewolff/minify/v2 v2.11.2 // indirect | ||
github.com/tdewolff/parse/v2 v2.5.29 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
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/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= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
<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{word-wrap:break-word;min-width:0;white-space:pre-wrap;text-underline-position:under}.d{color:var(--d)}div{display:grid;align-items:center;width:100%;grid-template:1fr/7fr 3fr 2fr}p{min-width:0}@media(max-width:880px){div{grid-template:1fr/7fr 3fr}.t{display:none}}</style><title>Index of /</title></head><body><h1>Index of /</h1><hr><pre><div><a href="http://localhost/README" >README</a><p class="t">17 May 22 13:32 CEST</p><p>477 B</p></div><div><a href="http://localhost/go.mod" >go.mod</a><p class="t">17 May 22 14:46 CEST</p><p>155 B</p></div><div><a href="http://localhost/go.sum" >go.sum</a><p class="t">17 May 22 14:46 CEST</p><p>1.1 kB</p></div><div><a href="http://localhost/statik" >statik</a><p class="t">17 May 22 16:56 CEST</p><p>4.5 MB</p></div><div><a href="http://localhost/statik.go" >statik.go</a><p class="t">17 May 22 16:46 CEST</p><p>7.1 kB</p></div><div><a href="http://localhost/style.css" >style.css</a><p class="t">17 May 22 16:45 CEST</p><p>759 B</p></div></pre><hr><p>Generated by <a href="https://github.com/lucat1/statik">statik</a> on 17 May 22 16:56 CEST</p></body></html> |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,251 @@ | ||
package main | ||
|
||
import ( | ||
"flag" | ||
"fmt" | ||
"io/fs" | ||
"io/ioutil" | ||
"log" | ||
"net/url" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
"regexp" | ||
"sort" | ||
"strings" | ||
"time" | ||
_"embed" | ||
"github.com/tdewolff/minify/v2" | ||
"github.com/tdewolff/minify/v2/css" | ||
) | ||
//go:embed "style.css" | ||
var style string | ||
|
||
const ( | ||
formatLayout = time.RFC822 | ||
linkSuffix = ".link" | ||
) | ||
|
||
var ( | ||
baseDir, outDir string | ||
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]) | ||
} | ||
|
||
// 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 | ||
return res | ||
} | ||
|
||
func header(rel string, css string) string { | ||
path := path.Join(baseUrl.Path + rel) | ||
str := "<html><head><meta name='viewport' content='width=device-width'><style>" + css + "</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" | ||
} | ||
return str | ||
} | ||
|
||
func line(name string, path string, modTime time.Time, isDir bool, size int64, link bool) string { | ||
url := path | ||
if !link { | ||
url = join(path) | ||
} | ||
extra := "" | ||
if isDir { | ||
extra = "class=\"d\"" | ||
} | ||
return fmt.Sprintf("<div><a href=\"%s\" %s>%s</a><p class=\"t\">%s</p><p>%s</p></div>", url, extra, name, 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) { | ||
input, err := ioutil.ReadFile(src) | ||
if err != nil { | ||
log.Fatalf("Could not open source file for copying: %s\n%s\n", src, err) | ||
} | ||
err = ioutil.WriteFile(dest, input, 0644) | ||
if err != nil { | ||
log.Fatalf("Could not write to destination file for copying: %s\n%s\n", dest, err) | ||
} | ||
} | ||
|
||
func filter(entries []fs.FileInfo) []fs.FileInfo { | ||
filtered := []fs.FileInfo{} | ||
for _, entry := range entries { | ||
if entry.IsDir() && !exclude.MatchString(entry.Name()) || (!entry.IsDir() && include.MatchString(entry.Name()) && !exclude.MatchString(entry.Name())) { | ||
filtered = append(filtered, entry) | ||
} | ||
} | ||
return filtered | ||
} | ||
|
||
func generate(dir string, css string) bool { | ||
entries, err := ioutil.ReadDir(dir) | ||
if err != nil { | ||
log.Fatalf("Could not read input directory: %s\n%s\n", dir, err) | ||
} | ||
entries = filter(entries) | ||
if len(entries) == 0 { | ||
return empty | ||
} | ||
if sortEntries { | ||
sort.Slice(entries, func(i, j int) bool { | ||
isFirstEntryDir := entries[i].IsDir() | ||
isSecondEntryDir := entries[j].IsDir() | ||
return isFirstEntryDir && !isSecondEntryDir || | ||
(isFirstEntryDir || !isSecondEntryDir) && | ||
entries[i].Name() < entries[j].Name() | ||
}) | ||
} | ||
|
||
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) | ||
} | ||
htmlPath := path.Join(out, "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) | ||
} | ||
|
||
content := header(rel, css) | ||
for _, entry := range entries { | ||
pth := path.Join(dir, entry.Name()) | ||
// Avoid recursive infinite loop | ||
if pth == outDir { | ||
continue | ||
} | ||
|
||
if strings.HasSuffix(pth, linkSuffix) { | ||
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)], string(url), entry.ModTime(), entry.IsDir(), 0, true) | ||
continue | ||
} | ||
|
||
// Only list directories when recursing and only those which are not empty | ||
if !entry.IsDir() || recursive && generate(pth, css) { | ||
content += line(entry.Name(), path.Join(rel, entry.Name()), entry.ModTime(), entry.IsDir(), entry.Size(), false) | ||
} | ||
|
||
// Copy all files over to the web root | ||
if !entry.IsDir() { | ||
copy(pth, path.Join(out, entry.Name())) | ||
} | ||
} | ||
content += footer(time.Now()) | ||
if n, err := html.Write([]byte(content)); err != nil || n != len(content) { | ||
log.Fatalf("Could not write to index.html: %s\n%s\n", htmlPath, err) | ||
} | ||
if err := html.Close(); err != nil { | ||
log.Fatalf("Could not write to close index.html: %s\n%s\n", htmlPath, err) | ||
} | ||
log.Printf("Generated data for directory: %s\n", dir) | ||
|
||
return !empty | ||
} | ||
|
||
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") | ||
r := flag.Bool("r", true, "Recursively scan the file tree") | ||
emp := flag.Bool("empty", false, "Whether to list empty directories") | ||
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") | ||
flag.Parse() | ||
|
||
args := flag.Args() | ||
src, dest := ".", "site" | ||
if len(args) > 2 { | ||
log.Fatal("Invalid number of aruments, expected two at max") | ||
} | ||
if len(args) == 1 { | ||
dest = args[0] | ||
} else if len(args) == 2 { | ||
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("\tSource:\t\t", src) | ||
log.Println("\tDestination:\t", dest) | ||
log.Println("\tBase URL:\t", *b) | ||
|
||
var err error | ||
if include, err = regexp.Compile(*i); err != nil { | ||
log.Fatal("Invalid regexp for include matching", err) | ||
} | ||
if exclude, err = regexp.Compile(*e); err != nil { | ||
log.Fatal("Invalid regexp for exclude matching", err) | ||
} | ||
recursive = *r | ||
empty = *emp | ||
sortEntries = *s | ||
converLinks = *l | ||
|
||
var wd string | ||
if !filepath.IsAbs(src) || !filepath.IsAbs(dest) { | ||
wd, err = os.Getwd() | ||
if err != nil { | ||
log.Fatal("Could not get currently working directory", err) | ||
} | ||
} | ||
if baseDir = src; !filepath.IsAbs(src) { | ||
baseDir = path.Join(wd, src) | ||
} | ||
if outDir = dest; !filepath.IsAbs(dest) { | ||
outDir = path.Join(wd, dest) | ||
} | ||
if _, err := os.Stat(outDir); err == nil { | ||
if err = os.RemoveAll(outDir); err != nil { | ||
log.Fatalf("Could not remove output directory previous contents: %s\n%s\n", outDir, err) | ||
} | ||
} | ||
if baseUrl, err = url.Parse(*b); err != nil { | ||
log.Fatalf("Could not parse base URL: %s\n%s\n", *b, err) | ||
} | ||
m := minify.New() | ||
m.AddFunc("text/css", css.Minify) | ||
out, err := m.String("text/css", style) | ||
if err != nil { | ||
log.Fatalf("Could not minify css") | ||
panic(err) | ||
} | ||
log.Printf("css code minified correctly") | ||
generate(baseDir, out) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* NOTE: this stylesheet is compressed and directly embedded into the go code */ | ||
: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 { | ||
word-wrap: break-word; | ||
min-width: 0; | ||
white-space: pre-wrap; | ||
text-underline-position: under; | ||
} | ||
.d { | ||
color: var(--d); | ||
} | ||
div { | ||
display: grid; | ||
align-items: center; | ||
width: 100%; | ||
grid-template: 1fr / 7fr 3fr 2fr; | ||
} | ||
p { | ||
min-width: 0; | ||
} | ||
@media (max-width:880px) { | ||
div{ | ||
grid-template: 1fr / 7fr 3fr; | ||
} | ||
.t{ | ||
display: none; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters