init
This commit is contained in:
commit
1190775e94
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
test
|
41
README.md
Normal file
41
README.md
Normal file
@ -0,0 +1,41 @@
|
||||
# distribusi-go
|
||||
|
||||
> This is still very experimental, please take a backup of your archives if you
|
||||
> are running it on files you care about. It hasn't been tested on large
|
||||
> archives. It may still thrash files. Please [report issues] as you find them
|
||||
> :100:
|
||||
|
||||
A [Go] implementation of [distribusi].
|
||||
|
||||
This is a compiled `distribusi` which is simpler to install on your computer,
|
||||
just download the binary, `chmod +x` and run it.
|
||||
|
||||
The command-line interface is quite different from the Python version. There
|
||||
are less optional flags and more defaults. I shuffled a number of things around
|
||||
according to my preferences. For example, I always like my images to be
|
||||
thumbnail'd. There's a handy web server built-in now, just run with `-s`
|
||||
:metal:
|
||||
|
||||
There is no need to install [Pillow] for handling images, that is now built-in.
|
||||
The only external dependency is [exiftool] for image captions from embedded
|
||||
metadata. If you don't have it `exiftool` installed, then it gracefully skips
|
||||
that feature. So, you don't need to install anything else to run `distribusi`
|
||||
now :pray:
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
curl https://git.vvvvvvaria.org/decentral1se/distribusi-go/raw/branch/main/distribusi -o distribusi
|
||||
chmod +x distribusi
|
||||
./distribusi
|
||||
```
|
||||
|
||||
## Hacking
|
||||
|
||||
You'll need [Go] >= 1.13 installed. Run `go build .` to build a new `./distribusi` executable.
|
||||
|
||||
[Go]: https://go.dev
|
||||
[distribusi]: https://git.vvvvvvaria.org/varia/distribusi
|
||||
[Pillow]: https://pillow.readthedocs.io/en/stable/installation.html#external-libraries
|
||||
[exiftool]: https://exiftool.org/
|
||||
[report issues]: https://git.vvvvvvaria.org/decentral1se/distribusi-go/issues
|
BIN
distribusi
Executable file
BIN
distribusi
Executable file
Binary file not shown.
640
distribusi.go
Normal file
640
distribusi.go
Normal file
@ -0,0 +1,640 @@
|
||||
// Package main is the command-line entrypoint for the distribusi command.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"image/png"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
logrusStack "github.com/Gurpartap/logrus-stack"
|
||||
"github.com/barasher/go-exiftool"
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// exiftooInstalled tells us if the exiftool binary is installed or not.
|
||||
var exiftoolInstalled = true
|
||||
|
||||
// generatedInDistribusi is an internal marker to help recognise when
|
||||
// distribusi-go has generated files.
|
||||
var generatedInDistribusi = "<meta name='generator' content='distribusi-go' />"
|
||||
|
||||
// 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 https://git.vvvvvvaria.org/decentral1se/distribusi-go -->
|
||||
%s
|
||||
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||
|
||||
<style>
|
||||
.image {
|
||||
max-width: 100%%;
|
||||
}
|
||||
|
||||
.pdf {
|
||||
width: 640px;
|
||||
height: 640px;
|
||||
}
|
||||
|
||||
.os::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
|
||||
`
|
||||
|
||||
// main is the command-line entrypoint.
|
||||
func main() {
|
||||
app := &cli.App{
|
||||
Name: "distribusi-go",
|
||||
Version: "0.1.0-alpha",
|
||||
Usage: "low-tech content management system for the web",
|
||||
Description: `
|
||||
Distribusi is a 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. Distribusi
|
||||
works by traversing the file system and directory hierarchy to automatically
|
||||
list all the files in the directory, detect the file types and providing them
|
||||
with relevant html classes and tags for easy styling.
|
||||
|
||||
Example:
|
||||
|
||||
distribusi -p myarchive -c styles.css -i .git -s
|
||||
`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "css",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "css file for custom styles",
|
||||
Value: "",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "debug logging",
|
||||
Value: false,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "hidden",
|
||||
Aliases: []string{"n"},
|
||||
Usage: "include hidden directories (e.g. \".git\")",
|
||||
Value: false,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "ignore",
|
||||
Aliases: []string{"i"},
|
||||
Usage: "ignore specific paths (e.g. \"mydir, myfile.txt\" )",
|
||||
Value: "",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "path",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "path to distribusify",
|
||||
Required: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "serve",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "serve distribusi for the web",
|
||||
Value: false,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "wipe",
|
||||
Aliases: []string{"w"},
|
||||
Usage: "remove all generated files",
|
||||
Value: false,
|
||||
},
|
||||
},
|
||||
Authors: []*cli.Author{
|
||||
{
|
||||
Name: "decentral1se",
|
||||
Email: "cellarspoon@riseup.net",
|
||||
},
|
||||
},
|
||||
Before: func(c *cli.Context) error {
|
||||
if c.Bool("debug") {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
logrus.SetFormatter(&logrus.TextFormatter{})
|
||||
logrus.SetOutput(os.Stderr)
|
||||
logrus.AddHook(logrusStack.StandardHook())
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
root, err := filepath.Abs(c.String("path"))
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
var ignore []string
|
||||
if c.String("ignore") != "" {
|
||||
ignore = strings.Split(c.String("ignore"), ",")
|
||||
|
||||
for i := range ignore {
|
||||
ignore[i] = strings.TrimSpace(ignore[i])
|
||||
}
|
||||
|
||||
logrus.Debugf("parsed %s as ignore patterns", strings.Join(ignore, " "))
|
||||
}
|
||||
|
||||
if c.Bool("wipe") {
|
||||
if err := wipeGeneratedFiles(root); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
logrus.Infof("wiped all generated files in %s", root)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = exec.LookPath("exiftool")
|
||||
if err != nil {
|
||||
logrus.Warn("exiftool is not installed, skipping image captions")
|
||||
exiftoolInstalled = false
|
||||
}
|
||||
|
||||
logrus.Debugf("selecting %s as distribusi root", root)
|
||||
|
||||
if err := distribusify(c, root, ignore); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
|
||||
if c.Bool("serve") {
|
||||
if err := serveHTTP(root); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
sort.Sort(cli.FlagsByName(app.Flags))
|
||||
sort.Sort(cli.CommandsByName(app.Commands))
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// distribusify runs the main distribusi generation logic.
|
||||
func distribusify(c *cli.Context, root string, ignore []string) error {
|
||||
var allDirs []string
|
||||
|
||||
if err := filepath.Walk(root, func(fpath string, finfo os.FileInfo, err error) error {
|
||||
|
||||
if skip := shouldSkip(c, fpath, ignore); skip {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
var html []string
|
||||
|
||||
if finfo.IsDir() {
|
||||
var dirs []string
|
||||
var files []string
|
||||
|
||||
absPath, err := filepath.Abs(fpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadDir(absPath)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "permission denied") {
|
||||
return filepath.SkipDir
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, content := range contents {
|
||||
if content.IsDir() {
|
||||
dirs = append(dirs, path.Join(absPath, content.Name()))
|
||||
} else {
|
||||
if content.Name() == "index.html" {
|
||||
file, err := os.ReadFile(path.Join(absPath, content.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.Contains(string(file), generatedInDistribusi) {
|
||||
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 := mkDiv(c, "os/directory", href, "menu", false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
html = append(html, div)
|
||||
}
|
||||
|
||||
for _, dir := range dirs {
|
||||
fname := filepath.Base(dir)
|
||||
|
||||
if skip := shouldSkip(c, dir, ignore); skip {
|
||||
continue
|
||||
}
|
||||
|
||||
mtype := "os/directory"
|
||||
|
||||
href := fmt.Sprintf("<a href='%s'>%s/</a>", fname, fname)
|
||||
div, err := mkDiv(c, mtype, href, fname, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
html = append(html, div)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
fname := filepath.Base(file)
|
||||
|
||||
if skip := shouldSkip(c, file, ignore); skip {
|
||||
continue
|
||||
}
|
||||
|
||||
mtype, err := getMtype(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
unknown, href, err := getHref(c, file, mtype)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
div, err := mkDiv(c, mtype, href, fname, unknown)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
html = append(html, div)
|
||||
}
|
||||
|
||||
if err := writeIndex(absPath, html, c.String("css")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
allDirs = append(allDirs, strings.Join(dirs, " "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Debugf("generated files in following paths: %s", strings.Join(allDirs, " "))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// getMtype determines file mimetype which helps to arrange HTML tags
|
||||
// appropriate for the file type.
|
||||
func getMtype(fpath string) (string, error) {
|
||||
mtype, err := mimetype.DetectFile(fpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return mtype.String(), nil
|
||||
}
|
||||
|
||||
// genThumb generates an in-memory image thumbnail and encodes it into a base64
|
||||
// representation which is suitable for embedding in HTML pages.
|
||||
func genThumb(c *cli.Context, fpath, caption string) (string, error) {
|
||||
knownFailureExts := []string{".ico", ".svg", ".xcf"}
|
||||
if sliceContains(knownFailureExts, filepath.Ext(fpath)) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
imgSrc, err := imaging.Open(fpath, imaging.AutoOrientation(true))
|
||||
if err != nil {
|
||||
logrus.Debugf("failed to generate thumbnail for %s", fpath)
|
||||
return "", err
|
||||
}
|
||||
|
||||
img := imaging.Thumbnail(imgSrc, 450, 450, imaging.Lanczos)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
png.Encode(buf, img)
|
||||
|
||||
imgBase64Str := base64.StdEncoding.EncodeToString(buf.Bytes())
|
||||
|
||||
return imgBase64Str, nil
|
||||
}
|
||||
|
||||
// getCaption retrieves an embedded image caption via exif-tool. If not
|
||||
// exiftool is installed, we gracefully bail out. The caller is responsible for
|
||||
// handling the alternative.
|
||||
func getCaption(c *cli.Context, fpath string) (string, error) {
|
||||
var caption string
|
||||
|
||||
if !exiftoolInstalled {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
exif, err := exiftool.NewExiftool()
|
||||
if err != nil {
|
||||
return caption, fmt.Errorf("failed to initialise exiftool, saw %v", err)
|
||||
}
|
||||
defer exif.Close()
|
||||
|
||||
for _, finfo := range exif.ExtractMetadata(fpath) {
|
||||
if finfo.Err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for k, v := range finfo.Fields {
|
||||
if k == "Comment" {
|
||||
caption = fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return caption, nil
|
||||
}
|
||||
|
||||
// parseMtype parses a mimetype string to simplify programmatic type lookups.
|
||||
func parseMtype(mtype string) (string, string) {
|
||||
stripCharset := strings.Split(mtype, ";")
|
||||
splitTypes := strings.Split(stripCharset[0], "/")
|
||||
|
||||
ftype, stype := splitTypes[0], splitTypes[1]
|
||||
|
||||
return ftype, stype
|
||||
}
|
||||
|
||||
// sliceContains checks if an element is present in a list.
|
||||
func sliceContains(items []string, target string) bool {
|
||||
for _, item := range items {
|
||||
if item == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// trimFinalNewline trims newlines from the end of bytes just read from files.
|
||||
func trimFinalNewline(contents []byte) string {
|
||||
return strings.TrimSuffix(string(contents), "\n")
|
||||
}
|
||||
|
||||
// getHref 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 getHref(c *cli.Context, fpath string, mtype string) (bool, string, error) {
|
||||
var href string
|
||||
var caption string
|
||||
var unknown bool
|
||||
|
||||
fname := filepath.Base(fpath)
|
||||
ftype, stype := parseMtype(mtype)
|
||||
|
||||
if ftype == "text" {
|
||||
fcontents, err := os.ReadFile(fpath)
|
||||
if err != nil {
|
||||
return unknown, href, err
|
||||
}
|
||||
if stype == "html" {
|
||||
href = fmt.Sprintf("<section id=\"%s\">%s</section>", fname, trimFinalNewline(fcontents))
|
||||
} else {
|
||||
href = fmt.Sprintf("<pre>%s</pre>", trimFinalNewline(fcontents))
|
||||
}
|
||||
} else if ftype == "image" {
|
||||
caption = ""
|
||||
|
||||
exifCaption, err := getCaption(c, fpath)
|
||||
if err != nil {
|
||||
return unknown, href, nil
|
||||
}
|
||||
|
||||
if exifCaption != "" {
|
||||
caption = exifCaption
|
||||
}
|
||||
|
||||
if stype == "gif" {
|
||||
href = fmt.Sprintf("<img class='gif' src=\"%s\" />", fname)
|
||||
} else {
|
||||
thumb, err := genThumb(c, fpath, caption)
|
||||
if err != nil {
|
||||
unknown = true
|
||||
href = fmt.Sprintf("<a class='%s' href=\"%s\">%s</a>", stype, fname, fname)
|
||||
} else {
|
||||
href = fmt.Sprintf(
|
||||
"<figure><a href=\"%s\"><img class='thumbnail' src='data:image/jpg;base64,%s'></a><figcaption>%s</figcaption></figure>",
|
||||
fname, thumb, caption,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if ftype == "application" {
|
||||
if stype == "pdf" {
|
||||
href = fmt.Sprintf("<object data=\"%s\" class='pdf' type='application/pdf'><embed src=\"%s\" type='application/pdf'/></object>", fname, fname)
|
||||
} else {
|
||||
unknown = true
|
||||
href = fmt.Sprintf("<a class='%s' href=\"%s\">%s</a>", stype, fname, fname)
|
||||
}
|
||||
} else if ftype == "audio" {
|
||||
href = fmt.Sprintf("<audio controls> <source src=\"%s\" type='audio/%s'> Your browser does not support the audio element. </audio>", fname, stype)
|
||||
} else if ftype == "video" {
|
||||
href = fmt.Sprintf("<video controls> <source src=\"%s\" type='video/%s'> </video>", fname, stype)
|
||||
} else {
|
||||
unknown = true
|
||||
href = fmt.Sprintf("<a class='%s' href=\"%s\">%s</a>", stype, fname, fname)
|
||||
}
|
||||
|
||||
return unknown, href, nil
|
||||
}
|
||||
|
||||
// mkDiv cosntructs a HTML div for inclusion in the generated index.html.
|
||||
func mkDiv(c *cli.Context, mtype string, href, fname string, unknown bool) (string, error) {
|
||||
var div string
|
||||
|
||||
filename := fmt.Sprintf("<span class='filename'>%s</span>", fname)
|
||||
|
||||
ftype, _ := parseMtype(mtype)
|
||||
|
||||
if ftype == "text" {
|
||||
div = fmt.Sprintf("<div id=\"%s\" class='%s'>%s%s</div>", fname, ftype, href, filename)
|
||||
} else if ftype == "os" {
|
||||
div = fmt.Sprintf("<div id=\"%s\" class='%s'>%s</div>", fname, ftype, href)
|
||||
} else {
|
||||
if unknown {
|
||||
// we really don't know what this is, so the filename is the href and we avoid adding it again
|
||||
div = fmt.Sprintf("<div id=\"%s\" class='%s'>%s</div>", fname, ftype, href)
|
||||
} else {
|
||||
// images, videos, etc. still get a filename
|
||||
div = fmt.Sprintf("<div id=\"%s\" class='%s'>%s%s</div>", fname, ftype, href, filename)
|
||||
}
|
||||
}
|
||||
|
||||
return div, nil
|
||||
}
|
||||
|
||||
// writeIndex writes a new index.html.
|
||||
func writeIndex(fpath string, html []string, styles string) error {
|
||||
body := fmt.Sprintf(htmlBody, generatedInDistribusi, "", strings.Join(html, "\n"))
|
||||
HTMLPath := path.Join(fpath, "index.html")
|
||||
contents := []byte(body)
|
||||
|
||||
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 err
|
||||
}
|
||||
|
||||
body = fmt.Sprintf(htmlBody, generatedInDistribusi, contents, strings.Join(html, "\n"))
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(HTMLPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if err := ioutil.WriteFile(HTMLPath, contents, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
file, err := os.ReadFile(HTMLPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.Contains(string(file), generatedInDistribusi) {
|
||||
if err := ioutil.WriteFile(HTMLPath, contents, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// serveHTTP serves a web server for browsing distribusi.
|
||||
func serveHTTP(fpath string) error {
|
||||
fs := http.FileServer(http.Dir(fpath))
|
||||
http.Handle("/", fs)
|
||||
|
||||
logrus.Info("distribusi live @ http://localhost:3000")
|
||||
|
||||
if err := http.ListenAndServe(":3000", nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// wipeGeneratedFiles removes all distribusi generated files.
|
||||
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 err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// shouldSkip checks if paths should be skipped over.
|
||||
func shouldSkip(c *cli.Context, fpath string, ignore []string) bool {
|
||||
if sliceContains(ignore, filepath.Base(fpath)) {
|
||||
return true
|
||||
}
|
||||
|
||||
fpaths := strings.Split(fpath, "/")
|
||||
for _, part := range fpaths {
|
||||
if strings.HasPrefix(part, ".") {
|
||||
if !c.Bool("hidden") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
22
go.mod
Normal file
22
go.mod
Normal file
@ -0,0 +1,22 @@
|
||||
module varia.zone/distribusi
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4
|
||||
github.com/barasher/go-exiftool v1.7.0
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/gabriel-vasile/mimetype v1.4.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/urfave/cli/v2 v2.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.0.1 // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 // indirect
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da // indirect
|
||||
)
|
44
go.sum
Normal file
44
go.sum
Normal file
@ -0,0 +1,44 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4 h1:vdT7QwBhJJEVNFMBNhRSFDRCB6O16T28VhvqRgqFyn8=
|
||||
github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4/go.mod h1:SvXOG8ElV28oAiG9zv91SDe5+9PfIr7PPccpr8YyXNs=
|
||||
github.com/barasher/go-exiftool v1.7.0 h1:EOGb5D6TpWXmqsnEjJ0ai6+tIW2gZFwIoS9O/33Nixs=
|
||||
github.com/barasher/go-exiftool v1.7.0/go.mod h1:F9s/a3uHSM8YniVfwF+sbQUtP8Gmh9nyzigNF+8vsWo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
|
||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
|
||||
github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro=
|
||||
github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 h1:Ugb8sMTWuWRC3+sz5WeN/4kejDx9BvIwnPUiJBjJE+8=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
Loading…
Reference in New Issue
Block a user