func serve(port int) { jww.FEEDBACK.Println("Serving pages from " + helpers.AbsPathify(viper.GetString("PublishDir"))) httpFs := &afero.HttpFs{SourceFs: hugofs.DestinationFS} fileserver := http.FileServer(httpFs.Dir(helpers.AbsPathify(viper.GetString("PublishDir")))) 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)) } u.Scheme = "http" jww.FEEDBACK.Printf("Web Server is available at %s\n", u.String()) fmt.Println("Press Ctrl+C to stop") err = http.ListenAndServe(":"+strconv.Itoa(port), nil) if err != nil { jww.ERROR.Printf("Error: %s\n", err.Error()) os.Exit(1) } }
func copyStatic() error { staticDir := helpers.AbsPathify(viper.GetString("StaticDir")) + "/" if _, err := os.Stat(staticDir); os.IsNotExist(err) { jww.ERROR.Println("Unable to find Static Directory:", staticDir) return nil } publishDir := helpers.AbsPathify(viper.GetString("PublishDir")) + "/" syncer := fsync.NewSyncer() syncer.NoTimes = viper.GetBool("notimes") syncer.SrcFs = hugofs.SourceFs syncer.DestFs = hugofs.DestinationFS themeDir, err := helpers.GetThemeStaticDirPath() if err != nil { jww.ERROR.Println(err) return nil } if themeDir != "" { // Copy Static to Destination jww.INFO.Println("syncing from", themeDir, "to", publishDir) utils.CheckErr(syncer.Sync(publishDir, themeDir), fmt.Sprintf("Error copying static files of theme to %s", publishDir)) } // Copy Static to Destination jww.INFO.Println("syncing from", staticDir, "to", publishDir) return syncer.Sync(publishDir, staticDir) }
func FindArchetype(kind string) (outpath string) { search := []string{helpers.AbsPathify(viper.GetString("archetypeDir"))} if viper.GetString("theme") != "" { themeDir := path.Join(helpers.AbsPathify("themes/"+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) } else { search = append(search, themeDir) } } for _, x := range search { // If the new content isn't in a subdirectory, kind == "". // Therefore it should be excluded otherwise `is a directory` // error will occur. github.com/spf13/hugo/issues/411 var pathsToCheck []string if kind == "" { pathsToCheck = []string{"default.md", "default"} } else { pathsToCheck = []string{kind + ".md", kind, "default.md", "default"} } for _, p := range pathsToCheck { curpath := path.Join(x, p) jww.DEBUG.Println("checking", curpath, "for archetypes") if exists, _ := helpers.Exists(curpath); exists { jww.INFO.Println("curpath: " + curpath) return curpath } } } return "" }
func copyStatic() error { publishDir := helpers.AbsPathify(viper.GetString("PublishDir")) + "/" // If root, remove the second '/' if publishDir == "//" { publishDir = "/" } syncer := fsync.NewSyncer() syncer.NoTimes = viper.GetBool("notimes") syncer.SrcFs = hugofs.SourceFs syncer.DestFs = hugofs.DestinationFS themeDir, err := helpers.GetThemeStaticDirPath() if err != nil { jww.ERROR.Println(err) return nil } // Copy the theme's static directory if themeDir != "" { jww.INFO.Println("syncing from", themeDir, "to", publishDir) utils.CheckErr(syncer.Sync(publishDir, themeDir), fmt.Sprintf("Error copying static files of theme to %s", publishDir)) } // Copy the site's own static directory staticDir := helpers.AbsPathify(viper.GetString("StaticDir")) + "/" if _, err := os.Stat(staticDir); err == nil { jww.INFO.Println("syncing from", staticDir, "to", publishDir) return syncer.Sync(publishDir, staticDir) } else if os.IsNotExist(err) { jww.WARN.Println("Unable to find Static Directory:", staticDir) } return nil }
func build(watches ...bool) error { // Hugo writes the output to memory instead of the disk // This is only used for benchmark testing. Cause the content is only visible // in memory if renderToMemory { hugofs.DestinationFS = new(afero.MemMapFs) // Rendering to memoryFS, publish to Root regardless of publishDir. viper.Set("PublishDir", "/") } if err := copyStatic(); err != nil { return fmt.Errorf("Error copying static files to %s: %s", helpers.AbsPathify(viper.GetString("PublishDir")), err) } watch := false if len(watches) > 0 && watches[0] { watch = true } if err := buildSite(buildWatch || watch); err != nil { return fmt.Errorf("Error building site: %s", err) } if buildWatch { jww.FEEDBACK.Println("Watching for changes in", helpers.AbsPathify(viper.GetString("ContentDir"))) jww.FEEDBACK.Println("Press Ctrl+C to stop") utils.CheckErr(NewWatcher(0)) } return nil }
func copyStatic() error { staticDir := helpers.AbsPathify(viper.GetString("StaticDir")) + "/" if _, err := os.Stat(staticDir); os.IsNotExist(err) { jww.ERROR.Println("Unable to find Static Directory:", viper.GetString("theme"), "in", staticDir) return nil } publishDir := helpers.AbsPathify(viper.GetString("PublishDir")) + "/" if themeSet() { themeDir := helpers.AbsPathify("themes/"+viper.GetString("theme")) + "/static/" if _, err := os.Stat(themeDir); os.IsNotExist(err) { jww.ERROR.Println("Unable to find static directory for theme :", viper.GetString("theme"), "in", themeDir) return nil } // Copy Static to Destination jww.INFO.Println("syncing from", themeDir, "to", publishDir) utils.CheckErr(fsync.Sync(publishDir, themeDir), fmt.Sprintf("Error copying static files of theme to %s", publishDir)) } // Copy Static to Destination jww.INFO.Println("syncing from", staticDir, "to", publishDir) return fsync.Sync(publishDir, staticDir) }
func FindArchetype(kind string) (outpath string) { search := []string{helpers.AbsPathify(viper.GetString("archetypeDir"))} if viper.GetString("theme") != "" { themeDir := path.Join(helpers.AbsPathify("themes/"+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) } else { search = append(search, themeDir) } } for _, x := range search { pathsToCheck := []string{kind + ".md", kind, "default.md", "default"} for _, p := range pathsToCheck { curpath := path.Join(x, p) jww.DEBUG.Println("checking", curpath, "for archetypes") if exists, _ := helpers.Exists(curpath); exists { return curpath } } } return "" }
func serve(port int) { jww.FEEDBACK.Println("Serving pages from " + helpers.AbsPathify(viper.GetString("PublishDir"))) httpFs := &afero.HttpFs{SourceFs: hugofs.DestinationFS} 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)) } u.Scheme = "http" 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 getDirList() []string { var a []string walker := func(path string, fi os.FileInfo, err error) error { if err != nil { jww.ERROR.Println("Walker: ", err) return nil } if fi.Mode()&os.ModeSymlink == os.ModeSymlink { jww.ERROR.Printf("Symbolic links not supported, skipping '%s'", path) return nil } if fi.IsDir() { a = append(a, path) } return nil } filepath.Walk(helpers.AbsPathify(viper.GetString("ContentDir")), walker) filepath.Walk(helpers.AbsPathify(viper.GetString("LayoutDir")), walker) filepath.Walk(helpers.AbsPathify(viper.GetString("StaticDir")), walker) if themeSet() { filepath.Walk(helpers.AbsPathify("themes/"+viper.GetString("theme")), walker) } return a }
// 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(fs, kind) var by []byte if location != "" { by, err = afero.ReadFile(fs, location) if err != nil { jww.ERROR.Println(err) } } if location == "" || err != nil { by = []byte("+++\n title = \"title\"\n draft = true \n+++\n") } psr, err := parser.ReadFrom(bytes.NewReader(by)) if err != nil { return err } metadata, err := createMetadata(psr, name) if err != nil { jww.ERROR.Printf("Error processing archetype file %s: %s\n", location, err) return err } page, err := hugolib.NewPage(name) if err != nil { return err } if err = page.SetSourceMetaData(metadata, parser.FormatToLeadRune(viper.GetString("metaDataFormat"))); err != nil { return } page.SetSourceContent(psr.Content()) if err = page.SafeSaveSourceAs(filepath.Join(viper.GetString("contentDir"), name)); err != nil { return } jww.FEEDBACK.Println(helpers.AbsPathify(filepath.Join(viper.GetString("contentDir"), name)), "created") editor := viper.GetString("newContentEditor") if editor != "" { jww.FEEDBACK.Printf("Editing %s with %q ...\n", name, editor) cmd := exec.Command(editor, helpers.AbsPathify(path.Join(viper.GetString("contentDir"), name))) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() } return nil }
func copyStatic() error { staticDir := helpers.AbsPathify(viper.GetString("StaticDir")) + "/" if _, err := os.Stat(staticDir); os.IsNotExist(err) { return nil } publishDir := helpers.AbsPathify(viper.GetString("PublishDir")) + "/" // Copy Static to Destination jww.INFO.Println("syncing from", staticDir, "to", publishDir) return fsync.Sync(publishDir, staticDir) }
func serve(port int) { jww.FEEDBACK.Println("Serving pages from " + helpers.AbsPathify(viper.GetString("PublishDir"))) jww.FEEDBACK.Printf("Web Server is available at %s\n", viper.GetString("BaseUrl")) fmt.Println("Press ctrl+c to stop") http.Handle("/", http.FileServer(http.Dir(helpers.AbsPathify(viper.GetString("PublishDir"))))) err := http.ListenAndServe(":"+strconv.Itoa(port), nil) if err != nil { jww.ERROR.Printf("Error: %s\n", err.Error()) os.Exit(1) } }
// getDirList provides NewWatcher() with a list of directories to watch for changes. func getDirList() []string { var a []string dataDir := helpers.AbsPathify(viper.GetString("DataDir")) walker := func(path string, fi os.FileInfo, err error) error { if err != nil { if path == dataDir && os.IsNotExist(err) { jww.WARN.Println("Skip DataDir:", err) return nil } jww.ERROR.Println("Walker: ", err) return nil } if fi.Mode()&os.ModeSymlink == os.ModeSymlink { link, err := filepath.EvalSymlinks(path) if err != nil { jww.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", path, err) return nil } linkfi, err := os.Stat(link) if err != nil { jww.ERROR.Printf("Cannot stat '%s', error was: %s", link, err) return nil } if !linkfi.Mode().IsRegular() { jww.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", path) } return nil } if fi.IsDir() { if fi.Name() == ".git" || fi.Name() == "node_modules" || fi.Name() == "bower_components" { return filepath.SkipDir } a = append(a, path) } return nil } filepath.Walk(dataDir, walker) filepath.Walk(helpers.AbsPathify(viper.GetString("ContentDir")), walker) filepath.Walk(helpers.AbsPathify(viper.GetString("LayoutDir")), walker) filepath.Walk(helpers.AbsPathify(viper.GetString("StaticDir")), walker) if helpers.ThemeSet() { filepath.Walk(helpers.AbsPathify("themes/"+viper.GetString("theme")), walker) } return a }
func build(watches ...bool) { utils.CheckErr(copyStatic(), fmt.Sprintf("Error copying static files to %s", helpers.AbsPathify(viper.GetString("PublishDir")))) watch := false if len(watches) > 0 && watches[0] { watch = true } utils.StopOnErr(buildSite(BuildWatch || watch)) if BuildWatch { jww.FEEDBACK.Println("Watching for changes in", helpers.AbsPathify(viper.GetString("ContentDir"))) jww.FEEDBACK.Println("Press Ctrl+C to stop") utils.CheckErr(NewWatcher(0)) } }
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 server(cmd *cobra.Command, args []string) { InitializeConfig() if BaseUrl == "" { BaseUrl = "http://localhost" } if !strings.HasPrefix(BaseUrl, "http://") { BaseUrl = "http://" + BaseUrl } if serverAppend { viper.Set("BaseUrl", strings.TrimSuffix(BaseUrl, "/")+":"+strconv.Itoa(serverPort)) } else { viper.Set("BaseUrl", strings.TrimSuffix(BaseUrl, "/")) } build(serverWatch) // Watch runs its own server as part of the routine if serverWatch { jww.FEEDBACK.Println("Watching for changes in", helpers.AbsPathify(viper.GetString("ContentDir"))) err := NewWatcher(serverPort) if err != nil { fmt.Println(err) } } serve(serverPort) }
func NewTheme(cmd *cobra.Command, args []string) { InitializeConfig() if len(args) < 1 { cmd.Usage() jww.FATAL.Fatalln("theme name needs to be provided") } createpath := helpers.AbsPathify(path.Join("themes", args[0])) jww.INFO.Println("creating theme at", createpath) if x, _ := helpers.Exists(createpath); x { jww.FATAL.Fatalln(createpath, "already exists") } mkdir(createpath, "layouts", "_default") mkdir(createpath, "layouts", "chrome") touchFile(createpath, "layouts", "index.html") touchFile(createpath, "layouts", "_default", "list.html") touchFile(createpath, "layouts", "_default", "single.html") touchFile(createpath, "layouts", "chrome", "header.html") touchFile(createpath, "layouts", "chrome", "footer.html") mkdir(createpath, "archetypes") touchFile(createpath, "archetypes", "default.md") mkdir(createpath, "static", "js") mkdir(createpath, "static", "css") by := []byte(`The MIT License (MIT) Copyright (c) 2014 YOUR_NAME_HERE Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 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(path.Join(createpath, "LICENSE.md"), bytes.NewReader(by)) if err != nil { jww.FATAL.Fatalln(err) } createThemeMD(createpath) }
func TestTargetPath(t *testing.T) { 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/x/y/deepfile.md", SIMPLE_PAGE, "x/y/deepfile.html", "x/y"}, {"content/x/y/z/deeperfile.md", SIMPLE_PAGE, "x/y/z/deeperfile.html", "x/y/z"}, {"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", ""}, } if true { return } for _, test := range tests { p := pageMust(NewPageFrom(strings.NewReader(test.content), helpers.AbsPathify(test.doc))) expected := test.expectedOutFile if p.TargetPath() != expected { t.Errorf("%s => OutFile expected: '%s', got: '%s'", test.doc, expected, p.TargetPath()) } if p.Section != test.expectedSection { t.Errorf("%s => p.Section expected: %s, got: %s", test.doc, test.expectedSection, p.Section) } } }
func server(cmd *cobra.Command, args []string) { InitializeConfig() if cmd.Flags().Lookup("disableLiveReload").Changed { viper.Set("DisableLiveReload", disableLiveReload) } if serverWatch { viper.Set("Watch", true) } if viper.GetBool("watch") { serverWatch = true } l, err := net.Listen("tcp", net.JoinHostPort(serverInterface, strconv.Itoa(serverPort))) if err == nil { l.Close() } else { jww.ERROR.Println("port", serverPort, "already in use, attempting to use an available port") sp, err := helpers.FindAvailablePort() if err != nil { jww.ERROR.Println("Unable to find alternative port to use") jww.ERROR.Fatalln(err) } serverPort = sp.Port } viper.Set("port", serverPort) BaseURL, err := fixURL(BaseURL) if err != nil { jww.ERROR.Fatal(err) } viper.Set("BaseURL", BaseURL) if err := memStats(); err != nil { jww.ERROR.Println("memstats error:", err) } build(serverWatch) // Watch runs its own server as part of the routine if serverWatch { watched := getDirList() workingDir := helpers.AbsPathify(viper.GetString("WorkingDir")) for i, dir := range watched { watched[i], _ = helpers.GetRelativePath(dir, workingDir) } unique := strings.Join(helpers.RemoveSubpaths(watched), ",") jww.FEEDBACK.Printf("Watching for changes in %s/{%s}\n", workingDir, unique) err := NewWatcher(serverPort) if err != nil { fmt.Println(err) } } serve(serverPort) }
func getDirList() []string { var a []string walker := func(path string, fi os.FileInfo, err error) error { if err != nil { jww.ERROR.Println("Walker: ", err) return nil } if fi.IsDir() { a = append(a, path) } return nil } filepath.Walk(helpers.AbsPathify(viper.GetString("ContentDir")), walker) filepath.Walk(helpers.AbsPathify(viper.GetString("LayoutDir")), walker) filepath.Walk(helpers.AbsPathify(viper.GetString("StaticDir")), walker) return a }
func getDirList() []string { var a []string walker := func(path string, fi os.FileInfo, err error) error { if err != nil { jww.ERROR.Println("Walker: ", err) return nil } if fi.Mode()&os.ModeSymlink == os.ModeSymlink { link, err := filepath.EvalSymlinks(path) if err != nil { jww.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", path, err) return nil } linkfi, err := os.Stat(link) if err != nil { jww.ERROR.Printf("Cannot stat '%s', error was: %s", link, err) return nil } if !linkfi.Mode().IsRegular() { jww.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", path) } return nil } if fi.IsDir() { a = append(a, path) } return nil } filepath.Walk(helpers.AbsPathify(viper.GetString("DataDir")), walker) filepath.Walk(helpers.AbsPathify(viper.GetString("ContentDir")), walker) filepath.Walk(helpers.AbsPathify(viper.GetString("LayoutDir")), walker) filepath.Walk(helpers.AbsPathify(viper.GetString("StaticDir")), walker) if helpers.ThemeSet() { filepath.Walk(helpers.AbsPathify("themes/"+viper.GetString("theme")), walker) } return a }
func server(cmd *cobra.Command, args []string) { InitializeConfig() if BaseUrl == "" { BaseUrl = "http://localhost" } if cmd.Flags().Lookup("disableLiveReload").Changed { viper.Set("DisableLiveReload", disableLiveReload) } if serverWatch { viper.Set("Watch", true) } if !strings.HasPrefix(BaseUrl, "http://") { BaseUrl = "http://" + BaseUrl } l, err := net.Listen("tcp", ":"+strconv.Itoa(serverPort)) if err == nil { l.Close() } else { jww.ERROR.Println("port", serverPort, "already in use, attempting to use an available port") sp, err := helpers.FindAvailablePort() if err != nil { jww.ERROR.Println("Unable to find alternative port to use") jww.ERROR.Fatalln(err) } serverPort = sp.Port } viper.Set("port", serverPort) if serverAppend { viper.Set("BaseUrl", strings.TrimSuffix(BaseUrl, "/")+":"+strconv.Itoa(serverPort)) } else { viper.Set("BaseUrl", strings.TrimSuffix(BaseUrl, "/")) } build(serverWatch) // Watch runs its own server as part of the routine if serverWatch { jww.FEEDBACK.Println("Watching for changes in", helpers.AbsPathify(viper.GetString("ContentDir"))) err := NewWatcher(serverPort) if err != nil { fmt.Println(err) } } serve(serverPort) }
func build(watches ...bool) error { if err := copyStatic(); err != nil { return fmt.Errorf("Error copying static files to %s: %s", helpers.AbsPathify(viper.GetString("PublishDir")), err) } watch := false if len(watches) > 0 && watches[0] { watch = true } if err := buildSite(BuildWatch || watch); err != nil { return fmt.Errorf("Error building site: %s", err) } if BuildWatch { jww.FEEDBACK.Println("Watching for changes in", helpers.AbsPathify(viper.GetString("ContentDir"))) jww.FEEDBACK.Println("Press Ctrl+C to stop") utils.CheckErr(NewWatcher(0)) } return nil }
func serve(port int) { jww.FEEDBACK.Println("Serving pages from " + helpers.AbsPathify(viper.GetString("PublishDir"))) jww.FEEDBACK.Printf("Web Server is available at %s\n", viper.GetString("BaseUrl")) fmt.Println("Press ctrl+c to stop") fileserver := http.FileServer(http.Dir(helpers.AbsPathify(viper.GetString("PublishDir")))) 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)) } err = http.ListenAndServe(":"+strconv.Itoa(port), nil) if err != nil { jww.ERROR.Printf("Error: %s\n", err.Error()) os.Exit(1) } }
func server(cmd *cobra.Command, args []string) { InitializeConfig() if cmd.Flags().Lookup("disableLiveReload").Changed { viper.Set("DisableLiveReload", disableLiveReload) } if serverWatch { viper.Set("Watch", true) } l, err := net.Listen("tcp", ":"+strconv.Itoa(serverPort)) if err == nil { l.Close() } else { jww.ERROR.Println("port", serverPort, "already in use, attempting to use an available port") sp, err := helpers.FindAvailablePort() if err != nil { jww.ERROR.Println("Unable to find alternative port to use") jww.ERROR.Fatalln(err) } serverPort = sp.Port } viper.Set("port", serverPort) BaseURL, err := fixURL(BaseURL) if err != nil { jww.ERROR.Fatal(err) } viper.Set("BaseURL", BaseURL) if err := memStats(); err != nil { jww.ERROR.Println("memstats error:", err) } build(serverWatch) // Watch runs its own server as part of the routine if serverWatch { jww.FEEDBACK.Println("Watching for changes in", helpers.AbsPathify(viper.GetString("ContentDir"))) err := NewWatcher(serverPort) if err != nil { fmt.Println(err) } } serve(serverPort) }
func (p *Page) saveSource(by []byte, inpath string, safe bool) (err error) { if !filepath.IsAbs(inpath) { inpath = helpers.AbsPathify(inpath) } jww.INFO.Println("creating", inpath) if safe { err = helpers.SafeWriteToDisk(inpath, bytes.NewReader(by), hugofs.SourceFs) } else { err = helpers.WriteToDisk(inpath, bytes.NewReader(by), hugofs.SourceFs) } if err != nil { return } return nil }
func (s *Site) initialize() (err error) { if err = s.checkDirectories(); err != nil { return err } staticDir := helpers.AbsPathify(viper.GetString("StaticDir") + "/") s.Source = &source.Filesystem{ AvoidPaths: []string{staticDir}, Base: s.absContentDir(), } s.initializeSiteInfo() s.Shortcodes = make(map[string]ShortcodeFunc) return }
// 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) istemp := strings.HasSuffix(ext, "~") || (ext == ".swp") || (ext == ".swx") || (ext == ".tmp") || (ext == ".DS_Store") || filepath.Base(ev.Name) == "4913" || strings.HasPrefix(ext, ".goutputstream") || strings.HasSuffix(ext, "jb_old___") || strings.HasSuffix(ext, "jb_bak___") 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. if ev.Op&fsnotify.Chmod == 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.SourceFs.Stat(ev.Name); err == nil && s.Mode().IsDir() { afero.Walk(hugofs.SourceFs, 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.DestinationFS // 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.DestinationFS.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 (s *Site) absPublishDir() string { return helpers.AbsPathify(viper.GetString("PublishDir")) }
func (s *Site) absContentDir() string { return helpers.AbsPathify(viper.GetString("ContentDir")) }