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

add ziptree package for sharing code #63

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
language: go

go:
- 1.9.x
- 1.10.3
- 1.11.x
- 1.12.1

install:
- go build -v
Expand Down
46 changes: 5 additions & 41 deletions fs/fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,17 @@ package fs

import (
"archive/zip"
"bytes"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"reflect"
"sort"
"strings"
"sync"
"testing"
"time"

"github.com/rakyll/statik/ziptree"
)

type wantFile struct {
Expand Down Expand Up @@ -388,46 +387,11 @@ func BenchmarkOpen(b *testing.B) {
// mustZipTree walks on the source path and returns the zipped file contents
// as a string. Panics on any errors.
func mustZipTree(srcPath string) string {
var out bytes.Buffer
w := zip.NewWriter(&out)
if err := filepath.Walk(srcPath, func(path string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
// Ignore directories and hidden files.
// No entry is needed for directories in a zip file.
// Each file is represented with a path, no directory
// entities are required to build the hierarchy.
if fi.IsDir() || strings.HasPrefix(fi.Name(), ".") {
return nil
}
relPath, err := filepath.Rel(srcPath, path)
if err != nil {
return err
}
b, err := ioutil.ReadFile(path)
if err != nil {
return err
}
fHeader, err := zip.FileInfoHeader(fi)
if err != nil {
return err
}
fHeader.Name = filepath.ToSlash(relPath)
fHeader.Method = zip.Deflate
f, err := w.CreateHeader(fHeader)
if err != nil {
return err
}
_, err = f.Write(b)
return err
}); err != nil {
panic(err)
}
if err := w.Close(); err != nil {
bs, err := ziptree.Zip(srcPath)
if err != nil {
panic(err)
}
return out.String()
return string(bs)
}

// mustReadFile returns the file contents. Panics on any errors.
Expand Down
100 changes: 16 additions & 84 deletions statik.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@
package main

import (
"archive/zip"
"bytes"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"time"

"github.com/rakyll/statik/ziptree"
)

const (
Expand Down Expand Up @@ -119,65 +119,16 @@ func rename(src, dest string) error {
// Generates source registers generated zip contents data to
// be read by the statik/fs HTTP file system.
func generateSource(srcPath string) (file *os.File, err error) {
var (
buffer bytes.Buffer
zipWriter io.Writer
)

zipWriter = &buffer
f, err := ioutil.TempFile("", namePackage)
if err != nil {
return
var opts []ziptree.Option
if *flagNoCompress {
opts = append(opts, ziptree.Compress(false))
}

zipWriter = io.MultiWriter(zipWriter, f)
defer f.Close()

w := zip.NewWriter(zipWriter)
if err = filepath.Walk(srcPath, func(path string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
// Ignore directories and hidden files.
// No entry is needed for directories in a zip file.
// Each file is represented with a path, no directory
// entities are required to build the hierarchy.
if fi.IsDir() || strings.HasPrefix(fi.Name(), ".") {
return nil
}
relPath, err := filepath.Rel(srcPath, path)
if err != nil {
return err
}
b, err := ioutil.ReadFile(path)
if err != nil {
return err
}
fHeader, err := zip.FileInfoHeader(fi)
if err != nil {
return err
}
if *flagNoMtime {
// Always use the same modification time so that
// the output is deterministic with respect to the file contents.
// Do NOT use fHeader.Modified as it only works on go >= 1.10
fHeader.SetModTime(mtimeDate)
}
fHeader.Name = filepath.ToSlash(relPath)
if !*flagNoCompress {
fHeader.Method = zip.Deflate
}
f, err := w.CreateHeader(fHeader)
if err != nil {
return err
}
_, err = f.Write(b)
return err
}); err != nil {
return
if *flagNoMtime {
opts = append(opts, ziptree.FixMtime(mtimeDate))
}
if err = w.Close(); err != nil {
return
bs, err := ziptree.Zip(srcPath, opts...)
if err != nil {
return nil, err
}

var tags string
Expand All @@ -202,41 +153,22 @@ import (

func init() {
data := "`, tags, comment, namePackage)
FprintZipData(&qb, buffer.Bytes())
ziptree.FprintZipData(&qb, bs)
fmt.Fprint(&qb, `"
fs.Register(data)
}
`)

f, err := ioutil.TempFile("", namePackage)
if err != nil {
return nil, err
}
f.Close()
if err = ioutil.WriteFile(f.Name(), qb.Bytes(), 0644); err != nil {
return
}
return f, nil
}

// FprintZipData converts zip binary contents to a string literal.
func FprintZipData(dest *bytes.Buffer, zipData []byte) {
for _, b := range zipData {
if b == '\n' {
dest.WriteString(`\n`)
continue
}
if b == '\\' {
dest.WriteString(`\\`)
continue
}
if b == '"' {
dest.WriteString(`\"`)
continue
}
if (b >= 32 && b <= 126) || b == '\t' {
dest.WriteByte(b)
continue
}
fmt.Fprintf(dest, "\\x%02x", b)
}
}

// comment lines prefixes each line in lines with "// ".
func commentLines(lines string) string {
lines = "// " + strings.Replace(lines, "\n", "\n// ", -1)
Expand Down
Empty file added testdata/ziptree/hello
Empty file.
33 changes: 33 additions & 0 deletions ziptree/fprint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package ziptree

import (
"bufio"
"fmt"
"io"
)

// FprintZipData converts zip binary contents to a string literal.
func FprintZipData(w io.Writer, zipData []byte) error {
dest := bufio.NewWriter(w)
for _, b := range zipData {
if err := func(b byte) error {
switch b {
case '\n':
_, err := dest.WriteString(`\n`)
return err
case '\\', '"':
_, err := dest.WriteString(`\` + string(b))
return err
default:
if (b >= 32 && b <= 126) || b == '\t' {
return dest.WriteByte(b)
}
_, err := fmt.Fprintf(dest, "\\x%02x", b)
return err
}
}(b); err != nil {
return err
}
}
return dest.Flush()
}
95 changes: 95 additions & 0 deletions ziptree/zipper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Package ziptree contains code to zip a directory tree and write it out.
package ziptree

import (
"archive/zip"
"bytes"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
)

// Option for zipping
type Option func(*config)

type config struct {
Compress bool
Mtime time.Time
}

func newConfig() *config {
return &config{Compress: true}
}

// Compress is an Option to compress a zip or not
func Compress(f bool) Option {
return func(c *config) {
c.Compress = f
}
}

// FixMtime is an Option to fix mtimes of the file in the zip
func FixMtime(t time.Time) Option {
return func(c *config) {
c.Mtime = t
}
}

// Zip a directory tree
func Zip(srcPath string, opts ...Option) ([]byte, error) {
c := newConfig()
for _, opt := range opts {
opt(c)
}

var buffer bytes.Buffer
w := zip.NewWriter(&buffer)
if err := filepath.Walk(srcPath, func(path string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
// Ignore directories and hidden files.
// No entry is needed for directories in a zip file.
// Each file is represented with a path, no directory
// entities are required to build the hierarchy.
if fi.IsDir() || strings.HasPrefix(fi.Name(), ".") {
return nil
}
relPath, err := filepath.Rel(srcPath, path)
if err != nil {
return err
}
b, err := ioutil.ReadFile(path)
if err != nil {
return err
}
fHeader, err := zip.FileInfoHeader(fi)
if err != nil {
return err
}
if !c.Mtime.IsZero() {
// Always use the same modification time so that
// the output is deterministic with respect to the file contents.
// Do NOT use fHeader.Modified as it only works on go >= 1.10
fHeader.SetModTime(c.Mtime)
}
fHeader.Name = filepath.ToSlash(relPath)
if c.Compress {
fHeader.Method = zip.Deflate
}
f, err := w.CreateHeader(fHeader)
if err != nil {
return err
}
_, err = f.Write(b)
return err
}); err != nil {
return nil, err
}
if err := w.Close(); err != nil {
return nil, err
}
return buffer.Bytes(), nil
}