752 lines
19 KiB
Go
752 lines
19 KiB
Go
// Package main provides the command-line entrypoint.
|
|
package main
|
|
|
|
import (
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io/fs"
|
|
"io/ioutil"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gabriel-vasile/mimetype"
|
|
thumb "github.com/prplecake/go-thumbnail"
|
|
)
|
|
|
|
// logger is the global logger.
|
|
var logger *slog.Logger
|
|
|
|
// thumbnailSuffix is the file name suffix used for generated thumbnails.
|
|
var thumbnailSuffix = "_distribusi_thumbnail"
|
|
|
|
// logStripMsg strips content from logs messages for brevity.
|
|
var logStripMsg = "...stripped from logs for brevity..."
|
|
|
|
// port is for serving locally
|
|
var port = ":1312"
|
|
|
|
// htmlBody is the template for the index.html files that are generated by distribusi-go.
|
|
var htmlBody = `
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<!-- Generated with distribusi-go -->
|
|
%s
|
|
|
|
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
|
|
|
<style>
|
|
.image {
|
|
max-width: 100%%;
|
|
}
|
|
|
|
.pdf {
|
|
width: 640px;
|
|
height: 640px;
|
|
}
|
|
|
|
.x-directory::before {
|
|
content: "📁 ";
|
|
font-size: 18px;
|
|
}
|
|
|
|
.filename {
|
|
display: block;
|
|
font-family: mono;
|
|
}
|
|
|
|
.gif {
|
|
width: 450px;
|
|
max-height: 450px;
|
|
}
|
|
|
|
div {
|
|
display: inline-block;
|
|
vertical-align: top;
|
|
margin: 1em;
|
|
padding: 1em;
|
|
}
|
|
|
|
video {
|
|
width: 450px;
|
|
max-height: 450px;
|
|
}
|
|
|
|
%s
|
|
</style>
|
|
</head>
|
|
<body>
|
|
%s
|
|
</body>
|
|
</html
|
|
`
|
|
|
|
// fileType is a file type identifier such as "image" or "audio".
|
|
type fileType = string
|
|
|
|
// subType is a more specific file type identifier compared to fileType, such as "gif" or "mp4".
|
|
type subType = string
|
|
|
|
// htmlTag is an appropriate HTML tag element for a specific fileType & subType.
|
|
type htmlTag = string
|
|
|
|
// htmlTags is a fileType/subType to htmlTag mapping.
|
|
var htmlTags = map[fileType]map[subType]htmlTag{
|
|
"text": {
|
|
"html": `<section id="%s">%s</section>`,
|
|
"generic": `<pre>%s</pre>`,
|
|
},
|
|
"image": {
|
|
"thumbnail": trimAllNewlines(`<figure>
|
|
<a href="%s">
|
|
<img class="thumbnail" loading="lazy" src="%s">
|
|
</a>
|
|
</figure>`),
|
|
"generic": trimAllNewlines(`<figure>
|
|
<a href="%s">
|
|
<img class=%s loading="lazy" src="%s">
|
|
</a>
|
|
</figure>`),
|
|
},
|
|
"application": {
|
|
"pdf": trimAllNewlines(`<object data="%s" class="pdf" type="application/pdf">
|
|
<embed src="%s" type="application/pdf"/>
|
|
</object>`),
|
|
},
|
|
"audio": {
|
|
"generic": trimAllNewlines(`<audio controls>
|
|
<source src="%s" type="audio/%s">
|
|
Sorry, your browser does not support the audio element.
|
|
</audio>`),
|
|
},
|
|
"video": {
|
|
"generic": trimAllNewlines(`<video controls>
|
|
<source src="%s" type="video/%s">
|
|
</video>`),
|
|
},
|
|
"unknown": {
|
|
"generic": `<a class="%s" href="%s">%s</a>`,
|
|
},
|
|
}
|
|
|
|
// generatedInDistribusi is an internal marker to help recognise when
|
|
// distribusi-go has generated files.
|
|
var generatedInDistribusi = "<meta name='generator' content='distribusi-go' />"
|
|
|
|
var helpOutput = `USAGE:
|
|
distribusi [options] [arguments]
|
|
|
|
DESCRIPTION:
|
|
A low-tech content management system for the web that produces static index
|
|
pages based on folders in the files system. It is inspired by the automatic
|
|
index functions featured in several popular web servers.
|
|
|
|
ARGUMENTS:
|
|
<path> path to distribusify (default: ".")
|
|
|
|
OPTIONS:
|
|
-d show debug output
|
|
-c css file for custom styles
|
|
-h output help
|
|
-i ignore paths (e.g. "*.gif")
|
|
-s serve locally
|
|
-v output version
|
|
-w wipe generated files
|
|
`
|
|
|
|
var (
|
|
cssFlag string
|
|
ignoreFlag string
|
|
|
|
debugFlag bool
|
|
helpFlag bool
|
|
serveFlag bool
|
|
wipeFlag bool
|
|
|
|
version = "0.2.0"
|
|
versionFlag bool
|
|
)
|
|
|
|
func main() {
|
|
flag.StringVar(&cssFlag, "c", "", "")
|
|
flag.StringVar(&ignoreFlag, "i", "", "")
|
|
|
|
flag.BoolVar(&debugFlag, "d", false, "")
|
|
flag.BoolVar(&helpFlag, "h", false, "")
|
|
flag.BoolVar(&serveFlag, "s", false, "")
|
|
flag.BoolVar(&versionFlag, "v", false, "")
|
|
flag.BoolVar(&wipeFlag, "w", false, "")
|
|
|
|
flag.Usage = func() {
|
|
return
|
|
}
|
|
|
|
flag.Parse()
|
|
|
|
opts := &slog.HandlerOptions{
|
|
Level: slog.LevelInfo,
|
|
ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr {
|
|
if a.Key == "time" {
|
|
t := a.Value.Time()
|
|
return slog.String("time", t.Format(time.Kitchen))
|
|
}
|
|
return a
|
|
},
|
|
}
|
|
if debugFlag {
|
|
opts.Level = slog.LevelDebug
|
|
opts.AddSource = true
|
|
}
|
|
logger = slog.New(slog.NewTextHandler(os.Stdout, opts))
|
|
slog.SetDefault(logger)
|
|
|
|
if helpFlag {
|
|
fmt.Print(helpOutput)
|
|
os.Exit(0)
|
|
}
|
|
|
|
if versionFlag {
|
|
fmt.Printf("%s\n", version)
|
|
os.Exit(0)
|
|
}
|
|
|
|
path, err := os.Getwd()
|
|
if err != nil {
|
|
logger.Error(fmt.Sprintf("unable to determine current working directory: %s", err))
|
|
}
|
|
if len(flag.Args()) == 1 {
|
|
path = flag.Args()[0]
|
|
}
|
|
|
|
root, err := filepath.Abs(path)
|
|
if err != nil {
|
|
logger.Error(fmt.Sprintf("unable to determine absolute path of %s: %s", path, err))
|
|
}
|
|
|
|
if _, err := os.Stat(root); os.IsNotExist(err) {
|
|
logger.Error(fmt.Sprintf("%s does not exist?", root))
|
|
}
|
|
|
|
logger.Debug(fmt.Sprintf("selecting %s as distribusi root", root))
|
|
|
|
var ignore []string
|
|
if ignoreFlag != "" {
|
|
ignore = strings.Split(ignoreFlag, ",")
|
|
for i := range ignore {
|
|
ignore[i] = strings.TrimSpace(ignore[i])
|
|
}
|
|
logger.Debug(fmt.Sprintf("parsed %s as ignore patterns", strings.Join(ignore, " ")))
|
|
}
|
|
|
|
if wipeFlag {
|
|
if err := wipeGeneratedFiles(root); err != nil {
|
|
logger.Error(err.Error())
|
|
}
|
|
logger.Info(fmt.Sprintf("wiped generated files in %s", root))
|
|
return
|
|
}
|
|
|
|
ch := make(chan error, 2)
|
|
go func() {
|
|
if err := distribusify(root, ignore); err != nil {
|
|
ch <- err
|
|
return
|
|
}
|
|
|
|
msg := " done!"
|
|
if serveFlag {
|
|
fmt.Printf(msg)
|
|
} else {
|
|
fmt.Println(msg)
|
|
}
|
|
|
|
ch <- nil
|
|
return
|
|
}()
|
|
|
|
if serveFlag {
|
|
go func() {
|
|
logger.Debug("attempting to start up the web server")
|
|
if err := serveHTTP(root); err != nil {
|
|
ch <- err
|
|
return
|
|
}
|
|
ch <- nil
|
|
return
|
|
}()
|
|
} else {
|
|
// NOTE(d1): close the channel, we're not serving anything
|
|
ch <- nil
|
|
}
|
|
|
|
for i := 1; i <= 2; i++ {
|
|
err := <-ch
|
|
if err != nil {
|
|
logger.Error(err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
// removeIndex safely removes an index.html, making sure to check that
|
|
// distribusi generated it.
|
|
func removeIndex(fpath string) error {
|
|
file, err := os.ReadFile(fpath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if strings.Contains(string(file), generatedInDistribusi) {
|
|
if err := os.Remove(fpath); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// wipeGeneratedFiles removes all distribusi generated files under a file
|
|
// system path. We do take care to avoid deleting files that distribusi has not
|
|
// generated by checking their contents.
|
|
func wipeGeneratedFiles(dir string) error {
|
|
if err := filepath.WalkDir(dir, func(fpath string, dirEntry fs.DirEntry, err error) error {
|
|
fname := filepath.Base(fpath)
|
|
if fname == "index.html" {
|
|
if err := removeIndex(fpath); err != nil {
|
|
return fmt.Errorf("unable to remove %s: %s", fpath, err)
|
|
}
|
|
logger.Debug(fmt.Sprintf("wiping %s as requested", fpath))
|
|
} else if strings.Contains(fname, thumbnailSuffix) {
|
|
if err := os.Remove(fpath); err != nil {
|
|
return fmt.Errorf("unable to remove %s: %s", fpath, err)
|
|
}
|
|
logger.Debug(fmt.Sprintf("wiping %s as requested", fpath))
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// createDiv cosntructs a HTML div for inclusion in the generated index.html. These
|
|
// dives are used to wrap the elements that appear on generated pages with
|
|
// relevant identifiers for convenient styling.
|
|
func createDiv(mtype string, href, fname string, unknown bool) (string, error) {
|
|
var div string
|
|
var divTemplate string
|
|
var strippedDebugOutput string
|
|
|
|
filename := fmt.Sprintf("<span class='filename'>%s</span>", fname)
|
|
|
|
ftype, stype, err := parseMtype(mtype)
|
|
if err != nil {
|
|
return div, err
|
|
}
|
|
|
|
if ftype == "text" {
|
|
divTemplate = "<div id=\"%s\" class='%s'>%s%s</div>"
|
|
div = fmt.Sprintf(divTemplate, fname, ftype, href, filename)
|
|
strippedDebugOutput = fmt.Sprintf(divTemplate, fname, ftype, logStripMsg, filename)
|
|
} else if ftype == "os" {
|
|
if stype == "directory" {
|
|
divTemplate = "<div id=\"%s\" class='x-%s'>%s</div>"
|
|
div = fmt.Sprintf(divTemplate, fname, stype, href)
|
|
} else {
|
|
// don't include filename since link already has it
|
|
divTemplate = "<div id=\"%s\" class='%s'>%s</div>"
|
|
div = fmt.Sprintf(divTemplate, fname, ftype, href)
|
|
}
|
|
} else {
|
|
if unknown {
|
|
// don't include filename since link already has it
|
|
divTemplate = "<div id=\"%s\" class='%s'>%s</div>"
|
|
div = fmt.Sprintf(divTemplate, fname, ftype, href)
|
|
} else {
|
|
divTemplate = "<div id=\"%s\" class='%s'>%s%s</div>"
|
|
div = fmt.Sprintf(divTemplate, fname, ftype, href, filename)
|
|
strippedDebugOutput = fmt.Sprintf(divTemplate, fname, ftype, logStripMsg, filename)
|
|
}
|
|
}
|
|
|
|
if strippedDebugOutput != "" {
|
|
logger.Debug(fmt.Sprintf("%s was wrapped in: %s", fname, strippedDebugOutput))
|
|
} else {
|
|
logger.Debug(fmt.Sprintf("%s was wrapped in: %s", fname, div))
|
|
}
|
|
|
|
return div, nil
|
|
}
|
|
|
|
// parseMtype parses a mimetype string to simplify programmatic type lookups.
|
|
func parseMtype(mtype string) (string, string, error) {
|
|
if !strings.Contains(mtype, "/") {
|
|
return "", "", fmt.Errorf("unable to parse %s", mtype)
|
|
}
|
|
|
|
stripCharset := strings.Split(mtype, ";")
|
|
splitTypes := strings.Split(stripCharset[0], "/")
|
|
ftype, stype := splitTypes[0], splitTypes[1]
|
|
|
|
return ftype, stype, nil
|
|
}
|
|
|
|
// trimAllNewlines removes all new lines.
|
|
func trimAllNewlines(contents string) string {
|
|
return strings.ReplaceAll(string(contents), "\n", "")
|
|
}
|
|
|
|
// createHref figures out which href tag corresponds to which file by navigating
|
|
// the mimetype. If a type of file is unknown, this is signalled via the bool
|
|
// return value.
|
|
func createHref(fpath string, mtype string) (bool, string, error) {
|
|
var (
|
|
href string
|
|
hrefTemplate string
|
|
strippedDebugOutput string
|
|
unknown bool
|
|
)
|
|
|
|
fname := filepath.Base(fpath)
|
|
ftype, stype, err := parseMtype(mtype)
|
|
if err != nil {
|
|
return unknown, href, err
|
|
}
|
|
|
|
if ftype == "text" {
|
|
fcontents, err := os.ReadFile(fpath)
|
|
if err != nil {
|
|
return unknown, href, err
|
|
}
|
|
|
|
trimmed := strings.TrimSuffix(string(fcontents), "\n")
|
|
|
|
if stype == "html" {
|
|
hrefTemplate = htmlTags[ftype][stype]
|
|
href = fmt.Sprintf(hrefTemplate, fname, trimmed)
|
|
strippedDebugOutput = fmt.Sprintf(hrefTemplate, fname, logStripMsg)
|
|
} else {
|
|
hrefTemplate = htmlTags[ftype]["generic"]
|
|
href = fmt.Sprintf(hrefTemplate, trimmed)
|
|
strippedDebugOutput = fmt.Sprintf(hrefTemplate, logStripMsg)
|
|
}
|
|
} else if ftype == "image" {
|
|
if stype == "gif" {
|
|
hrefTemplate = htmlTags[ftype]["generic"]
|
|
href = fmt.Sprintf(hrefTemplate, fname, stype, fname)
|
|
} else {
|
|
thumbPath, err := genThumb(fpath)
|
|
if err != nil {
|
|
hrefTemplate = htmlTags[ftype]["generic"]
|
|
href = fmt.Sprintf(hrefTemplate, fname, stype, fname)
|
|
logger.Debug(fmt.Sprintf("failed to generate thumbnail for %s, showing original image", fpath))
|
|
} else {
|
|
hrefTemplate = htmlTags[ftype]["thumbnail"]
|
|
href = fmt.Sprintf(hrefTemplate, fname, thumbPath)
|
|
strippedDebugOutput = fmt.Sprintf(hrefTemplate, fname, logStripMsg)
|
|
}
|
|
}
|
|
} else if ftype == "application" {
|
|
if stype == "pdf" {
|
|
hrefTemplate = htmlTags[ftype][stype]
|
|
href = fmt.Sprintf(hrefTemplate, fname, fname)
|
|
} else {
|
|
unknown = true
|
|
hrefTemplate = htmlTags["unknown"]["generic"]
|
|
href = fmt.Sprintf(hrefTemplate, stype, fname, fname)
|
|
}
|
|
} else if ftype == "audio" {
|
|
hrefTemplate = htmlTags[ftype]["generic"]
|
|
href = fmt.Sprintf(hrefTemplate, fname, stype)
|
|
} else if ftype == "video" {
|
|
hrefTemplate = htmlTags[ftype]["generic"]
|
|
href = fmt.Sprintf(hrefTemplate, fname, stype)
|
|
} else {
|
|
unknown = true
|
|
hrefTemplate = htmlTags["unknown"]["generic"]
|
|
href = fmt.Sprintf(hrefTemplate, stype, fname, fname)
|
|
}
|
|
|
|
if strippedDebugOutput != "" {
|
|
logger.Debug(fmt.Sprintf("%s was wrapped in: %s", fname, strippedDebugOutput))
|
|
} else {
|
|
logger.Debug(fmt.Sprintf("%s was wrapped in: %s", fname, href))
|
|
}
|
|
|
|
return unknown, href, nil
|
|
}
|
|
|
|
// genThumb generates a thumbnail alongside the image.
|
|
func genThumb(fpath string) (string, error) {
|
|
ext := filepath.Ext(fpath)
|
|
destPath := strings.Replace(fpath, ext, fmt.Sprintf("%s%s", thumbnailSuffix, ext), 1)
|
|
|
|
if _, err := os.Stat(destPath); err == nil || strings.Contains(fpath, thumbnailSuffix) {
|
|
return filepath.Base(destPath), nil
|
|
}
|
|
|
|
gen := thumb.NewGenerator(thumb.Generator{Scaler: "CatmullRom"})
|
|
img, err := gen.NewImageFromFile(fpath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("unable to read image file %s: %s", fpath, err)
|
|
}
|
|
|
|
thumbBytes, err := gen.CreateThumbnail(img)
|
|
if err != nil {
|
|
return "", fmt.Errorf("unable to generate thumbnail for %s: %s", fpath, err)
|
|
}
|
|
|
|
if _, err := os.Stat(destPath); errors.Is(err, os.ErrNotExist) {
|
|
if err = ioutil.WriteFile(destPath, thumbBytes, 0644); err != nil {
|
|
return "", fmt.Errorf("unable to write thumbnail to %s: %s", destPath, err)
|
|
}
|
|
logger.Debug(fmt.Sprintf("generated thumbnail for %s", fpath))
|
|
}
|
|
|
|
return filepath.Base(destPath), nil
|
|
}
|
|
|
|
// shouldSkip checks if a specific file system path should be skipped over when
|
|
// running distribusi file generation. This might happen due to being a hidden
|
|
// directory or matching a pattern provided by the end-user.
|
|
func shouldSkip(fpath string, ignore []string) (bool, error) {
|
|
base := filepath.Base(fpath)
|
|
|
|
for _, pattern := range ignore {
|
|
match, err := filepath.Match(pattern, base)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if match {
|
|
logger.Debug(fmt.Sprintf("skipping %s, matched %s", base, pattern))
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
if strings.Contains(base, thumbnailSuffix) {
|
|
logger.Debug(fmt.Sprintf("skipping %s (generated thumbnail)", base))
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// createIndex writes a new generated index.html file to the file system.
|
|
func createIndex(fpath string, html []string, styles string) error {
|
|
body := fmt.Sprintf(htmlBody, generatedInDistribusi, "", strings.Join(html, "\n"))
|
|
|
|
if styles != "" {
|
|
absPath, err := filepath.Abs(styles)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := os.Stat(absPath); !os.IsNotExist(err) {
|
|
contents, err := os.ReadFile(absPath)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
logger.Debug(fmt.Sprintf("loading custom styles from %s", absPath))
|
|
|
|
body = fmt.Sprintf(htmlBody, generatedInDistribusi, contents, strings.Join(html, "\n"))
|
|
}
|
|
}
|
|
|
|
HTMLPath := path.Join(fpath, "index.html")
|
|
contents := []byte(body)
|
|
|
|
if _, err := os.Stat(HTMLPath); err != nil {
|
|
if os.IsNotExist(err) {
|
|
if err := ioutil.WriteFile(HTMLPath, contents, 0644); err != nil {
|
|
logger.Debug(fmt.Sprintf("unable to write %s, skipping", HTMLPath))
|
|
return nil
|
|
}
|
|
} else {
|
|
logger.Debug(fmt.Sprintf("unable to read %s, skipping", HTMLPath))
|
|
return nil
|
|
}
|
|
} else {
|
|
file, err := os.ReadFile(HTMLPath)
|
|
if err != nil {
|
|
logger.Debug(fmt.Sprintf("unable to read %s, skipping", HTMLPath))
|
|
return nil
|
|
}
|
|
|
|
if strings.Contains(string(file), generatedInDistribusi) {
|
|
if err := ioutil.WriteFile(HTMLPath, contents, 0644); err != nil {
|
|
logger.Debug(fmt.Sprintf("unable to write %s, skipping", HTMLPath))
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// getMtype reads file mimetypes directlry from files.
|
|
func getMtype(fpath string) (string, error) {
|
|
mtype, err := mimetype.DetectFile(fpath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return mtype.String(), nil
|
|
}
|
|
|
|
// serveHTTP serves a web server for browsing distribusi generated files. This
|
|
// is mostly convenient when doing development work or showing something
|
|
// quickly on your work station. It should be fine to serve "for production"
|
|
// though too as it uses the stdlib Go HTTP server. Distribusi generated files
|
|
// still works just fine with the usual Nginx, Apache, etc.
|
|
func serveHTTP(fpath string) error {
|
|
fs := http.FileServer(http.Dir(fpath))
|
|
http.Handle("/", fs)
|
|
if err := http.ListenAndServe(port, nil); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// distribusify runs the main distribusi generation logic.
|
|
func distribusify(root string, ignore []string) error {
|
|
loading := "distribusifying..."
|
|
if serveFlag {
|
|
loading = fmt.Sprintf("distribusifying... live @ http://localhost%s", port)
|
|
}
|
|
fmt.Printf(loading)
|
|
|
|
if err := filepath.Walk(root, func(fpath string, finfo os.FileInfo, err error) error {
|
|
skip, err := shouldSkip(fpath, ignore)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if skip {
|
|
return nil
|
|
}
|
|
|
|
var html []string
|
|
|
|
if finfo.IsDir() {
|
|
var dirs []string
|
|
var files []string
|
|
|
|
absPath, err := filepath.Abs(fpath)
|
|
if err != nil {
|
|
logger.Debug(fmt.Sprintf("unable to read %s", absPath))
|
|
return nil
|
|
}
|
|
|
|
contents, err := ioutil.ReadDir(absPath)
|
|
if err != nil {
|
|
logger.Debug(fmt.Sprintf("unable to read %s", absPath))
|
|
return nil
|
|
}
|
|
|
|
for _, content := range contents {
|
|
if content.IsDir() {
|
|
dirs = append(dirs, path.Join(absPath, content.Name()))
|
|
} else {
|
|
if content.Name() == "index.html" {
|
|
indexPath := path.Join(absPath, content.Name())
|
|
file, err := os.ReadFile(indexPath)
|
|
if err != nil {
|
|
logger.Debug(fmt.Sprintf("unable to read %s, skipping", content.Name()))
|
|
continue
|
|
}
|
|
|
|
if strings.Contains(string(file), generatedInDistribusi) {
|
|
logger.Debug(fmt.Sprintf("%s was not generated by distribusi, skipping", indexPath))
|
|
continue
|
|
}
|
|
}
|
|
|
|
files = append(files, path.Join(absPath, content.Name()))
|
|
}
|
|
}
|
|
|
|
if len(dirs) == 0 && len(files) == 0 {
|
|
return nil
|
|
}
|
|
|
|
if root != fpath {
|
|
href := "<a href='../'>../</a>"
|
|
div, err := createDiv("os/directory", href, "menu", false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
html = append(html, div)
|
|
}
|
|
|
|
for _, dir := range dirs {
|
|
fname := filepath.Base(dir)
|
|
|
|
skip, err := shouldSkip(fname, ignore)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if skip {
|
|
continue
|
|
}
|
|
|
|
mtype := "os/directory"
|
|
|
|
href := fmt.Sprintf("<a href='%s/'>%s/</a>", fname, fname)
|
|
div, err := createDiv(mtype, href, fname, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
html = append(html, div)
|
|
}
|
|
|
|
for _, file := range files {
|
|
fname := filepath.Base(file)
|
|
|
|
skip, err := shouldSkip(fname, ignore)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if skip {
|
|
continue
|
|
}
|
|
|
|
mtype, err := getMtype(file)
|
|
if err != nil {
|
|
logger.Debug(fmt.Sprintf("failed to read mimetype of %s", file))
|
|
continue
|
|
}
|
|
|
|
unknown, href, err := createHref(file, mtype)
|
|
if err != nil {
|
|
logger.Debug(fmt.Sprintf("failed to generate href for %s", file))
|
|
continue
|
|
}
|
|
|
|
div, err := createDiv(mtype, href, fname, unknown)
|
|
if err != nil {
|
|
logger.Debug(fmt.Sprintf("failed to generate div for %s", file))
|
|
continue
|
|
}
|
|
|
|
html = append(html, div)
|
|
}
|
|
|
|
if err := createIndex(absPath, html, cssFlag); err != nil {
|
|
logger.Debug(fmt.Sprintf("unable to generated %s, skipping", path.Join(absPath, "index.html")))
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|