Skip to content

Commit

Permalink
Support multiple assets (#94)
Browse files Browse the repository at this point in the history
* Support multiple assets

* Add unit tests of RegisterWithName()

* fix logic to convert asset name

* Replace 'asset name' to 'asset namespace'

Co-authored-by: José Carlos Chávez <jcchavezs@gmail.com>
  • Loading branch information
shumon84 and jcchavezs authored Feb 7, 2020
1 parent 6b2f3ee commit e1285c6
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 10 deletions.
23 changes: 18 additions & 5 deletions fs/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,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 @@ -48,16 +48,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) {
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
54 changes: 49 additions & 5 deletions statik.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"path/filepath"
"strings"
"time"
"unicode"
)

const nameSourceFile = "statik.go"
Expand All @@ -43,7 +44,8 @@ var (
flagForce = flag.Bool("f", false, "")
flagTags = flag.String("tags", "", "")
flagPkg = flag.String("p", "statik", "")
flagPkgCmt = flag.String("c", "", "")
flagAstName = flag.String("a", "default", "")
flagPkgCmt = flag.String("c", "", "")
flagInclude = flag.String("include", "*.*", "")
)

Expand All @@ -53,6 +55,7 @@ Options:
-src The source directory of the assets. "public" by default.
-dest The destination directory of the generated package. "." by default.
-a Support different asset namespaces
-f Override destination if it already exists, false by default.
-include Wildcard to filter files to include, "*.*" by default.
-m Ignore modification times on files, false by default.
Expand Down Expand Up @@ -259,6 +262,12 @@ func generateSource(srcPath string, includes 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 @@ -269,13 +278,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 @@ -318,6 +334,34 @@ func exitWithError(err error) {
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))
}

func help() {
fmt.Println(helpText)
os.Exit(1)
Expand Down
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)
}
}
}

0 comments on commit e1285c6

Please sign in to comment.