Skip to content

Commit

Permalink
Fix #12 - embed.FS broken on Windows (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
vorlif committed Sep 4, 2022
1 parent 3c880d5 commit 681de3d
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 43 deletions.
8 changes: 4 additions & 4 deletions internal/util/osfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ type DirFS string
var _ fs.FS = (*DirFS)(nil)

func (dir DirFS) Open(name string) (fs.File, error) {
return os.Open(filepath.Join(string(dir), name))
return os.Open(filepath.Join(string(dir), filepath.FromSlash(name)))
}

func (dir DirFS) Stat(name string) (fs.FileInfo, error) {
return os.Stat(filepath.Join(string(dir), name))
return os.Stat(filepath.Join(string(dir), filepath.FromSlash(name)))
}

func (dir DirFS) ReadDir(name string) ([]fs.DirEntry, error) {
return os.ReadDir(filepath.Join(string(dir), name))
return os.ReadDir(filepath.Join(string(dir), filepath.FromSlash(name)))
}

func (dir DirFS) ReadFile(name string) ([]byte, error) {
return os.ReadFile(filepath.Join(string(dir), name))
return os.ReadFile(filepath.Join(string(dir), filepath.FromSlash(name)))
}

func (dir DirFS) Glob(pattern string) ([]string, error) {
Expand Down
24 changes: 24 additions & 0 deletions internal/util/osfs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/stretchr/testify/require"
)

var testdataDir = filepath.FromSlash("../../testdata/")

