diff --git a/commands/gendoc.go b/commands/gendoc.go index 0f65e7dc..046d3839 100644 --- a/commands/gendoc.go +++ b/commands/gendoc.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -51,9 +51,9 @@ for rendering in Hugo.`, if !strings.HasSuffix(gendocdir, helpers.FilePathSeparator) { gendocdir += helpers.FilePathSeparator } - if found, _ := helpers.Exists(gendocdir, hugofs.OsFs); !found { + if found, _ := helpers.Exists(gendocdir, hugofs.Os()); !found { jww.FEEDBACK.Println("Directory", gendocdir, "does not exist, creating...") - hugofs.OsFs.MkdirAll(gendocdir, 0777) + hugofs.Os().MkdirAll(gendocdir, 0777) } now := time.Now().Format(time.RFC3339) prepender := func(filename string) string { diff --git a/commands/genman.go b/commands/genman.go index e12d0277..d1f54ae3 100644 --- a/commands/genman.go +++ b/commands/genman.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -41,9 +41,9 @@ in the "man" directory under the current directory.`, if !strings.HasSuffix(genmandir, helpers.FilePathSeparator) { genmandir += helpers.FilePathSeparator } - if found, _ := helpers.Exists(genmandir, hugofs.OsFs); !found { + if found, _ := helpers.Exists(genmandir, hugofs.Os()); !found { jww.FEEDBACK.Println("Directory", genmandir, "does not exist, creating...") - hugofs.OsFs.MkdirAll(genmandir, 0777) + hugofs.Os().MkdirAll(genmandir, 0777) } cmd.Root().DisableAutoGenTag = true diff --git a/commands/hugo.go b/commands/hugo.go index 19fcb57f..131879ce 100644 --- a/commands/hugo.go +++ b/commands/hugo.go @@ -423,14 +423,14 @@ func InitializeConfig(subCmdVs ...*cobra.Command) error { if helpers.FilePathSeparator != cacheDir[len(cacheDir)-1:] { cacheDir = cacheDir + helpers.FilePathSeparator } - isDir, err := helpers.DirExists(cacheDir, hugofs.SourceFs) + isDir, err := helpers.DirExists(cacheDir, hugofs.Source()) utils.CheckErr(err) if isDir == false { mkdir(cacheDir) } viper.Set("CacheDir", cacheDir) } else { - viper.Set("CacheDir", helpers.GetTempDir("hugo_cache", hugofs.SourceFs)) + viper.Set("CacheDir", helpers.GetTempDir("hugo_cache", hugofs.Source())) } if verboseLog || logging || (viper.IsSet("LogFile") && viper.GetString("LogFile") != "") { @@ -453,6 +453,9 @@ func InitializeConfig(subCmdVs ...*cobra.Command) error { jww.INFO.Println("Using config file:", viper.ConfigFileUsed()) + // Init file systems. This may be changed at a later point. + hugofs.InitDefaultFs() + themeDir := helpers.GetThemeDir() if themeDir != "" { if _, err := os.Stat(themeDir); os.IsNotExist(err) { @@ -498,7 +501,7 @@ func build(watches ...bool) error { // This is only used for benchmark testing. Cause the content is only visible // in memory if renderToMemory { - hugofs.DestinationFS = new(afero.MemMapFs) + hugofs.SetDestination(new(afero.MemMapFs)) // Rendering to memoryFS, publish to Root regardless of publishDir. viper.Set("PublishDir", "/") } @@ -524,7 +527,7 @@ func build(watches ...bool) error { } func getStaticSourceFs() afero.Fs { - source := hugofs.SourceFs + source := hugofs.Source() themeDir, err := helpers.GetThemeStaticDirPath() staticDir := helpers.GetStaticDirPath() + helpers.FilePathSeparator @@ -563,8 +566,8 @@ func getStaticSourceFs() afero.Fs { jww.INFO.Println("using a UnionFS for static directory comprised of:") jww.INFO.Println("Base:", themeDir) jww.INFO.Println("Overlay:", staticDir) - base := afero.NewReadOnlyFs(afero.NewBasePathFs(hugofs.SourceFs, themeDir)) - overlay := afero.NewReadOnlyFs(afero.NewBasePathFs(hugofs.SourceFs, staticDir)) + base := afero.NewReadOnlyFs(afero.NewBasePathFs(hugofs.Source(), themeDir)) + overlay := afero.NewReadOnlyFs(afero.NewBasePathFs(hugofs.Source(), staticDir)) return afero.NewCopyOnWriteFs(base, overlay) } @@ -587,7 +590,7 @@ func copyStatic() error { syncer := fsync.NewSyncer() syncer.NoTimes = viper.GetBool("notimes") syncer.SrcFs = staticSourceFs - syncer.DestFs = hugofs.DestinationFS + syncer.DestFs = hugofs.Destination() // Now that we are using a unionFs for the static directories // We can effectively clean the publishDir on initial sync syncer.Delete = viper.GetBool("cleanDestinationDir") @@ -653,12 +656,12 @@ func getDirList() []string { return nil } - helpers.SymbolicWalk(hugofs.SourceFs, dataDir, walker) - helpers.SymbolicWalk(hugofs.SourceFs, helpers.AbsPathify(viper.GetString("ContentDir")), walker) - helpers.SymbolicWalk(hugofs.SourceFs, helpers.AbsPathify(viper.GetString("LayoutDir")), walker) - helpers.SymbolicWalk(hugofs.SourceFs, helpers.AbsPathify(viper.GetString("StaticDir")), walker) + helpers.SymbolicWalk(hugofs.Source(), dataDir, walker) + helpers.SymbolicWalk(hugofs.Source(), helpers.AbsPathify(viper.GetString("ContentDir")), walker) + helpers.SymbolicWalk(hugofs.Source(), helpers.AbsPathify(viper.GetString("LayoutDir")), walker) + helpers.SymbolicWalk(hugofs.Source(), helpers.AbsPathify(viper.GetString("StaticDir")), walker) if helpers.ThemeSet() { - helpers.SymbolicWalk(hugofs.SourceFs, helpers.AbsPathify(viper.GetString("themesDir")+"/"+viper.GetString("theme")), walker) + helpers.SymbolicWalk(hugofs.Source(), helpers.AbsPathify(viper.GetString("themesDir")+"/"+viper.GetString("theme")), walker) } return a @@ -770,8 +773,8 @@ func NewWatcher(port int) error { // recursively add new directories to watch list // When mkdir -p is used, only the top directory triggers an event (at least on OSX) if ev.Op&fsnotify.Create == fsnotify.Create { - if s, err := hugofs.SourceFs.Stat(ev.Name); err == nil && s.Mode().IsDir() { - helpers.SymbolicWalk(hugofs.SourceFs, ev.Name, walkAdder) + if s, err := hugofs.Source().Stat(ev.Name); err == nil && s.Mode().IsDir() { + helpers.SymbolicWalk(hugofs.Source(), ev.Name, walkAdder) } } @@ -813,7 +816,7 @@ func NewWatcher(port int) error { syncer := fsync.NewSyncer() syncer.NoTimes = viper.GetBool("notimes") syncer.SrcFs = staticSourceFs - syncer.DestFs = hugofs.DestinationFS + syncer.DestFs = hugofs.Destination() // prevent spamming the log on changes logger := helpers.NewDistinctFeedbackLogger() @@ -858,7 +861,7 @@ func NewWatcher(port int) error { // If file doesn't exist in any static dir, remove it toRemove := filepath.Join(publishDir, relPath) logger.Println("File no longer exists in static dir, removing", toRemove) - hugofs.DestinationFS.RemoveAll(toRemove) + hugofs.Destination().RemoveAll(toRemove) } else if err == nil { // If file still exists, sync it logger.Println("Syncing", relPath, "to", publishDir) @@ -939,7 +942,7 @@ func isThemeVsHugoVersionMismatch() (mismatch bool, requiredMinVersion string) { themeDir := helpers.GetThemeDir() - fs := hugofs.SourceFs + fs := hugofs.Source() path := filepath.Join(themeDir, "theme.toml") exists, err := helpers.Exists(path, fs) diff --git a/commands/import_jekyll.go b/commands/import_jekyll.go index 26020b0f..20356f1e 100644 --- a/commands/import_jekyll.go +++ b/commands/import_jekyll.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -124,7 +124,7 @@ func importFromJekyll(cmd *cobra.Command, args []string) error { return convertJekyllPost(path, relPath, targetDir, draft) } - err = helpers.SymbolicWalk(hugofs.OsFs, jekyllRoot, callback) + err = helpers.SymbolicWalk(hugofs.Os(), jekyllRoot, callback) if err != nil { return err @@ -139,7 +139,7 @@ func importFromJekyll(cmd *cobra.Command, args []string) error { // TODO: Consider calling doNewSite() instead? func createSiteFromJekyll(jekyllRoot, targetDir string, force bool) error { - fs := hugofs.SourceFs + fs := hugofs.Source() if exists, _ := helpers.Exists(targetDir, fs); exists { if isDir, _ := helpers.IsDir(targetDir, fs); !isDir { return errors.New("Target path \"" + targetDir + "\" already exists but not a directory") @@ -187,7 +187,7 @@ func createSiteFromJekyll(jekyllRoot, targetDir string, force bool) error { } func loadJekyllConfig(jekyllRoot string) map[string]interface{} { - fs := hugofs.SourceFs + fs := hugofs.Source() path := filepath.Join(jekyllRoot, "_config.yml") exists, err := helpers.Exists(path, fs) @@ -252,7 +252,7 @@ func createConfigFromJekyll(inpath string, kind string, jekyllConfig map[string] return err } - err = helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), bytes.NewReader(by), hugofs.SourceFs) + err = helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), bytes.NewReader(by), hugofs.Source()) if err != nil { return } diff --git a/commands/new.go b/commands/new.go index 4fc00031..c3f04f47 100644 --- a/commands/new.go +++ b/commands/new.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -110,7 +110,7 @@ func NewContent(cmd *cobra.Command, args []string) error { kind = contentType } - return create.NewContent(hugofs.SourceFs, kind, createpath) + return create.NewContent(hugofs.Source(), kind, createpath) } func doNewSite(basepath string, force bool) error { @@ -123,12 +123,12 @@ func doNewSite(basepath string, force bool) error { filepath.Join(basepath, "themes"), } - if exists, _ := helpers.Exists(basepath, hugofs.SourceFs); exists { - if isDir, _ := helpers.IsDir(basepath, hugofs.SourceFs); !isDir { + if exists, _ := helpers.Exists(basepath, hugofs.Source()); exists { + if isDir, _ := helpers.IsDir(basepath, hugofs.Source()); !isDir { return errors.New(basepath + " already exists but not a directory") } - isEmpty, _ := helpers.IsEmpty(basepath, hugofs.SourceFs) + isEmpty, _ := helpers.IsEmpty(basepath, hugofs.Source()) switch { case !isEmpty && !force: @@ -137,7 +137,7 @@ func doNewSite(basepath string, force bool) error { case !isEmpty && force: all := append(dirs, filepath.Join(basepath, "config."+configFormat)) for _, path := range all { - if exists, _ := helpers.Exists(path, hugofs.SourceFs); exists { + if exists, _ := helpers.Exists(path, hugofs.Source()); exists { return errors.New(path + " already exists") } } @@ -145,7 +145,7 @@ func doNewSite(basepath string, force bool) error { } for _, dir := range dirs { - hugofs.SourceFs.MkdirAll(dir, 0777) + hugofs.Source().MkdirAll(dir, 0777) } createConfig(basepath, configFormat) @@ -185,7 +185,7 @@ func NewTheme(cmd *cobra.Command, args []string) error { createpath := helpers.AbsPathify(filepath.Join(viper.GetString("themesDir"), args[0])) jww.INFO.Println("creating theme at", createpath) - if x, _ := helpers.Exists(createpath, hugofs.SourceFs); x { + if x, _ := helpers.Exists(createpath, hugofs.Source()); x { return newUserError(createpath, "already exists") } @@ -204,7 +204,7 @@ func NewTheme(cmd *cobra.Command, args []string) error { archDefault := []byte("+++\n+++\n") - err := helpers.WriteToDisk(filepath.Join(createpath, "archetypes", "default.md"), bytes.NewReader(archDefault), hugofs.SourceFs) + err := helpers.WriteToDisk(filepath.Join(createpath, "archetypes", "default.md"), bytes.NewReader(archDefault), hugofs.Source()) if err != nil { return err } @@ -234,7 +234,7 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. `) - err = helpers.WriteToDisk(filepath.Join(createpath, "LICENSE.md"), bytes.NewReader(by), hugofs.SourceFs) + err = helpers.WriteToDisk(filepath.Join(createpath, "LICENSE.md"), bytes.NewReader(by), hugofs.Source()) if err != nil { return err } @@ -256,7 +256,7 @@ func mkdir(x ...string) { func touchFile(x ...string) { inpath := filepath.Join(x...) mkdir(filepath.Dir(inpath)) - err := helpers.WriteToDisk(inpath, bytes.NewReader([]byte{}), hugofs.SourceFs) + err := helpers.WriteToDisk(inpath, bytes.NewReader([]byte{}), hugofs.Source()) if err != nil { jww.FATAL.Fatalln(err) } @@ -287,7 +287,7 @@ min_version = 0.15 repo = "" `) - err = helpers.WriteToDisk(filepath.Join(inpath, "theme.toml"), bytes.NewReader(by), hugofs.SourceFs) + err = helpers.WriteToDisk(filepath.Join(inpath, "theme.toml"), bytes.NewReader(by), hugofs.Source()) if err != nil { return } @@ -321,7 +321,7 @@ func createConfig(inpath string, kind string) (err error) { return err } - err = helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), bytes.NewReader(by), hugofs.SourceFs) + err = helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), bytes.NewReader(by), hugofs.Source()) if err != nil { return } diff --git a/commands/new_test.go b/commands/new_test.go index e74e9cfc..5991e181 100644 --- a/commands/new_test.go +++ b/commands/new_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import ( "path/filepath" "testing" - "github.com/spf13/afero" "github.com/spf13/hugo/hugofs" "github.com/stretchr/testify/assert" ) @@ -41,14 +40,14 @@ func checkNewSiteInited(basepath string, t *testing.T) { } for _, path := range paths { - _, err := hugofs.SourceFs.Stat(path) + _, err := hugofs.Source().Stat(path) assert.Nil(t, err) } } func TestDoNewSite(t *testing.T) { basepath := filepath.Join(os.TempDir(), "blog") - hugofs.SourceFs = new(afero.MemMapFs) + hugofs.InitMemFs() err := doNewSite(basepath, false) assert.Nil(t, err) @@ -57,17 +56,17 @@ func TestDoNewSite(t *testing.T) { func TestDoNewSite_noerror_base_exists_but_empty(t *testing.T) { basepath := filepath.Join(os.TempDir(), "blog") - hugofs.SourceFs = new(afero.MemMapFs) - hugofs.SourceFs.MkdirAll(basepath, 777) + hugofs.InitMemFs() + hugofs.Source().MkdirAll(basepath, 777) err := doNewSite(basepath, false) assert.Nil(t, err) } func TestDoNewSite_error_base_exists(t *testing.T) { basepath := filepath.Join(os.TempDir(), "blog") - hugofs.SourceFs = new(afero.MemMapFs) - hugofs.SourceFs.MkdirAll(basepath, 777) - hugofs.SourceFs.Create(filepath.Join(basepath, "foo")) + hugofs.InitMemFs() + hugofs.Source().MkdirAll(basepath, 777) + hugofs.Source().Create(filepath.Join(basepath, "foo")) // Since the directory already exists and isn't empty, expect an error err := doNewSite(basepath, false) assert.NotNil(t, err) @@ -75,8 +74,8 @@ func TestDoNewSite_error_base_exists(t *testing.T) { func TestDoNewSite_force_empty_dir(t *testing.T) { basepath := filepath.Join(os.TempDir(), "blog") - hugofs.SourceFs = new(afero.MemMapFs) - hugofs.SourceFs.MkdirAll(basepath, 777) + hugofs.InitMemFs() + hugofs.Source().MkdirAll(basepath, 777) err := doNewSite(basepath, true) assert.Nil(t, err) @@ -86,8 +85,8 @@ func TestDoNewSite_force_empty_dir(t *testing.T) { func TestDoNewSite_error_force_dir_inside_exists(t *testing.T) { basepath := filepath.Join(os.TempDir(), "blog") contentPath := filepath.Join(basepath, "content") - hugofs.SourceFs = new(afero.MemMapFs) - hugofs.SourceFs.MkdirAll(contentPath, 777) + hugofs.InitMemFs() + hugofs.Source().MkdirAll(contentPath, 777) err := doNewSite(basepath, true) assert.NotNil(t, err) } @@ -95,9 +94,9 @@ func TestDoNewSite_error_force_dir_inside_exists(t *testing.T) { func TestDoNewSite_error_force_config_inside_exists(t *testing.T) { basepath := filepath.Join(os.TempDir(), "blog") configPath := filepath.Join(basepath, "config.toml") - hugofs.SourceFs = new(afero.MemMapFs) - hugofs.SourceFs.MkdirAll(basepath, 777) - hugofs.SourceFs.Create(configPath) + hugofs.InitMemFs() + hugofs.Source().MkdirAll(basepath, 777) + hugofs.Source().Create(configPath) err := doNewSite(basepath, true) assert.NotNil(t, err) } diff --git a/commands/server.go b/commands/server.go index 124541b9..345d2dec 100644 --- a/commands/server.go +++ b/commands/server.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -152,7 +152,7 @@ func server(cmd *cobra.Command, args []string) error { // Hugo writes the output to memory instead of the disk if !renderToDisk { - hugofs.DestinationFS = new(afero.MemMapFs) + hugofs.SetDestination(new(afero.MemMapFs)) // Rendering to memoryFS, publish to Root regardless of publishDir. viper.Set("PublishDir", "/") } @@ -191,7 +191,7 @@ func serve(port int) { jww.FEEDBACK.Println("Serving pages from memory") } - httpFs := afero.NewHttpFs(hugofs.DestinationFS) + httpFs := afero.NewHttpFs(hugofs.Destination()) fs := filesOnlyFs{httpFs.Dir(helpers.AbsPathify(viper.GetString("PublishDir")))} fileserver := http.FileServer(fs) diff --git a/create/content_test.go b/create/content_test.go index f0b3de1a..0ccbd03e 100644 --- a/create/content_test.go +++ b/create/content_test.go @@ -44,19 +44,19 @@ func TestNewContent(t *testing.T) { } for i, c := range cases { - err = create.NewContent(hugofs.SourceFs, c.kind, c.path) + err = create.NewContent(hugofs.Source(), c.kind, c.path) if err != nil { t.Errorf("[%d] NewContent: %s", i, err) } fname := filepath.Join(os.TempDir(), "content", filepath.FromSlash(c.path)) - _, err = hugofs.SourceFs.Stat(fname) + _, err = hugofs.Source().Stat(fname) if err != nil { t.Errorf("[%d] Stat: %s", i, err) } for _, v := range c.resultStrings { - found, err := afero.FileContainsBytes(hugofs.SourceFs, fname, []byte(v)) + found, err := afero.FileContainsBytes(hugofs.Source(), fname, []byte(v)) if err != nil { t.Errorf("[%d] FileContainsBytes: %s", i, err) } @@ -77,7 +77,7 @@ func initViper() { } func initFs() error { - hugofs.SourceFs = new(afero.MemMapFs) + hugofs.SetSource(new(afero.MemMapFs)) perm := os.FileMode(0755) var err error @@ -89,7 +89,7 @@ func initFs() error { } for _, dir := range dirs { dir = filepath.Join(os.TempDir(), dir) - err = hugofs.SourceFs.Mkdir(dir, perm) + err = hugofs.Source().Mkdir(dir, perm) if err != nil { return err } @@ -109,7 +109,7 @@ func initFs() error { content: "+++\n+++\n", }, } { - f, err := hugofs.SourceFs.Create(v.path) + f, err := hugofs.Source().Create(v.path) if err != nil { return err } diff --git a/docs/content/templates/functions.md b/docs/content/templates/functions.md index b271e33e..fe0f2e25 100644 --- a/docs/content/templates/functions.md +++ b/docs/content/templates/functions.md @@ -83,7 +83,7 @@ e.g. Pass into "foo.html" a map with the keys "important, content" or create a map on the fly to pass into {{partial "foo" (dict "important" "Smiles" "content" "You should do more")}} - + ### slice @@ -336,6 +336,12 @@ e.g. {{ .Content }} {{ end }} +## Files +### readFile +Reads a file from disk and converts it into a string. Note that the filename must be relative to the current project working dir. + So, if you have a file with the name `README.txt` in the root of your project with the content `Hugo Rocks!`: + + `{{readFile "README.txt"}}` → `"Hugo Rocks!"` ## Math diff --git a/helpers/pygments.go b/helpers/pygments.go index fe14ad49..637f61b1 100644 --- a/helpers/pygments.go +++ b/helpers/pygments.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -60,7 +60,7 @@ func Highlight(code, lang, optsStr string) string { io.WriteString(hash, lang) io.WriteString(hash, options) - fs := hugofs.OsFs + fs := hugofs.Os() cacheDir := viper.GetString("CacheDir") var cachefile string diff --git a/hugofs/fs.go b/hugofs/fs.go index 7807bf93..a3cb55ea 100644 --- a/hugofs/fs.go +++ b/hugofs/fs.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,8 +13,81 @@ package hugofs -import "github.com/spf13/afero" +import ( + "github.com/spf13/afero" + "github.com/spf13/viper" +) -var SourceFs afero.Fs = new(afero.OsFs) -var DestinationFS afero.Fs = new(afero.OsFs) -var OsFs afero.Fs = new(afero.OsFs) +var ( + sourceFs afero.Fs + destinationFs afero.Fs + osFs afero.Fs = &afero.OsFs{} + workingDirFs *afero.BasePathFs +) + +// Source returns Hugo's source file system. +func Source() afero.Fs { + return sourceFs +} + +// SetSource sets Hugo's source file system +// and re-initializes dependent file systems. +func SetSource(fs afero.Fs) { + sourceFs = fs + initSourceDependencies() +} + +// Destination returns Hugo's destionation file system. +func Destination() afero.Fs { + return destinationFs +} + +// SetDestination sets Hugo's destionation file system +func SetDestination(fs afero.Fs) { + destinationFs = fs +} + +// Os returns an OS file system. +func Os() afero.Fs { + return osFs +} + +// WorkingDir returns a read-only file system +// restricted to the project working dir. +func WorkingDir() *afero.BasePathFs { + return workingDirFs +} + +// InitFs initializes with the OS file system +// as source and destination file systems. +func InitDefaultFs() { + InitFs(&afero.OsFs{}) +} + +// InitMemFs initializes with a MemMapFs as source and destination file systems. +// Useful for testing. +func InitMemFs() { + InitFs(&afero.MemMapFs{}) +} + +// InitFs initializes with the given file system +// as source and destination file systems. +func InitFs(fs afero.Fs) { + sourceFs = fs + destinationFs = fs + + initSourceDependencies() +} + +func initSourceDependencies() { + workingDir := viper.GetString("WorkingDir") + + if workingDir != "" { + workingDirFs = afero.NewBasePathFs(afero.NewReadOnlyFs(sourceFs), workingDir).(*afero.BasePathFs) + } + +} + +func init() { + InitDefaultFs() +} diff --git a/hugofs/fs_test.go b/hugofs/fs_test.go new file mode 100644 index 00000000..06d63a78 --- /dev/null +++ b/hugofs/fs_test.go @@ -0,0 +1,72 @@ +// Copyright 2016 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hugofs + +import ( + "github.com/spf13/afero" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestInitDefault(t *testing.T) { + viper.Reset() + defer viper.Reset() + + InitDefaultFs() + + assert.IsType(t, new(afero.OsFs), Source()) + assert.IsType(t, new(afero.OsFs), Destination()) + assert.IsType(t, new(afero.OsFs), Os()) + assert.Nil(t, WorkingDir()) +} + +func TestInitMemFs(t *testing.T) { + viper.Reset() + defer viper.Reset() + + InitMemFs() + + assert.IsType(t, new(afero.MemMapFs), Source()) + assert.IsType(t, new(afero.MemMapFs), Destination()) + assert.IsType(t, new(afero.OsFs), Os()) + assert.Nil(t, WorkingDir()) +} + +func TestSetSource(t *testing.T) { + + InitMemFs() + + SetSource(new(afero.OsFs)) + assert.IsType(t, new(afero.OsFs), Source()) +} + +func TestSetDestination(t *testing.T) { + + InitMemFs() + + SetDestination(new(afero.OsFs)) + assert.IsType(t, new(afero.OsFs), Destination()) +} + +func TestWorkingDir(t *testing.T) { + viper.Reset() + defer viper.Reset() + + viper.Set("WorkingDir", "/a/b/") + + InitMemFs() + + assert.IsType(t, new(afero.BasePathFs), WorkingDir()) +} diff --git a/hugolib/handler_test.go b/hugolib/handler_test.go index df5b970c..29b1161e 100644 --- a/hugolib/handler_test.go +++ b/hugolib/handler_test.go @@ -17,7 +17,6 @@ import ( "path/filepath" "testing" - "github.com/spf13/afero" "github.com/spf13/hugo/helpers" "github.com/spf13/hugo/hugofs" "github.com/spf13/hugo/source" @@ -29,7 +28,7 @@ func TestDefaultHandler(t *testing.T) { viper.Reset() defer viper.Reset() - hugofs.DestinationFS = new(afero.MemMapFs) + hugofs.InitMemFs() sources := []source.ByteSource{ {filepath.FromSlash("sect/doc1.html"), []byte("---\nmarkup: markdown\n---\n# title\nsome *content*")}, {filepath.FromSlash("sect/doc2.html"), []byte("more content")}, @@ -75,7 +74,7 @@ func TestDefaultHandler(t *testing.T) { } for _, test := range tests { - file, err := hugofs.DestinationFS.Open(test.doc) + file, err := hugofs.Destination().Open(test.doc) if err != nil { t.Fatalf("Did not find %s in target.", test.doc) } diff --git a/hugolib/menu_test.go b/hugolib/menu_test.go index 609a87d0..aaa172eb 100644 --- a/hugolib/menu_test.go +++ b/hugolib/menu_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,7 +22,6 @@ import ( "github.com/BurntSushi/toml" "github.com/kr/pretty" - "github.com/spf13/afero" "github.com/spf13/hugo/hugofs" "github.com/spf13/hugo/source" "github.com/spf13/viper" @@ -684,7 +683,7 @@ func setupMenuTests(t *testing.T, pageSources []source.ByteSource) *Site { } func createTestSite(pageSources []source.ByteSource) *Site { - hugofs.DestinationFS = new(afero.MemMapFs) + hugofs.InitMemFs() s := &Site{ Source: &source.InMemorySource{ByteSource: pageSources}, diff --git a/hugolib/page.go b/hugolib/page.go index a2b4d311..3d1b4873 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -890,9 +890,9 @@ func (p *Page) saveSource(by []byte, inpath string, safe bool) (err error) { jww.INFO.Println("creating", inpath) if safe { - err = helpers.SafeWriteToDisk(inpath, bytes.NewReader(by), hugofs.SourceFs) + err = helpers.SafeWriteToDisk(inpath, bytes.NewReader(by), hugofs.Source()) } else { - err = helpers.WriteToDisk(inpath, bytes.NewReader(by), hugofs.SourceFs) + err = helpers.WriteToDisk(inpath, bytes.NewReader(by), hugofs.Source()) } if err != nil { return diff --git a/hugolib/robotstxt_test.go b/hugolib/robotstxt_test.go index 31f89b84..b0a843a7 100644 --- a/hugolib/robotstxt_test.go +++ b/hugolib/robotstxt_test.go @@ -1,10 +1,22 @@ +// Copyright 2016 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package hugolib import ( "bytes" "testing" - "github.com/spf13/afero" "github.com/spf13/hugo/helpers" "github.com/spf13/hugo/hugofs" "github.com/spf13/hugo/source" @@ -21,7 +33,7 @@ func TestRobotsTXTOutput(t *testing.T) { viper.Reset() defer viper.Reset() - hugofs.DestinationFS = new(afero.MemMapFs) + hugofs.InitMemFs() viper.Set("baseurl", "http://auth/bub/") @@ -53,7 +65,7 @@ func TestRobotsTXTOutput(t *testing.T) { t.Fatalf("Unable to RenderRobotsTXT :%s", err) } - robotsFile, err := hugofs.DestinationFS.Open("robots.txt") + robotsFile, err := hugofs.Destination().Open("robots.txt") if err != nil { t.Fatalf("Unable to locate: robots.txt") diff --git a/hugolib/rss_test.go b/hugolib/rss_test.go index b8a16665..e42202a5 100644 --- a/hugolib/rss_test.go +++ b/hugolib/rss_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ import ( "bytes" "testing" - "github.com/spf13/afero" "github.com/spf13/hugo/helpers" "github.com/spf13/hugo/hugofs" "github.com/spf13/hugo/source" @@ -53,7 +52,7 @@ func TestRSSOutput(t *testing.T) { viper.Set("baseurl", "http://auth/bub/") viper.Set("RSSUri", rssURI) - hugofs.DestinationFS = new(afero.MemMapFs) + hugofs.InitMemFs() s := &Site{ Source: &source.InMemorySource{ByteSource: WEIGHTED_SOURCES}, } @@ -72,7 +71,7 @@ func TestRSSOutput(t *testing.T) { t.Fatalf("Unable to RenderHomePage: %s", err) } - file, err := hugofs.DestinationFS.Open(rssURI) + file, err := hugofs.Destination().Open(rssURI) if err != nil { t.Fatalf("Unable to locate: %s", rssURI) diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go index 833cdae9..ab764b84 100644 --- a/hugolib/shortcode_test.go +++ b/hugolib/shortcode_test.go @@ -487,7 +487,7 @@ e`, createAndRenderPages(t, s) for _, test := range tests { - file, err := hugofs.DestinationFS.Open(test.outFile) + file, err := hugofs.Destination().Open(test.outFile) if err != nil { t.Fatalf("Did not find %s in target: %s", test.outFile, err) diff --git a/hugolib/site.go b/hugolib/site.go index b1c5090f..19262805 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -529,7 +529,7 @@ func (s *Site) ReBuild(events []fsnotify.Event) error { // it's been updated if ev.Op&fsnotify.Rename == fsnotify.Rename { // If the file is still on disk, it's only been updated, if it's not, it's been moved - if ex, err := afero.Exists(hugofs.SourceFs, ev.Name); !ex || err != nil { + if ex, err := afero.Exists(hugofs.Source(), ev.Name); !ex || err != nil { path, _ := helpers.GetRelativePath(ev.Name, s.absContentDir()) s.RemovePageByPath(path) continue @@ -852,7 +852,7 @@ func (s *Site) absPublishDir() string { } func (s *Site) checkDirectories() (err error) { - if b, _ := helpers.DirExists(s.absContentDir(), hugofs.SourceFs); !b { + if b, _ := helpers.DirExists(s.absContentDir(), hugofs.Source()); !b { return fmt.Errorf("No source directory found, expecting to find it at " + s.absContentDir()) } return diff --git a/hugolib/site_test.go b/hugolib/site_test.go index d96db432..1173d0f3 100644 --- a/hugolib/site_test.go +++ b/hugolib/site_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -25,7 +25,6 @@ import ( "bitbucket.org/pkg/inflect" - "github.com/spf13/afero" "github.com/spf13/hugo/helpers" "github.com/spf13/hugo/hugofs" "github.com/spf13/hugo/source" @@ -200,7 +199,7 @@ func TestRenderThingOrDefault(t *testing.T) { {false, TEMPLATE_FUNC, HTML("simple-template")}, } - hugofs.DestinationFS = new(afero.MemMapFs) + hugofs.InitMemFs() for i, test := range tests { @@ -226,7 +225,7 @@ func TestRenderThingOrDefault(t *testing.T) { t.Errorf("Unable to render html: %s", err) } - file, err := hugofs.DestinationFS.Open(filepath.FromSlash("out/index.html")) + file, err := hugofs.Destination().Open(filepath.FromSlash("out/index.html")) if err != nil { t.Errorf("Unable to open html: %s", err) } @@ -240,7 +239,7 @@ func TestDraftAndFutureRender(t *testing.T) { viper.Reset() defer viper.Reset() - hugofs.DestinationFS = new(afero.MemMapFs) + hugofs.InitMemFs() sources := []source.ByteSource{ {filepath.FromSlash("sect/doc1.md"), []byte("---\ntitle: doc1\ndraft: true\npublishdate: \"2414-05-29\"\n---\n# doc1\n*some content*")}, {filepath.FromSlash("sect/doc2.md"), []byte("---\ntitle: doc2\ndraft: true\npublishdate: \"2012-05-29\"\n---\n# doc2\n*some content*")}, @@ -299,7 +298,7 @@ func TestDraftAndFutureRender(t *testing.T) { // Issue #957 func TestCrossrefs(t *testing.T) { - hugofs.DestinationFS = new(afero.MemMapFs) + hugofs.InitMemFs() for _, uglyURLs := range []bool{true, false} { for _, relative := range []bool{true, false} { doTestCrossrefs(t, relative, uglyURLs) @@ -374,7 +373,7 @@ THE END.`, refShortcode))}, } for _, test := range tests { - file, err := hugofs.DestinationFS.Open(test.doc) + file, err := hugofs.Destination().Open(test.doc) if err != nil { t.Fatalf("Did not find %s in target: %s", test.doc, err) @@ -392,7 +391,7 @@ THE END.`, refShortcode))}, // Issue #939 // Issue #1923 func TestShouldAlwaysHaveUglyURLs(t *testing.T) { - hugofs.DestinationFS = new(afero.MemMapFs) + hugofs.InitMemFs() for _, uglyURLs := range []bool{true, false} { doTestShouldAlwaysHaveUglyURLs(t, uglyURLs) } @@ -462,7 +461,7 @@ func doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) { } for _, test := range tests { - file, err := hugofs.DestinationFS.Open(test.doc) + file, err := hugofs.Destination().Open(test.doc) if err != nil { t.Fatalf("Did not find %s in target: %s", test.doc, err) } @@ -489,7 +488,7 @@ func TestSectionNaming(t *testing.T) { } func doTestSectionNaming(t *testing.T, canonify, uglify, pluralize bool) { - hugofs.DestinationFS = new(afero.MemMapFs) + hugofs.InitMemFs() viper.Reset() defer viper.Reset() viper.Set("baseurl", "http://auth/sub/") @@ -539,7 +538,7 @@ func doTestSectionNaming(t *testing.T, canonify, uglify, pluralize bool) { } for _, test := range tests { - file, err := hugofs.DestinationFS.Open(test.doc) + file, err := hugofs.Destination().Open(test.doc) if err != nil { t.Fatalf("Did not find %s in target: %s", test.doc, err) } @@ -560,7 +559,7 @@ func TestSkipRender(t *testing.T) { viper.Reset() defer viper.Reset() - hugofs.DestinationFS = new(afero.MemMapFs) + hugofs.InitMemFs() sources := []source.ByteSource{ {filepath.FromSlash("sect/doc1.html"), []byte("---\nmarkup: markdown\n---\n# title\nsome *content*")}, {filepath.FromSlash("sect/doc2.html"), []byte("more content")}, @@ -605,7 +604,7 @@ func TestSkipRender(t *testing.T) { } for _, test := range tests { - file, err := hugofs.DestinationFS.Open(test.doc) + file, err := hugofs.Destination().Open(test.doc) if err != nil { t.Fatalf("Did not find %s in target.", test.doc) } @@ -624,7 +623,7 @@ func TestAbsURLify(t *testing.T) { viper.Set("DefaultExtension", "html") - hugofs.DestinationFS = new(afero.MemMapFs) + hugofs.InitMemFs() sources := []source.ByteSource{ {filepath.FromSlash("sect/doc1.html"), []byte("link")}, {filepath.FromSlash("content/blue/doc2.html"), []byte("---\nf: t\n---\nmore content")}, @@ -662,7 +661,7 @@ func TestAbsURLify(t *testing.T) { for _, test := range tests { - file, err := hugofs.DestinationFS.Open(filepath.FromSlash(test.file)) + file, err := hugofs.Destination().Open(filepath.FromSlash(test.file)) if err != nil { t.Fatalf("Unable to locate rendered content: %s", test.file) } @@ -730,7 +729,7 @@ func TestOrderedPages(t *testing.T) { viper.Reset() defer viper.Reset() - hugofs.DestinationFS = new(afero.MemMapFs) + hugofs.InitMemFs() viper.Set("baseurl", "http://auth/bub") s := &Site{ @@ -804,7 +803,7 @@ func TestGroupedPages(t *testing.T) { } }() - hugofs.DestinationFS = new(afero.MemMapFs) + hugofs.InitMemFs() viper.Set("baseurl", "http://auth/bub") s := &Site{ @@ -984,7 +983,7 @@ func TestWeightedTaxonomies(t *testing.T) { viper.Reset() defer viper.Reset() - hugofs.DestinationFS = new(afero.MemMapFs) + hugofs.InitMemFs() sources := []source.ByteSource{ {filepath.FromSlash("sect/doc1.md"), PAGE_WITH_WEIGHTED_TAXONOMIES_1}, {filepath.FromSlash("sect/doc2.md"), PAGE_WITH_WEIGHTED_TAXONOMIES_2}, @@ -1039,7 +1038,7 @@ func findPage(site *Site, f string) *Page { } func setupLinkingMockSite(t *testing.T) *Site { - hugofs.DestinationFS = new(afero.MemMapFs) + hugofs.InitMemFs() sources := []source.ByteSource{ {filepath.FromSlash("index.md"), []byte("")}, {filepath.FromSlash("rootfile.md"), []byte("")}, diff --git a/hugolib/site_url_test.go b/hugolib/site_url_test.go index 2bccd99a..c4c918c8 100644 --- a/hugolib/site_url_test.go +++ b/hugolib/site_url_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import ( "html/template" - "github.com/spf13/afero" "github.com/spf13/hugo/hugofs" "github.com/spf13/hugo/source" "github.com/spf13/hugo/target" @@ -88,7 +87,7 @@ func TestPageCount(t *testing.T) { viper.Reset() defer viper.Reset() - hugofs.DestinationFS = new(afero.MemMapFs) + hugofs.InitMemFs() viper.Set("uglyurls", false) viper.Set("paginate", 10) @@ -113,7 +112,7 @@ func TestPageCount(t *testing.T) { t.Errorf("Unable to render site lists: %s", err) } - _, err := hugofs.DestinationFS.Open("blue") + _, err := hugofs.Destination().Open("blue") if err != nil { t.Errorf("No indexed rendered.") } @@ -129,7 +128,7 @@ func TestPageCount(t *testing.T) { "sd3/index.html", "sd4.html", } { - if _, err := hugofs.DestinationFS.Open(filepath.FromSlash(s)); err != nil { + if _, err := hugofs.Destination().Open(filepath.FromSlash(s)); err != nil { t.Errorf("No alias rendered: %s", s) } } diff --git a/hugolib/sitemap_test.go b/hugolib/sitemap_test.go index 34f2d347..3f10bbcd 100644 --- a/hugolib/sitemap_test.go +++ b/hugolib/sitemap_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,7 +17,6 @@ import ( "bytes" "testing" - "github.com/spf13/afero" "github.com/spf13/hugo/helpers" "github.com/spf13/hugo/hugofs" "github.com/spf13/hugo/source" @@ -40,7 +39,7 @@ func TestSitemapOutput(t *testing.T) { viper.Reset() defer viper.Reset() - hugofs.DestinationFS = new(afero.MemMapFs) + hugofs.InitMemFs() viper.Set("baseurl", "http://auth/bub/") @@ -72,7 +71,7 @@ func TestSitemapOutput(t *testing.T) { t.Fatalf("Unable to RenderRobotsTXT :%s", err) } - sitemapFile, err := hugofs.DestinationFS.Open("sitemap.xml") + sitemapFile, err := hugofs.Destination().Open("sitemap.xml") if err != nil { t.Fatalf("Unable to locate: sitemap.xml") diff --git a/source/filesystem.go b/source/filesystem.go index 053081a7..0ce5808d 100644 --- a/source/filesystem.go +++ b/source/filesystem.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -93,7 +93,7 @@ func (f *Filesystem) captureFiles() { return err } - err := helpers.SymbolicWalk(hugofs.SourceFs, f.Base, walker) + err := helpers.SymbolicWalk(hugofs.Source(), f.Base, walker) if err != nil { jww.ERROR.Println(err) diff --git a/target/file.go b/target/file.go index 41fa0025..740741bb 100644 --- a/target/file.go +++ b/target/file.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -49,7 +49,7 @@ func (fs *Filesystem) Publish(path string, r io.Reader) (err error) { return } - return helpers.WriteToDisk(translated, r, hugofs.DestinationFS) + return helpers.WriteToDisk(translated, r, hugofs.Destination()) } func (fs *Filesystem) Translate(src string) (dest string, err error) { diff --git a/target/htmlredirect.go b/target/htmlredirect.go index b5b47a88..1e2abec4 100644 --- a/target/htmlredirect.go +++ b/target/htmlredirect.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -144,5 +144,5 @@ func (h *HTMLRedirectAlias) Publish(path string, permalink string) (err error) { return } - return helpers.WriteToDisk(path, buffer, hugofs.DestinationFS) + return helpers.WriteToDisk(path, buffer, hugofs.Destination()) } diff --git a/target/page.go b/target/page.go index 9e7efdb6..d67d678f 100644 --- a/target/page.go +++ b/target/page.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -41,7 +41,7 @@ func (pp *PagePub) Publish(path string, r io.Reader) (err error) { return } - return helpers.WriteToDisk(translated, r, hugofs.DestinationFS) + return helpers.WriteToDisk(translated, r, hugofs.Destination()) } func (pp *PagePub) Translate(src string) (dest string, err error) { diff --git a/tpl/template.go b/tpl/template.go index 9266fc8d..7fad9591 100644 --- a/tpl/template.go +++ b/tpl/template.go @@ -235,7 +235,7 @@ func (t *GoHTMLTemplate) AddTemplateFileWithMaster(name, overlayFilename, master masterTpl := t.Lookup(masterFilename) if masterTpl == nil { - b, err := afero.ReadFile(hugofs.SourceFs, masterFilename) + b, err := afero.ReadFile(hugofs.Source(), masterFilename) if err != nil { return err } @@ -248,7 +248,7 @@ func (t *GoHTMLTemplate) AddTemplateFileWithMaster(name, overlayFilename, master } } - b, err := afero.ReadFile(hugofs.SourceFs, overlayFilename) + b, err := afero.ReadFile(hugofs.Source(), overlayFilename) if err != nil { return err } @@ -309,14 +309,14 @@ func (t *GoHTMLTemplate) AddTemplateFile(name, baseTemplatePath, path string) er } case ".ace": var innerContent, baseContent []byte - innerContent, err := afero.ReadFile(hugofs.SourceFs, path) + innerContent, err := afero.ReadFile(hugofs.Source(), path) if err != nil { return err } if baseTemplatePath != "" { - baseContent, err = afero.ReadFile(hugofs.SourceFs, baseTemplatePath) + baseContent, err = afero.ReadFile(hugofs.Source(), baseTemplatePath) if err != nil { return err } @@ -329,7 +329,7 @@ func (t *GoHTMLTemplate) AddTemplateFile(name, baseTemplatePath, path string) er return t.AddTemplateFileWithMaster(name, path, baseTemplatePath) } - b, err := afero.ReadFile(hugofs.SourceFs, path) + b, err := afero.ReadFile(hugofs.Source(), path) if err != nil { return err @@ -414,7 +414,7 @@ func (t *GoHTMLTemplate) loadTemplates(absPath string, prefix string) { // This may be a view that shouldn't have base template // Have to look inside it to make sure - needsBase, err := helpers.FileContainsAny(path, innerMarkers, hugofs.OsFs) + needsBase, err := helpers.FileContainsAny(path, innerMarkers, hugofs.Os()) if err != nil { return err } @@ -442,7 +442,7 @@ func (t *GoHTMLTemplate) loadTemplates(absPath string, prefix string) { } for _, pathToCheck := range pathsToCheck { - if ok, err := helpers.Exists(pathToCheck, hugofs.OsFs); err == nil && ok { + if ok, err := helpers.Exists(pathToCheck, hugofs.Os()); err == nil && ok { baseTemplatePath = pathToCheck break } diff --git a/tpl/template_funcs.go b/tpl/template_funcs.go index 5fb496c1..8abd70b8 100644 --- a/tpl/template_funcs.go +++ b/tpl/template_funcs.go @@ -24,6 +24,8 @@ import ( "encoding/json" "errors" "fmt" + "github.com/spf13/afero" + "github.com/spf13/hugo/hugofs" "html" "html/template" "math/rand" @@ -1467,6 +1469,38 @@ func index(item interface{}, indices ...interface{}) (interface{}, error) { return v.Interface(), nil } +// readFile reads the file named by filename relative to the given basepath +// and returns the contents as a string. +// There is a upper size limit set at 1 megabytes. +func readFile(fs *afero.BasePathFs, filename string) (string, error) { + if filename == "" { + return "", errors.New("readFile needs a filename") + } + + if info, err := fs.Stat(filename); err == nil { + if info.Size() > 1000000 { + return "", fmt.Errorf("File %q is too big", filename) + } + } else { + return "", err + } + b, err := afero.ReadFile(fs, filename) + + if err != nil { + return "", err + } + + return string(b), nil +} + +// readFileFromWorkingDir reads the file named by filename relative to the +// configured WorkingDir. +// It returns the contents as a string. +// There is a upper size limit set at 1 megabytes. +func readFileFromWorkingDir(i interface{}) (string, error) { + return readFile(hugofs.WorkingDir(), cast.ToString(i)) +} + // safeHTMLAttr returns a given string as html/template HTMLAttr content. // // safeHTMLAttr is currently disabled, pending further discussion @@ -1689,6 +1723,7 @@ func init() { "plainify": plainify, "pluralize": pluralize, "readDir": readDir, + "readFile": readFileFromWorkingDir, "ref": ref, "relURL": func(a string) template.HTML { return template.HTML(helpers.RelURL(a)) }, "relref": relRef, diff --git a/tpl/template_funcs_test.go b/tpl/template_funcs_test.go index 77cd52a3..efb58386 100644 --- a/tpl/template_funcs_test.go +++ b/tpl/template_funcs_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,18 +18,20 @@ import ( "encoding/base64" "errors" "fmt" + "github.com/spf13/afero" + "github.com/spf13/cast" + "github.com/spf13/hugo/hugofs" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" "html/template" "math/rand" "path" + "path/filepath" "reflect" "runtime" "strings" "testing" "time" - - "github.com/spf13/cast" - "github.com/spf13/viper" - "github.com/stretchr/testify/assert" ) type tstNoStringer struct { @@ -63,6 +65,15 @@ func TestFuncsInTemplate(t *testing.T) { viper.Reset() defer viper.Reset() + workingDir := "/home/hugo" + + viper.Set("WorkingDir", workingDir) + + fs := &afero.MemMapFs{} + hugofs.InitFs(fs) + + afero.WriteFile(fs, filepath.Join(workingDir, "README.txt"), []byte("Hugo Rocks!"), 0755) + // Add the examples from the docs: As a smoke test and to make sure the examples work. // TODO(bep): docs: fix title example in := @@ -109,6 +120,7 @@ safeCSS: {{ "Bat&Man" | safeCSS | safeCSS }} safeURL: {{ "http://gohugo.io" | safeURL | safeURL }} safeJS: {{ "(1*2)" | safeJS | safeJS }} plainify: {{ plainify "Hello world, gophers!" }} +readFile: {{ readFile "README.txt" }} ` expected := `chomp:

Blockhead

dateFormat: Wednesday, Jan 21, 2015 @@ -153,6 +165,7 @@ safeCSS: Bat&Man safeURL: http://gohugo.io safeJS: (1*2) plainify: Hello world, gophers! +readFile: Hugo Rocks! ` var b bytes.Buffer @@ -2182,3 +2195,44 @@ func TestSHA1(t *testing.T) { } } } + +func TestReadFile(t *testing.T) { + viper.Reset() + defer viper.Reset() + + workingDir := "/home/hugo" + + viper.Set("WorkingDir", workingDir) + + fs := &afero.MemMapFs{} + hugofs.InitFs(fs) + + afero.WriteFile(fs, filepath.Join(workingDir, "/f/f1.txt"), []byte("f1-content"), 0755) + afero.WriteFile(fs, filepath.Join("/home", "f2.txt"), []byte("f2-content"), 0755) + + for i, this := range []struct { + filename string + expect interface{} + }{ + {"", false}, + {"b", false}, + {filepath.FromSlash("/f/f1.txt"), "f1-content"}, + {filepath.FromSlash("f/f1.txt"), "f1-content"}, + {filepath.FromSlash("../f2.txt"), false}, + } { + result, err := readFileFromWorkingDir(this.filename) + if b, ok := this.expect.(bool); ok && !b { + if err == nil { + t.Errorf("[%d] readFile didn't return an expected error", i) + } + } else { + if err != nil { + t.Errorf("[%d] readFile failed: %s", i, err) + continue + } + if result != this.expect { + t.Errorf("[%d] readFile got %q but expected %q", i, result, this.expect) + } + } + } +} diff --git a/tpl/template_resources.go b/tpl/template_resources.go index 1c0655d2..0d0ea833 100644 --- a/tpl/template_resources.go +++ b/tpl/template_resources.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -177,9 +177,9 @@ func resGetResource(url string) ([]byte, error) { return nil, nil } if strings.Contains(url, "://") { - return resGetRemote(url, hugofs.SourceFs, http.DefaultClient) + return resGetRemote(url, hugofs.Source(), http.DefaultClient) } - return resGetLocal(url, hugofs.SourceFs) + return resGetLocal(url, hugofs.Source()) } // getJSON expects one or n-parts of a URL to a resource which can either be a local or a remote one. @@ -201,7 +201,7 @@ func getJSON(urlParts ...string) interface{} { jww.ERROR.Printf("Cannot read json from resource %s with error message %s", url, err) jww.ERROR.Printf("Retry #%d for %s and sleeping for %s", i, url, resSleep) time.Sleep(resSleep) - resDeleteCache(url, hugofs.SourceFs) + resDeleteCache(url, hugofs.Source()) continue } break @@ -234,7 +234,7 @@ func getCSV(sep string, urlParts ...string) [][]string { var clearCacheSleep = func(i int, u string) { jww.ERROR.Printf("Retry #%d for %s and sleeping for %s", i, url, resSleep) time.Sleep(resSleep) - resDeleteCache(url, hugofs.SourceFs) + resDeleteCache(url, hugofs.Source()) } for i := 0; i <= resRetries; i++ { diff --git a/tpl/template_resources_test.go b/tpl/template_resources_test.go index 8e3a9d1d..d091595b 100644 --- a/tpl/template_resources_test.go +++ b/tpl/template_resources_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -214,7 +214,7 @@ type wd struct { func testRetryWhenDone() wd { cd := viper.GetString("CacheDir") - viper.Set("CacheDir", helpers.GetTempDir("", hugofs.SourceFs)) + viper.Set("CacheDir", helpers.GetTempDir("", hugofs.Source())) var tmpSleep time.Duration tmpSleep, resSleep = resSleep, time.Millisecond return wd{func() { diff --git a/tpl/template_test.go b/tpl/template_test.go index 9649a8fa..c96b8c87 100644 --- a/tpl/template_test.go +++ b/tpl/template_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. +// Copyright 2016 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -123,17 +123,17 @@ func TestAddTemplateFileWithMaster(t *testing.T) { {`tpl`, `{{.0.E}}`, 0, false}, } { - hugofs.SourceFs = afero.NewMemMapFs() + hugofs.InitMemFs() templ := New() overlayTplName := "ot" masterTplName := "mt" finalTplName := "tp" if this.writeSkipper != 1 { - afero.WriteFile(hugofs.SourceFs, masterTplName, []byte(this.masterTplContent), 0644) + afero.WriteFile(hugofs.Source(), masterTplName, []byte(this.masterTplContent), 0644) } if this.writeSkipper != 2 { - afero.WriteFile(hugofs.SourceFs, overlayTplName, []byte(this.overlayTplContent), 0644) + afero.WriteFile(hugofs.Source(), overlayTplName, []byte(this.overlayTplContent), 0644) } err := templ.AddTemplateFileWithMaster(finalTplName, overlayTplName, masterTplName)