Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dirs #46

Merged
merged 7 commits into from
Aug 21, 2018
Merged

Dirs #46

Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

[![Build Status](https://travis-ci.org/rakyll/statik.svg?branch=master)](https://travis-ci.org/rakyll/statik)

This is a fork of github.com/rakyll/statik - I wanted working Readdir.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this line please.


statik allows you to embed a directory of static files into your Go binary to be later served from an http.FileSystem.

Is this a crazy idea? No, not necessarily. If you're building a tool that has a Web component, you typically want to serve some images, CSS and JavaScript. You like the comfort of distributing a single binary, so you don't want to mess with deploying them elsewhere. If your static files are not large in size and will be browsed by a few people, statik is a solution you are looking for.
Expand Down
70 changes: 46 additions & 24 deletions fs/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ import (
"io/ioutil"
"net/http"
"os"
"path"
"strings"
"time"
)

var zipData string
Expand All @@ -32,6 +34,7 @@ var zipData string
type file struct {
os.FileInfo
data []byte
fs *statikFS
}

type statikFS struct {
Expand All @@ -54,20 +57,39 @@ func New() (http.FileSystem, error) {
if err != nil {
return nil, err
}
files := make(map[string]file)
files := make(map[string]file, len(zipReader.File))
fs := &statikFS{files: files}
for _, zipFile := range zipReader.File {
unzipped, err := unzip(zipFile)
fi := zipFile.FileInfo()
f := file{FileInfo: fi, fs: fs}
f.data, err = unzip(zipFile)
if err != nil {
return nil, fmt.Errorf("statik/fs: error unzipping file %q: %s", zipFile.Name, err)
}
files["/"+zipFile.Name] = file{
FileInfo: zipFile.FileInfo(),
data: unzipped,
files["/"+zipFile.Name] = f
}
for fn := range files {
dn := path.Dir(fn)
if _, ok := files[dn]; !ok {
files[dn] = file{FileInfo: dirInfo{dn}, fs: fs}
}
}
return &statikFS{files: files}, nil
return fs, nil
}

var _ = os.FileInfo(dirInfo{})

type dirInfo struct {
name string
}

func (di dirInfo) Name() string { return di.name }
func (di dirInfo) Size() int64 { return 0 }
func (di dirInfo) Mode() os.FileMode { return 0755 | os.ModeDir }
func (di dirInfo) ModTime() time.Time { return time.Time{} }
func (di dirInfo) IsDir() bool { return true }
func (di dirInfo) Sys() interface{} { return nil }

func unzip(zf *zip.File) ([]byte, error) {
rc, err := zf.Open()
if err != nil {
Expand All @@ -83,26 +105,17 @@ func unzip(zf *zip.File) ([]byte, error) {
// in the requested directory, if that file exists.
func (fs *statikFS) Open(name string) (http.File, error) {
name = strings.Replace(name, "//", "/", -1)
f, ok := fs.files[name]
if ok {
return newHTTPFile(f, false), nil
if f, ok := fs.files[name]; ok {
return newHTTPFile(f), nil
}
// The file doesn't match, but maybe it's a directory,
// thus we should look for index.html
indexName := strings.Replace(name+"/index.html", "//", "/", -1)
f, ok = fs.files[indexName]
if !ok {
return nil, os.ErrNotExist
}
return newHTTPFile(f, true), nil
return nil, os.ErrNotExist
}

func newHTTPFile(file file, isDir bool) *httpFile {
return &httpFile{
file: file,
reader: bytes.NewReader(file.data),
isDir: isDir,
func newHTTPFile(file file) *httpFile {
if file.IsDir() {
return &httpFile{file: file, isDir: true}
}
return &httpFile{file: file, reader: bytes.NewReader(file.data)}
}

// httpFile represents an HTTP file and acts as a bridge
Expand Down Expand Up @@ -137,8 +150,17 @@ func (f *httpFile) IsDir() bool {
// Readdir returns an empty slice of files, directory
// listing is disabled.
func (f *httpFile) Readdir(count int) ([]os.FileInfo, error) {
// directory listing is disabled.
return make([]os.FileInfo, 0), nil
var fis []os.FileInfo
if !f.isDir {
return fis, nil
}
prefix := f.Name()
for fn, f := range f.file.fs.files {
if strings.HasPrefix(fn, prefix) && len(fn) > len(prefix) {
fis = append(fis, f.FileInfo)
}
}
return fis, nil
}

func (f *httpFile) Close() error {
Expand Down
97 changes: 97 additions & 0 deletions fs/walk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2018 Tamás Gulácsi. All Rights Reserved.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert the license header to the previous.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is totally new, but I don't object.

//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package fs

import (
"bytes"
"errors"
"io"
"net/http"
"os"
"path"
)

var SkipDir = errors.New("skip dir")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be more

var DirectorySkippedError

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using filepath.SkipDir?
I copied filepath.WalkFunc and filepath.SkipDir, you suggested just referencing it - keep it consistent, and use filepath.SkipDir.


// WalkFunc is the type of the function called for each file or directory visited by Walk.
// The path argument contains the argument to Walk as a prefix;
// that is, if Walk is called with "dir", which is a directory containing the file "a",
// the walk function will be called with argument "dir/a".
// The info argument is the os.FileInfo for the named path.
//
// If there was a problem walking to the file or directory named by path,
// the incoming error will describe the problem and the function
// can decide how to handle that error (and Walk will not descend into that directory).
// If an error is returned, processing stops.
// The sole exception is when the function returns the special value SkipDir.
// If the function returns SkipDir when invoked on a directory,
// Walk skips the directory's contents entirely.
// If the function returns SkipDir when invoked on a non-directory file,
// Walk skips the remaining files in the containing directory.
type WalkFunc func(path string, info os.FileInfo, err error) error
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about just using filepath.WalkFunc instead of introducing this type?


// Walk walks the file tree rooted at root,
// calling walkFn for each file or directory in the tree, including root.
// All errors that arise visiting files and directories are filtered by walkFn.
func Walk(hfs http.FileSystem, root string, walkFn WalkFunc) error {
dh, err := hfs.Open(root)
if err != nil {
return err
}
di, err := dh.Stat()
if err != nil {
return err
}
fis, err := dh.Readdir(-1)
dh.Close()
if err = walkFn(root, di, err); err != nil {
if err == SkipDir {
return nil
}
return err
}
for _, fi := range fis {
fn := path.Join(root, fi.Name())
if fi.IsDir() {
if err = Walk(hfs, fn, walkFn); err != nil {
if err == SkipDir {
continue
}
return err
}
continue
}
if err = walkFn(fn, fi, nil); err != nil {
if err == SkipDir {
continue
}
return err
}
}
return nil
}

// ReadFile reads the contents of the file of hfs specified by name.
// Just as ioutil.ReadFile does.
func ReadFile(hfs http.FileSystem, name string) ([]byte, error) {
fh, err := hfs.Open(name)
if err != nil {
return nil, err
}
var buf bytes.Buffer
_, err = io.Copy(&buf, fh)
fh.Close()
return buf.Bytes(), err
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module github.com/rakyll/statik
Empty file added go.sum
Empty file.
2 changes: 1 addition & 1 deletion statik.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func generateSource(srcPath string) (file *os.File, err error) {
if *flagNoMtime {
// Always use the same modification time so that
// the output is deterministic with respect to the file contents.
fHeader.SetModTime(mtimeDate)
fHeader.Modified = mtimeDate
}
fHeader.Name = filepath.ToSlash(relPath)
if !*flagNoCompress {
Expand Down