// getDirList provides NewWatcher() with a list of directories to watch for changes. func getDirList() []string { var a []string dataDir := helpers.AbsPathify(viper.GetString("DataDir")) layoutDir := helpers.AbsPathify(viper.GetString("LayoutDir")) 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 } if path == layoutDir && os.IsNotExist(err) { jww.WARN.Println("Skip LayoutDir:", 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 } helpers.SymbolicWalk(hugofs.Source(), dataDir, walker) helpers.SymbolicWalk(hugofs.Source(), helpers.AbsPathify(viper.GetString("ContentDir")), walker) helpers.SymbolicWalk(hugofs.Source(), helpers.AbsPathify(viper.GetString("LayoutDir")), walker) helpers.SymbolicWalk(hugofs.Source(), helpers.AbsPathify(viper.GetString("StaticDir")), walker) if helpers.ThemeSet() { helpers.SymbolicWalk(hugofs.Source(), helpers.AbsPathify(viper.GetString("themesDir")+"/"+viper.GetString("theme")), walker) } return a }
func (f *Filesystem) captureFiles() { walker := func(filePath string, fi os.FileInfo, err error) error { if err != nil { return nil } b, err := f.shouldRead(filePath, fi) if err != nil { return err } if b { rd, err := NewLazyFileReader(hugofs.Source(), filePath) if err != nil { return err } f.add(filePath, rd) } return err } err := helpers.SymbolicWalk(hugofs.Source(), f.Base, walker) if err != nil { jww.ERROR.Println(err) } }
func (f *Filesystem) captureFiles() { walker := func(filePath string, fi os.FileInfo, err error) error { if err != nil { return nil } b, err := f.shouldRead(filePath, fi) if err != nil { return err } if b { rd, err := NewLazyFileReader(hugofs.Source(), filePath) if err != nil { return err } f.add(filePath, rd) } return err } err := helpers.SymbolicWalk(hugofs.Source(), f.Base, walker) if err != nil { jww.ERROR.Println(err) if err == helpers.WalkRootTooShortError { panic("The root path is too short. If this is a test, make sure to init the content paths.") } } }
// 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 importFromJekyll(cmd *cobra.Command, args []string) error { jww.SetLogThreshold(jww.LevelTrace) jww.SetStdoutThreshold(jww.LevelWarn) if len(args) < 2 { return newUserError(`Import from Jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.") } jekyllRoot, err := filepath.Abs(filepath.Clean(args[0])) if err != nil { return newUserError("Path error:", args[0]) } targetDir, err := filepath.Abs(filepath.Clean(args[1])) if err != nil { return newUserError("Path error:", args[1]) } jww.INFO.Println("Import Jekyll from:", jekyllRoot, "to:", targetDir) if strings.HasPrefix(filepath.Dir(targetDir), jekyllRoot) { return newUserError("Target path should not be inside the Jekyll root, aborting.") } forceImport, _ := cmd.Flags().GetBool("force") if err := createSiteFromJekyll(jekyllRoot, targetDir, forceImport); err != nil { return newUserError(err) } fmt.Println("Importing...") fileCount := 0 callback := func(path string, fi os.FileInfo, err error) error { if err != nil { return err } if fi.IsDir() { return nil } relPath, err := filepath.Rel(jekyllRoot, path) if err != nil { return newUserError("Get rel path error:", path) } relPath = filepath.ToSlash(relPath) draft := false switch { case strings.HasPrefix(relPath, "_posts/"): relPath = "content/post" + relPath[len("_posts"):] case strings.HasPrefix(relPath, "_drafts/"): relPath = "content/draft" + relPath[len("_drafts"):] draft = true default: return nil } fileCount++ return convertJekyllPost(path, relPath, targetDir, draft) } err = helpers.SymbolicWalk(hugofs.Os(), jekyllRoot, callback) if err != nil { return err } fmt.Println("Congratulations!", fileCount, "post(s) imported!") fmt.Println("Now, start Hugo by yourself:\n" + "$ git clone https://github.com/spf13/herring-cove.git " + args[1] + "/themes/herring-cove") fmt.Println("$ cd " + args[1] + "\n$ hugo server --theme=herring-cove") return nil }
func (t *GoHTMLTemplate) loadTemplates(absPath string, prefix string) { jww.DEBUG.Printf("Load templates from path %q prefix %q", absPath, prefix) walker := func(path string, fi os.FileInfo, err error) error { if err != nil { return nil } jww.DEBUG.Println("Template path", path) if fi.Mode()&os.ModeSymlink == os.ModeSymlink { link, err := filepath.EvalSymlinks(absPath) if err != nil { jww.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", absPath, err) return nil } linkfi, err := hugofs.Source().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'", absPath) } return nil } if !fi.IsDir() { if isDotFile(path) || isBackupFile(path) || isBaseTemplate(path) { return nil } tplName := t.GenerateTemplateNameFrom(absPath, path) if prefix != "" { tplName = strings.Trim(prefix, "/") + "/" + tplName } var baseTemplatePath string // Ace and Go templates may have both a base and inner template. pathDir := filepath.Dir(path) if filepath.Ext(path) != ".amber" && !strings.HasSuffix(pathDir, "partials") && !strings.HasSuffix(pathDir, "shortcodes") { innerMarkers := goTemplateInnerMarkers baseFileName := fmt.Sprintf("%s.html", baseFileBase) if filepath.Ext(path) == ".ace" { innerMarkers = aceTemplateInnerMarkers baseFileName = fmt.Sprintf("%s.ace", baseFileBase) } // This may be a view that shouldn't have base template // Have to look inside it to make sure needsBase, err := helpers.FileContainsAny(path, innerMarkers, hugofs.Source()) if err != nil { return err } if needsBase { // Look for base template in the follwing order: // 1. <current-path>/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>. // 2. <current-path>/baseof.<suffix> // 3. _default/<template-name>-baseof.<suffix>, e.g. list-baseof.<suffix>. // 4. _default/baseof.<suffix> // 5. <themedir>/layouts/_default/<template-name>-baseof.<suffix> // 6. <themedir>/layouts/_default/baseof.<suffix> currBaseFilename := fmt.Sprintf("%s-%s", helpers.Filename(path), baseFileName) templateDir := filepath.Dir(path) themeDir := helpers.GetThemeDir() pathsToCheck := []string{ filepath.Join(templateDir, currBaseFilename), filepath.Join(templateDir, baseFileName), filepath.Join(absPath, "_default", currBaseFilename), filepath.Join(absPath, "_default", baseFileName), filepath.Join(themeDir, "layouts", "_default", currBaseFilename), filepath.Join(themeDir, "layouts", "_default", baseFileName), } for _, pathToCheck := range pathsToCheck { if ok, err := helpers.Exists(pathToCheck, hugofs.Source()); err == nil && ok { baseTemplatePath = pathToCheck break } } } } if err := t.AddTemplateFile(tplName, baseTemplatePath, path); err != nil { jww.ERROR.Printf("Failed to add template %s: %s", tplName, err) } } return nil } if err := helpers.SymbolicWalk(hugofs.Source(), absPath, walker); err != nil { jww.ERROR.Printf("Failed to load templates: %s", err) } }