From 94d7fe52f87a3188a27767433e7a07f5ada84988 Mon Sep 17 00:00:00 2001 From: spf13 Date: Sun, 29 Sep 2013 02:09:03 -0400 Subject: [PATCH] Change the interface to use commands and flags instead of just flags. Better organization of main (controller style) code. --- commands/benchmark.go | 51 ++++++++++ commands/check.go | 31 ++++++ commands/hugo.go | 138 ++++++++++++++++++++++++++ commands/server.go | 123 +++++++++++++++++++++++ commands/version.go | 28 ++++++ main.go | 220 +----------------------------------------- 6 files changed, 373 insertions(+), 218 deletions(-) create mode 100644 commands/benchmark.go create mode 100644 commands/check.go create mode 100644 commands/hugo.go create mode 100644 commands/server.go create mode 100644 commands/version.go diff --git a/commands/benchmark.go b/commands/benchmark.go new file mode 100644 index 000000000..8e6b43139 --- /dev/null +++ b/commands/benchmark.go @@ -0,0 +1,51 @@ +// 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 ( + "github.com/spf13/cobra" + "os" + "runtime/pprof" +) + +var cpuProfilefile string +var benchmarkTimes int + +var benchmark = &cobra.Command{ + Use: "benchmark", + Short: "Benchmark hugo by building a site a number of times", + Long: `Hugo can build a site many times over and anlyze the + running process creating a `, + Run: bench, +} + +func init() { + benchmark.Flags().StringVar(&cpuProfilefile, "outputfile", "/tmp/hugo-cpuprofile", "path/filename for the profile file") + benchmark.Flags().IntVarP(&benchmarkTimes, "count", "n", 13, "number of times to build the site") +} + +func bench(cmd *cobra.Command, args []string) { + f, err := os.Create(cpuProfilefile) + + if err != nil { + panic(err) + } + + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + + for i := 0; i < benchmarkTimes; i++ { + _, _ = buildSite() + } +} diff --git a/commands/check.go b/commands/check.go new file mode 100644 index 000000000..6ce8b8177 --- /dev/null +++ b/commands/check.go @@ -0,0 +1,31 @@ +// 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 ( + "github.com/spf13/cobra" + "github.com/spf13/hugo/hugolib" +) + +var check = &cobra.Command{ + Use: "check", + Short: "Check content in the source directory", + Long: `Hugo will perform some basic analysis on the + content provided and will give feedback.`, + Run: func(cmd *cobra.Command, args []string) { + InitializeConfig() + site := hugolib.Site{Config: *Config} + site.Analyze() + }, +} diff --git a/commands/hugo.go b/commands/hugo.go new file mode 100644 index 000000000..2fdaff008 --- /dev/null +++ b/commands/hugo.go @@ -0,0 +1,138 @@ +// 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/mostafah/fsync" + "github.com/spf13/cobra" + "github.com/spf13/hugo/hugolib" + "log" + "os" + "path/filepath" + "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: build, +} + +var Hugo *cobra.Commander +var BuildWatch, Draft, UglyUrls, Verbose bool +var Source, Destination, BaseUrl, CfgFile string + +func Execute() { + AddCommands() + Hugo := HugoCmd.ToCommander() + 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.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(cmd *cobra.Command, args []string) { + InitializeConfig() + + err := copyStatic() + if err != nil { + log.Fatalf("Error copying static files to %s: %v", Config.GetAbsPath(Config.PublishDir), err) + } + if _, err := buildSite(); err != nil { + fmt.Println(err) + os.Exit(-1) + } + + // Does this even make sense without the server setting? + //if BuildWatch { + //fmt.Println("Watching for changes in", Config.GetAbsPath(Config.ContentDir)) + //_, err = buildSite() + //if err != nil { + //fmt.Println(err) + //os.Exit(-1) + //} + //} +} + +func copyStatic() error { + // 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() (site *hugolib.Site, err error) { + startTime := time.Now() + site = &hugolib.Site{Config: *Config} + err = site.Build() + if err != nil { + return + } + site.Stats() + fmt.Printf("in %v ms\n", int(1000*time.Since(startTime).Seconds())) + return site, nil +} diff --git a/commands/server.go b/commands/server.go new file mode 100644 index 000000000..459609e81 --- /dev/null +++ b/commands/server.go @@ -0,0 +1,123 @@ +// 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/spf13/cobra" + "net/http" + "strconv" + "strings" + "sync" +) + +var serverPort int +var serverWatch bool + +func init() { + serverCmd.Flags().IntVarP(&serverPort, "port", "p", 1313, "port to run the server on") + serverCmd.Flags().BoolVarP(&serverWatch, "watch", "w", false, "watch filesystem for changes and recreate as needed") +} + +var serverCmd = &cobra.Command{ + Use: "server", + Short: "Hugo runs it's own a webserver to render the files", + Long: `Hugo is able to run it's own high performance web server. +Hugo will render all the files defined in the source directory and +Serve them up.`, + Run: server, +} + +func server(cmd *cobra.Command, args []string) { + InitializeConfig() + + if Config.BaseUrl == "" { + Config.BaseUrl = "http://localhost:" + strconv.Itoa(serverPort) + } + + // Watch runs its own server as part of the routine + if serverWatch { + fmt.Println("Watching for changes in", Config.GetAbsPath(Config.ContentDir)) + err := NewWatcher(serverPort, true) + if err != nil { + fmt.Println(err) + } + } + + serve(serverPort) +} + +func serve(port int) { + if Verbose { + fmt.Println("Serving pages from " + Config.GetAbsPath(Config.PublishDir)) + } + + fmt.Println("Web Server is available at http://localhost:", port) + fmt.Println("Press ctrl+c to stop") + panic(http.ListenAndServe(":"+strconv.Itoa(port), http.FileServer(http.Dir(Config.GetAbsPath(Config.PublishDir))))) +} + +func NewWatcher(port int, server bool) 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 server { + 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") + copyStatic() + } else { + fmt.Println("Change detected, rebuilding site\n") + buildSite() + } +} diff --git a/commands/version.go b/commands/version.go new file mode 100644 index 000000000..d5729f00a --- /dev/null +++ b/commands/version.go @@ -0,0 +1,28 @@ +// 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/spf13/cobra" +) + +var version = &cobra.Command{ + Use: "version", + Short: "Print the version number of Hugo", + Long: `All software has versions. This is Hugo's`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Hugo Static Site Generator v0.9 -- HEAD") + }, +} diff --git a/main.go b/main.go index 5f421c483..24a1594f9 100644 --- a/main.go +++ b/main.go @@ -14,225 +14,9 @@ package main import ( - "fmt" - "github.com/howeyc/fsnotify" - "github.com/mostafah/fsync" - flag "github.com/ogier/pflag" - "github.com/spf13/hugo/hugolib" - "log" - "net/http" - "os" - "path/filepath" - "runtime/pprof" - "strings" - "sync" - "time" + "github.com/spf13/hugo/commands" ) -var ( - baseUrl = flag.StringP("base-url", "b", "", "hostname (and path) to the root eg. http://spf13.com/") - cfgfile = flag.String("config", "", "config file (default is path/config.yaml|json|toml)") - checkMode = flag.Bool("check", false, "analyze content and provide feedback") - draft = flag.BoolP("build-drafts", "D", false, "include content marked as draft") - help = flag.BoolP("help", "h", false, "show this help") - source = flag.StringP("source", "s", "", "filesystem path to read files relative from") - destination = flag.StringP("destination", "d", "", "filesystem path to write files to") - verbose = flag.BoolP("verbose", "v", false, "verbose output") - version = flag.Bool("version", false, "which version of hugo") - cpuprofile = flag.Int("profile", 0, "Number of times to create the site and profile it") - watchMode = flag.BoolP("watch", "w", false, "watch filesystem for changes and recreate as needed") - server = flag.BoolP("server", "S", false, "run a (very) simple web server") - port = flag.String("port", "1313", "port to run web server on, default :1313") - uglyUrls = flag.Bool("uglyurls", false, "if true, use /filename.html instead of /filename/") -) - -func usage() { - PrintErr("usage: hugo [flags]", "") - flag.PrintDefaults() - os.Exit(0) -} - func main() { - - flag.Usage = usage - flag.Parse() - - if *help { - usage() - } - - if *version { - fmt.Println("Hugo Static Site Generator v0.8") - return - } - - config := hugolib.SetupConfig(cfgfile, source) - config.BuildDrafts = *draft - config.UglyUrls = *uglyUrls - config.Verbose = *verbose - - if *baseUrl != "" { - config.BaseUrl = *baseUrl - } else if *server { - config.BaseUrl = "http://localhost:" + *port - } - - if *destination != "" { - config.PublishDir = *destination - } - - if *cpuprofile != 0 { - f, err := os.Create("/tmp/hugo-cpuprofile") - - if err != nil { - panic(err) - } - - pprof.StartCPUProfile(f) - defer pprof.StopCPUProfile() - - for i := 0; i < *cpuprofile; i++ { - _, _ = buildSite(config) - } - } - - err := copyStatic(config) - if err != nil { - log.Fatalf("Error copying static files to %s: %v", config.GetAbsPath(config.PublishDir), err) - } - - if *checkMode { - site := hugolib.Site{Config: *config} - site.Analyze() - os.Exit(0) - } - - if *watchMode { - fmt.Println("Watching for changes. Press ctrl+c to stop") - _, err = buildSite(config) - if err != nil { - fmt.Println(err) - os.Exit(-1) - } - err := NewWatcher(config, *port, *server) - if err != nil { - fmt.Println(err) - } - } - - if _, err = buildSite(config); err != nil { - fmt.Println(err) - os.Exit(-1) - } - - if *server { - serve(*port, config) - } - -} - -func copyStatic(config *hugolib.Config) error { - // Copy Static to Destination - return fsync.Sync(config.GetAbsPath(config.PublishDir+"/"), config.GetAbsPath(config.StaticDir+"/")) -} - -func serve(port string, config *hugolib.Config) { - - if config.Verbose { - fmt.Println("Serving pages from " + config.GetAbsPath(config.PublishDir)) - } - - fmt.Println("Web Server is available at http://localhost:" + port) - fmt.Println("Press ctrl+c to stop") - panic(http.ListenAndServe(":"+port, http.FileServer(http.Dir(config.GetAbsPath(config.PublishDir))))) -} - -func buildSite(config *hugolib.Config) (site *hugolib.Site, err error) { - startTime := time.Now() - site = &hugolib.Site{Config: *config} - err = site.Build() - if err != nil { - return - } - site.Stats() - fmt.Printf("in %v ms\n", int(1000*time.Since(startTime).Seconds())) - return site, nil -} - -func watchChange(c *hugolib.Config, ev *fsnotify.FileEvent) { - if strings.HasPrefix(ev.Name, c.GetAbsPath(c.StaticDir)) { - fmt.Println("Static file changed, syncing\n") - copyStatic(c) - } else { - fmt.Println("Change detected, rebuilding site\n") - buildSite(c) - } -} - -func NewWatcher(c *hugolib.Config, port string, server bool) 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 c.Verbose { - fmt.Println(ev) - } - watchChange(c, 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(c) { - if d != "" { - _ = watcher.Watch(d) - } - } - - if server { - go serve(port, c) - } - - wg.Wait() - return nil -} - -func getDirList(c *hugolib.Config) []string { - var a []string - walker := func(path string, fi os.FileInfo, err error) error { - if err != nil { - PrintErr("Walker: ", err) - return nil - } - - if fi.IsDir() { - a = append(a, path) - } - return nil - } - - filepath.Walk(c.GetAbsPath(c.ContentDir), walker) - filepath.Walk(c.GetAbsPath(c.LayoutDir), walker) - filepath.Walk(c.GetAbsPath(c.StaticDir), walker) - - return a -} - -func PrintErr(str string, a ...interface{}) { - fmt.Fprintln(os.Stderr, str, a) + commands.Execute() }