diff --git a/README.md b/README.md index 88dfe7bb499..6d0d3636ca3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,15 @@ -# Hugo +![Hugo](https://raw.githubusercontent.com/spf13/hugo/master/docs/static/img/hugo-logo.png) + A Fast and Flexible Static Site Generator built with love by [spf13](http://spf13.com/) and [friends](https://github.com/spf13/hugo/graphs/contributors) in [Go][]. -[![Build Status](https://travis-ci.org/spf13/hugo.png)](https://travis-ci.org/spf13/hugo) [![wercker status](https://app.wercker.com/status/1a0de7d703ce3b80527f00f675e1eb32 "wercker status")](https://app.wercker.com/project/bykey/1a0de7d703ce3b80527f00f675e1eb32) [![Build status](https://ci.appveyor.com/api/projects/status/n2mo912b8s2505e8/branch/master?svg=true)](https://ci.appveyor.com/project/spf13/hugo/branch/master) +[Website](http://gohugo.io) | +[Forum](https://discuss.gohugo.io) | +[Chat](https://gitter.im/spf13/hugo) | +[Documentation](http://gohugo.io/overview/introduction/) | +[Installation Guide](http://gohugo.io/overview/installing/) | +[Twitter](http://twitter.com/spf13) + +[![Build Status](https://travis-ci.org/spf13/hugo.png)](https://travis-ci.org/spf13/hugo) [![wercker status](https://app.wercker.com/status/1a0de7d703ce3b80527f00f675e1eb32 "wercker status")](https://app.wercker.com/project/bykey/1a0de7d703ce3b80527f00f675e1eb32) [![Build status](https://ci.appveyor.com/api/projects/status/n2mo912b8s2505e8/branch/master?svg=true)](https://ci.appveyor.com/project/spf13/hugo/branch/master) [![Join the chat at https://gitter.im/spf13/hugo](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/spf13/hugo?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Overview @@ -74,7 +82,16 @@ To update Hugo’s dependencies, use `go get` with the `-u` option. go get -u -v github.com/spf13/hugo -## Contributing Code +## Contributing to Hugo + +We welcome contributions to Hugo of any kind including documentation, themes, organization, tutorials, blog posts, bug reports, issues, feature requests, feature implementation, pull requests, answering questions on the forum, helping to manage issues, etc. The Hugo community and maintainers are very active and helpful and the project benefits greatly from this activity. + +[![Throughput Graph](https://graphs.waffle.io/spf13/hugo/throughput.svg)](https://waffle.io/spf13/hugo/metrics) + +If you have any questions about how to contribute or what to contribute please ask on the [forum](http://discuss.gohugo.io) + + +## Code Contribution Guide Contributors should build Hugo and test their changes before submitting a code change. diff --git a/bufferpool/bufpool.go b/bufferpool/bufpool.go index 0b7829b2cf1..5a550e0e7cb 100644 --- a/bufferpool/bufpool.go +++ b/bufferpool/bufpool.go @@ -24,10 +24,13 @@ var bufferPool = &sync.Pool{ }, } +// GetBuffer returns a buffer from the pool. func GetBuffer() (buf *bytes.Buffer) { return bufferPool.Get().(*bytes.Buffer) } +// PutBuffer returns a buffer to the pool. +// The buffer is reset before it is put back into circulation. func PutBuffer(buf *bytes.Buffer) { buf.Reset() bufferPool.Put(buf) diff --git a/commands/convert.go b/commands/convert.go index ec6e2e5518d..6268fb2a236 100644 --- a/commands/convert.go +++ b/commands/convert.go @@ -27,8 +27,8 @@ import ( "github.com/spf13/viper" ) -var OutputDir string -var Unsafe bool +var outputDir string +var unsafe bool var convertCmd = &cobra.Command{ Use: "convert", @@ -80,8 +80,8 @@ func init() { convertCmd.AddCommand(toJSONCmd) convertCmd.AddCommand(toTOMLCmd) convertCmd.AddCommand(toYAMLCmd) - convertCmd.PersistentFlags().StringVarP(&OutputDir, "output", "o", "", "filesystem path to write files to") - convertCmd.PersistentFlags().BoolVar(&Unsafe, "unsafe", false, "enable less safe operations, please backup first") + convertCmd.PersistentFlags().StringVarP(&outputDir, "output", "o", "", "filesystem path to write files to") + convertCmd.PersistentFlags().BoolVar(&unsafe, "unsafe", false, "enable less safe operations, please backup first") } func convertContents(mark rune) (err error) { @@ -134,10 +134,10 @@ func convertContents(mark rune) (err error) { page.SetSourceContent(psr.Content()) page.SetSourceMetaData(metadata, mark) - if OutputDir != "" { - page.SaveSourceAs(filepath.Join(OutputDir, page.FullFilePath())) + if outputDir != "" { + page.SaveSourceAs(filepath.Join(outputDir, page.FullFilePath())) } else { - if Unsafe { + if unsafe { page.SaveSource() } else { jww.FEEDBACK.Println("Unsafe operation not allowed, use --unsafe or set a different output path") diff --git a/commands/hugo.go b/commands/hugo.go index 1b19c34a4b8..3a6bb5142c3 100644 --- a/commands/hugo.go +++ b/commands/hugo.go @@ -1,4 +1,4 @@ -// Copyright © 2013 Steve Francia . +// Copyright © 2013-2015 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. @@ -36,6 +36,7 @@ import ( jww "github.com/spf13/jwalterweatherman" "github.com/spf13/nitro" "github.com/spf13/viper" + "gopkg.in/fsnotify.v1" ) //HugoCmd is Hugo's root command. Every other command attached to HugoCmd is a child command to it. @@ -55,8 +56,8 @@ Complete documentation is available at http://gohugo.io`, var hugoCmdV *cobra.Command //Flags that are to be added to commands. -var BuildWatch, IgnoreCache, Draft, Future, UglyUrls, Verbose, Logging, VerboseLog, DisableRSS, DisableSitemap, PluralizeListTitles, NoTimes bool -var Source, CacheDir, Destination, Theme, BaseUrl, CfgFile, LogFile, Editor string +var BuildWatch, IgnoreCache, Draft, Future, UglyURLs, Verbose, Logging, VerboseLog, DisableRSS, DisableSitemap, PluralizeListTitles, NoTimes bool +var Source, CacheDir, Destination, Theme, BaseURL, CfgFile, LogFile, Editor string //Execute adds all child commands to the root command HugoCmd and sets flags appropriately. func Execute() { @@ -74,6 +75,7 @@ func AddCommands() { HugoCmd.AddCommand(convertCmd) HugoCmd.AddCommand(newCmd) HugoCmd.AddCommand(listCmd) + HugoCmd.AddCommand(undraftCmd) } //Initializes flags @@ -88,8 +90,8 @@ func init() { HugoCmd.PersistentFlags().StringVarP(&Destination, "destination", "d", "", "filesystem path to write files to") HugoCmd.PersistentFlags().StringVarP(&Theme, "theme", "t", "", "theme to use (located in /themes/THEMENAME/)") 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, "baseUrl", "b", "", "hostname (and path) to the root eg. http://spf13.com/") + HugoCmd.PersistentFlags().BoolVar(&UglyURLs, "uglyUrls", false, "if true, use /filename.html instead of /filename/") + HugoCmd.PersistentFlags().StringVarP(&BaseURL, "baseUrl", "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().StringVar(&Editor, "editor", "", "edit new content with this editor, if provided") HugoCmd.PersistentFlags().BoolVar(&Logging, "log", false, "Enable Logging") @@ -126,10 +128,10 @@ func InitializeConfig() { viper.SetDefault("DefaultLayout", "post") viper.SetDefault("BuildDrafts", false) viper.SetDefault("BuildFuture", false) - viper.SetDefault("UglyUrls", false) + viper.SetDefault("UglyURLs", false) viper.SetDefault("Verbose", false) viper.SetDefault("IgnoreCache", false) - viper.SetDefault("CanonifyUrls", false) + viper.SetDefault("CanonifyURLs", false) viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"}) viper.SetDefault("Permalinks", make(hugolib.PermalinkOverrides, 0)) viper.SetDefault("Sitemap", hugolib.Sitemap{Priority: -1}) @@ -154,7 +156,7 @@ func InitializeConfig() { } if hugoCmdV.PersistentFlags().Lookup("uglyUrls").Changed { - viper.Set("UglyUrls", UglyUrls) + viper.Set("UglyURLs", UglyURLs) } if hugoCmdV.PersistentFlags().Lookup("disableRSS").Changed { @@ -180,14 +182,14 @@ func InitializeConfig() { if hugoCmdV.PersistentFlags().Lookup("logFile").Changed { viper.Set("LogFile", LogFile) } - if BaseUrl != "" { - if !strings.HasSuffix(BaseUrl, "/") { - BaseUrl = BaseUrl + "/" + if BaseURL != "" { + if !strings.HasSuffix(BaseURL, "/") { + BaseURL = BaseURL + "/" } - viper.Set("BaseUrl", BaseUrl) + viper.Set("BaseURL", BaseURL) } - if viper.GetString("BaseUrl") == "" { + if viper.GetString("BaseURL") == "" { jww.ERROR.Println("No 'baseurl' set in configuration or as a flag. Features like page menus will not work without one.") } @@ -291,10 +293,17 @@ func copyStatic() error { return syncer.Sync(publishDir, staticDir) } +// 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 } @@ -317,12 +326,16 @@ func getDirList() []string { } 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(helpers.AbsPathify(viper.GetString("DataDir")), walker) + 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) @@ -349,7 +362,7 @@ func buildSite(watching ...bool) (err error) { return nil } -//NewWatcher creates a new watcher to watch filesystem events. +// NewWatcher creates a new watcher to watch filesystem events. func NewWatcher(port int) error { if runtime.GOOS == "darwin" { tweakLimit() @@ -369,50 +382,50 @@ func NewWatcher(port int) error { for _, d := range getDirList() { if d != "" { - _ = watcher.Watch(d) + _ = watcher.Add(d) } } go func() { for { select { - case evs := <-watcher.Event: + case evs := <-watcher.Events: jww.INFO.Println("File System Event:", evs) - static_changed := false - dynamic_changed := false - static_files_changed := make(map[string]bool) + staticChanged := false + dynamicChanged := false + staticFilesChanged := make(map[string]bool) for _, ev := range evs { ext := filepath.Ext(ev.Name) - istemp := strings.HasSuffix(ext, "~") || (ext == ".swp") || (ext == ".swx") || (ext == ".tmp") || (strings.HasPrefix(ext, ".goutputstream")) + istemp := strings.HasSuffix(ext, "~") || (ext == ".swp") || (ext == ".swx") || (ext == ".tmp") || strings.HasPrefix(ext, ".goutputstream") if istemp { continue } // renames are always followed with Create/Modify - if ev.IsRename() { + if ev.Op&fsnotify.Rename == fsnotify.Rename { continue } isstatic := strings.HasPrefix(ev.Name, helpers.GetStaticDirPath()) || strings.HasPrefix(ev.Name, helpers.GetThemesDirPath()) - static_changed = static_changed || isstatic - dynamic_changed = dynamic_changed || !isstatic + staticChanged = staticChanged || isstatic + dynamicChanged = dynamicChanged || !isstatic if isstatic { if staticPath, err := helpers.MakeStaticPathRelative(ev.Name); err == nil { - static_files_changed[staticPath] = true + staticFilesChanged[staticPath] = true } } // add new directory to watch list if s, err := os.Stat(ev.Name); err == nil && s.Mode().IsDir() { - if ev.IsCreate() { - watcher.Watch(ev.Name) + if ev.Op&fsnotify.Create == fsnotify.Create { + watcher.Add(ev.Name) } } } - if static_changed { + if staticChanged { jww.FEEDBACK.Println("Static file changed, syncing\n") utils.StopOnErr(copyStatic(), fmt.Sprintf("Error copying static files to %s", helpers.AbsPathify(viper.GetString("PublishDir")))) @@ -420,8 +433,8 @@ func NewWatcher(port int) error { // Will block forever trying to write to a channel that nobody is reading if livereload isn't initalized // force refresh when more than one file - if len(static_files_changed) == 1 { - for path := range static_files_changed { + if len(staticFilesChanged) == 1 { + for path := range staticFilesChanged { livereload.RefreshPath(path) } @@ -431,7 +444,7 @@ func NewWatcher(port int) error { } } - if dynamic_changed { + if dynamicChanged { fmt.Print("\nChange detected, rebuilding site\n") const layout = "2006-01-02 15:04 -0700" fmt.Println(time.Now().Format(layout)) @@ -442,7 +455,7 @@ func NewWatcher(port int) error { livereload.ForceRefresh() } } - case err := <-watcher.Error: + case err := <-watcher.Errors: if err != nil { fmt.Println("error:", err) } diff --git a/commands/new.go b/commands/new.go index 781351e4638..5721b7f8892 100644 --- a/commands/new.go +++ b/commands/new.go @@ -220,13 +220,17 @@ func touchFile(x ...string) { func createThemeMD(inpath string) (err error) { - by := []byte(`name = "` + strings.Title(helpers.MakeTitle(filepath.Base(inpath))) + `" + by := []byte(`# theme.toml template for a Hugo theme +# See https://github.com/spf13/hugoThemes#themetoml for an example + +name = "` + strings.Title(helpers.MakeTitle(filepath.Base(inpath))) + `" license = "MIT" -licenselink = "https://github.com/.../.../LICENSE.md" +licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE.md" description = "" homepage = "http://siteforthistheme.com/" tags = ["", ""] features = ["", ""] +min_version = 0.13 [author] name = "" diff --git a/commands/server.go b/commands/server.go index 6b145449d97..29900f9ce54 100644 --- a/commands/server.go +++ b/commands/server.go @@ -84,11 +84,11 @@ func server(cmd *cobra.Command, args []string) { viper.Set("port", serverPort) - BaseUrl, err := fixUrl(BaseUrl) + BaseURL, err := fixURL(BaseURL) if err != nil { jww.ERROR.Fatal(err) } - viper.Set("BaseUrl", BaseUrl) + viper.Set("BaseURL", BaseURL) if err := memStats(); err != nil { jww.ERROR.Println("memstats error:", err) @@ -114,9 +114,9 @@ func serve(port int) { httpFs := &afero.HttpFs{SourceFs: hugofs.DestinationFS} fileserver := http.FileServer(httpFs.Dir(helpers.AbsPathify(viper.GetString("PublishDir")))) - u, err := url.Parse(viper.GetString("BaseUrl")) + u, err := url.Parse(viper.GetString("BaseURL")) if err != nil { - jww.ERROR.Fatalf("Invalid BaseUrl: %s", err) + jww.ERROR.Fatalf("Invalid BaseURL: %s", err) } if u.Path == "" || u.Path == "/" { http.Handle("/", fileserver) @@ -137,10 +137,10 @@ func serve(port int) { // fixUrl massages the BaseUrl into a form needed for serving // all pages correctly. -func fixUrl(s string) (string, error) { +func fixURL(s string) (string, error) { useLocalhost := false if s == "" { - s = viper.GetString("BaseUrl") + s = viper.GetString("BaseURL") useLocalhost = true } if !strings.HasPrefix(s, "http://") && !strings.HasPrefix(s, "https://") { diff --git a/commands/server_test.go b/commands/server_test.go index 1e1343d44c6..ea853e8016f 100644 --- a/commands/server_test.go +++ b/commands/server_test.go @@ -6,11 +6,11 @@ import ( "github.com/spf13/viper" ) -func TestFixUrl(t *testing.T) { +func TestFixURL(t *testing.T) { type data struct { TestName string - CliBaseUrl string - CfgBaseUrl string + CLIBaseURL string + CfgBaseURL string AppendPort bool Port int Result string @@ -28,13 +28,13 @@ func TestFixUrl(t *testing.T) { } for i, test := range tests { - BaseUrl = test.CliBaseUrl - viper.Set("BaseUrl", test.CfgBaseUrl) + BaseURL = test.CLIBaseURL + viper.Set("BaseURL", test.CfgBaseURL) serverAppend = test.AppendPort serverPort = test.Port - result, err := fixUrl(BaseUrl) + result, err := fixURL(BaseURL) if err != nil { - t.Errorf("Test #%d %s: unexpected error %s", err) + t.Errorf("Test #%d %s: unexpected error %s", i, test.TestName, err) } if result != test.Result { t.Errorf("Test #%d %s: expected %q, got %q", i, test.TestName, test.Result, result) diff --git a/commands/undraft.go b/commands/undraft.go new file mode 100644 index 00000000000..4dbdf45c927 --- /dev/null +++ b/commands/undraft.go @@ -0,0 +1,157 @@ +// 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 ( + "bytes" + "fmt" + "os" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/hugo/parser" + jww "github.com/spf13/jwalterweatherman" +) + +var undraftCmd = &cobra.Command{ + Use: "undraft path/to/content", + Short: "Undraft changes the content's draft status from 'True' to 'False'", + Long: `Undraft changes the content's draft status from 'True' to 'False' and updates the date to the current date and time. If the content's draft status is 'False', nothing is done`, + Run: Undraft, +} + +// Publish publishes the specified content by setting its draft status +// to false and setting its publish date to now. If the specified content is +// not a draft, it will log an error. +func Undraft(cmd *cobra.Command, args []string) { + InitializeConfig() + + if len(args) < 1 { + cmd.Usage() + jww.FATAL.Fatalln("a piece of content needs to be specified") + } + + location := args[0] + // open the file + f, err := os.Open(location) + if err != nil { + jww.ERROR.Print(err) + return + } + + // get the page from file + p, err := parser.ReadFrom(f) + f.Close() + if err != nil { + jww.ERROR.Print(err) + return + } + + w, err := undraftContent(p) + if err != nil { + jww.ERROR.Printf("an error occurred while undrafting %q: %s", location, err) + return + } + + f, err = os.OpenFile(location, os.O_WRONLY|os.O_TRUNC, 0644) + if err != nil { + jww.ERROR.Printf("%q not be undrafted due to error opening file to save changes: %q\n", location, err) + return + } + defer f.Close() + _, err = w.WriteTo(f) + if err != nil { + jww.ERROR.Printf("%q not be undrafted due to save error: %q\n", location, err) + } + return +} + +// undraftContent: if the content is a draft, change it's draft status to +// 'false' and set the date to time.Now(). If the draft status is already +// 'false', don't do anything. +func undraftContent(p parser.Page) (bytes.Buffer, error) { + var buff bytes.Buffer + // get the metadata; easiest way to see if it's a draft + meta, err := p.Metadata() + if err != nil { + return buff, err + } + // since the metadata was obtainable, we can also get the key/value separator for + // Front Matter + fm := p.FrontMatter() + if fm == nil { + err := fmt.Errorf("Front Matter was found, nothing was finalized") + return buff, err + } + + var isDraft, gotDate bool + var date string +L: + for k, v := range meta.(map[string]interface{}) { + switch k { + case "draft": + if !v.(bool) { + return buff, fmt.Errorf("not a Draft: nothing was done") + } + isDraft = true + if gotDate { + break L + } + case "date": + date = v.(string) // capture the value to make replacement easier + gotDate = true + if isDraft { + break L + } + } + } + + // if draft wasn't found in FrontMatter, it isn't a draft. + if !isDraft { + return buff, fmt.Errorf("not a Draft: nothing was done") + } + + // get the front matter as bytes and split it into lines + var lineEnding []byte + fmLines := bytes.Split(fm, parser.UnixEnding) + if len(fmLines) == 1 { // if the result is only 1 element, try to split on dos line endings + fmLines = bytes.Split(fm, parser.DosEnding) + if len(fmLines) == 1 { + return buff, fmt.Errorf("unable to split FrontMatter into lines") + } + lineEnding = append(lineEnding, parser.DosEnding...) + } else { + lineEnding = append(lineEnding, parser.UnixEnding...) + } + + // Write the front matter lines to the buffer, replacing as necessary + for _, v := range fmLines { + pos := bytes.Index(v, []byte("draft")) + if pos != -1 { + v = bytes.Replace(v, []byte("true"), []byte("false"), 1) + goto write + } + pos = bytes.Index(v, []byte("date")) + if pos != -1 { // if date field wasn't found, add it + v = bytes.Replace(v, []byte(date), []byte(time.Now().Format(time.RFC3339)), 1) + } + write: + buff.Write(v) + buff.Write(lineEnding) + } + + // append the actual content + buff.Write([]byte(p.Content())) + + return buff, nil +} diff --git a/commands/undraft_test.go b/commands/undraft_test.go new file mode 100644 index 00000000000..e0a654f3a71 --- /dev/null +++ b/commands/undraft_test.go @@ -0,0 +1,72 @@ +package commands + +// TODO Support Mac Encoding (\r) + +import ( + "bytes" + "strings" + "testing" + "time" + + "github.com/spf13/hugo/parser" +) + +var ( + jsonFM = "{\n \"date\": \"12-04-06\",\n \"title\": \"test json\"\n}" + jsonDraftFM = "{\n \"draft\": true,\n \"date\": \"12-04-06\",\n \"title\":\"test json\"\n}" + tomlFM = "+++\n date= \"12-04-06\"\n title= \"test toml\"\n+++" + tomlDraftFM = "+++\n draft= true\n date= \"12-04-06\"\n title=\"test toml\"\n+++" + yamlFM = "---\n date: \"12-04-06\"\n title: \"test yaml\"\n---" + yamlDraftFM = "---\n draft: true\n date: \"12-04-06\"\n title: \"test yaml\"\n---" +) + +func TestUndraftContent(t *testing.T) { + tests := []struct { + fm string + expectedErr string + }{ + {jsonFM, "not a Draft: nothing was done"}, + {jsonDraftFM, ""}, + {tomlFM, "not a Draft: nothing was done"}, + {tomlDraftFM, ""}, + {yamlFM, "not a Draft: nothing was done"}, + {yamlDraftFM, ""}, + } + + for _, test := range tests { + r := bytes.NewReader([]byte(test.fm)) + p, _ := parser.ReadFrom(r) + res, err := undraftContent(p) + if test.expectedErr != "" { + if err == nil { + t.Error("Expected error, got none") + continue + } + if err.Error() != test.expectedErr { + t.Errorf("Expected %q, got %q", test.expectedErr, err) + continue + } + } else { + r = bytes.NewReader(res.Bytes()) + p, _ = parser.ReadFrom(r) + meta, err := p.Metadata() + if err != nil { + t.Errorf("unexpected error %q", err) + continue + } + for k, v := range meta.(map[string]interface{}) { + if k == "draft" { + if v.(bool) { + t.Errorf("Expected %q to be \"false\", got \"true\"", k) + continue + } + } + if k == "date" { + if !strings.HasPrefix(v.(string), time.Now().Format("2006-01-02")) { + t.Errorf("Expected %v to start with %v", v.(string), time.Now().Format("2006-01-02")) + } + } + } + } + } +} diff --git a/commands/version.go b/commands/version.go index 799ff70df74..88ea941f7b1 100644 --- a/commands/version.go +++ b/commands/version.go @@ -20,7 +20,7 @@ import ( "strings" "time" - "bitbucket.org/kardianos/osext" + "github.com/kardianos/osext" "github.com/spf13/cobra" "github.com/spf13/hugo/hugolib" ) diff --git a/create/content.go b/create/content.go index fa37f266649..acb788c631d 100644 --- a/create/content.go +++ b/create/content.go @@ -63,7 +63,7 @@ func NewContent(kind, name string) (err error) { return err } - for k, _ := range newmetadata { + for k := range newmetadata { switch strings.ToLower(k) { case "date": newmetadata[k] = time.Now() @@ -73,7 +73,7 @@ func NewContent(kind, name string) (err error) { } caseimatch := func(m map[string]interface{}, key string) bool { - for k, _ := range m { + for k := range m { if strings.ToLower(k) == strings.ToLower(key) { return true } diff --git a/docs/content/community/press.md b/docs/content/community/press.md index eb6d3e604e8..eb2845e26f0 100644 --- a/docs/content/community/press.md +++ b/docs/content/community/press.md @@ -16,8 +16,9 @@ Hugo has been featured in the following Blog Posts, Press and Media. | Title | Author | Date | | ------ | ------ | -----: | +| [服务器上 hugo 的安装和配置 (Installing and configuring Hugo on the server)](http://hucsmn.com/post/hugo-tutorial-make-it-work/) | hucsmn | 11 Feb 2015 | | [把这个博客静态化了 (Migrate to Hugo)](http://lich-eng.com/2015/01/03/migrate-to-hugo/) | Li Cheng | 3 Jan 2015 | -| [My Hugo Experiment](http://baty.net/2014/12/2014-12-31-my-hugo-experiment/) | Jack Baty | 31 Dec 2014 | +| [My Hugo Experiment](http://tilde.club/~jbaty/2014/12/2014-12-31-my-hugo-experiment/) | Jack Baty | 31 Dec 2014 | | [6 Static Blog Generators That Aren’t Jekyll](http://www.sitepoint.com/6-static-blog-generators-arent-jekyll/) | David Turnbull | 8 Dec 2014 | | [Travel Blogging Setup](http://www.stou.dk/2014/11/travel-blogging-setup/) | Rasmus Stougaard | 23 Nov 2014 | | [使用Hugo搭建免费个人Blog (How to use Hugo)](http://ulricqin.com/post/how-to-use-hugo/) | Ulric Qin 秦晓辉 | 11 Nov 2014 | diff --git a/docs/content/extras/shortcodes.md b/docs/content/extras/shortcodes.md index ecb087082e9..79c6871cd0a 100644 --- a/docs/content/extras/shortcodes.md +++ b/docs/content/extras/shortcodes.md @@ -193,6 +193,10 @@ of the content between the opening and closing shortcodes. If a closing shortcode is required, you can check the length of `.Inner` and provide a warning to the user. +A shortcode with `.Inner` content can be used wihout the inline content, and without the closing shortcode, by using the self-closing syntax: + + {{}} + The variable `.Params` contains the list of parameters in case you need to do more complicated things than `.Get`. You can also use the variable `.Page` to access all the normal [Page Variables](/templates/variables/). diff --git a/docs/content/overview/usage.md b/docs/content/overview/usage.md index c5ab765d6f9..d7a3c4944a8 100644 --- a/docs/content/overview/usage.md +++ b/docs/content/overview/usage.md @@ -14,69 +14,154 @@ weight: 30 Make sure either `hugo` is in your `PATH` or provide a path to it. - $ hugo help - A Fast and Flexible Static Site Generator - built with love by spf13 and friends in Go. - - Complete documentation is available at http://gohugo.io - - Usage: - hugo [flags] - hugo [command] - - Available Commands: - server Hugo runs its own webserver to render the files - version Print the version number of Hugo - check Check content in the source directory - benchmark Benchmark hugo by building a site a number of times - new [path] Create new content for your site - help [command] Help about any command - - Available Flags: - -b, --baseUrl="": hostname (and path) to the root eg. http://spf13.com/ - -D, --buildDrafts=false: build content marked as draft - -F, --buildFuture=false: build content with PublishDate in the future - --config="": config file (default is path/config.yaml|json|toml) - -d, --destination="": filesystem path to write files to - --disableRSS=false: Do not build RSS files - --disableSitemap=false: Do not build Sitemap file - --log=false: Enable Logging - --logFile="": Log File path (if set, logging enabled automatically) - -s, --source="": filesystem path to read files relative from - --stepAnalysis=false: display memory and timing of different steps of the program - -t, --theme="": theme to use (located in /themes/THEMENAME/) - --uglyUrls=false: if true, use /filename.html instead of /filename/ - -v, --verbose=false: verbose output - --verboseLog=false: verbose logging - -w, --watch=false: watch filesystem for changes and recreate as needed - - Use "hugo help [command]" for more information about that command. +
$ hugo help
+A Fast and Flexible Static Site Generator built with
+love by spf13 and friends in Go.
+
+Complete documentation is available at http://gohugo.io
+
+Usage:
+  hugo [flags]
+  hugo [command]
+Available Commands:
+  server      Hugo runs its own webserver to render the files
+  version     Print the version number of Hugo
+  config      Print the site configuration
+  check       Check content in the source directory
+  benchmark   Benchmark hugo by building a site a number of times
+  new         Create new content for your site
+  help        Help about any command
+
+Flags:
+      --noTimes=false: Don't sync modification time of files
+  -w, --watch=false: watch filesystem for changes and recreate as needed
+
+Global Flags:
+  -b, --baseUrl="": hostname (and path) to the root eg. http://spf13.com/
+  -D, --buildDrafts=false: include content marked as draft
+  -F, --buildFuture=false: include content with datePublished in the future
+      --cacheDir="": filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/
+      --config="": config file (default is path/config.yaml|json|toml)
+  -d, --destination="": filesystem path to write files to
+      --disableRSS=false: Do not build RSS files
+      --disableSitemap=false: Do not build Sitemap file
+      --editor="": edit new content with this editor, if provided
+  -h, --help=false: help for hugo
+      --ignoreCache=false: Ignores the cache directory for reading but still writes to it
+      --log=false: Enable Logging
+      --logFile="": Log File path (if set, logging enabled automatically)
+      --pluralizeListTitles=true: Pluralize titles in lists using inflect
+  -s, --source="": filesystem path to read files relative from
+      --stepAnalysis=false: display memory and timing of different steps of the program
+  -t, --theme="": theme to use (located in /themes/THEMENAME/)
+      --uglyUrls=false: if true, use /filename.html instead of /filename/
+  -v, --verbose=false: verbose output
+      --verboseLog=false: verbose logging
+
+Use "hugo help [command]" for more information about a command.
+
## Common Usage Example -The most common use is probably to run `hugo` with your current directory being the input directory. +The most common use is probably to run `hugo` with your current directory being the input directory: $ hugo - > X pages created - in 8 ms + 0 draft content + 0 future content + 99 pages created + 0 paginator pages created + 16 tags created + 0 groups created + in 120 ms + +This generates your web site to the `public/` directory, +ready to be deployed to your web server. + + +## Instant feedback as you develop your web site If you are working on things and want to see the changes immediately, tell Hugo to watch for changes. +Hugo will watch the filesystem for changes, and rebuild your site as soon as a file is saved: + + $ hugo -s ~/Code/hugo/docs --watch + 0 draft content + 0 future content + 99 pages created + 0 paginator pages created + 16 tags created + 0 groups created + in 120 ms + Watching for changes in /Users/spf13/Code/hugo/docs/content + Press Ctrl+C to stop + +Hugo can even run a server and create a site preview at the same time! +Hugo implements [LiveReload](/extras/livereload/) technology to automatically +reload any open pages in all JavaScript-enabled browsers, including mobile. +This is the easiest and most common way to develop a Hugo web site: + + $ hugo server -ws ~/Code/hugo/docs + 0 draft content + 0 future content + 99 pages created + 0 paginator pages created + 16 tags created + 0 groups created + in 120 ms + Watching for changes in /Users/spf13/Code/hugo/docs/content + Serving pages from /Users/spf13/Code/hugo/docs/public + Web Server is available at http://localhost:1313/ + Press Ctrl+C to stop + + +## Deploying your web site + +After running `hugo server` for local web development, +you need to do a final `hugo` run **without the `server` command** +and **without `--watch` or `-w`** to rebuild your site. +You may then **deploy your site** by copying the `public/` directory +(by FTP, SFTP, WebDAV, Rsync, git push, etc.) to your production web server. + +Since Hugo generates a static website, your site can be hosted anywhere, +including [Heroku][], [GoDaddy][], [DreamHost][], [GitHub Pages][], +[Amazon S3][] and [CloudFront][], or any other cheap or even free +static web hosting services. + +[Apache][], [nginx][], [IIS][]... Any web server software would do! + +[Apache]: http://httpd.apache.org/ "Apache HTTP Server" +[nginx]: http://nginx.org/ +[IIS]: http://www.iis.net/ +[Heroku]: https://www.heroku.com/ +[GoDaddy]: https://www.godaddy.com/ +[DreamHost]: http://www.dreamhost.com/ +[GitHub Pages]: https://pages.github.com/ +[Amazon S3]: http://aws.amazon.com/s3/ +[CloudFront]: http://aws.amazon.com/cloudfront/ "Amazon CloudFront" + + +### Alternatively, serve your web site with Hugo! + +Yes, that's right! Because Hugo is so blazingly fast both in web site creation +*and* in web serving (thanks to its concurrent and multi-threaded design and +its Go heritage), some users actually prefer using Hugo itself to serve their +web site *on their production server*! + +No other web server software (Apache, nginx, IIS...) is necessary. + +Here is the command: + + hugo server --watch \ + --baseUrl=http://yoursite.org/ --port=80 \ + --appendPort=false -Hugo will watch the filesystem for changes, rebuild your site as soon as a file is saved. +This way, you may actually deploy just the source files, +and Hugo on your server will generate the resulting web site +on-the-fly and serve them at the same time. - $ hugo -s ~/mysite --watch - 28 pages created - in 18 ms - Watching for changes in /Users/spf13/Code/hugo/docs/content - Press Ctrl+C to stop +You may optionally add `--disableLiveReload=true` if you do not want +the JavaScript code for LiveReload to be added to your web pages. -Hugo can even run a server and create a site preview at the same time! Hugo -implements [LiveReload](/extras/livereload/) technology to automatically reload any open pages in all browsers (including mobile). (Note that you'll need to run without -w before you deploy your site.) +Interested? Here are some great tutorials contributed by Hugo users: - $ hugo server -ws ~/mysite - Watching for changes in /Users/spf13/Code/hugo/docs/content - Web Server is available at http://localhost:1313 - Press Ctrl+C to stop - 28 pages created - 0 tags created - in 18 ms \ No newline at end of file +* [hugo, syncthing](http://fredix.ovh/2014/10/hugo-syncthing/) (French) by Frédéric Logier (@fredix) +* [服务器上 hugo 的安装和配置 (Installing and configuring Hugo on the server)](http://hucsmn.com/post/hugo-tutorial-make-it-work/) (Chinese) by hucsmn diff --git a/docs/content/tutorials/github-pages-blog.md b/docs/content/tutorials/github-pages-blog.md index 69ae93ef77a..c32399226b1 100644 --- a/docs/content/tutorials/github-pages-blog.md +++ b/docs/content/tutorials/github-pages-blog.md @@ -251,7 +251,7 @@ Step by step: 1. Create on GitHub `-hugo` repository (it will host Hugo's content) 2. Create on GitHub `.github.io` repository (it will host the `public` folder: the static website) 2. `git clone <-hugo-url> && cd -hugo` -3. Make your website work locally (`hugo serve --watch -t `) +3. Make your website work locally (`hugo server --watch -t `) 4. Once you are happy with the results, Ctrl+C (kill server) and `rm -rf public` (don't worry, it can always be regenerated with `hugo -t `) 5. `git submodule add git@github.com:/.github.io.git public` 6. Almost done: add a `deploy.sh` script to help you (and make it executable: `chmod +x deploy.sh`): diff --git a/helpers/content.go b/helpers/content.go index 914fdfda51c..daf7e267da4 100644 --- a/helpers/content.go +++ b/helpers/content.go @@ -37,18 +37,20 @@ var SummaryLength = 70 // Custom divider let's user define where summarization ends. var SummaryDivider = []byte("") +// Blackfriday holds configuration values for Blackfriday rendering. type Blackfriday struct { AngledQuotes bool Fractions bool - PlainIdAnchors bool + PlainIDAnchors bool Extensions []string } +// NewBlackfriday creates a new Blackfriday with some sane defaults. func NewBlackfriday() *Blackfriday { return &Blackfriday{ AngledQuotes: false, Fractions: true, - PlainIdAnchors: false, + PlainIDAnchors: false, } } @@ -77,28 +79,27 @@ func StripHTML(s string) string { // Shortcut strings with no tags in them if !strings.ContainsAny(s, "<>") { return s - } else { - s = stripHTMLReplacer.Replace(s) - - // Walk through the string removing all tags - b := bp.GetBuffer() - defer bp.PutBuffer(b) - - inTag := false - for _, r := range s { - switch r { - case '<': - inTag = true - case '>': - inTag = false - default: - if !inTag { - b.WriteRune(r) - } + } + s = stripHTMLReplacer.Replace(s) + + // Walk through the string removing all tags + b := bp.GetBuffer() + defer bp.PutBuffer(b) + + inTag := false + for _, r := range s { + switch r { + case '<': + inTag = true + case '>': + inTag = false + default: + if !inTag { + b.WriteRune(r) } } - return b.String() } + return b.String() } // StripEmptyNav strips out empty