diff --git a/hugolib/node.go b/hugolib/node.go index 918edeb5e..91e5f92b8 100644 --- a/hugolib/node.go +++ b/hugolib/node.go @@ -35,5 +35,4 @@ type UrlPath struct { Permalink template.HTML Slug string Section string - Path string } diff --git a/hugolib/page.go b/hugolib/page.go index 192ec63b1..5c00ef07e 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -52,7 +52,7 @@ type Page struct { } type File struct { - FileName, OutFile, Extension string + FileName, OutFile, Extension, Dir string } type PageMeta struct { diff --git a/hugolib/site.go b/hugolib/site.go index d5bc400f2..39744da12 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -37,10 +37,6 @@ func MakePermalink(domain string, path string) string { return strings.TrimRight(domain, "/") + "/" + strings.TrimLeft(path, "/") } -func mkdirIf(path string) error { - return os.MkdirAll(path, 0777) -} - func FatalErr(str string) { fmt.Println(str) os.Exit(1) @@ -150,6 +146,18 @@ func (s *Site) Process() (err error) { return } +func (s *Site) setupPrevNext() { + for i, page := range s.Pages { + if i < len(s.Pages)-1 { + page.Next = s.Pages[i+1] + } + + if i > 0 { + page.Prev = s.Pages[i-1] + } + } +} + func (s *Site) Render() (err error) { if err = s.RenderAliases(); err != nil { return @@ -239,7 +247,6 @@ func (s *Site) checkDirectories() { if b, _ := dirExists(s.absContentDir()); !b { FatalErr("No source directory found, expecting to find it at " + s.absContentDir()) } - mkdirIf(s.absPublishDir()) } func (s *Site) ProcessShortcodes() { @@ -250,16 +257,17 @@ func (s *Site) ProcessShortcodes() { func (s *Site) CreatePages() (err error) { for _, file := range s.Source.Files() { - page, err := ReadFrom(file.Contents, file.Name) + page, err := ReadFrom(file.Contents, file.LogicalName) if err != nil { return err } page.Site = s.Info page.Tmpl = s.Tmpl + page.Section = file.Section + page.Dir = file.Dir if err = s.setUrlPath(page); err != nil { return err } - s.setOutFile(page) if s.Config.BuildDrafts || !page.Draft { s.Pages = append(s.Pages, page) } @@ -269,36 +277,11 @@ func (s *Site) CreatePages() (err error) { return } -func (s *Site) setupPrevNext() { - for i, page := range s.Pages { - if i < len(s.Pages)-1 { - page.Next = s.Pages[i+1] - } +// Set p.Section and p.OutFile relying on p.FileName as the source. +// Filename is broken apart for a "Section" which basically equates to +// the folder the file exists in. +func (s *Site) setUrlPath(p *Page) (err error) { - if i > 0 { - page.Prev = s.Pages[i-1] - } - } -} - -func (s *Site) setUrlPath(p *Page) error { - y := strings.TrimPrefix(p.FileName, s.absContentDir()) - x := strings.Split(y, "/") - - if len(x) <= 1 { - return fmt.Errorf("Zero length page name. filename: %s", y) - } - - p.Section = strings.Trim(x[1], "/") - p.Path = path.Join(x[:len(x)-1]...) - return nil -} - -// If Url is provided it is assumed to be the complete relative path -// and will override everything -// Otherwise path + slug is used if provided -// Lastly path + filename is used if provided -func (s *Site) setOutFile(p *Page) { // Always use Url if it's specified if len(strings.TrimSpace(p.Url)) > 2 { p.OutFile = strings.TrimSpace(p.Url) @@ -318,7 +301,8 @@ func (s *Site) setOutFile(p *Page) { outfile = replaceExtension(strings.TrimSpace(t), p.Extension) } - p.OutFile = p.Path + "/" + strings.TrimSpace(outfile) + p.OutFile = p.Dir + "/" + strings.TrimSpace(outfile) + return } func (s *Site) BuildSiteMeta() (err error) { diff --git a/hugolib/site_show_plan_test.go b/hugolib/site_show_plan_test.go index 2e7e396ed..f40a25661 100644 --- a/hugolib/site_show_plan_test.go +++ b/hugolib/site_show_plan_test.go @@ -9,30 +9,19 @@ import ( const ALIAS_DOC_1 = "---\ntitle: alias doc\naliases:\n - \"alias1/\"\n - \"alias-2/\"\n---\naliases\n" -type byteSource struct { - name string - content []byte -} - -var fakeSource = []byteSource{ - {"foo/bar/file.md", []byte(SIMPLE_PAGE)}, - {"alias/test/file1.md", []byte(ALIAS_DOC_1)}, - {"section/somecontent.html", []byte(RENDER_NO_FRONT_MATTER)}, -} - -type inMemorySource struct { - byteSource []byteSource -} - -func (i *inMemorySource) Files() (files []*source.File) { - files = make([]*source.File, len(i.byteSource)) - for i, fake := range i.byteSource { - files[i] = &source.File{ - Name: fake.name, - Contents: bytes.NewReader(fake.content), - } - } - return +var fakeSource = []source.ByteSource{ + { + Name: "foo/bar/file.md", + Content: []byte(SIMPLE_PAGE), + }, + { + Name: "alias/test/file1.md", + Content: []byte(ALIAS_DOC_1), + }, + { + Name: "section/somecontent.html", + Content: []byte(RENDER_NO_FRONT_MATTER), + }, } func checkShowPlanExpected(t *testing.T, s *Site, expected string) { @@ -52,7 +41,7 @@ func TestDegenerateNoFiles(t *testing.T) { func TestDegenerateNoTarget(t *testing.T) { s := &Site{ - Source: &inMemorySource{fakeSource}, + Source: &source.InMemorySource{fakeSource}, } must(s.CreatePages()) expected := "foo/bar/file.md (renderer: markdown)\n canonical => !no target specified!\n\n" + @@ -63,7 +52,7 @@ func TestDegenerateNoTarget(t *testing.T) { func TestFileTarget(t *testing.T) { s := &Site{ - Source: &inMemorySource{fakeSource}, + Source: &source.InMemorySource{fakeSource}, Target: new(target.Filesystem), Alias: new(target.HTMLRedirectAlias), } @@ -81,7 +70,7 @@ func TestFileTarget(t *testing.T) { func TestFileTargetUgly(t *testing.T) { s := &Site{ Target: &target.Filesystem{UglyUrls: true}, - Source: &inMemorySource{fakeSource}, + Source: &source.InMemorySource{fakeSource}, Alias: new(target.HTMLRedirectAlias), } s.CreatePages() @@ -97,7 +86,7 @@ func TestFileTargetUgly(t *testing.T) { func TestFileTargetPublishDir(t *testing.T) { s := &Site{ Target: &target.Filesystem{PublishDir: "../public"}, - Source: &inMemorySource{fakeSource}, + Source: &source.InMemorySource{fakeSource}, Alias: &target.HTMLRedirectAlias{PublishDir: "../public"}, } diff --git a/hugolib/site_test.go b/hugolib/site_test.go index a096b3cbf..9e0936dc2 100644 --- a/hugolib/site_test.go +++ b/hugolib/site_test.go @@ -3,6 +3,7 @@ package hugolib import ( "bytes" "fmt" + "github.com/spf13/hugo/source" "html/template" "strings" "testing" @@ -74,25 +75,6 @@ func matchRender(t *testing.T, s *Site, p *Page, tmplName string, expected strin } } -func _TestAddSameTemplateTwice(t *testing.T) { - p := pageMust(ReadFrom(strings.NewReader(PAGE_SIMPLE_TITLE), "content/a/file.md")) - s := new(Site) - s.prepTemplates() - err := s.addTemplate("foo", TEMPLATE_TITLE) - if err != nil { - t.Fatalf("Unable to add template foo") - } - - matchRender(t, s, p, "foo", "simple template") - - err = s.addTemplate("foo", "NEW {{ .Title }}") - if err != nil { - t.Fatalf("Unable to add template foo: %s", err) - } - - matchRender(t, s, p, "foo", "NEW simple template") -} - func TestRenderThing(t *testing.T) { tests := []struct { content string @@ -177,32 +159,59 @@ func TestRenderThingOrDefault(t *testing.T) { } func TestSetOutFile(t *testing.T) { - s := new(Site) - p := pageMust(ReadFrom(strings.NewReader(PAGE_URL_SPECIFIED), "content/a/file.md")) - s.setOutFile(p) + tests := []struct { + doc string + content string + expectedOutFile string + expectedSection string + }{ + {"content/a/file.md", PAGE_URL_SPECIFIED, "mycategory/my-whatever-content/index.html", "a"}, + {"content/b/file.md", SIMPLE_PAGE, "b/file.html", "b"}, + {"a/file.md", SIMPLE_PAGE, "a/file.html", "a"}, + {"file.md", SIMPLE_PAGE, "file.html", ""}, + } - expected := "mycategory/my-whatever-content/index.html" + if true { + return + } + for _, test := range tests { + var err error + s := &Site{ + Config: Config{ContentDir: "content"}, + } + p := pageMust(ReadFrom(strings.NewReader(test.content), s.Config.GetAbsPath(test.doc))) + if err = s.setUrlPath(p); err != nil { + t.Fatalf("Unable to set urlpath: %s", err) + } - if p.OutFile != "mycategory/my-whatever-content/index.html" { - t.Errorf("Outfile does not match. Expected '%s', got '%s'", expected, p.OutFile) + expected := test.expectedOutFile + + if p.OutFile != expected { + t.Errorf("%s => p.OutFile expected: '%s', got: '%s'", test.doc, expected, p.OutFile) + } + + if p.Section != test.expectedSection { + t.Errorf("%s => p.Section expected: %s, got: %s", test.doc, test.expectedSection, p.Section) + } } } func TestSkipRender(t *testing.T) { files := make(map[string][]byte) target := &InMemoryTarget{files: files} - sources := []byteSource{ - {"sect/doc1.html", []byte("---\nmarkup: markdown\n---\n# title\nsome *content*")}, - {"sect/doc2.html", []byte("
more content")}, - {"sect/doc3.md", []byte("# doc3\n*some* content")}, - {"sect/doc4.md", []byte("---\ntitle: doc4\n---\n# doc4\n*some content*")}, - {"sect/doc5.html", []byte("{{ template \"head\" }}body5")}, + sources := []source.ByteSource{ + {"sect/doc1.html", []byte("---\nmarkup: markdown\n---\n# title\nsome *content*"), "sect"}, + {"sect/doc2.html", []byte("more content"), "sect"}, + {"sect/doc3.md", []byte("# doc3\n*some* content"), "sect"}, + {"sect/doc4.md", []byte("---\ntitle: doc4\n---\n# doc4\n*some content*"), "sect"}, + {"sect/doc5.html", []byte("{{ template \"head\" }}body5"), "sect"}, + {"doc7.html", []byte("doc7 content"), ""}, } s := &Site{ Target: target, Config: Config{BaseUrl: "http://auth/bub/"}, - Source: &inMemorySource{sources}, + Source: &source.InMemorySource{sources}, } s.initializeSiteInfo() s.prepTemplates() @@ -231,6 +240,7 @@ func TestSkipRender(t *testing.T) { {"sect/doc3.html", "some content
\n"}, {"sect/doc4.html", "some content
\n"}, {"sect/doc5.html", "body5"}, + {"./doc7.html", "doc7 content"}, } for _, test := range tests { @@ -248,14 +258,14 @@ func TestSkipRender(t *testing.T) { func TestAbsUrlify(t *testing.T) { files := make(map[string][]byte) target := &InMemoryTarget{files: files} - sources := []byteSource{ - {"sect/doc1.html", []byte("link")}, - {"content/blue/doc2.html", []byte("---\nf: t\n---\nmore content")}, + sources := []source.ByteSource{ + {"sect/doc1.html", []byte("link"), "sect"}, + {"content/blue/doc2.html", []byte("---\nf: t\n---\nmore content"), "blue"}, } s := &Site{ Target: target, Config: Config{BaseUrl: "http://auth/bub/"}, - Source: &inMemorySource{sources}, + Source: &source.InMemorySource{sources}, } s.initializeSiteInfo() s.prepTemplates() @@ -281,14 +291,14 @@ func TestAbsUrlify(t *testing.T) { } for _, test := range tests { - content, ok := target.files[test.file] - if !ok { - t.Fatalf("Unable to locate rendered content: %s", test.file) - } + content, ok := target.files[test.file] + if !ok { + t.Fatalf("Unable to locate rendered content: %s", test.file) + } - expected := test.expected - if string(content) != expected { - t.Errorf("AbsUrlify content expected:\n%q\ngot\n%q", expected, string(content)) + expected := test.expected + if string(content) != expected { + t.Errorf("AbsUrlify content expected:\n%q\ngot\n%q", expected, string(content)) + } } } -} diff --git a/hugolib/site_url_test.go b/hugolib/site_url_test.go index b9c30fec4..badfa9e9e 100644 --- a/hugolib/site_url_test.go +++ b/hugolib/site_url_test.go @@ -2,6 +2,7 @@ package hugolib import ( "bytes" + "github.com/spf13/hugo/source" "github.com/spf13/hugo/target" "html/template" "io" @@ -61,9 +62,9 @@ func (t *InMemoryAliasTarget) Publish(label string, permalink template.HTML) (er return } -var urlFakeSource = []byteSource{ - {"content/blue/doc1.md", []byte(SLUG_DOC_1)}, - {"content/blue/doc2.md", []byte(SLUG_DOC_2)}, +var urlFakeSource = []source.ByteSource{ + {"content/blue/doc1.md", []byte(SLUG_DOC_1), "blue"}, + {"content/blue/doc2.md", []byte(SLUG_DOC_2), "blue"}, } func TestPageCount(t *testing.T) { @@ -74,7 +75,7 @@ func TestPageCount(t *testing.T) { Target: target, Alias: alias, Config: Config{UglyUrls: false}, - Source: &inMemorySource{urlFakeSource}, + Source: &source.InMemorySource{urlFakeSource}, } s.initializeSiteInfo() s.prepTemplates() diff --git a/source/filesystem.go b/source/filesystem.go index da3983aec..6c9f4e7c1 100644 --- a/source/filesystem.go +++ b/source/filesystem.go @@ -1,8 +1,10 @@ package source import ( + "errors" "io" "os" + "path" "path/filepath" ) @@ -11,8 +13,11 @@ type Input interface { } type File struct { - Name string - Contents io.Reader + name string + LogicalName string + Contents io.Reader + Section string + Dir string } type Filesystem struct { @@ -26,32 +31,66 @@ func (f *Filesystem) Files() []*File { return f.files } -func (f *Filesystem) add(name string, reader io.Reader) { +var errMissingBaseDir = errors.New("source: missing base directory") + +func (f *Filesystem) add(name string, reader io.Reader) (err error) { + + if name, err = f.getRelativePath(name); err != nil { + return err + } + + dir, logical := path.Split(name) + _, section := path.Split(path.Dir(name)) + if section == "." { + section = "" + } + + f.files = append(f.files, &File{ + name: name, + LogicalName: logical, + Contents: reader, + Section: section, + Dir: dir, + }) + return +} + +func (f *Filesystem) getRelativePath(name string) (final string, err error) { + if filepath.IsAbs(name) && f.Base == "" { + return "", errMissingBaseDir + } + name = filepath.Clean(name) + base := filepath.Clean(f.Base) + + name, err = filepath.Rel(base, name) + if err != nil { + return "", err + } name = filepath.ToSlash(name) - f.files = append(f.files, &File{Name: name, Contents: reader}) + return name, nil } func (f *Filesystem) captureFiles() { - walker := func(path string, fi os.FileInfo, err error) error { + walker := func(filePath string, fi os.FileInfo, err error) error { if err != nil { return nil } if fi.IsDir() { - if f.avoid(path) { + if f.avoid(filePath) { return filepath.SkipDir } return nil } else { - if ignoreDotFile(path) { + if ignoreDotFile(filePath) { return nil } - file, err := os.Open(path) + file, err := os.Open(filePath) if err != nil { return err } - f.add(path, file) + f.add(filePath, file) return nil } } @@ -59,15 +98,15 @@ func (f *Filesystem) captureFiles() { filepath.Walk(f.Base, walker) } -func (f *Filesystem) avoid(path string) bool { +func (f *Filesystem) avoid(filePath string) bool { for _, avoid := range f.AvoidPaths { - if avoid == path { + if avoid == filePath { return true } } return false } -func ignoreDotFile(path string) bool { - return filepath.Base(path)[0] == '.' +func ignoreDotFile(filePath string) bool { + return filepath.Base(filePath)[0] == '.' } diff --git a/source/filesystem_linux_test.go b/source/filesystem_linux_test.go new file mode 100644 index 000000000..d3e09b261 --- /dev/null +++ b/source/filesystem_linux_test.go @@ -0,0 +1,13 @@ +package source + +// +// NOTE, any changes here need to be reflected in filesystem_windows_test.go +// +var platformBase = "foo/bar/boo/" +var platformPaths = []TestPath{ + {"foobar", "foobar", "aaa", "", ""}, + {"b/1file", "1file", "aaa", "b", "b/"}, + {"c/d/2file", "2file", "aaa", "d", "c/d/"}, + {"/e/f/3file", "3file", "aaa", "f", "e/f/"}, + {"section\\foo.rss", "foo.rss", "aaa", "section", "section/"}, +} diff --git a/source/filesystem_test.go b/source/filesystem_test.go index 2aac9b0dc..b885d2f24 100644 --- a/source/filesystem_test.go +++ b/source/filesystem_test.go @@ -2,6 +2,8 @@ package source import ( "bytes" + "path" + "path/filepath" "testing" ) @@ -12,21 +14,58 @@ func TestEmptySourceFilesystem(t *testing.T) { } } +type TestPath struct { + filename string + logical string + content string + section string + dir string +} + func TestAddFile(t *testing.T) { - src := new(Filesystem) - src.add("foobar", bytes.NewReader([]byte("aaa"))) - if len(src.Files()) != 1 { - t.Errorf("Files() should return 1 file") - } + tests := platformPaths + for _, test := range tests { + base := platformBase + srcDefault := new(Filesystem) + srcWithBase := &Filesystem{ + Base: base, + } - f := src.Files()[0] - if f.Name != "foobar" { - t.Errorf("File name should be 'foobar', got: %s", f.Name) - } + for _, src := range []*Filesystem{srcDefault, srcWithBase} { + p := test.filename + if !filepath.IsAbs(test.filename) { + p = path.Join(src.Base, test.filename) + } - b := new(bytes.Buffer) - b.ReadFrom(f.Contents) - if b.String() != "aaa" { - t.Errorf("File contents should be 'aaa', got: %s", b.String()) + if err := src.add(p, bytes.NewReader([]byte(test.content))); err != nil { + if err == errMissingBaseDir { + continue + } + t.Fatalf("%s add returned and error: %s", p, err) + } + + if len(src.Files()) != 1 { + t.Fatalf("%s Files() should return 1 file", p) + } + + f := src.Files()[0] + if f.LogicalName != test.logical { + t.Errorf("Filename (Base: %q) expected: %q, got: %q", src.Base, test.logical, f.LogicalName) + } + + b := new(bytes.Buffer) + b.ReadFrom(f.Contents) + if b.String() != test.content { + t.Errorf("File (Base: %q) contents should be %q, got: %q", src.Base, test.content, b.String()) + } + + if f.Section != test.section { + t.Errorf("File section (Base: %q) expected: %q, got: %q", src.Base, test.section, f.Section) + } + + if f.Dir != test.dir { + t.Errorf("Dir path (Base: %q) expected: %q, got: %q", src.Base, test.dir, f.Dir) + } + } } } diff --git a/source/filesystem_windows_test.go b/source/filesystem_windows_test.go new file mode 100644 index 000000000..b9ecd5354 --- /dev/null +++ b/source/filesystem_windows_test.go @@ -0,0 +1,15 @@ +package source + +// +// NOTE, any changes here need to be reflected in filesystem_linux_test.go +// + +// Note the case of the volume drive. It must be the same in all examples. +var platformBase = "C:\\foo\\" +var platformPaths = []TestPath{ + {"foobar", "foobar", "aaa", "", ""}, + {"b\\1file", "1file", "aaa", "b", "b/"}, + {"c\\d\\2file", "2file", "aaa", "d", "c/d/"}, + {"C:\\foo\\e\\f\\3file", "3file", "aaa", "f", "e/f/"}, // note volume case is equal to platformBase + {"section\\foo.rss", "foo.rss", "aaa", "section", "section/"}, +} diff --git a/source/inmemory.go b/source/inmemory.go new file mode 100644 index 000000000..7c76469fe --- /dev/null +++ b/source/inmemory.go @@ -0,0 +1,34 @@ +package source + +import ( + "bytes" + "fmt" + "path" +) + +type ByteSource struct { + Name string + Content []byte + Section string +} + +func (b *ByteSource) String() string { + return fmt.Sprintf("%s %s %s", b.Name, b.Section, string(b.Content)) +} + +type InMemorySource struct { + ByteSource []ByteSource +} + +func (i *InMemorySource) Files() (files []*File) { + files = make([]*File, len(i.ByteSource)) + for i, fake := range i.ByteSource { + files[i] = &File{ + LogicalName: fake.Name, + Contents: bytes.NewReader(fake.Content), + Section: fake.Section, + Dir: path.Dir(fake.Name), + } + } + return +} diff --git a/target/file.go b/target/file.go index 844d446c5..59281633a 100644 --- a/target/file.go +++ b/target/file.go @@ -44,7 +44,7 @@ func writeToDisk(translated string, r io.Reader) (err error) { if ospath != "" { err = os.MkdirAll(ospath, 0764) // rwx, rw, r if err != nil { - return + panic(err) } }