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

Support multiple assets #94

Merged
merged 5 commits into from
Feb 7, 2020
Merged
Show file tree
Hide file tree
Changes from 4 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
23 changes: 18 additions & 5 deletions fs/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
"time"
)

var zipData string
var zipData = map[string]string{}

// file holds unzipped read-only file contents and file metadata.
type file struct {
Expand All @@ -47,16 +47,29 @@ type statikFS struct {
// Register registers zip contents data, later used to initialize
// the statik file system.
func Register(data string) {
zipData = data
RegisterWithName("default", data)
}

// New creates a new file system with the registered zip contents data.
// RegisterWithName registers zip contents data and set asset namespace,
// later used to initialize the statik file system.
func RegisterWithName(assetNamespace string, data string) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we also change this to RegisterWithNamespace?

zipData[assetNamespace] = data
}

// New creates a new file system with the default registered zip contents data.
// It unzips all files and stores them in an in-memory map.
func New() (http.FileSystem, error) {
if zipData == "" {
return NewWithName("default")
}

// NewWithName creates a new file system with the registered zip contents data.
// It unzips all files and stores them in an in-memory map.
func NewWithName(assetNamespace string) (http.FileSystem, error) {
asset, ok := zipData[assetNamespace]
if !ok {
return nil, errors.New("statik/fs: no zip data registered")
}
zipReader, err := zip.NewReader(strings.NewReader(zipData), int64(len(zipData)))
zipReader, err := zip.NewReader(strings.NewReader(asset), int64(len(asset)))
if err != nil {
return nil, err
}
Expand Down
65 changes: 65 additions & 0 deletions fs/fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package fs
import (
"archive/zip"
"bytes"
"errors"
"io"
"io/ioutil"
"os"
Expand All @@ -39,6 +40,70 @@ type wantFile struct {
err error
}

func TestRegisterWithName(t *testing.T) {
tests := []struct {
description string
assetName string
zipData string
condition func() error
}{
{
description: "RegisterWithName() should set zipData with assetName to be key",
assetName: "file",
zipData: "file test",
condition: func() error {
data, ok := zipData["file"]
if !ok {
return errors.New("fail to register zipData")
}
if data != "file test" {
return errors.New("fail to register zipData[\"file\"]")
}
return nil
},
},
{
description: "zipData[\"default\"] should be able to open by Open()",
assetName: "default",
zipData: mustZipTree("../testdata/file"),
condition: func() error {
fs, err := New()
if err != nil {
return err
}
if _, err := fs.Open("/file.txt"); err != nil {
return err
}
return nil
},
},
{
description: "zipData[\"foo\"] should be able to open by OpenWithName(\"foo\")",
assetName: "foo",
zipData: mustZipTree("../testdata/file"),
condition: func() error {
fs, err := NewWithName("foo")
if err != nil {
return err
}
if _, err := fs.Open("/file.txt"); err != nil {
return err
}
return nil
},
},
}
for _, tc := range tests {
t.Run(tc.description, func(t *testing.T) {
RegisterWithName(tc.assetName, tc.zipData)
if err := tc.condition(); err != nil {
t.Error(err)
}
})
delete(zipData, tc.assetName)
}
}

func TestOpen(t *testing.T) {
fileTxtHeader := mustFileHeader("../testdata/file/file.txt")
pixelGifHeader := mustFileHeader("../testdata/image/pixel.gif")
Expand Down
51 changes: 47 additions & 4 deletions statik.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"path/filepath"
"strings"
"time"
"unicode"
)

const (
Expand All @@ -44,6 +45,7 @@ var (
flagForce = flag.Bool("f", false, "Overwrite destination file if it already exists.")
flagTags = flag.String("tags", "", "Write build constraint tags")
flagPkg = flag.String("p", "statik", "Name of the generated package")
flagAstName = flag.String("a", "default", "Set static assets name")
flagPkgCmt = flag.String("c", "Package statik contains static assets.", "The package comment. An empty value disables this comment.\n")
)

Expand Down Expand Up @@ -190,6 +192,12 @@ func generateSource(srcPath string) (file *os.File, err error) {
comment = "\n" + commentLines(*flagPkgCmt)
}

// e.g.)
// assetNamespaceIdentify is "AbcDeF_G"
// when assetNamespace is "abc de f-g"
assetNamespace := *flagAstName
assetNamespaceIdentify := toSymbolSafe(assetNamespace)

// then embed it as a quoted string
var qb bytes.Buffer
fmt.Fprintf(&qb, `// Code generated by statik. DO NOT EDIT.
Expand All @@ -200,13 +208,20 @@ import (
"github.com/rakyll/statik/fs"
)

`, tags, comment, namePackage)
if assetNamespace != "default" {
fmt.Fprintf(&qb, `
const %s = "%s" // static asset namespace
`, assetNamespaceIdentify, assetNamespace)
}
fmt.Fprint(&qb, `
func init() {
data := "`, tags, comment, namePackage)
data := "`)
FprintZipData(&qb, buffer.Bytes())
fmt.Fprint(&qb, `"
fs.Register(data)
fmt.Fprintf(&qb, `"
fs.RegisterWithName("%s", data)
}
`)
`, assetNamespace)

if err = ioutil.WriteFile(f.Name(), qb.Bytes(), 0644); err != nil {
return
Expand Down Expand Up @@ -248,3 +263,31 @@ func exitWithError(err error) {
fmt.Println(err)
os.Exit(1)
}

// convert src to symbol safe string with upper camel case
func toSymbolSafe(str string) string {
isBeforeRuneNoGeneralCase := false
replace := func(r rune) rune {
if unicode.IsLetter(r) {
if isBeforeRuneNoGeneralCase {
isBeforeRuneNoGeneralCase = true
return r
} else {
isBeforeRuneNoGeneralCase = true
return unicode.ToTitle(r)
}
} else if unicode.IsDigit(r) {
if isBeforeRuneNoGeneralCase {
isBeforeRuneNoGeneralCase = true
return r
} else {
isBeforeRuneNoGeneralCase = false
return -1
}
} else {
isBeforeRuneNoGeneralCase = false
return -1
}
}
return strings.TrimSpace(strings.Map(replace, str))
}
21 changes: 21 additions & 0 deletions statik_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package main

import "testing"

func TestToSymbolSafe(t *testing.T) {
testCase := [][]string{
{"abc", "Abc"},
{"_abc", "Abc"},
{"3abc", "Abc"},
{"abc3", "Abc3"},
{"/abc", "Abc"},
{"abc abc", "AbcAbc"},
}
for i, test := range testCase {
got := toSymbolSafe(test[0])
wont := test[1]
if got != wont {
t.Errorf("#%02d toSymbolSafe(%s) => %s != %s", i, test[0], got, wont)
}
}
}