// Copyright © 2013 Steve Francia . // // Licensed under the Simple Public License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://opensource.org/licenses/Simple-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package commands import ( "fmt" "github.com/howeyc/fsnotify" "github.com/mostafah/fsync" "github.com/spf13/cobra" "github.com/spf13/hugo/hugolib" "github.com/spf13/hugo/utils" "github.com/spf13/nitro" "os" "path/filepath" "strings" "sync" "time" ) var Config *hugolib.Config var HugoCmd = &cobra.Command{ Use: "hugo", Short: "Hugo is a very fast static site generator", Long: `A Fast and Flexible Static Site Generator built with love by spf13 and friends in Go. Complete documentation is available at http://hugo.spf13.com`, Run: func(cmd *cobra.Command, args []string) { InitializeConfig() build() }, } var Hugo *cobra.Commander var BuildWatch, Draft, UglyUrls, Verbose bool var Source, Destination, BaseUrl, CfgFile string func Execute() { AddCommands() Hugo := HugoCmd.ToCommander() utils.StopOnErr(Hugo.Execute()) } func AddCommands() { HugoCmd.AddCommand(serverCmd) HugoCmd.AddCommand(version) HugoCmd.AddCommand(check) HugoCmd.AddCommand(benchmark) } func init() { HugoCmd.PersistentFlags().BoolVarP(&Draft, "build-drafts", "D", false, "include content marked as draft") HugoCmd.PersistentFlags().StringVarP(&Source, "source", "s", "", "filesystem path to read files relative from") HugoCmd.PersistentFlags().StringVarP(&Destination, "destination", "d", "", "filesystem path to write files to") HugoCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") HugoCmd.PersistentFlags().BoolVar(&UglyUrls, "uglyurls", false, "if true, use /filename.html instead of /filename/") HugoCmd.PersistentFlags().StringVarP(&BaseUrl, "base-url", "b", "", "hostname (and path) to the root eg. http://spf13.com/") HugoCmd.PersistentFlags().StringVar(&CfgFile, "config", "", "config file (default is path/config.yaml|json|toml)") HugoCmd.PersistentFlags().BoolVar(&nitro.AnalysisOn, "stepAnalysis", false, "display memory and timing of different steps of the program") HugoCmd.Flags().BoolVarP(&BuildWatch, "watch", "w", false, "watch filesystem for changes and recreate as needed") } func InitializeConfig() { Config = hugolib.SetupConfig(&CfgFile, &Source) Config.BuildDrafts = Draft Config.UglyUrls = UglyUrls Config.Verbose = Verbose if BaseUrl != "" { Config.BaseUrl = BaseUrl } if Destination != "" { Config.PublishDir = Destination } } func build(watches ...bool) { utils.CheckErr(copyStatic(), fmt.Sprintf("Error copying static files to %s", Config.GetAbsPath(Config.PublishDir))) watch := false if len(watches) > 0 && watches[0] { watch = true } utils.StopOnErr(buildSite(BuildWatch || watch)) if BuildWatch { fmt.Println("Watching for changes in", Config.GetAbsPath(Config.ContentDir)) fmt.Println("Press ctrl+c to stop") utils.CheckErr(NewWatcher(0)) } } func copyStatic() error { staticDir := Config.GetAbsPath(Config.StaticDir + "/") if _, err := os.Stat(staticDir); os.IsNotExist(err) { return nil } // Copy Static to Destination return fsync.Sync(Config.GetAbsPath(Config.PublishDir+"/"), Config.GetAbsPath(Config.StaticDir+"/")) } func getDirList() []string { var a []string walker := func(path string, fi os.FileInfo, err error) error { if err != nil { fmt.Println("Walker: ", err) return nil } if fi.IsDir() { a = append(a, path) } return nil } filepath.Walk(Config.GetAbsPath(Config.ContentDir), walker) filepath.Walk(Config.GetAbsPath(Config.LayoutDir), walker) filepath.Walk(Config.GetAbsPath(Config.StaticDir), walker) return a } func buildSite(watching ...bool) (err error) { startTime := time.Now() site := &hugolib.Site{Config: *Config} if len(watching) > 0 && watching[0] { site.RunMode.Watching = true } err = site.Build() if err != nil { return } site.Stats() fmt.Printf("in %v ms\n", int(1000*time.Since(startTime).Seconds())) return nil } func NewWatcher(port int) error { watcher, err := fsnotify.NewWatcher() var wg sync.WaitGroup if err != nil { fmt.Println(err) return err } defer watcher.Close() wg.Add(1) go func() { for { select { case ev := <-watcher.Event: if Verbose { fmt.Println(ev) } watchChange(ev) // TODO add newly created directories to the watch list case err := <-watcher.Error: if err != nil { fmt.Println("error:", err) } } } }() for _, d := range getDirList() { if d != "" { _ = watcher.Watch(d) } } if port > 0 { go serve(port) } wg.Wait() return nil } func watchChange(ev *fsnotify.FileEvent) { if strings.HasPrefix(ev.Name, Config.GetAbsPath(Config.StaticDir)) { fmt.Println("Static file changed, syncing\n") utils.CheckErr(copyStatic(), fmt.Sprintf("Error copying static files to %s", Config.GetAbsPath(Config.PublishDir))) } else { if !ev.IsRename() { // Rename is always accompanied by a create or modify // Ignoring temp files created by editors (vim) if !strings.HasSuffix(ev.Name, "~") && !strings.HasSuffix(ev.Name, ".swp") { fmt.Println("Change detected, rebuilding site\n") utils.StopOnErr(buildSite(true)) } } } }