github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/hugofs/rootmapping_fs_test.go (about)

     1  // Copyright 2019 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package hugofs
    15  
    16  import (
    17  	"fmt"
    18  	"io"
    19  	"path/filepath"
    20  	"sort"
    21  	"testing"
    22  
    23  	"github.com/gohugoio/hugo/config"
    24  	"github.com/gohugoio/hugo/hugofs/glob"
    25  
    26  	qt "github.com/frankban/quicktest"
    27  	"github.com/gohugoio/hugo/htesting"
    28  	"github.com/spf13/afero"
    29  )
    30  
    31  func TestLanguageRootMapping(t *testing.T) {
    32  	c := qt.New(t)
    33  	v := config.New()
    34  	v.Set("contentDir", "content")
    35  
    36  	fs := NewBaseFileDecorator(afero.NewMemMapFs())
    37  
    38  	c.Assert(afero.WriteFile(fs, filepath.Join("content/sv/svdir", "main.txt"), []byte("main sv"), 0755), qt.IsNil)
    39  
    40  	c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/mysvblogcontent", "sv-f.txt"), []byte("some sv blog content"), 0755), qt.IsNil)
    41  	c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/myenblogcontent", "en-f.txt"), []byte("some en blog content in a"), 0755), qt.IsNil)
    42  	c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/mysvblogcontent/d1", "sv-d1-f.txt"), []byte("some sv blog content"), 0755), qt.IsNil)
    43  	c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/myenblogcontent/d1", "en-d1-f.txt"), []byte("some en blog content in a"), 0755), qt.IsNil)
    44  
    45  	c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/myotherenblogcontent", "en-f2.txt"), []byte("some en content"), 0755), qt.IsNil)
    46  	c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/mysvdocs", "sv-docs.txt"), []byte("some sv docs content"), 0755), qt.IsNil)
    47  	c.Assert(afero.WriteFile(fs, filepath.Join("themes/b/myenblogcontent", "en-b-f.txt"), []byte("some en content"), 0755), qt.IsNil)
    48  
    49  	rfs, err := NewRootMappingFs(fs,
    50  		RootMapping{
    51  			From: "content/blog",             // Virtual path, first element is one of content, static, layouts etc.
    52  			To:   "themes/a/mysvblogcontent", // Real path
    53  			Meta: &FileMeta{Lang: "sv"},
    54  		},
    55  		RootMapping{
    56  			From: "content/blog",
    57  			To:   "themes/a/myenblogcontent",
    58  			Meta: &FileMeta{Lang: "en"},
    59  		},
    60  		RootMapping{
    61  			From: "content/blog",
    62  			To:   "content/sv",
    63  			Meta: &FileMeta{Lang: "sv"},
    64  		},
    65  		RootMapping{
    66  			From: "content/blog",
    67  			To:   "themes/a/myotherenblogcontent",
    68  			Meta: &FileMeta{Lang: "en"},
    69  		},
    70  		RootMapping{
    71  			From: "content/docs",
    72  			To:   "themes/a/mysvdocs",
    73  			Meta: &FileMeta{Lang: "sv"},
    74  		},
    75  	)
    76  
    77  	c.Assert(err, qt.IsNil)
    78  
    79  	collected, err := collectFilenames(rfs, "content", "content")
    80  	c.Assert(err, qt.IsNil)
    81  	c.Assert(collected, qt.DeepEquals,
    82  		[]string{"blog/d1/en-d1-f.txt", "blog/d1/sv-d1-f.txt", "blog/en-f.txt", "blog/en-f2.txt", "blog/sv-f.txt", "blog/svdir/main.txt", "docs/sv-docs.txt"}, qt.Commentf("%#v", collected))
    83  
    84  	dirs, err := rfs.Dirs(filepath.FromSlash("content/blog"))
    85  	c.Assert(err, qt.IsNil)
    86  	c.Assert(len(dirs), qt.Equals, 4)
    87  	for _, dir := range dirs {
    88  		f, err := dir.Meta().Open()
    89  		c.Assert(err, qt.IsNil)
    90  		f.Close()
    91  	}
    92  
    93  	blog, err := rfs.Open(filepath.FromSlash("content/blog"))
    94  	c.Assert(err, qt.IsNil)
    95  	fis, err := blog.Readdir(-1)
    96  	for _, fi := range fis {
    97  		f, err := fi.(FileMetaInfo).Meta().Open()
    98  		c.Assert(err, qt.IsNil)
    99  		f.Close()
   100  	}
   101  	blog.Close()
   102  
   103  	getDirnames := func(name string, rfs *RootMappingFs) []string {
   104  		c.Helper()
   105  		filename := filepath.FromSlash(name)
   106  		f, err := rfs.Open(filename)
   107  		c.Assert(err, qt.IsNil)
   108  		names, err := f.Readdirnames(-1)
   109  
   110  		f.Close()
   111  		c.Assert(err, qt.IsNil)
   112  
   113  		info, err := rfs.Stat(filename)
   114  		c.Assert(err, qt.IsNil)
   115  		f2, err := info.(FileMetaInfo).Meta().Open()
   116  		c.Assert(err, qt.IsNil)
   117  		names2, err := f2.Readdirnames(-1)
   118  		c.Assert(err, qt.IsNil)
   119  		c.Assert(names2, qt.DeepEquals, names)
   120  		f2.Close()
   121  
   122  		return names
   123  	}
   124  
   125  	rfsEn := rfs.Filter(func(rm RootMapping) bool {
   126  		return rm.Meta.Lang == "en"
   127  	})
   128  
   129  	c.Assert(getDirnames("content/blog", rfsEn), qt.DeepEquals, []string{"d1", "en-f.txt", "en-f2.txt"})
   130  
   131  	rfsSv := rfs.Filter(func(rm RootMapping) bool {
   132  		return rm.Meta.Lang == "sv"
   133  	})
   134  
   135  	c.Assert(getDirnames("content/blog", rfsSv), qt.DeepEquals, []string{"d1", "sv-f.txt", "svdir"})
   136  
   137  	// Make sure we have not messed with the original
   138  	c.Assert(getDirnames("content/blog", rfs), qt.DeepEquals, []string{"d1", "sv-f.txt", "en-f.txt", "svdir", "en-f2.txt"})
   139  
   140  	c.Assert(getDirnames("content", rfsSv), qt.DeepEquals, []string{"blog", "docs"})
   141  	c.Assert(getDirnames("content", rfs), qt.DeepEquals, []string{"blog", "docs"})
   142  }
   143  
   144  func TestRootMappingFsDirnames(t *testing.T) {
   145  	c := qt.New(t)
   146  	fs := NewBaseFileDecorator(afero.NewMemMapFs())
   147  
   148  	testfile := "myfile.txt"
   149  	c.Assert(fs.Mkdir("f1t", 0755), qt.IsNil)
   150  	c.Assert(fs.Mkdir("f2t", 0755), qt.IsNil)
   151  	c.Assert(fs.Mkdir("f3t", 0755), qt.IsNil)
   152  	c.Assert(afero.WriteFile(fs, filepath.Join("f2t", testfile), []byte("some content"), 0755), qt.IsNil)
   153  
   154  	rfs, err := newRootMappingFsFromFromTo("", fs, "static/bf1", "f1t", "static/cf2", "f2t", "static/af3", "f3t")
   155  	c.Assert(err, qt.IsNil)
   156  
   157  	fif, err := rfs.Stat(filepath.Join("static/cf2", testfile))
   158  	c.Assert(err, qt.IsNil)
   159  	c.Assert(fif.Name(), qt.Equals, "myfile.txt")
   160  	fifm := fif.(FileMetaInfo).Meta()
   161  	c.Assert(fifm.Filename, qt.Equals, filepath.FromSlash("f2t/myfile.txt"))
   162  
   163  	root, err := rfs.Open("static")
   164  	c.Assert(err, qt.IsNil)
   165  
   166  	dirnames, err := root.Readdirnames(-1)
   167  	c.Assert(err, qt.IsNil)
   168  	c.Assert(dirnames, qt.DeepEquals, []string{"af3", "bf1", "cf2"})
   169  }
   170  
   171  func TestRootMappingFsFilename(t *testing.T) {
   172  	c := qt.New(t)
   173  	workDir, clean, err := htesting.CreateTempDir(Os, "hugo-root-filename")
   174  	c.Assert(err, qt.IsNil)
   175  	defer clean()
   176  	fs := NewBaseFileDecorator(Os)
   177  
   178  	testfilename := filepath.Join(workDir, "f1t/foo/file.txt")
   179  
   180  	c.Assert(fs.MkdirAll(filepath.Join(workDir, "f1t/foo"), 0777), qt.IsNil)
   181  	c.Assert(afero.WriteFile(fs, testfilename, []byte("content"), 0666), qt.IsNil)
   182  
   183  	rfs, err := newRootMappingFsFromFromTo(workDir, fs, "static/f1", filepath.Join(workDir, "f1t"), "static/f2", filepath.Join(workDir, "f2t"))
   184  	c.Assert(err, qt.IsNil)
   185  
   186  	fi, err := rfs.Stat(filepath.FromSlash("static/f1/foo/file.txt"))
   187  	c.Assert(err, qt.IsNil)
   188  	fim := fi.(FileMetaInfo)
   189  	c.Assert(fim.Meta().Filename, qt.Equals, testfilename)
   190  	_, err = rfs.Stat(filepath.FromSlash("static/f1"))
   191  	c.Assert(err, qt.IsNil)
   192  }
   193  
   194  func TestRootMappingFsMount(t *testing.T) {
   195  	c := qt.New(t)
   196  	fs := NewBaseFileDecorator(afero.NewMemMapFs())
   197  
   198  	testfile := "test.txt"
   199  
   200  	c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/mynoblogcontent", testfile), []byte("some no content"), 0755), qt.IsNil)
   201  	c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/myenblogcontent", testfile), []byte("some en content"), 0755), qt.IsNil)
   202  	c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/mysvblogcontent", testfile), []byte("some sv content"), 0755), qt.IsNil)
   203  	c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/mysvblogcontent", "other.txt"), []byte("some sv content"), 0755), qt.IsNil)
   204  	c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/singlefiles", "no.txt"), []byte("no text"), 0755), qt.IsNil)
   205  	c.Assert(afero.WriteFile(fs, filepath.Join("themes/a/singlefiles", "sv.txt"), []byte("sv text"), 0755), qt.IsNil)
   206  
   207  	bfs := afero.NewBasePathFs(fs, "themes/a").(*afero.BasePathFs)
   208  	rm := []RootMapping{
   209  		// Directories
   210  		{
   211  			From: "content/blog",
   212  			To:   "mynoblogcontent",
   213  			Meta: &FileMeta{Lang: "no"},
   214  		},
   215  		{
   216  			From: "content/blog",
   217  			To:   "myenblogcontent",
   218  			Meta: &FileMeta{Lang: "en"},
   219  		},
   220  		{
   221  			From: "content/blog",
   222  			To:   "mysvblogcontent",
   223  			Meta: &FileMeta{Lang: "sv"},
   224  		},
   225  		// Files
   226  		{
   227  			From:      "content/singles/p1.md",
   228  			To:        "singlefiles/no.txt",
   229  			ToBasedir: "singlefiles",
   230  			Meta:      &FileMeta{Lang: "no"},
   231  		},
   232  		{
   233  			From:      "content/singles/p1.md",
   234  			To:        "singlefiles/sv.txt",
   235  			ToBasedir: "singlefiles",
   236  			Meta:      &FileMeta{Lang: "sv"},
   237  		},
   238  	}
   239  
   240  	rfs, err := NewRootMappingFs(bfs, rm...)
   241  	c.Assert(err, qt.IsNil)
   242  
   243  	blog, err := rfs.Stat(filepath.FromSlash("content/blog"))
   244  	c.Assert(err, qt.IsNil)
   245  	c.Assert(blog.IsDir(), qt.Equals, true)
   246  	blogm := blog.(FileMetaInfo).Meta()
   247  	c.Assert(blogm.Lang, qt.Equals, "no") // First match
   248  
   249  	f, err := blogm.Open()
   250  	c.Assert(err, qt.IsNil)
   251  	defer f.Close()
   252  	dirs1, err := f.Readdirnames(-1)
   253  	c.Assert(err, qt.IsNil)
   254  	// Union with duplicate dir names filtered.
   255  	c.Assert(dirs1, qt.DeepEquals, []string{"test.txt", "test.txt", "other.txt", "test.txt"})
   256  
   257  	files, err := afero.ReadDir(rfs, filepath.FromSlash("content/blog"))
   258  	c.Assert(err, qt.IsNil)
   259  	c.Assert(len(files), qt.Equals, 4)
   260  
   261  	testfilefi := files[1]
   262  	c.Assert(testfilefi.Name(), qt.Equals, testfile)
   263  
   264  	testfilem := testfilefi.(FileMetaInfo).Meta()
   265  	c.Assert(testfilem.Filename, qt.Equals, filepath.FromSlash("themes/a/mynoblogcontent/test.txt"))
   266  
   267  	tf, err := testfilem.Open()
   268  	c.Assert(err, qt.IsNil)
   269  	defer tf.Close()
   270  	b, err := io.ReadAll(tf)
   271  	c.Assert(err, qt.IsNil)
   272  	c.Assert(string(b), qt.Equals, "some no content")
   273  
   274  	// Ambiguous
   275  	_, err = rfs.Stat(filepath.FromSlash("content/singles/p1.md"))
   276  	c.Assert(err, qt.Not(qt.IsNil))
   277  
   278  	singlesDir, err := rfs.Open(filepath.FromSlash("content/singles"))
   279  	c.Assert(err, qt.IsNil)
   280  	defer singlesDir.Close()
   281  	singles, err := singlesDir.Readdir(-1)
   282  	c.Assert(err, qt.IsNil)
   283  	c.Assert(singles, qt.HasLen, 2)
   284  	for i, lang := range []string{"no", "sv"} {
   285  		fi := singles[i].(FileMetaInfo)
   286  		c.Assert(fi.Meta().PathFile(), qt.Equals, filepath.FromSlash("themes/a/singlefiles/"+lang+".txt"))
   287  		c.Assert(fi.Meta().Lang, qt.Equals, lang)
   288  		c.Assert(fi.Name(), qt.Equals, "p1.md")
   289  	}
   290  }
   291  
   292  func TestRootMappingFsMountOverlap(t *testing.T) {
   293  	c := qt.New(t)
   294  	fs := NewBaseFileDecorator(afero.NewMemMapFs())
   295  
   296  	c.Assert(afero.WriteFile(fs, filepath.FromSlash("da/a.txt"), []byte("some no content"), 0755), qt.IsNil)
   297  	c.Assert(afero.WriteFile(fs, filepath.FromSlash("db/b.txt"), []byte("some no content"), 0755), qt.IsNil)
   298  	c.Assert(afero.WriteFile(fs, filepath.FromSlash("dc/c.txt"), []byte("some no content"), 0755), qt.IsNil)
   299  	c.Assert(afero.WriteFile(fs, filepath.FromSlash("de/e.txt"), []byte("some no content"), 0755), qt.IsNil)
   300  
   301  	rm := []RootMapping{
   302  		{
   303  			From: "static",
   304  			To:   "da",
   305  		},
   306  		{
   307  			From: "static/b",
   308  			To:   "db",
   309  		},
   310  		{
   311  			From: "static/b/c",
   312  			To:   "dc",
   313  		},
   314  		{
   315  			From: "/static/e/",
   316  			To:   "de",
   317  		},
   318  	}
   319  
   320  	rfs, err := NewRootMappingFs(fs, rm...)
   321  	c.Assert(err, qt.IsNil)
   322  
   323  	checkDirnames := func(name string, expect []string) {
   324  		c.Helper()
   325  		name = filepath.FromSlash(name)
   326  		f, err := rfs.Open(name)
   327  		c.Assert(err, qt.IsNil)
   328  		defer f.Close()
   329  		names, err := f.Readdirnames(-1)
   330  		c.Assert(err, qt.IsNil)
   331  		c.Assert(names, qt.DeepEquals, expect, qt.Commentf(fmt.Sprintf("%#v", names)))
   332  	}
   333  
   334  	checkDirnames("static", []string{"a.txt", "b", "e"})
   335  	checkDirnames("static/b", []string{"b.txt", "c"})
   336  	checkDirnames("static/b/c", []string{"c.txt"})
   337  
   338  	fi, err := rfs.Stat(filepath.FromSlash("static/b/b.txt"))
   339  	c.Assert(err, qt.IsNil)
   340  	c.Assert(fi.Name(), qt.Equals, "b.txt")
   341  }
   342  
   343  func TestRootMappingFsOs(t *testing.T) {
   344  	c := qt.New(t)
   345  	fs := NewBaseFileDecorator(afero.NewOsFs())
   346  
   347  	d, clean, err := htesting.CreateTempDir(fs, "hugo-root-mapping-os")
   348  	c.Assert(err, qt.IsNil)
   349  	defer clean()
   350  
   351  	testfile := "myfile.txt"
   352  	c.Assert(fs.Mkdir(filepath.Join(d, "f1t"), 0755), qt.IsNil)
   353  	c.Assert(fs.Mkdir(filepath.Join(d, "f2t"), 0755), qt.IsNil)
   354  	c.Assert(fs.Mkdir(filepath.Join(d, "f3t"), 0755), qt.IsNil)
   355  
   356  	// Deep structure
   357  	deepDir := filepath.Join(d, "d1", "d2", "d3", "d4", "d5")
   358  	c.Assert(fs.MkdirAll(deepDir, 0755), qt.IsNil)
   359  	for i := 1; i <= 3; i++ {
   360  		c.Assert(fs.MkdirAll(filepath.Join(d, "d1", "d2", "d3", "d4", fmt.Sprintf("d4-%d", i)), 0755), qt.IsNil)
   361  		c.Assert(afero.WriteFile(fs, filepath.Join(d, "d1", "d2", "d3", fmt.Sprintf("f-%d.txt", i)), []byte("some content"), 0755), qt.IsNil)
   362  	}
   363  
   364  	c.Assert(afero.WriteFile(fs, filepath.Join(d, "f2t", testfile), []byte("some content"), 0755), qt.IsNil)
   365  
   366  	// https://github.com/gohugoio/hugo/issues/6854
   367  	mystaticDir := filepath.Join(d, "mystatic", "a", "b", "c")
   368  	c.Assert(fs.MkdirAll(mystaticDir, 0755), qt.IsNil)
   369  	c.Assert(afero.WriteFile(fs, filepath.Join(mystaticDir, "ms-1.txt"), []byte("some content"), 0755), qt.IsNil)
   370  
   371  	rfs, err := newRootMappingFsFromFromTo(
   372  		d,
   373  		fs,
   374  		"static/bf1", filepath.Join(d, "f1t"),
   375  		"static/cf2", filepath.Join(d, "f2t"),
   376  		"static/af3", filepath.Join(d, "f3t"),
   377  		"static", filepath.Join(d, "mystatic"),
   378  		"static/a/b/c", filepath.Join(d, "d1", "d2", "d3"),
   379  		"layouts", filepath.Join(d, "d1"),
   380  	)
   381  
   382  	c.Assert(err, qt.IsNil)
   383  
   384  	fif, err := rfs.Stat(filepath.Join("static/cf2", testfile))
   385  	c.Assert(err, qt.IsNil)
   386  	c.Assert(fif.Name(), qt.Equals, "myfile.txt")
   387  
   388  	root, err := rfs.Open("static")
   389  	c.Assert(err, qt.IsNil)
   390  
   391  	dirnames, err := root.Readdirnames(-1)
   392  	c.Assert(err, qt.IsNil)
   393  	c.Assert(dirnames, qt.DeepEquals, []string{"a", "af3", "bf1", "cf2"}, qt.Commentf(fmt.Sprintf("%#v", dirnames)))
   394  
   395  	getDirnames := func(dirname string) []string {
   396  		dirname = filepath.FromSlash(dirname)
   397  		f, err := rfs.Open(dirname)
   398  		c.Assert(err, qt.IsNil)
   399  		defer f.Close()
   400  		dirnames, err := f.Readdirnames(-1)
   401  		c.Assert(err, qt.IsNil)
   402  		sort.Strings(dirnames)
   403  		return dirnames
   404  	}
   405  
   406  	c.Assert(getDirnames("static/a/b"), qt.DeepEquals, []string{"c"})
   407  	c.Assert(getDirnames("static/a/b/c"), qt.DeepEquals, []string{"d4", "f-1.txt", "f-2.txt", "f-3.txt", "ms-1.txt"})
   408  	c.Assert(getDirnames("static/a/b/c/d4"), qt.DeepEquals, []string{"d4-1", "d4-2", "d4-3", "d5"})
   409  
   410  	all, err := collectFilenames(rfs, "static", "static")
   411  	c.Assert(err, qt.IsNil)
   412  
   413  	c.Assert(all, qt.DeepEquals, []string{"a/b/c/f-1.txt", "a/b/c/f-2.txt", "a/b/c/f-3.txt", "a/b/c/ms-1.txt", "cf2/myfile.txt"})
   414  
   415  	fis, err := collectFileinfos(rfs, "static", "static")
   416  	c.Assert(err, qt.IsNil)
   417  
   418  	c.Assert(fis[9].Meta().PathFile(), qt.Equals, filepath.FromSlash("d1/d2/d3/f-1.txt"))
   419  
   420  	dirc := fis[3].Meta()
   421  
   422  	f, err := dirc.Open()
   423  	c.Assert(err, qt.IsNil)
   424  	defer f.Close()
   425  	fileInfos, err := f.Readdir(-1)
   426  	c.Assert(err, qt.IsNil)
   427  	sortFileInfos(fileInfos)
   428  	i := 0
   429  	for _, fi := range fileInfos {
   430  		if fi.IsDir() || fi.Name() == "ms-1.txt" {
   431  			continue
   432  		}
   433  		i++
   434  		meta := fi.(FileMetaInfo).Meta()
   435  		c.Assert(meta.Filename, qt.Equals, filepath.Join(d, fmt.Sprintf("/d1/d2/d3/f-%d.txt", i)))
   436  		c.Assert(meta.PathFile(), qt.Equals, filepath.FromSlash(fmt.Sprintf("d1/d2/d3/f-%d.txt", i)))
   437  	}
   438  
   439  	_, err = rfs.Stat(filepath.FromSlash("layouts/d2/d3/f-1.txt"))
   440  	c.Assert(err, qt.IsNil)
   441  	_, err = rfs.Stat(filepath.FromSlash("layouts/d2/d3"))
   442  	c.Assert(err, qt.IsNil)
   443  }
   444  
   445  func TestRootMappingFsOsBase(t *testing.T) {
   446  	c := qt.New(t)
   447  	fs := NewBaseFileDecorator(afero.NewOsFs())
   448  
   449  	d, clean, err := htesting.CreateTempDir(fs, "hugo-root-mapping-os-base")
   450  	c.Assert(err, qt.IsNil)
   451  	defer clean()
   452  
   453  	// Deep structure
   454  	deepDir := filepath.Join(d, "d1", "d2", "d3", "d4", "d5")
   455  	c.Assert(fs.MkdirAll(deepDir, 0755), qt.IsNil)
   456  	for i := 1; i <= 3; i++ {
   457  		c.Assert(fs.MkdirAll(filepath.Join(d, "d1", "d2", "d3", "d4", fmt.Sprintf("d4-%d", i)), 0755), qt.IsNil)
   458  		c.Assert(afero.WriteFile(fs, filepath.Join(d, "d1", "d2", "d3", fmt.Sprintf("f-%d.txt", i)), []byte("some content"), 0755), qt.IsNil)
   459  	}
   460  
   461  	mystaticDir := filepath.Join(d, "mystatic", "a", "b", "c")
   462  	c.Assert(fs.MkdirAll(mystaticDir, 0755), qt.IsNil)
   463  	c.Assert(afero.WriteFile(fs, filepath.Join(mystaticDir, "ms-1.txt"), []byte("some content"), 0755), qt.IsNil)
   464  
   465  	bfs := afero.NewBasePathFs(fs, d)
   466  
   467  	rfs, err := newRootMappingFsFromFromTo(
   468  		"",
   469  		bfs,
   470  		"static", "mystatic",
   471  		"static/a/b/c", filepath.Join("d1", "d2", "d3"),
   472  	)
   473  
   474  	getDirnames := func(dirname string) []string {
   475  		dirname = filepath.FromSlash(dirname)
   476  		f, err := rfs.Open(dirname)
   477  		c.Assert(err, qt.IsNil)
   478  		defer f.Close()
   479  		dirnames, err := f.Readdirnames(-1)
   480  		c.Assert(err, qt.IsNil)
   481  		sort.Strings(dirnames)
   482  		return dirnames
   483  	}
   484  
   485  	c.Assert(getDirnames("static/a/b/c"), qt.DeepEquals, []string{"d4", "f-1.txt", "f-2.txt", "f-3.txt", "ms-1.txt"})
   486  }
   487  
   488  func TestRootMappingFileFilter(t *testing.T) {
   489  	c := qt.New(t)
   490  	fs := NewBaseFileDecorator(afero.NewMemMapFs())
   491  
   492  	for _, lang := range []string{"no", "en", "fr"} {
   493  		for i := 1; i <= 3; i++ {
   494  			c.Assert(afero.WriteFile(fs, filepath.Join(lang, fmt.Sprintf("my%s%d.txt", lang, i)), []byte("some text file for"+lang), 0755), qt.IsNil)
   495  		}
   496  	}
   497  
   498  	for _, lang := range []string{"no", "en", "fr"} {
   499  		for i := 1; i <= 3; i++ {
   500  			c.Assert(afero.WriteFile(fs, filepath.Join(lang, "sub", fmt.Sprintf("mysub%s%d.txt", lang, i)), []byte("some text file for"+lang), 0755), qt.IsNil)
   501  		}
   502  	}
   503  
   504  	rm := []RootMapping{
   505  		{
   506  			From: "content",
   507  			To:   "no",
   508  			Meta: &FileMeta{Lang: "no", InclusionFilter: glob.MustNewFilenameFilter(nil, []string{"**.txt"})},
   509  		},
   510  		{
   511  			From: "content",
   512  			To:   "en",
   513  			Meta: &FileMeta{Lang: "en"},
   514  		},
   515  		{
   516  			From: "content",
   517  			To:   "fr",
   518  			Meta: &FileMeta{Lang: "fr", InclusionFilter: glob.MustNewFilenameFilter(nil, []string{"**.txt"})},
   519  		},
   520  	}
   521  
   522  	rfs, err := NewRootMappingFs(fs, rm...)
   523  	c.Assert(err, qt.IsNil)
   524  
   525  	assertExists := func(filename string, shouldExist bool) {
   526  		c.Helper()
   527  		filename = filepath.Clean(filename)
   528  		_, err1 := rfs.Stat(filename)
   529  		f, err2 := rfs.Open(filename)
   530  		if shouldExist {
   531  			c.Assert(err1, qt.IsNil)
   532  			c.Assert(err2, qt.IsNil)
   533  			c.Assert(f.Close(), qt.IsNil)
   534  		} else {
   535  			c.Assert(err1, qt.Not(qt.IsNil))
   536  			c.Assert(err2, qt.Not(qt.IsNil))
   537  		}
   538  	}
   539  
   540  	assertExists("content/myno1.txt", false)
   541  	assertExists("content/myen1.txt", true)
   542  	assertExists("content/myfr1.txt", false)
   543  
   544  	dirEntriesSub, err := afero.ReadDir(rfs, filepath.Join("content", "sub"))
   545  	c.Assert(err, qt.IsNil)
   546  	c.Assert(len(dirEntriesSub), qt.Equals, 3)
   547  
   548  	dirEntries, err := afero.ReadDir(rfs, "content")
   549  
   550  	c.Assert(err, qt.IsNil)
   551  	c.Assert(len(dirEntries), qt.Equals, 4)
   552  
   553  }