func TestDirFS(t *testing.T) {
dir, err := os.MkdirTemp("", "spreakTest")
require.NoError(t, err)
Expand Down Expand Up @@ -50,3 +52,25 @@ func TestDirFS(t *testing.T) {
assert.NoError(t, err)
assert.NoError(t, file2.Close())
}

func TestDirFS_CrossPlatform(t *testing.T) {
fsys := DirFS(testdataDir)

t.Run("stat", func(t *testing.T) {
info, err := fsys.Stat("structure/es/")
assert.NoError(t, err)
assert.True(t, info.IsDir())

info, err = fsys.Stat("structure/es/helloworld.po")
assert.NoError(t, err)
assert.Equal(t, "helloworld.po", info.Name())
})

t.Run("open", func(t *testing.T) {
f, err := fsys.Open("structure/es/helloworld.po")
if assert.NoError(t, err) {
defer f.Close()
}
})

}
57 changes: 29 additions & 28 deletions loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"io"
"io/fs"
"os"
"path/filepath"
"path"

"golang.org/x/text/language"

Expand Down Expand Up @@ -110,15 +110,15 @@ func NewFilesystemLoader(opts ...FsOption) (*FilesystemLoader, error) {
func (l *FilesystemLoader) Load(lang language.Tag, domain string) (catalog.Catalog, error) {

for i, extension := range l.extensions {
path, errP := l.resolver.Resolve(l.fsys, extension, lang, domain)
resolvedPath, errP := l.resolver.Resolve(l.fsys, extension, lang, domain)
if errP != nil {
if errors.Is(errP, os.ErrNotExist) {
continue
}
return nil, errP
}

f, errF := l.fsys.Open(path)
f, errF := l.fsys.Open(resolvedPath)
if errF != nil {
if f != nil {
_ = f.Close()
Expand All @@ -134,7 +134,7 @@ func (l *FilesystemLoader) Load(lang language.Tag, domain string) (catalog.Catal

catl, errC := l.decoder[i].Decode(lang, domain, data)
if errC != nil {
return nil, fmt.Errorf("spreak: file %s could not be decoded: %w", path, errC)
return nil, fmt.Errorf("spreak: file %s could not be decoded: %w", resolvedPath, errC)
}
return catl, nil
}
Expand All @@ -148,6 +148,8 @@ func (l *FilesystemLoader) addDecoder(ext string, decoder catalog.Decoder) {
}

// WithFs stores a fs.FS as filesystem.
// The file system can only be accessed with paths which are separated by slashes (Unix style).
// If a different behavior is desired, a separate resolver must be stored with WithResolver.
// Lets the creation of the FilesystemLoader fail, if a filesystem was already deposited.
func WithFs(fsys fs.FS) FsOption {
return func(l *FilesystemLoader) error {
Expand Down Expand Up @@ -230,67 +232,66 @@ func WithCategory(category string) ResolverOption {

func (r *defaultResolver) Resolve(fsys fs.FS, extension string, tag language.Tag, domain string) (string, error) {
for _, lang := range ExpandLanguage(tag) {
path, err := r.searchFileForLanguageName(fsys, lang, domain, extension)
searchPath, err := r.searchFileForLanguageName(fsys, lang, domain, extension)
if errors.Is(err, os.ErrNotExist) {
continue
}

return path, nil
return searchPath, nil
}

return "", os.ErrNotExist
}

func (r *defaultResolver) searchFileForLanguageName(fsys fs.FS, locale, domain, ext string) (string, error) {

if domain != "" {
// .../locale/category/domain.mo
path := filepath.Join(locale, r.category, domain+ext)
if _, err := fs.Stat(fsys, path); err == nil {
return path, nil
searchPath := path.Join(locale, r.category, domain+ext)
if _, err := fs.Stat(fsys, searchPath); err == nil {
return searchPath, nil
}
}

if r.search {
if domain != "" {
// .../locale/LC_MESSAGES/domain.mo
path := filepath.Join(locale, "LC_MESSAGES", domain+ext)
if _, err := fs.Stat(fsys, path); err == nil {
return path, nil
searchPath := path.Join(locale, "LC_MESSAGES", domain+ext)
if _, err := fs.Stat(fsys, searchPath); err == nil {
return searchPath, nil
}

// .../locale/domain.mo
path = filepath.Join(locale, domain+ext)
if _, err := fs.Stat(fsys, path); err == nil {
return path, nil
searchPath = path.Join(locale, domain+ext)
if _, err := fs.Stat(fsys, searchPath); err == nil {
return searchPath, nil
}

// .../domain/locale.mo
path = filepath.Join(domain, locale+ext)
if _, err := fs.Stat(fsys, path); err == nil {
return path, nil
searchPath = path.Join(domain, locale+ext)
if _, err := fs.Stat(fsys, searchPath); err == nil {
return searchPath, nil
}
}

// .../locale.mo
path := filepath.Join(locale + ext)
if _, err := fs.Stat(fsys, path); err == nil {
return path, nil
searchPath := path.Join(locale + ext)
if _, err := fs.Stat(fsys, searchPath); err == nil {
return searchPath, nil
}

if r.category != "" {
// .../category/locale.mo
path = filepath.Join(r.category, locale+ext)
if _, err := fs.Stat(fsys, path); err == nil {
return path, nil
searchPath = path.Join(r.category, locale+ext)
if _, err := fs.Stat(fsys, searchPath); err == nil {
return searchPath, nil
}
}

if r.category != "LC_MESSAGES" {
// .../LC_MESSAGES/locale.mo
path = filepath.Join("LC_MESSAGES", locale+ext)
if _, err := fs.Stat(fsys, path); err == nil {
return path, nil
searchPath = path.Join("LC_MESSAGES", locale+ext)
if _, err := fs.Stat(fsys, searchPath); err == nil {
return searchPath, nil
}
}
}
Expand Down
65 changes: 54 additions & 11 deletions loader_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package spreak

import (
"embed"
"io/fs"
"os"
"path/filepath"
"path"
"strconv"
"testing"
"testing/fstest"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand All @@ -14,6 +17,9 @@ import (
"github.com/vorlif/spreak/internal/util"
)

//go:embed testdata/structure/*
var embedTestFS embed.FS

func TestNewFilesystemLoader(t *testing.T) {
t.Run("error is returned when a nil option is passed", func(t *testing.T) {
fsLoader, err := NewFilesystemLoader(WithPath(testdataStructureDir), nil)
Expand Down Expand Up @@ -95,11 +101,11 @@ func TestLoadPo(t *testing.T) {
}{
{
language.German, "b", "my_category",
false, filepath.Join("de", "my_category", "b.po"), PoFile,
false, path.Join("de", "my_category", "b.po"), PoFile,
},
{
language.German, "a", "",
false, filepath.Join("de", "LC_MESSAGES", "a.po"), PoFile,
false, path.Join("de", "LC_MESSAGES", "a.po"), PoFile,
},
{
language.French, "a", "",
Expand All @@ -119,14 +125,14 @@ func TestLoadPo(t *testing.T) {
reducer, errR := NewDefaultResolver(WithCategory(tt.category))
require.NoError(t, errR)
require.NotNil(t, reducer)
path, err := reducer.Resolve(fsys, tt.extension, tt.lang, tt.domain)
resolvedPath, err := reducer.Resolve(fsys, tt.extension, tt.lang, tt.domain)
if tt.wantErr {
assert.Error(t, err)
continue
}

if assert.NoError(t, err, "Resolve(... %v %v %v %v...", tt.lang, tt.domain, tt.category, tt.extension) {
assert.Equal(t, tt.wantPath, path)
assert.Equal(t, tt.wantPath, resolvedPath)
}
}

Expand Down Expand Up @@ -162,14 +168,14 @@ func TestReduceMoFiles(t *testing.T) {
}

for _, tt := range tests {
path, err := reducer.Resolve(fsys, tt.extension, tt.lang, tt.domain)
resolvedPath, err := reducer.Resolve(fsys, tt.extension, tt.lang, tt.domain)
if tt.wantErr {
assert.Error(t, err)
continue
}

if assert.NoError(t, err) {
assert.Equal(t, tt.wantPath, path)
assert.Equal(t, tt.wantPath, resolvedPath)
}
}
}
Expand All @@ -190,11 +196,11 @@ func TestDisableSearch(t *testing.T) {
},
{
language.German, "a", "LC_MESSAGES",
false, filepath.Join("de", "LC_MESSAGES", "a.po"), PoFile,
false, path.Join("de", "LC_MESSAGES", "a.po"), PoFile,
},
{
language.German, "b", "my_category",
false, filepath.Join("de", "my_category", "b.po"), PoFile,
false, path.Join("de", "my_category", "b.po"), PoFile,
},
{
language.English, "domain", "cat",
Expand All @@ -207,14 +213,14 @@ func TestDisableSearch(t *testing.T) {
require.NoError(t, errR)
require.NotNil(t, reducer)

path, err := reducer.Resolve(fsys, tt.extension, tt.lang, tt.domain)
resolvedPath, err := reducer.Resolve(fsys, tt.extension, tt.lang, tt.domain)
if tt.wantErr {
assert.Error(t, err, idx)
continue
}

if assert.NoError(t, err, idx) {
assert.Equal(t, tt.wantPath, path)
assert.Equal(t, tt.wantPath, resolvedPath)
}
}
}
Expand Down Expand Up @@ -298,6 +304,43 @@ func TestWithFs(t *testing.T) {
assert.Error(t, err)
require.Nil(t, fl)
})

t.Run("embeddedFS", func(t *testing.T) {
testFS, err := fs.Sub(embedTestFS, "testdata/structure")

require.NoError(t, err)
require.NoError(t, fstest.TestFS(testFS, "de_AT.po", "es/helloworld.po"))

resolver, err := NewDefaultResolver()
require.NoError(t, err)

tests := []struct {
lang language.Tag
domain string
wantErr bool
wantPath string
}{
{language.Spanish, "", true, ""},
{language.Spanish, "helloworld", false, "es/helloworld.po"},
{language.Zulu, "", true, ""},
{language.German, "a", false, "de/LC_MESSAGES/a.po"},
{language.German, "c", false, "de/c.po"},
{language.German, "d", true, ""},
}

for i, tt := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
getPath, getErr := resolver.Resolve(testFS, PoFile, tt.lang, tt.domain)
if tt.wantErr {
assert.Error(t, getErr)
assert.Empty(t, getPath)
} else {
assert.NoError(t, getErr)
assert.Equal(t, tt.wantPath, getPath)
}
})
}
})
}

func TestWithResolver(t *testing.T) {
Expand Down
25 changes: 25 additions & 0 deletions testdata/structure/es/helloworld.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE VERSION package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-05-06 14:53+0200\n"
"PO-Revision-Date: 2022-05-06 14:57+0200\n"
"Last-Translator: Florian Vogt\n"
"Language-Team: \n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.0.1\n"

#. TRANSLATORS: This comment is automatically extracted by xspreak
#. and can be used to leave useful hints for the translators.
#: ../helloworld/main.go:26
msgid "Hello world"
msgstr "Hola Mundo"

0 comments on commit 681de3d

Please sign in to comment.