func TestPageCount(t *testing.T) { testCommonResetState() hugofs.InitMemFs() viper.Set("uglyURLs", false) viper.Set("paginate", 10) s := &Site{ Source: &source.InMemorySource{ByteSource: urlFakeSource}, Language: helpers.NewDefaultLanguage(), } if err := buildAndRenderSite(s, "indexes/blue.html", indexTemplate); err != nil { t.Fatalf("Failed to build site: %s", err) } _, err := hugofs.Destination().Open("public/blue") if err != nil { t.Errorf("No indexed rendered.") } for _, s := range []string{ "public/sd1/foo/index.html", "public/sd2/index.html", "public/sd3/index.html", "public/sd4.html", } { if _, err := hugofs.Destination().Open(filepath.FromSlash(s)); err != nil { t.Errorf("No alias rendered: %s", s) } } }
func TestPageCount(t *testing.T) { viper.Reset() defer viper.Reset() hugofs.InitMemFs() viper.Set("uglyurls", false) viper.Set("paginate", 10) s := &Site{ Source: &source.InMemorySource{ByteSource: urlFakeSource}, } s.initializeSiteInfo() s.prepTemplates("indexes/blue.html", indexTemplate) if err := s.createPages(); err != nil { t.Errorf("Unable to create pages: %s", err) } if err := s.buildSiteMeta(); err != nil { t.Errorf("Unable to build site metadata: %s", err) } if err := s.renderSectionLists(); err != nil { t.Errorf("Unable to render section lists: %s", err) } if err := s.renderAliases(); err != nil { t.Errorf("Unable to render site lists: %s", err) } _, err := hugofs.Destination().Open("blue") if err != nil { t.Errorf("No indexed rendered.") } //expected := ".." //if string(blueIndex) != expected { //t.Errorf("Index template does not match expected: %q, got: %q", expected, string(blueIndex)) //} for _, s := range []string{ "sd1/foo/index.html", "sd2/index.html", "sd3/index.html", "sd4.html", } { if _, err := hugofs.Destination().Open(filepath.FromSlash(s)); err != nil { t.Errorf("No alias rendered: %s", s) } } }
func copyStatic() error { publishDir := helpers.AbsPathify(viper.GetString("publishDir")) + helpers.FilePathSeparator // If root, remove the second '/' if publishDir == "//" { publishDir = helpers.FilePathSeparator } // Includes both theme/static & /static staticSourceFs := getStaticSourceFs() if staticSourceFs == nil { jww.WARN.Println("No static directories found to sync") return nil } syncer := fsync.NewSyncer() syncer.NoTimes = viper.GetBool("notimes") syncer.SrcFs = staticSourceFs 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") if syncer.Delete { jww.INFO.Println("removing all files from destination that don't exist in static dirs") } jww.INFO.Println("syncing static files to", publishDir) // because we are using a baseFs (to get the union right). // set sync src to root return syncer.Sync(publishDir, helpers.FilePathSeparator) }
func destinationExists(filename string) bool { b, err := helpers.Exists(filename, hugofs.Destination()) if err != nil { panic(err) } return b }
func serve(port int) { if renderToDisk { jww.FEEDBACK.Println("Serving pages from " + helpers.AbsPathify(viper.GetString("publishDir"))) } else { jww.FEEDBACK.Println("Serving pages from memory") } httpFs := afero.NewHttpFs(hugofs.Destination()) fs := filesOnlyFs{httpFs.Dir(helpers.AbsPathify(viper.GetString("publishDir")))} fileserver := http.FileServer(fs) // We're only interested in the path u, err := url.Parse(viper.GetString("baseURL")) if err != nil { jww.ERROR.Fatalf("Invalid baseURL: %s", err) } if u.Path == "" || u.Path == "/" { http.Handle("/", fileserver) } else { http.Handle(u.Path, http.StripPrefix(u.Path, fileserver)) } jww.FEEDBACK.Printf("Web Server is available at %s (bind address %s)\n", u.String(), serverInterface) fmt.Println("Press Ctrl+C to stop") endpoint := net.JoinHostPort(serverInterface, strconv.Itoa(port)) err = http.ListenAndServe(endpoint, nil) if err != nil { jww.ERROR.Printf("Error: %s\n", err.Error()) os.Exit(1) } }
func (h *HTMLRedirectAlias) Publish(path string, permalink string, page interface{}) (err error) { if path, err = h.Translate(path); err != nil { jww.ERROR.Printf("%s, skipping.", err) return nil } t := "alias" if strings.HasSuffix(path, ".xhtml") { t = "alias-xhtml" } template := defaultAliasTemplates if h.Templates != nil { template = h.Templates t = "alias.html" } buffer := new(bytes.Buffer) err = template.ExecuteTemplate(buffer, t, &AliasNode{permalink, page}) if err != nil { return } return helpers.WriteToDisk(path, buffer, hugofs.Destination()) }
func TestDefaultHandler(t *testing.T) { testCommonResetState() hugofs.InitMemFs() sources := []source.ByteSource{ {Name: filepath.FromSlash("sect/doc1.html"), Content: []byte("---\nmarkup: markdown\n---\n# title\nsome *content*")}, {Name: filepath.FromSlash("sect/doc2.html"), Content: []byte("<!doctype html><html><body>more content</body></html>")}, {Name: filepath.FromSlash("sect/doc3.md"), Content: []byte("# doc3\n*some* content")}, {Name: filepath.FromSlash("sect/doc4.md"), Content: []byte("---\ntitle: doc4\n---\n# doc4\n*some content*")}, {Name: filepath.FromSlash("sect/doc3/img1.png"), Content: []byte("‰PNG ��� IHDR����������:~›U��� IDATWcø��ZMoñ����IEND®B`‚")}, {Name: filepath.FromSlash("sect/img2.gif"), Content: []byte("GIF89a��€��ÿÿÿ���,�������D�;")}, {Name: filepath.FromSlash("sect/img2.spf"), Content: []byte("****FAKE-FILETYPE****")}, {Name: filepath.FromSlash("doc7.html"), Content: []byte("<html><body>doc7 content</body></html>")}, {Name: filepath.FromSlash("sect/doc8.html"), Content: []byte("---\nmarkup: md\n---\n# title\nsome *content*")}, } viper.Set("DefaultExtension", "html") viper.Set("verbose", true) s := &Site{ Source: &source.InMemorySource{ByteSource: sources}, targets: targetList{page: &target.PagePub{UglyURLs: true, PublishDir: "public"}}, Language: helpers.NewLanguage("en"), } if err := buildAndRenderSite(s, "_default/single.html", "{{.Content}}", "head", "<head><script src=\"script.js\"></script></head>", "head_abs", "<head><script src=\"/script.js\"></script></head>"); err != nil { t.Fatalf("Failed to render site: %s", err) } tests := []struct { doc string expected string }{ {filepath.FromSlash("public/sect/doc1.html"), "\n\n<h1 id=\"title\">title</h1>\n\n<p>some <em>content</em></p>\n"}, {filepath.FromSlash("public/sect/doc2.html"), "<!doctype html><html><body>more content</body></html>"}, {filepath.FromSlash("public/sect/doc3.html"), "\n\n<h1 id=\"doc3\">doc3</h1>\n\n<p><em>some</em> content</p>\n"}, {filepath.FromSlash("public/sect/doc3/img1.png"), string([]byte("‰PNG ��� IHDR����������:~›U��� IDATWcø��ZMoñ����IEND®B`‚"))}, {filepath.FromSlash("public/sect/img2.gif"), string([]byte("GIF89a��€��ÿÿÿ���,�������D�;"))}, {filepath.FromSlash("public/sect/img2.spf"), string([]byte("****FAKE-FILETYPE****"))}, {filepath.FromSlash("public/doc7.html"), "<html><body>doc7 content</body></html>"}, {filepath.FromSlash("public/sect/doc8.html"), "\n\n<h1 id=\"title\">title</h1>\n\n<p>some <em>content</em></p>\n"}, } for _, test := range tests { file, err := hugofs.Destination().Open(test.doc) if err != nil { t.Fatalf("Did not find %s in target.", test.doc) } content := helpers.ReaderToString(file) if content != test.expected { t.Errorf("%s content expected:\n%q\ngot:\n%q", test.doc, test.expected, content) } } }
func TestRobotsTXTOutput(t *testing.T) { testCommonResetState() hugofs.InitMemFs() viper.Set("baseurl", "http://auth/bub/") viper.Set("enableRobotsTXT", true) s := &Site{ Source: &source.InMemorySource{ByteSource: weightedSources}, Language: helpers.NewDefaultLanguage(), } if err := buildAndRenderSite(s, "robots.txt", robotTxtTemplate); err != nil { t.Fatalf("Failed to build site: %s", err) } robotsFile, err := hugofs.Destination().Open("public/robots.txt") if err != nil { t.Fatalf("Unable to locate: robots.txt") } robots := helpers.ReaderToBytes(robotsFile) if !bytes.HasPrefix(robots, []byte("User-agent: Googlebot")) { t.Errorf("Robots file should start with 'User-agentL Googlebot'. %s", robots) } }
func TestSkipRender(t *testing.T) { testCommonResetState() hugofs.InitMemFs() sources := []source.ByteSource{ {Name: filepath.FromSlash("sect/doc1.html"), Content: []byte("---\nmarkup: markdown\n---\n# title\nsome *content*")}, {Name: filepath.FromSlash("sect/doc2.html"), Content: []byte("<!doctype html><html><body>more content</body></html>")}, {Name: filepath.FromSlash("sect/doc3.md"), Content: []byte("# doc3\n*some* content")}, {Name: filepath.FromSlash("sect/doc4.md"), Content: []byte("---\ntitle: doc4\n---\n# doc4\n*some content*")}, {Name: filepath.FromSlash("sect/doc5.html"), Content: []byte("<!doctype html><html>{{ template \"head\" }}<body>body5</body></html>")}, {Name: filepath.FromSlash("sect/doc6.html"), Content: []byte("<!doctype html><html>{{ template \"head_abs\" }}<body>body5</body></html>")}, {Name: filepath.FromSlash("doc7.html"), Content: []byte("<html><body>doc7 content</body></html>")}, {Name: filepath.FromSlash("sect/doc8.html"), Content: []byte("---\nmarkup: md\n---\n# title\nsome *content*")}, } viper.Set("defaultExtension", "html") viper.Set("verbose", true) viper.Set("canonifyURLs", true) viper.Set("baseURL", "http://auth/bub") s := &Site{ Source: &source.InMemorySource{ByteSource: sources}, targets: targetList{page: &target.PagePub{UglyURLs: true}}, Language: helpers.NewDefaultLanguage(), } if err := buildAndRenderSite(s, "_default/single.html", "{{.Content}}", "head", "<head><script src=\"script.js\"></script></head>", "head_abs", "<head><script src=\"/script.js\"></script></head>"); err != nil { t.Fatalf("Failed to build site: %s", err) } tests := []struct { doc string expected string }{ {filepath.FromSlash("sect/doc1.html"), "\n\n<h1 id=\"title\">title</h1>\n\n<p>some <em>content</em></p>\n"}, {filepath.FromSlash("sect/doc2.html"), "<!doctype html><html><body>more content</body></html>"}, {filepath.FromSlash("sect/doc3.html"), "\n\n<h1 id=\"doc3\">doc3</h1>\n\n<p><em>some</em> content</p>\n"}, {filepath.FromSlash("sect/doc4.html"), "\n\n<h1 id=\"doc4\">doc4</h1>\n\n<p><em>some content</em></p>\n"}, {filepath.FromSlash("sect/doc5.html"), "<!doctype html><html><head><script src=\"script.js\"></script></head><body>body5</body></html>"}, {filepath.FromSlash("sect/doc6.html"), "<!doctype html><html><head><script src=\"http://auth/bub/script.js\"></script></head><body>body5</body></html>"}, {filepath.FromSlash("doc7.html"), "<html><body>doc7 content</body></html>"}, {filepath.FromSlash("sect/doc8.html"), "\n\n<h1 id=\"title\">title</h1>\n\n<p>some <em>content</em></p>\n"}, } for _, test := range tests { file, err := hugofs.Destination().Open(test.doc) if err != nil { t.Fatalf("Did not find %s in target.", test.doc) } content := helpers.ReaderToString(file) if content != test.expected { t.Errorf("%s content expected:\n%q\ngot:\n%q", test.doc, test.expected, content) } } }
func (fs *Filesystem) Publish(path string, r io.Reader) (err error) { translated, err := fs.Translate(path) if err != nil { return } return helpers.WriteToDisk(translated, r, hugofs.Destination()) }
func TestAbsURLify(t *testing.T) { testCommonResetState() viper.Set("defaultExtension", "html") hugofs.InitMemFs() sources := []source.ByteSource{ {Name: filepath.FromSlash("sect/doc1.html"), Content: []byte("<!doctype html><html><head></head><body><a href=\"#frag1\">link</a></body></html>")}, {Name: filepath.FromSlash("blue/doc2.html"), Content: []byte("---\nf: t\n---\n<!doctype html><html><body>more content</body></html>")}, } for _, baseURL := range []string{"http://auth/bub", "http://base", "//base"} { for _, canonify := range []bool{true, false} { viper.Set("canonifyURLs", canonify) viper.Set("baseURL", baseURL) s := &Site{ Source: &source.InMemorySource{ByteSource: sources}, targets: targetList{page: &target.PagePub{UglyURLs: true}}, Language: helpers.NewDefaultLanguage(), } t.Logf("Rendering with baseURL %q and canonifyURLs set %v", viper.GetString("baseURL"), canonify) if err := buildAndRenderSite(s, "blue/single.html", templateWithURLAbs); err != nil { t.Fatalf("Failed to build site: %s", err) } tests := []struct { file, expected string }{ {"blue/doc2.html", "<a href=\"%s/foobar.jpg\">Going</a>"}, {"sect/doc1.html", "<!doctype html><html><head></head><body><a href=\"#frag1\">link</a></body></html>"}, } for _, test := range tests { file, err := hugofs.Destination().Open(filepath.FromSlash(test.file)) if err != nil { t.Fatalf("Unable to locate rendered content: %s", test.file) } content := helpers.ReaderToString(file) expected := test.expected if strings.Contains(expected, "%s") { expected = fmt.Sprintf(expected, baseURL) } if !canonify { expected = strings.Replace(expected, baseURL, "", -1) } if content != expected { t.Errorf("AbsURLify with baseURL %q content expected:\n%q\ngot\n%q", baseURL, expected, content) } } } } }
func (pp *PagePub) Publish(path string, r io.Reader) (err error) { translated, err := pp.Translate(path) if err != nil { return } return helpers.WriteToDisk(translated, r, hugofs.Destination()) }
func TestRobotsTXTOutput(t *testing.T) { viper.Reset() defer viper.Reset() hugofs.InitMemFs() viper.Set("baseurl", "http://auth/bub/") viper.Set("enableRobotsTXT", true) s := &Site{ Source: &source.InMemorySource{ByteSource: weightedSources}, } s.initializeSiteInfo() s.prepTemplates("robots.txt", robotTxtTemplate) if err := s.createPages(); err != nil { t.Fatalf("Unable to create pages: %s", err) } if err := s.buildSiteMeta(); err != nil { t.Fatalf("Unable to build site metadata: %s", err) } if err := s.renderHomePage(); err != nil { t.Fatalf("Unable to RenderHomePage: %s", err) } if err := s.renderSitemap(); err != nil { t.Fatalf("Unable to RenderSitemap: %s", err) } if err := s.renderRobotsTXT(); err != nil { t.Fatalf("Unable to RenderRobotsTXT :%s", err) } robotsFile, err := hugofs.Destination().Open("robots.txt") if err != nil { t.Fatalf("Unable to locate: robots.txt") } robots := helpers.ReaderToBytes(robotsFile) if !bytes.HasPrefix(robots, []byte("User-agent: Googlebot")) { t.Errorf("Robots file should start with 'User-agentL Googlebot'. %s", robots) } }
func TestRenderThingOrDefault(t *testing.T) { tests := []struct { missing bool template string expected string }{ {true, templateTitle, HTML("simple template")}, {true, templateFunc, HTML("simple-template")}, {false, templateTitle, HTML("simple template")}, {false, templateFunc, HTML("simple-template")}, } hugofs.InitMemFs() for i, test := range tests { s := &Site{} p, err := NewPageFrom(strings.NewReader(pageSimpleTitle), "content/a/file.md") if err != nil { t.Fatalf("Error parsing buffer: %s", err) } templateName := fmt.Sprintf("default%d", i) s.prepTemplates(templateName, test.template) var err2 error if test.missing { err2 = s.renderAndWritePage("name", "out", p, "missing", templateName) } else { err2 = s.renderAndWritePage("name", "out", p, templateName, "missing_default") } if err2 != nil { t.Errorf("Unable to render html: %s", err) } file, err := hugofs.Destination().Open(filepath.FromSlash("out/index.html")) if err != nil { t.Errorf("Unable to open html: %s", err) } if helpers.ReaderToString(file) != test.expected { t.Errorf("Content does not match. Expected '%s', got '%s'", test.expected, helpers.ReaderToString(file)) } } }
func TestSitemapOutput(t *testing.T) { viper.Reset() defer viper.Reset() hugofs.InitMemFs() viper.Set("baseurl", "http://auth/bub/") s := &Site{ Source: &source.InMemorySource{ByteSource: weightedSources}, } s.initializeSiteInfo() s.prepTemplates("sitemap.xml", SITEMAP_TEMPLATE) if err := s.createPages(); err != nil { t.Fatalf("Unable to create pages: %s", err) } if err := s.buildSiteMeta(); err != nil { t.Fatalf("Unable to build site metadata: %s", err) } if err := s.renderHomePage(); err != nil { t.Fatalf("Unable to RenderHomePage: %s", err) } if err := s.renderSitemap(); err != nil { t.Fatalf("Unable to RenderSitemap: %s", err) } if err := s.renderRobotsTXT(); err != nil { t.Fatalf("Unable to RenderRobotsTXT :%s", err) } sitemapFile, err := hugofs.Destination().Open("sitemap.xml") if err != nil { t.Fatalf("Unable to locate: sitemap.xml") } sitemap := helpers.ReaderToBytes(sitemapFile) if !bytes.HasPrefix(sitemap, []byte("<?xml")) { t.Errorf("Sitemap file should start with <?xml. %s", sitemap) } }
func TestRSSOutput(t *testing.T) { viper.Reset() defer viper.Reset() rssURI := "customrss.xml" viper.Set("baseurl", "http://auth/bub/") viper.Set("RSSUri", rssURI) hugofs.InitMemFs() s := &Site{ Source: &source.InMemorySource{ByteSource: weightedSources}, } s.initializeSiteInfo() s.prepTemplates("rss.xml", rssTemplate) if err := s.createPages(); err != nil { t.Fatalf("Unable to create pages: %s", err) } if err := s.buildSiteMeta(); err != nil { t.Fatalf("Unable to build site metadata: %s", err) } if err := s.renderHomePage(); err != nil { t.Fatalf("Unable to RenderHomePage: %s", err) } file, err := hugofs.Destination().Open(rssURI) if err != nil { t.Fatalf("Unable to locate: %s", rssURI) } rss := helpers.ReaderToBytes(file) if !bytes.HasPrefix(rss, []byte("<?xml")) { t.Errorf("rss feed should start with <?xml. %s", rss) } }
func doTestSectionNaming(t *testing.T, canonify, uglify, pluralize bool) { hugofs.InitMemFs() viper.Reset() defer viper.Reset() viper.Set("baseurl", "http://auth/sub/") viper.Set("DefaultExtension", "html") viper.Set("UglyURLs", uglify) viper.Set("PluralizeListTitles", pluralize) viper.Set("CanonifyURLs", canonify) var expectedPathSuffix string if uglify { expectedPathSuffix = ".html" } else { expectedPathSuffix = "/index.html" } sources := []source.ByteSource{ {filepath.FromSlash("sect/doc1.html"), []byte("doc1")}, {filepath.FromSlash("Fish and Chips/doc2.html"), []byte("doc2")}, {filepath.FromSlash("ラーメン/doc3.html"), []byte("doc3")}, } s := &Site{ Source: &source.InMemorySource{ByteSource: sources}, targets: targetList{page: &target.PagePub{UglyURLs: uglify}}, } s.initializeSiteInfo() s.prepTemplates( "_default/single.html", "{{.Content}}", "_default/list.html", "{{ .Title }}") createAndRenderPages(t, s) s.renderSectionLists() tests := []struct { doc string pluralAware bool expected string }{ {filepath.FromSlash(fmt.Sprintf("sect/doc1%s", expectedPathSuffix)), false, "doc1"}, {filepath.FromSlash(fmt.Sprintf("sect%s", expectedPathSuffix)), true, "Sect"}, {filepath.FromSlash(fmt.Sprintf("fish-and-chips/doc2%s", expectedPathSuffix)), false, "doc2"}, {filepath.FromSlash(fmt.Sprintf("fish-and-chips%s", expectedPathSuffix)), true, "Fish and Chips"}, {filepath.FromSlash(fmt.Sprintf("ラーメン/doc3%s", expectedPathSuffix)), false, "doc3"}, {filepath.FromSlash(fmt.Sprintf("ラーメン%s", expectedPathSuffix)), true, "ラーメン"}, } for _, test := range tests { file, err := hugofs.Destination().Open(test.doc) if err != nil { t.Fatalf("Did not find %s in target: %s", test.doc, err) } content := helpers.ReaderToString(file) if test.pluralAware && pluralize { test.expected = inflect.Pluralize(test.expected) } if content != test.expected { t.Errorf("%s content expected:\n%q\ngot:\n%q", test.doc, test.expected, content) } } }
func doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) { viper.Reset() defer viper.Reset() viper.Set("DefaultExtension", "html") viper.Set("verbose", true) viper.Set("baseurl", "http://auth/bub") viper.Set("DisableSitemap", false) viper.Set("DisableRSS", false) viper.Set("RSSUri", "index.xml") viper.Set("blackfriday", map[string]interface{}{ "plainIDAnchors": true}) viper.Set("UglyURLs", uglyURLs) sources := []source.ByteSource{ {filepath.FromSlash("sect/doc1.md"), []byte("---\nmarkup: markdown\n---\n# title\nsome *content*")}, {filepath.FromSlash("sect/doc2.md"), []byte("---\nurl: /ugly.html\nmarkup: markdown\n---\n# title\ndoc2 *content*")}, } s := &Site{ Source: &source.InMemorySource{ByteSource: sources}, targets: targetList{page: &target.PagePub{UglyURLs: uglyURLs}}, } s.initializeSiteInfo() s.prepTemplates( "index.html", "Home Sweet {{ if.IsHome }}Home{{ end }}.", "_default/single.html", "{{.Content}}{{ if.IsHome }}This is not home!{{ end }}", "404.html", "Page Not Found.{{ if.IsHome }}This is not home!{{ end }}", "rss.xml", "<root>RSS</root>", "sitemap.xml", "<root>SITEMAP</root>") createAndRenderPages(t, s) s.renderHomePage() s.renderSitemap() var expectedPagePath string if uglyURLs { expectedPagePath = "sect/doc1.html" } else { expectedPagePath = "sect/doc1/index.html" } tests := []struct { doc string expected string }{ {filepath.FromSlash("index.html"), "Home Sweet Home."}, {filepath.FromSlash(expectedPagePath), "\n\n<h1 id=\"title\">title</h1>\n\n<p>some <em>content</em></p>\n"}, {filepath.FromSlash("404.html"), "Page Not Found."}, {filepath.FromSlash("index.xml"), "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>\n<root>RSS</root>"}, {filepath.FromSlash("sitemap.xml"), "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>\n<root>SITEMAP</root>"}, // Issue #1923 {filepath.FromSlash("ugly.html"), "\n\n<h1 id=\"title\">title</h1>\n\n<p>doc2 <em>content</em></p>\n"}, } for _, p := range s.Pages { assert.False(t, p.IsHome) } for _, test := range tests { file, err := hugofs.Destination().Open(test.doc) if err != nil { t.Fatalf("Did not find %s in target: %s", test.doc, err) } content := helpers.ReaderToString(file) if content != test.expected { t.Errorf("%s content expected:\n%q\ngot:\n%q", test.doc, test.expected, content) } } }
func doTestCrossrefs(t *testing.T, relative, uglyURLs bool) { viper.Reset() defer viper.Reset() baseURL := "http://foo/bar" viper.Set("DefaultExtension", "html") viper.Set("baseurl", baseURL) viper.Set("UglyURLs", uglyURLs) viper.Set("verbose", true) var refShortcode string var expectedBase string var expectedURLSuffix string var expectedPathSuffix string if relative { refShortcode = "relref" expectedBase = "/bar" } else { refShortcode = "ref" expectedBase = baseURL } if uglyURLs { expectedURLSuffix = ".html" expectedPathSuffix = ".html" } else { expectedURLSuffix = "/" expectedPathSuffix = "/index.html" } sources := []source.ByteSource{ {filepath.FromSlash("sect/doc1.md"), []byte(fmt.Sprintf(`Ref 2: {{< %s "sect/doc2.md" >}}`, refShortcode))}, // Issue #1148: Make sure that no P-tags is added around shortcodes. {filepath.FromSlash("sect/doc2.md"), []byte(fmt.Sprintf(`**Ref 1:** {{< %s "sect/doc1.md" >}} THE END.`, refShortcode))}, // Issue #1753: Should not add a trailing newline after shortcode. {filepath.FromSlash("sect/doc3.md"), []byte(fmt.Sprintf(`**Ref 1:**{{< %s "sect/doc3.md" >}}.`, refShortcode))}, } s := &Site{ Source: &source.InMemorySource{ByteSource: sources}, targets: targetList{page: &target.PagePub{UglyURLs: uglyURLs}}, } s.initializeSiteInfo() s.prepTemplates("_default/single.html", "{{.Content}}") createAndRenderPages(t, s) tests := []struct { doc string expected string }{ {filepath.FromSlash(fmt.Sprintf("sect/doc1%s", expectedPathSuffix)), fmt.Sprintf("<p>Ref 2: %s/sect/doc2%s</p>\n", expectedBase, expectedURLSuffix)}, {filepath.FromSlash(fmt.Sprintf("sect/doc2%s", expectedPathSuffix)), fmt.Sprintf("<p><strong>Ref 1:</strong></p>\n\n%s/sect/doc1%s\n\n<p>THE END.</p>\n", expectedBase, expectedURLSuffix)}, {filepath.FromSlash(fmt.Sprintf("sect/doc3%s", expectedPathSuffix)), fmt.Sprintf("<p><strong>Ref 1:</strong>%s/sect/doc3%s.</p>\n", expectedBase, expectedURLSuffix)}, } for _, test := range tests { file, err := hugofs.Destination().Open(test.doc) if err != nil { t.Fatalf("Did not find %s in target: %s", test.doc, err) } content := helpers.ReaderToString(file) if content != test.expected { t.Errorf("%s content expected:\n%q\ngot:\n%q", test.doc, test.expected, content) } } }
func TestShortcodesInSite(t *testing.T) { testCommonResetState() baseURL := "http://foo/bar" viper.Set("DefaultExtension", "html") viper.Set("DefaultContentLanguage", "en") viper.Set("baseurl", baseURL) viper.Set("UglyURLs", false) viper.Set("verbose", true) viper.Set("pygmentsuseclasses", true) viper.Set("pygmentscodefences", true) tests := []struct { contentPath string content string outFile string expected string }{ {"sect/doc1.md", `a{{< b >}}c`, filepath.FromSlash("sect/doc1/index.html"), "<p>abc</p>\n"}, // Issue #1642: Multiple shortcodes wrapped in P // Deliberately forced to pass even if they maybe shouldn't. {"sect/doc2.md", `a {{< b >}} {{< c >}} {{< d >}} e`, filepath.FromSlash("sect/doc2/index.html"), "<p>a</p>\n\n<p>b<br />\nc\nd</p>\n\n<p>e</p>\n"}, {"sect/doc3.md", `a {{< b >}} {{< c >}} {{< d >}} e`, filepath.FromSlash("sect/doc3/index.html"), "<p>a</p>\n\n<p>b<br />\nc</p>\n\nd\n\n<p>e</p>\n"}, {"sect/doc4.md", `a {{< b >}} {{< b >}} {{< b >}} {{< b >}} {{< b >}} `, filepath.FromSlash("sect/doc4/index.html"), "<p>a\nb\nb\nb\nb\nb</p>\n"}, // #2192 #2209: Shortcodes in markdown headers {"sect/doc5.md", `# {{< b >}} ## {{% c %}}`, filepath.FromSlash("sect/doc5/index.html"), "\n\n<h1 id=\"hahahugoshortcode-1hbhb\">b</h1>\n\n<h2 id=\"hahahugoshortcode-2hbhb\">c</h2>\n"}, // #2223 pygments {"sect/doc6.md", "\n```bash\nb: {{< b >}} c: {{% c %}}\n```\n", filepath.FromSlash("sect/doc6/index.html"), "b: b c: c\n</code></pre></div>\n"}, // #2249 {"sect/doc7.ad", `_Shortcodes:_ *b: {{< b >}} c: {{% c %}}*`, filepath.FromSlash("sect/doc7/index.html"), "<div class=\"paragraph\">\n<p><em>Shortcodes:</em> <strong>b: b c: c</strong></p>\n</div>\n"}, {"sect/doc8.rst", `**Shortcodes:** *b: {{< b >}} c: {{% c %}}*`, filepath.FromSlash("sect/doc8/index.html"), "<div class=\"document\">\n\n\n<p><strong>Shortcodes:</strong> <em>b: b c: c</em></p>\n</div>"}, {"sect/doc9.mmark", ` --- menu: main: parent: 'parent' --- **Shortcodes:** *b: {{< b >}} c: {{% c %}}*`, filepath.FromSlash("sect/doc9/index.html"), "<p><strong>Shortcodes:</strong> <em>b: b c: c</em></p>\n"}, // Issue #1229: Menus not available in shortcode. {"sect/doc10.md", `--- menu: main: identifier: 'parent' tags: - Menu --- **Menus:** {{< menu >}}`, filepath.FromSlash("sect/doc10/index.html"), "<p><strong>Menus:</strong> 1</p>\n"}, // Issue #2323: Taxonomies not available in shortcode. {"sect/doc11.md", `--- tags: - Bugs --- **Tags:** {{< tags >}}`, filepath.FromSlash("sect/doc11/index.html"), "<p><strong>Tags:</strong> 2</p>\n"}, } sources := make([]source.ByteSource, len(tests)) for i, test := range tests { sources[i] = source.ByteSource{Name: filepath.FromSlash(test.contentPath), Content: []byte(test.content)} } s := &Site{ Source: &source.InMemorySource{ByteSource: sources}, targets: targetList{page: &target.PagePub{UglyURLs: false}}, Language: helpers.NewDefaultLanguage(), } addTemplates := func(templ tpl.Template) error { templ.AddTemplate("_default/single.html", "{{.Content}}") templ.AddInternalShortcode("b.html", `b`) templ.AddInternalShortcode("c.html", `c`) templ.AddInternalShortcode("d.html", `d`) templ.AddInternalShortcode("menu.html", `{{ len (index .Page.Menus "main").Children }}`) templ.AddInternalShortcode("tags.html", `{{ len .Page.Site.Taxonomies.tags }}`) return nil } sites, err := newHugoSites(s) if err != nil { t.Fatalf("Failed to build site: %s", err) } if err = sites.Build(BuildCfg{withTemplate: addTemplates}); err != nil { t.Fatalf("Failed to build site: %s", err) } for _, test := range tests { if strings.HasSuffix(test.contentPath, ".ad") && !helpers.HasAsciidoc() { fmt.Println("Skip Asciidoc test case as no Asciidoc present.") continue } else if strings.HasSuffix(test.contentPath, ".rst") && !helpers.HasRst() { fmt.Println("Skip Rst test case as no rst2html present.") continue } else if strings.Contains(test.expected, "code") && !helpers.HasPygments() { fmt.Println("Skip Pygments test case as no pygments present.") continue } file, err := hugofs.Destination().Open(test.outFile) if err != nil { t.Fatalf("Did not find %s in target: %s", test.outFile, err) } content := helpers.ReaderToString(file) if !strings.Contains(content, test.expected) { t.Fatalf("%s content expected:\n%q\ngot:\n%q", test.outFile, test.expected, content) } } }
func TestShortcodesInSite(t *testing.T) { viper.Reset() defer viper.Reset() baseURL := "http://foo/bar" viper.Set("DefaultExtension", "html") viper.Set("baseurl", baseURL) viper.Set("UglyURLs", false) viper.Set("verbose", true) tests := []struct { contentPath string content string outFile string expected string }{ {"sect/doc1.md", `a{{< b >}}c`, filepath.FromSlash("sect/doc1/index.html"), "<p>abc</p>\n"}, // Issue #1642: Multiple shortcodes wrapped in P // Deliberately forced to pass even if they maybe shouldn't. {"sect/doc2.md", `a {{< b >}} {{< c >}} {{< d >}} e`, filepath.FromSlash("sect/doc2/index.html"), "<p>a</p>\n\n<p>b<br />\nc\nd</p>\n\n<p>e</p>\n"}, {"sect/doc3.md", `a {{< b >}} {{< c >}} {{< d >}} e`, filepath.FromSlash("sect/doc3/index.html"), "<p>a</p>\n\n<p>b<br />\nc</p>\n\nd\n\n<p>e</p>\n"}, {"sect/doc4.md", `a {{< b >}} {{< b >}} {{< b >}} {{< b >}} {{< b >}} `, filepath.FromSlash("sect/doc4/index.html"), "<p>a\nb\nb\nb\nb\nb</p>\n"}, // #2192 #2209: Shortcodes in markdown headers {"sect/doc5.md", `# {{< b >}} ## {{% c %}}`, filepath.FromSlash("sect/doc5/index.html"), "\n\n<h1 id=\"hugoshortcode-1\">b</h1>\n\n<h2 id=\"hugoshortcode-2\">c</h2>\n"}, {"sect/doc6.md", "\n```bash\n{{< b >}}\n{{% c %}}\n```\n", filepath.FromSlash("sect/doc6/index.html"), "<pre><code class=\"language-bash\">b\nc\n</code></pre>\n"}, // #2249 {"sect/doc7.ad", `_Shortcodes:_ *b: {{< b >}} c: {{% c %}}*`, filepath.FromSlash("sect/doc7/index.html"), "<div class=\"paragraph\">\n<p><em>Shortcodes:</em> <strong>b: b c: c</strong></p>\n</div>\n"}, } sources := make([]source.ByteSource, len(tests)) for i, test := range tests { sources[i] = source.ByteSource{Name: filepath.FromSlash(test.contentPath), Content: []byte(test.content)} } s := &Site{ Source: &source.InMemorySource{ByteSource: sources}, targets: targetList{page: &target.PagePub{UglyURLs: false}}, } s.initializeSiteInfo() s.loadTemplates() s.Tmpl.AddTemplate("_default/single.html", "{{.Content}}") s.Tmpl.AddInternalShortcode("b.html", `b`) s.Tmpl.AddInternalShortcode("c.html", `c`) s.Tmpl.AddInternalShortcode("d.html", `d`) s.Tmpl.MarkReady() createAndRenderPages(t, s) for _, test := range tests { if strings.HasSuffix(test.contentPath, ".ad") && !helpers.HasAsciidoc() { fmt.Println("Skip Asciidoc test case as no Asciidoc present.") continue } file, err := hugofs.Destination().Open(test.outFile) if err != nil { t.Fatalf("Did not find %s in target: %s", test.outFile, err) } content := helpers.ReaderToString(file) if content != test.expected { t.Errorf("%s content expected:\n%q\ngot:\n%q", test.outFile, test.expected, content) } } }
func readDestination(t *testing.T, filename string) string { return readFileFromFs(t, hugofs.Destination(), filename) }
func TestMultiSitesRebuild(t *testing.T) { defer leaktest.Check(t)() testCommonResetState() siteConfig := testSiteConfig{DefaultContentLanguage: "fr"} sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate) cfg := BuildCfg{Watching: true} err := sites.Build(cfg) if err != nil { t.Fatalf("Failed to build sites: %s", err) } _, err = hugofs.Destination().Open("public/en/sect/doc2/index.html") if err != nil { t.Fatalf("Unable to locate file") } enSite := sites.Sites[0] frSite := sites.Sites[1] assert.Len(t, enSite.Pages, 3) assert.Len(t, frSite.Pages, 3) // Verify translations docEn := readDestination(t, "public/en/sect/doc1-slug/index.html") assert.True(t, strings.Contains(docEn, "Hello"), "No Hello") docFr := readDestination(t, "public/fr/sect/doc1/index.html") assert.True(t, strings.Contains(docFr, "Bonjour"), "No Bonjour") // check single page content assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Single", "Shortcode: Bonjour") assertFileContent(t, "public/en/sect/doc1-slug/index.html", true, "Single", "Shortcode: Hello") for i, this := range []struct { preFunc func(t *testing.T) events []fsnotify.Event assertFunc func(t *testing.T) }{ // * Remove doc // * Add docs existing languages // (Add doc new language: TODO(bep) we should load config.toml as part of these so we can add languages). // * Rename file // * Change doc // * Change a template // * Change language file { nil, []fsnotify.Event{{Name: "content/sect/doc2.en.md", Op: fsnotify.Remove}}, func(t *testing.T) { assert.Len(t, enSite.Pages, 2, "1 en removed") // Check build stats assert.Equal(t, 1, enSite.draftCount, "Draft") assert.Equal(t, 1, enSite.futureCount, "Future") assert.Equal(t, 1, enSite.expiredCount, "Expired") assert.Equal(t, 0, frSite.draftCount, "Draft") assert.Equal(t, 1, frSite.futureCount, "Future") assert.Equal(t, 1, frSite.expiredCount, "Expired") }, }, { func(t *testing.T) { writeNewContentFile(t, "new_en_1", "2016-07-31", "content/new1.en.md", -5) writeNewContentFile(t, "new_en_2", "1989-07-30", "content/new2.en.md", -10) writeNewContentFile(t, "new_fr_1", "2016-07-30", "content/new1.fr.md", 10) }, []fsnotify.Event{ {Name: "content/new1.en.md", Op: fsnotify.Create}, {Name: "content/new2.en.md", Op: fsnotify.Create}, {Name: "content/new1.fr.md", Op: fsnotify.Create}, }, func(t *testing.T) { assert.Len(t, enSite.Pages, 4) assert.Len(t, enSite.AllPages, 10) assert.Len(t, frSite.Pages, 4) assert.Equal(t, "new_fr_1", frSite.Pages[3].Title) assert.Equal(t, "new_en_2", enSite.Pages[0].Title) assert.Equal(t, "new_en_1", enSite.Pages[1].Title) rendered := readDestination(t, "public/en/new1/index.html") assert.True(t, strings.Contains(rendered, "new_en_1"), rendered) }, }, { func(t *testing.T) { p := "content/sect/doc1.en.md" doc1 := readSource(t, p) doc1 += "CHANGED" writeSource(t, p, doc1) }, []fsnotify.Event{{Name: "content/sect/doc1.en.md", Op: fsnotify.Write}}, func(t *testing.T) { assert.Len(t, enSite.Pages, 4) doc1 := readDestination(t, "public/en/sect/doc1-slug/index.html") assert.True(t, strings.Contains(doc1, "CHANGED"), doc1) }, }, // Rename a file { func(t *testing.T) { if err := hugofs.Source().Rename("content/new1.en.md", "content/new1renamed.en.md"); err != nil { t.Fatalf("Rename failed: %s", err) } }, []fsnotify.Event{ {Name: "content/new1renamed.en.md", Op: fsnotify.Rename}, {Name: "content/new1.en.md", Op: fsnotify.Rename}, }, func(t *testing.T) { assert.Len(t, enSite.Pages, 4, "Rename") assert.Equal(t, "new_en_1", enSite.Pages[1].Title) rendered := readDestination(t, "public/en/new1renamed/index.html") assert.True(t, strings.Contains(rendered, "new_en_1"), rendered) }}, { // Change a template func(t *testing.T) { template := "layouts/_default/single.html" templateContent := readSource(t, template) templateContent += "{{ print \"Template Changed\"}}" writeSource(t, template, templateContent) }, []fsnotify.Event{{Name: "layouts/_default/single.html", Op: fsnotify.Write}}, func(t *testing.T) { assert.Len(t, enSite.Pages, 4) assert.Len(t, enSite.AllPages, 10) assert.Len(t, frSite.Pages, 4) doc1 := readDestination(t, "public/en/sect/doc1-slug/index.html") assert.True(t, strings.Contains(doc1, "Template Changed"), doc1) }, }, { // Change a language file func(t *testing.T) { languageFile := "i18n/fr.yaml" langContent := readSource(t, languageFile) langContent = strings.Replace(langContent, "Bonjour", "Salut", 1) writeSource(t, languageFile, langContent) }, []fsnotify.Event{{Name: "i18n/fr.yaml", Op: fsnotify.Write}}, func(t *testing.T) { assert.Len(t, enSite.Pages, 4) assert.Len(t, enSite.AllPages, 10) assert.Len(t, frSite.Pages, 4) docEn := readDestination(t, "public/en/sect/doc1-slug/index.html") assert.True(t, strings.Contains(docEn, "Hello"), "No Hello") docFr := readDestination(t, "public/fr/sect/doc1/index.html") assert.True(t, strings.Contains(docFr, "Salut"), "No Salut") homeEn := enSite.getNode("home-0") require.NotNil(t, homeEn) require.Len(t, homeEn.Translations(), 3) require.Equal(t, "fr", homeEn.Translations()[0].Lang()) }, }, // Change a shortcode { func(t *testing.T) { writeSource(t, "layouts/shortcodes/shortcode.html", "Modified Shortcode: {{ i18n \"hello\" }}") }, []fsnotify.Event{ {Name: "layouts/shortcodes/shortcode.html", Op: fsnotify.Write}, }, func(t *testing.T) { assert.Len(t, enSite.Pages, 4) assert.Len(t, enSite.AllPages, 10) assert.Len(t, frSite.Pages, 4) assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Single", "Modified Shortcode: Salut") assertFileContent(t, "public/en/sect/doc1-slug/index.html", true, "Single", "Modified Shortcode: Hello") }, }, } { if this.preFunc != nil { this.preFunc(t) } err = sites.Rebuild(cfg, this.events...) if err != nil { t.Fatalf("[%d] Failed to rebuild sites: %s", i, err) } this.assertFunc(t) } // Check that the drafts etc. are not built/processed/rendered. assertShouldNotBuild(t, sites) }
// NewWatcher creates a new watcher to watch filesystem events. func NewWatcher(port int) error { if runtime.GOOS == "darwin" { tweakLimit() } watcher, err := watcher.New(1 * time.Second) var wg sync.WaitGroup if err != nil { return err } defer watcher.Close() wg.Add(1) for _, d := range getDirList() { if d != "" { _ = watcher.Add(d) } } go func() { for { select { case evs := <-watcher.Events: jww.INFO.Println("Received System Events:", evs) staticEvents := []fsnotify.Event{} dynamicEvents := []fsnotify.Event{} for _, ev := range evs { ext := filepath.Ext(ev.Name) baseName := filepath.Base(ev.Name) istemp := strings.HasSuffix(ext, "~") || (ext == ".swp") || // vim (ext == ".swx") || // vim (ext == ".tmp") || // generic temp file (ext == ".DS_Store") || // OSX Thumbnail baseName == "4913" || // vim strings.HasPrefix(ext, ".goutputstream") || // gnome strings.HasSuffix(ext, "jb_old___") || // intelliJ strings.HasSuffix(ext, "jb_tmp___") || // intelliJ strings.HasSuffix(ext, "jb_bak___") || // intelliJ strings.HasPrefix(ext, ".sb-") || // byword strings.HasPrefix(baseName, ".#") || // emacs strings.HasPrefix(baseName, "#") // emacs if istemp { continue } // Sometimes during rm -rf operations a '"": REMOVE' is triggered. Just ignore these if ev.Name == "" { continue } // Write and rename operations are often followed by CHMOD. // There may be valid use cases for rebuilding the site on CHMOD, // but that will require more complex logic than this simple conditional. // On OS X this seems to be related to Spotlight, see: // https://github.com/go-fsnotify/fsnotify/issues/15 // A workaround is to put your site(s) on the Spotlight exception list, // but that may be a little mysterious for most end users. // So, for now, we skip reload on CHMOD. // We do have to check for WRITE though. On slower laptops a Chmod // could be aggregated with other important events, and we still want // to rebuild on those if ev.Op&(fsnotify.Chmod|fsnotify.Write|fsnotify.Create) == fsnotify.Chmod { continue } walkAdder := func(path string, f os.FileInfo, err error) error { if f.IsDir() { jww.FEEDBACK.Println("adding created directory to watchlist", path) watcher.Add(path) } return nil } // 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.Source().Stat(ev.Name); err == nil && s.Mode().IsDir() { helpers.SymbolicWalk(hugofs.Source(), ev.Name, walkAdder) } } isstatic := strings.HasPrefix(ev.Name, helpers.GetStaticDirPath()) || (len(helpers.GetThemesDirPath()) > 0 && strings.HasPrefix(ev.Name, helpers.GetThemesDirPath())) if isstatic { staticEvents = append(staticEvents, ev) } else { dynamicEvents = append(dynamicEvents, ev) } } if len(staticEvents) > 0 { publishDir := helpers.AbsPathify(viper.GetString("PublishDir")) + helpers.FilePathSeparator // If root, remove the second '/' if publishDir == "//" { publishDir = helpers.FilePathSeparator } jww.FEEDBACK.Println("\nStatic file changes detected") const layout = "2006-01-02 15:04 -0700" fmt.Println(time.Now().Format(layout)) if viper.GetBool("ForceSyncStatic") { jww.FEEDBACK.Printf("Syncing all static files\n") err := copyStatic() if err != nil { utils.StopOnErr(err, fmt.Sprintf("Error copying static files to %s", helpers.AbsPathify(viper.GetString("PublishDir")))) } } else { staticSourceFs := getStaticSourceFs() if staticSourceFs == nil { jww.WARN.Println("No static directories found to sync") return } syncer := fsync.NewSyncer() syncer.NoTimes = viper.GetBool("notimes") syncer.SrcFs = staticSourceFs syncer.DestFs = hugofs.Destination() // prevent spamming the log on changes logger := helpers.NewDistinctFeedbackLogger() for _, ev := range staticEvents { // Due to our approach of layering both directories and the content's rendered output // into one we can't accurately remove a file not in one of the source directories. // If a file is in the local static dir and also in the theme static dir and we remove // it from one of those locations we expect it to still exist in the destination // // If Hugo generates a file (from the content dir) over a static file // the content generated file should take precedence. // // Because we are now watching and handling individual events it is possible that a static // event that occupies the same path as a content generated file will take precedence // until a regeneration of the content takes places. // // Hugo assumes that these cases are very rare and will permit this bad behavior // The alternative is to track every single file and which pipeline rendered it // and then to handle conflict resolution on every event. fromPath := ev.Name // If we are here we already know the event took place in a static dir relPath, err := helpers.MakeStaticPathRelative(fromPath) if err != nil { fmt.Println(err) continue } // Remove || rename is harder and will require an assumption. // Hugo takes the following approach: // If the static file exists in any of the static source directories after this event // Hugo will re-sync it. // If it does not exist in all of the static directories Hugo will remove it. // // This assumes that Hugo has not generated content on top of a static file and then removed // the source of that static file. In this case Hugo will incorrectly remove that file // from the published directory. if ev.Op&fsnotify.Rename == fsnotify.Rename || ev.Op&fsnotify.Remove == fsnotify.Remove { if _, err := staticSourceFs.Stat(relPath); os.IsNotExist(err) { // 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.Destination().RemoveAll(toRemove) } else if err == nil { // If file still exists, sync it logger.Println("Syncing", relPath, "to", publishDir) if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil { jww.ERROR.Println(err) } } else { jww.ERROR.Println(err) } continue } // For all other event operations Hugo will sync static. logger.Println("Syncing", relPath, "to", publishDir) if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil { jww.ERROR.Println(err) } } } if !buildWatch && !viper.GetBool("DisableLiveReload") { // Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized // force refresh when more than one file if len(staticEvents) > 0 { for _, ev := range staticEvents { path, _ := helpers.MakeStaticPathRelative(ev.Name) livereload.RefreshPath(path) } } else { livereload.ForceRefresh() } } } if len(dynamicEvents) > 0 { fmt.Print("\nChange detected, rebuilding site\n") const layout = "2006-01-02 15:04 -0700" fmt.Println(time.Now().Format(layout)) rebuildSite(dynamicEvents) if !buildWatch && !viper.GetBool("DisableLiveReload") { // Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized livereload.ForceRefresh() } } case err := <-watcher.Errors: if err != nil { fmt.Println("error:", err) } } } }() if port > 0 { if !viper.GetBool("DisableLiveReload") { livereload.Initialize() http.HandleFunc("/livereload.js", livereload.ServeJS) http.HandleFunc("/livereload", livereload.Handler) } go serve(port) } wg.Wait() return nil }
func TestShortcodesInSite(t *testing.T) { viper.Reset() defer viper.Reset() baseURL := "http://foo/bar" viper.Set("DefaultExtension", "html") viper.Set("baseurl", baseURL) viper.Set("UglyURLs", false) viper.Set("verbose", true) tests := []struct { contentPath string content string outFile string expected string }{ {"sect/doc1.md", `a{{< b >}}c`, filepath.FromSlash("sect/doc1/index.html"), "<p>abc</p>\n"}, // Issue #1642: Multiple shortcodes wrapped in P // Deliberately forced to pass even if they maybe shouldn't. {"sect/doc2.md", `a {{< b >}} {{< c >}} {{< d >}} e`, filepath.FromSlash("sect/doc2/index.html"), "<p>a</p>\n\n<p>b<br />\nc\nd</p>\n\n<p>e</p>\n"}, {"sect/doc3.md", `a {{< b >}} {{< c >}} {{< d >}} e`, filepath.FromSlash("sect/doc3/index.html"), "<p>a</p>\n\n<p>b<br />\nc</p>\n\nd\n\n<p>e</p>\n"}, {"sect/doc4.md", `a {{< b >}} {{< b >}} {{< b >}} {{< b >}} {{< b >}} `, filepath.FromSlash("sect/doc4/index.html"), "<p>a\nb\nb\nb\nb\nb</p>\n"}, } sources := make([]source.ByteSource, len(tests)) for i, test := range tests { sources[i] = source.ByteSource{Name: filepath.FromSlash(test.contentPath), Content: []byte(test.content)} } s := &Site{ Source: &source.InMemorySource{ByteSource: sources}, targets: targetList{page: &target.PagePub{UglyURLs: false}}, } s.initializeSiteInfo() s.loadTemplates() s.Tmpl.AddTemplate("_default/single.html", "{{.Content}}") s.Tmpl.AddInternalShortcode("b.html", `b`) s.Tmpl.AddInternalShortcode("c.html", `c`) s.Tmpl.AddInternalShortcode("d.html", `d`) s.Tmpl.MarkReady() createAndRenderPages(t, s) for _, test := range tests { file, err := hugofs.Destination().Open(test.outFile) if err != nil { t.Fatalf("Did not find %s in target: %s", test.outFile, err) } content := helpers.ReaderToString(file) if content != test.expected { t.Errorf("%s content expected:\n%q\ngot:\n%q", test.outFile, test.expected, content) } } }