diff --git a/commands/new.go b/commands/new.go index 170952a0..4fc00031 100644 --- a/commands/new.go +++ b/commands/new.go @@ -110,8 +110,7 @@ func NewContent(cmd *cobra.Command, args []string) error { kind = contentType } - return create.NewContent(kind, createpath) - + return create.NewContent(hugofs.SourceFs, kind, createpath) } func doNewSite(basepath string, force bool) error { diff --git a/create/content.go b/create/content.go index 6d38bbde..b9774448 100644 --- a/create/content.go +++ b/create/content.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. @@ -15,7 +15,6 @@ package create import ( "bytes" - "io/ioutil" "os" "os/exec" "path" @@ -23,24 +22,26 @@ import ( "strings" "time" + "github.com/spf13/afero" "github.com/spf13/cast" "github.com/spf13/hugo/helpers" - "github.com/spf13/hugo/hugofs" "github.com/spf13/hugo/hugolib" "github.com/spf13/hugo/parser" jww "github.com/spf13/jwalterweatherman" "github.com/spf13/viper" ) -func NewContent(kind, name string) (err error) { +// NewContent creates a new content file in the content directory based upon the +// given kind, which is used to lookup an archetype. +func NewContent(fs afero.Fs, kind, name string) (err error) { jww.INFO.Println("attempting to create ", name, "of", kind) - location := FindArchetype(kind) + location := FindArchetype(fs, kind) var by []byte if location != "" { - by, err = ioutil.ReadFile(location) + by, err = afero.ReadFile(fs, location) if err != nil { jww.ERROR.Println(err) } @@ -53,58 +54,24 @@ func NewContent(kind, name string) (err error) { if err != nil { return err } - metadata, err := psr.Metadata() + + metadata, err := createMetadata(psr, name) if err != nil { + jww.ERROR.Printf("Error processing archetype file %s: %s\n", location, err) return err } - newmetadata, err := cast.ToStringMapE(metadata) - if err != nil { - jww.ERROR.Println("Error processing archetype file:", location) - return err - } - - for k := range newmetadata { - switch strings.ToLower(k) { - case "date": - newmetadata[k] = time.Now() - case "title": - newmetadata[k] = helpers.MakeTitle(helpers.Filename(name)) - } - } - - caseimatch := func(m map[string]interface{}, key string) bool { - for k := range m { - if strings.ToLower(k) == strings.ToLower(key) { - return true - } - } - return false - } - - if newmetadata == nil { - newmetadata = make(map[string]interface{}) - } - - if !caseimatch(newmetadata, "date") { - newmetadata["date"] = time.Now() - } - - if !caseimatch(newmetadata, "title") { - newmetadata["title"] = helpers.MakeTitle(helpers.Filename(name)) - } page, err := hugolib.NewPage(name) if err != nil { return err } - if x := parser.FormatSanitize(viper.GetString("MetaDataFormat")); x == "json" || x == "yaml" || x == "toml" { - newmetadata["date"] = time.Now().Format(time.RFC3339) + if err = page.SetSourceMetaData(metadata, parser.FormatToLeadRune(viper.GetString("MetaDataFormat"))); err != nil { + return } - //page.Dir = viper.GetString("sourceDir") - page.SetSourceMetaData(newmetadata, parser.FormatToLeadRune(viper.GetString("MetaDataFormat"))) page.SetSourceContent(psr.Content()) + if err = page.SafeSaveSourceAs(filepath.Join(viper.GetString("contentDir"), name)); err != nil { return } @@ -120,21 +87,72 @@ func NewContent(kind, name string) (err error) { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - if err = cmd.Run(); err != nil { - return - } + return cmd.Run() } return nil } -func FindArchetype(kind string) (outpath string) { +// createMetadata generates Metadata for a new page based upon the metadata +// found in an archetype. +func createMetadata(archetype parser.Page, name string) (map[string]interface{}, error) { + archMetadata, err := archetype.Metadata() + if err != nil { + return nil, err + } + + metadata, err := cast.ToStringMapE(archMetadata) + if err != nil { + return nil, err + } + + for k := range metadata { + switch strings.ToLower(k) { + case "date": + metadata[k] = time.Now() + case "title": + metadata[k] = helpers.MakeTitle(helpers.Filename(name)) + } + } + + caseimatch := func(m map[string]interface{}, key string) bool { + for k := range m { + if strings.ToLower(k) == strings.ToLower(key) { + return true + } + } + return false + } + + if metadata == nil { + metadata = make(map[string]interface{}) + } + + if !caseimatch(metadata, "date") { + metadata["date"] = time.Now() + } + + if !caseimatch(metadata, "title") { + metadata["title"] = helpers.MakeTitle(helpers.Filename(name)) + } + + if x := parser.FormatSanitize(viper.GetString("MetaDataFormat")); x == "json" || x == "yaml" || x == "toml" { + metadata["date"] = time.Now().Format(time.RFC3339) + } + + return metadata, nil +} + +// FindArchetype takes a given kind/archetype of content and returns an output +// path for that archetype. If no archetype is found, an empty string is +// returned. +func FindArchetype(fs afero.Fs, kind string) (outpath string) { search := []string{helpers.AbsPathify(viper.GetString("archetypeDir"))} if viper.GetString("theme") != "" { themeDir := filepath.Join(helpers.AbsPathify(viper.GetString("themesDir")+"/"+viper.GetString("theme")), "/archetypes/") - if _, err := os.Stat(themeDir); os.IsNotExist(err) { - jww.ERROR.Println("Unable to find archetypes directory for theme :", viper.GetString("theme"), "in", themeDir) + if _, err := fs.Stat(themeDir); os.IsNotExist(err) { + jww.ERROR.Printf("Unable to find archetypes directory for theme %q at %q", viper.GetString("theme"), themeDir) } else { search = append(search, themeDir) } @@ -154,7 +172,7 @@ func FindArchetype(kind string) (outpath string) { for _, p := range pathsToCheck { curpath := filepath.Join(x, p) jww.DEBUG.Println("checking", curpath, "for archetypes") - if exists, _ := helpers.Exists(curpath, hugofs.SourceFs); exists { + if exists, _ := helpers.Exists(curpath, fs); exists { jww.INFO.Println("curpath: " + curpath) return curpath } diff --git a/create/content_test.go b/create/content_test.go new file mode 100644 index 00000000..f0b3de1a --- /dev/null +++ b/create/content_test.go @@ -0,0 +1,125 @@ +// 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 create_test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/spf13/afero" + "github.com/spf13/hugo/create" + "github.com/spf13/hugo/hugofs" + "github.com/spf13/viper" +) + +func TestNewContent(t *testing.T) { + initViper() + + err := initFs() + if err != nil { + t.Fatalf("initialization error: %s", err) + } + + cases := []struct { + kind string + path string + resultStrings []string + }{ + {"post", "post/sample-1.md", []string{`title = "sample 1"`, `test = "test1"`}}, + {"stump", "stump/sample-2.md", []string{`title = "sample 2"`}}, // no archetype file + {"", "sample-3.md", []string{`title = "sample 3"`}}, // no archetype + {"product", "product/sample-4.md", []string{`title = "sample 4"`}}, // empty archetype front matter + } + + for i, c := range cases { + err = create.NewContent(hugofs.SourceFs, 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) + if err != nil { + t.Errorf("[%d] Stat: %s", i, err) + } + + for _, v := range c.resultStrings { + found, err := afero.FileContainsBytes(hugofs.SourceFs, fname, []byte(v)) + if err != nil { + t.Errorf("[%d] FileContainsBytes: %s", i, err) + } + if !found { + t.Errorf("content missing from output: %q", v) + } + } + } +} + +func initViper() { + viper.Reset() + viper.Set("MetaDataFormat", "toml") + viper.Set("archetypeDir", filepath.Join(os.TempDir(), "archetypes")) + viper.Set("contentDir", filepath.Join(os.TempDir(), "content")) + viper.Set("themesDir", filepath.Join(os.TempDir(), "themes")) + viper.Set("theme", "sample") +} + +func initFs() error { + hugofs.SourceFs = new(afero.MemMapFs) + perm := os.FileMode(0755) + var err error + + // create directories + dirs := []string{ + "archetypes", + "content", + filepath.Join("themes", "sample", "archetypes"), + } + for _, dir := range dirs { + dir = filepath.Join(os.TempDir(), dir) + err = hugofs.SourceFs.Mkdir(dir, perm) + if err != nil { + return err + } + } + + // create files + for _, v := range []struct { + path string + content string + }{ + { + path: filepath.Join(os.TempDir(), "archetypes", "post.md"), + content: "+++\ndate = \"2015-01-12T19:20:04-07:00\"\ntitle = \"post arch\"\ntest = \"test1\"\n+++\n", + }, + { + path: filepath.Join(os.TempDir(), "archetypes", "product.md"), + content: "+++\n+++\n", + }, + } { + f, err := hugofs.SourceFs.Create(v.path) + if err != nil { + return err + } + defer f.Close() + + _, err = f.Write([]byte(v.content)) + if err != nil { + return err + } + } + + return nil +}