2015-12-07 13:57:01 -05:00
// Copyright 2015 The Hugo Authors. All rights reserved.
2013-09-29 02:09:03 -04:00
//
2015-11-23 22:16:36 -05:00
// Licensed under the Apache License, Version 2.0 (the "License");
2013-09-29 02:09:03 -04:00
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
2015-11-23 22:16:36 -05:00
// http://www.apache.org/licenses/LICENSE-2.0
2013-09-29 02:09:03 -04:00
//
// 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.
2014-12-11 13:05:02 -05:00
//Package commands defines and implements command-line commands and flags used by Hugo. Commands and flags are implemented using
//cobra.
2013-09-29 02:09:03 -04:00
package commands
import (
2014-02-17 05:54:15 -05:00
"fmt"
2015-09-14 11:31:39 -04:00
"io/ioutil"
2014-05-16 17:49:27 -04:00
"net/http"
2014-02-17 05:54:15 -05:00
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
2014-03-31 13:23:34 -04:00
2015-11-09 23:31:52 -05:00
"github.com/spf13/hugo/parser"
2014-03-31 13:23:34 -04:00
"github.com/spf13/cobra"
2014-11-01 11:57:29 -04:00
"github.com/spf13/fsync"
2014-04-05 01:26:43 -04:00
"github.com/spf13/hugo/helpers"
2014-11-01 11:57:29 -04:00
"github.com/spf13/hugo/hugofs"
2014-03-31 13:23:34 -04:00
"github.com/spf13/hugo/hugolib"
2014-05-16 17:49:27 -04:00
"github.com/spf13/hugo/livereload"
2014-03-31 13:23:34 -04:00
"github.com/spf13/hugo/utils"
"github.com/spf13/hugo/watcher"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/nitro"
2014-04-05 01:26:43 -04:00
"github.com/spf13/viper"
2015-03-10 11:59:55 -04:00
"gopkg.in/fsnotify.v1"
2015-12-02 05:42:53 -05:00
"regexp"
2013-09-29 02:09:03 -04:00
)
2015-12-02 05:42:53 -05:00
// userError is an error used to signal different error situations in command handling.
type commandError struct {
s string
userError bool
}
func ( u commandError ) Error ( ) string {
return u . s
}
func ( u commandError ) isUserError ( ) bool {
return u . userError
}
2015-12-02 17:37:40 -05:00
func newUserError ( a ... interface { } ) commandError {
return commandError { s : fmt . Sprintln ( a ... ) , userError : true }
2015-12-02 05:42:53 -05:00
}
2015-12-02 17:37:40 -05:00
func newUserErrorF ( format string , a ... interface { } ) commandError {
return commandError { s : fmt . Sprintf ( format , a ... ) , userError : true }
}
func newSystemError ( a ... interface { } ) commandError {
return commandError { s : fmt . Sprintln ( a ... ) , userError : false }
}
func newSystemErrorF ( format string , a ... interface { } ) commandError {
return commandError { s : fmt . Sprintf ( format , a ... ) , userError : false }
2015-12-02 05:42:53 -05:00
}
// catch some of the obvious user errors from Cobra.
// We don't want to show the usage message for every error.
// The below may be to generic. Time will show.
var userErrorRegexp = regexp . MustCompile ( "argument|flag|shorthand" )
func isUserError ( err error ) bool {
if cErr , ok := err . ( commandError ) ; ok && cErr . isUserError ( ) {
return true
}
return userErrorRegexp . MatchString ( err . Error ( ) )
}
2014-12-11 13:05:02 -05:00
//HugoCmd is Hugo's root command. Every other command attached to HugoCmd is a child command to it.
2013-09-29 02:09:03 -04:00
var HugoCmd = & cobra . Command {
2014-02-17 05:54:15 -05:00
Use : "hugo" ,
2015-05-21 07:30:01 -04:00
Short : "hugo builds your site" ,
2015-08-04 05:15:12 -04:00
Long : ` hugo is the main command , used to build your Hugo site .
2013-09-29 02:09:03 -04:00
2015-08-04 05:15:12 -04:00
Hugo is a Fast and Flexible Static Site Generator
built with love by spf13 and friends in Go .
Complete documentation is available at http : //gohugo.io/.`,
2015-12-02 05:42:53 -05:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
if err := InitializeConfig ( ) ; err != nil {
return err
}
2015-11-09 23:31:52 -05:00
watchConfig ( )
2015-12-02 05:42:53 -05:00
return build ( )
2014-02-17 05:54:15 -05:00
} ,
2013-09-29 02:09:03 -04:00
}
2014-05-16 17:49:27 -04:00
2013-11-12 18:36:23 -05:00
var hugoCmdV * cobra . Command
2015-12-02 13:56:36 -05:00
// Flags that are to be added to commands.
2015-10-10 04:50:55 -04:00
var BuildWatch , IgnoreCache , Draft , Future , UglyURLs , CanonifyURLs , Verbose , Logging , VerboseLog , DisableRSS , DisableSitemap , PluralizeListTitles , PreserveTaxonomyNames , NoTimes bool
2015-03-11 13:34:57 -04:00
var Source , CacheDir , Destination , Theme , BaseURL , CfgFile , LogFile , Editor string
2013-09-29 02:09:03 -04:00
2015-12-02 13:56:36 -05:00
// Execute adds all child commands to the root command HugoCmd and sets flags appropriately.
2013-09-29 02:09:03 -04:00
func Execute ( ) {
2015-10-02 00:47:34 -04:00
HugoCmd . SetGlobalNormalizationFunc ( helpers . NormalizeHugoFlags )
2015-12-02 05:42:53 -05:00
HugoCmd . SilenceUsage = true
2014-02-17 05:54:15 -05:00
AddCommands ( )
2015-12-02 05:42:53 -05:00
if c , err := HugoCmd . ExecuteC ( ) ; err != nil {
if isUserError ( err ) {
c . Println ( "" )
c . Println ( c . UsageString ( ) )
}
2015-10-05 14:26:49 -04:00
os . Exit ( - 1 )
}
2013-09-29 02:09:03 -04:00
}
2015-12-02 13:56:36 -05:00
// AddCommands adds child commands to the root command HugoCmd.
2013-09-29 02:09:03 -04:00
func AddCommands ( ) {
2014-02-17 05:54:15 -05:00
HugoCmd . AddCommand ( serverCmd )
2015-12-02 13:56:36 -05:00
HugoCmd . AddCommand ( versionCmd )
HugoCmd . AddCommand ( configCmd )
HugoCmd . AddCommand ( checkCmd )
HugoCmd . AddCommand ( benchmarkCmd )
2014-05-01 13:23:32 -04:00
HugoCmd . AddCommand ( convertCmd )
2014-05-02 01:06:01 -04:00
HugoCmd . AddCommand ( newCmd )
2014-11-19 16:24:30 -05:00
HugoCmd . AddCommand ( listCmd )
2015-03-15 18:32:41 -04:00
HugoCmd . AddCommand ( undraftCmd )
2015-09-30 21:04:30 -04:00
HugoCmd . AddCommand ( importCmd )
2015-11-23 10:51:12 -05:00
HugoCmd . AddCommand ( genCmd )
genCmd . AddCommand ( genautocompleteCmd )
genCmd . AddCommand ( gendocCmd )
genCmd . AddCommand ( genmanCmd )
2015-12-02 14:00:47 -05:00
}
// initCoreCommonFlags initializes common flags used by Hugo core commands
// such as hugo itself, server, check, config and benchmark.
func initCoreCommonFlags ( cmd * cobra . Command ) {
cmd . Flags ( ) . BoolVarP ( & Draft , "buildDrafts" , "D" , false , "include content marked as draft" )
cmd . Flags ( ) . BoolVarP ( & Future , "buildFuture" , "F" , false , "include content with publishdate in the future" )
cmd . Flags ( ) . BoolVar ( & DisableRSS , "disableRSS" , false , "Do not build RSS files" )
cmd . Flags ( ) . BoolVar ( & DisableSitemap , "disableSitemap" , false , "Do not build Sitemap file" )
cmd . Flags ( ) . StringVarP ( & Source , "source" , "s" , "" , "filesystem path to read files relative from" )
cmd . Flags ( ) . StringVarP ( & CacheDir , "cacheDir" , "" , "" , "filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/" )
cmd . Flags ( ) . BoolVarP ( & IgnoreCache , "ignoreCache" , "" , false , "Ignores the cache directory for reading but still writes to it" )
cmd . Flags ( ) . StringVarP ( & Destination , "destination" , "d" , "" , "filesystem path to write files to" )
cmd . Flags ( ) . StringVarP ( & Theme , "theme" , "t" , "" , "theme to use (located in /themes/THEMENAME/)" )
cmd . Flags ( ) . BoolVar ( & UglyURLs , "uglyURLs" , false , "if true, use /filename.html instead of /filename/" )
cmd . Flags ( ) . BoolVar ( & CanonifyURLs , "canonifyURLs" , false , "if true, all relative URLs will be canonicalized using baseURL" )
cmd . Flags ( ) . StringVarP ( & BaseURL , "baseURL" , "b" , "" , "hostname (and path) to the root, e.g. http://spf13.com/" )
cmd . Flags ( ) . StringVar ( & CfgFile , "config" , "" , "config file (default is path/config.yaml|json|toml)" )
cmd . Flags ( ) . StringVar ( & Editor , "editor" , "" , "edit new content with this editor, if provided" )
cmd . Flags ( ) . BoolVar ( & nitro . AnalysisOn , "stepAnalysis" , false , "display memory and timing of different steps of the program" )
cmd . Flags ( ) . BoolVar ( & PluralizeListTitles , "pluralizeListTitles" , true , "Pluralize titles in lists using inflect" )
cmd . Flags ( ) . BoolVar ( & PreserveTaxonomyNames , "preserveTaxonomyNames" , false , ` Preserve taxonomy names as written ("Gérard Depardieu" vs "gerard-depardieu") ` )
2015-11-23 10:51:12 -05:00
2015-12-02 14:00:47 -05:00
// For bash-completion
validConfigFilenames := [ ] string { "json" , "js" , "yaml" , "yml" , "toml" , "tml" }
cmd . Flags ( ) . SetAnnotation ( "config" , cobra . BashCompFilenameExt , validConfigFilenames )
cmd . Flags ( ) . SetAnnotation ( "source" , cobra . BashCompSubdirsInDir , [ ] string { } )
cmd . Flags ( ) . SetAnnotation ( "cacheDir" , cobra . BashCompSubdirsInDir , [ ] string { } )
cmd . Flags ( ) . SetAnnotation ( "destination" , cobra . BashCompSubdirsInDir , [ ] string { } )
cmd . Flags ( ) . SetAnnotation ( "theme" , cobra . BashCompSubdirsInDir , [ ] string { "themes" } )
2013-09-29 02:09:03 -04:00
}
2015-12-02 13:56:36 -05:00
// init initializes flags.
2013-09-29 02:09:03 -04:00
func init ( ) {
2014-02-17 05:54:15 -05:00
HugoCmd . PersistentFlags ( ) . BoolVarP ( & Verbose , "verbose" , "v" , false , "verbose output" )
2014-03-31 13:23:34 -04:00
HugoCmd . PersistentFlags ( ) . BoolVar ( & Logging , "log" , false , "Enable Logging" )
2014-05-16 17:49:27 -04:00
HugoCmd . PersistentFlags ( ) . StringVar ( & LogFile , "logFile" , "" , "Log File path (if set, logging enabled automatically)" )
HugoCmd . PersistentFlags ( ) . BoolVar ( & VerboseLog , "verboseLog" , false , "verbose logging" )
2015-12-02 14:00:47 -05:00
initCoreCommonFlags ( HugoCmd )
2015-05-31 14:30:53 -04:00
2014-02-17 05:54:15 -05:00
HugoCmd . Flags ( ) . BoolVarP ( & BuildWatch , "watch" , "w" , false , "watch filesystem for changes and recreate as needed" )
2014-11-05 21:13:22 -05:00
HugoCmd . Flags ( ) . BoolVarP ( & NoTimes , "noTimes" , "" , false , "Don't sync modification time of files" )
2014-02-17 05:54:15 -05:00
hugoCmdV = HugoCmd
2015-04-07 07:01:04 -04:00
2015-12-02 04:20:55 -05:00
// For bash-completion
HugoCmd . PersistentFlags ( ) . SetAnnotation ( "logFile" , cobra . BashCompFilenameExt , [ ] string { } )
2015-05-16 12:04:56 -04:00
2015-04-07 09:17:24 -04:00
// This message will be shown to Windows users if Hugo is opened from explorer.exe
2015-04-07 07:01:04 -04:00
cobra . MousetrapHelpText = `
2015-01-06 12:11:06 -05:00
2015-12-02 13:56:36 -05:00
Hugo is a command - line tool
2015-04-07 07:01:04 -04:00
You need to open cmd . exe and run it from there . `
2013-09-29 02:09:03 -04:00
}
2015-05-20 18:50:32 -04:00
func LoadDefaultSettings ( ) {
2014-05-16 17:49:27 -04:00
viper . SetDefault ( "Watch" , false )
2014-05-29 18:40:16 -04:00
viper . SetDefault ( "MetaDataFormat" , "toml" )
2014-04-09 17:45:34 -04:00
viper . SetDefault ( "DisableRSS" , false )
2014-05-06 06:50:23 -04:00
viper . SetDefault ( "DisableSitemap" , false )
2014-04-05 01:26:43 -04:00
viper . SetDefault ( "ContentDir" , "content" )
viper . SetDefault ( "LayoutDir" , "layouts" )
viper . SetDefault ( "StaticDir" , "static" )
2014-05-02 01:06:01 -04:00
viper . SetDefault ( "ArchetypeDir" , "archetypes" )
2014-04-05 01:26:43 -04:00
viper . SetDefault ( "PublishDir" , "public" )
2015-01-20 17:08:01 -05:00
viper . SetDefault ( "DataDir" , "data" )
2014-04-05 01:26:43 -04:00
viper . SetDefault ( "DefaultLayout" , "post" )
viper . SetDefault ( "BuildDrafts" , false )
2014-05-29 00:48:40 -04:00
viper . SetDefault ( "BuildFuture" , false )
2015-03-11 13:34:57 -04:00
viper . SetDefault ( "UglyURLs" , false )
2014-04-05 01:26:43 -04:00
viper . SetDefault ( "Verbose" , false )
2015-02-02 04:14:59 -05:00
viper . SetDefault ( "IgnoreCache" , false )
2015-03-11 13:34:57 -04:00
viper . SetDefault ( "CanonifyURLs" , false )
2015-05-15 18:11:39 -04:00
viper . SetDefault ( "RelativeURLs" , false )
2015-06-16 13:25:48 -04:00
viper . SetDefault ( "RemovePathAccents" , false )
2015-02-16 15:14:47 -05:00
viper . SetDefault ( "Taxonomies" , map [ string ] string { "tag" : "tags" , "category" : "categories" } )
2014-04-05 01:26:43 -04:00
viper . SetDefault ( "Permalinks" , make ( hugolib . PermalinkOverrides , 0 ) )
2014-05-07 02:56:57 -04:00
viper . SetDefault ( "Sitemap" , hugolib . Sitemap { Priority : - 1 } )
2014-10-16 20:20:09 -04:00
viper . SetDefault ( "DefaultExtension" , "html" )
2015-07-08 21:51:54 -04:00
viper . SetDefault ( "PygmentsStyle" , "monokai" )
2014-05-07 12:38:14 -04:00
viper . SetDefault ( "PygmentsUseClasses" , false )
2015-07-03 17:53:50 -04:00
viper . SetDefault ( "PygmentsCodeFences" , false )
2015-07-08 21:51:54 -04:00
viper . SetDefault ( "PygmentsOptions" , "" )
2014-05-16 17:49:27 -04:00
viper . SetDefault ( "DisableLiveReload" , false )
2014-06-24 17:44:16 -04:00
viper . SetDefault ( "PluralizeListTitles" , true )
2015-05-31 14:30:53 -04:00
viper . SetDefault ( "PreserveTaxonomyNames" , false )
2014-09-26 23:44:09 -04:00
viper . SetDefault ( "FootnoteAnchorPrefix" , "" )
viper . SetDefault ( "FootnoteReturnLinkContents" , "" )
2014-10-14 22:48:55 -04:00
viper . SetDefault ( "NewContentEditor" , "" )
2015-01-26 09:26:19 -05:00
viper . SetDefault ( "Paginate" , 10 )
2014-12-27 08:11:19 -05:00
viper . SetDefault ( "PaginatePath" , "page" )
2015-01-31 12:24:00 -05:00
viper . SetDefault ( "Blackfriday" , helpers . NewBlackfriday ( ) )
2015-04-24 14:25:09 -04:00
viper . SetDefault ( "RSSUri" , "index.xml" )
2015-01-06 12:11:06 -05:00
viper . SetDefault ( "SectionPagesMenu" , "" )
2015-09-01 08:53:25 -04:00
viper . SetDefault ( "DisablePathToLower" , false )
2015-09-03 06:22:20 -04:00
viper . SetDefault ( "HasCJKLanguage" , false )
2015-05-20 18:50:32 -04:00
}
// InitializeConfig initializes a config file with sensible default configuration flags.
2015-12-02 14:00:47 -05:00
// A Hugo command that calls initCoreCommonFlags() can pass itself
// as an argument to have its command-line flags processed here.
func InitializeConfig ( subCmdVs ... * cobra . Command ) error {
2015-05-20 18:50:32 -04:00
viper . SetConfigFile ( CfgFile )
2015-08-19 02:36:22 -04:00
// See https://github.com/spf13/viper/issues/73#issuecomment-126970794
if Source == "" {
viper . AddConfigPath ( "." )
} else {
viper . AddConfigPath ( Source )
}
2015-05-20 18:50:32 -04:00
err := viper . ReadInConfig ( )
if err != nil {
2015-11-11 16:47:09 -05:00
if _ , ok := err . ( viper . ConfigParseError ) ; ok {
2015-12-02 05:42:53 -05:00
return newSystemError ( err )
2015-11-11 16:47:09 -05:00
} else {
2015-12-03 02:28:09 -05:00
return newSystemErrorF ( "Unable to locate Config file. Perhaps you need to create a new site.\n Run `hugo help new` for details. (%s)\n" , err )
2015-11-11 16:47:09 -05:00
}
2015-05-20 18:50:32 -04:00
}
viper . RegisterAlias ( "indexes" , "taxonomies" )
LoadDefaultSettings ( )
2014-02-17 05:54:15 -05:00
if hugoCmdV . PersistentFlags ( ) . Lookup ( "verbose" ) . Changed {
2014-04-05 01:26:43 -04:00
viper . Set ( "Verbose" , Verbose )
2014-02-17 05:54:15 -05:00
}
2014-05-16 17:49:27 -04:00
if hugoCmdV . PersistentFlags ( ) . Lookup ( "logFile" ) . Changed {
2014-04-05 01:26:43 -04:00
viper . Set ( "LogFile" , LogFile )
2014-03-31 13:23:34 -04:00
}
2015-12-01 11:48:52 -05:00
2015-12-02 14:00:47 -05:00
for _ , cmdV := range append ( [ ] * cobra . Command { hugoCmdV } , subCmdVs ... ) {
if cmdV . Flags ( ) . Lookup ( "buildDrafts" ) . Changed {
viper . Set ( "BuildDrafts" , Draft )
}
if cmdV . Flags ( ) . Lookup ( "buildFuture" ) . Changed {
viper . Set ( "BuildFuture" , Future )
}
if cmdV . Flags ( ) . Lookup ( "uglyURLs" ) . Changed {
viper . Set ( "UglyURLs" , UglyURLs )
}
if cmdV . Flags ( ) . Lookup ( "canonifyURLs" ) . Changed {
viper . Set ( "CanonifyURLs" , CanonifyURLs )
}
if cmdV . Flags ( ) . Lookup ( "disableRSS" ) . Changed {
viper . Set ( "DisableRSS" , DisableRSS )
}
if cmdV . Flags ( ) . Lookup ( "disableSitemap" ) . Changed {
viper . Set ( "DisableSitemap" , DisableSitemap )
}
if cmdV . Flags ( ) . Lookup ( "pluralizeListTitles" ) . Changed {
viper . Set ( "PluralizeListTitles" , PluralizeListTitles )
}
if cmdV . Flags ( ) . Lookup ( "preserveTaxonomyNames" ) . Changed {
viper . Set ( "PreserveTaxonomyNames" , PreserveTaxonomyNames )
}
if cmdV . Flags ( ) . Lookup ( "editor" ) . Changed {
viper . Set ( "NewContentEditor" , Editor )
}
if cmdV . Flags ( ) . Lookup ( "ignoreCache" ) . Changed {
viper . Set ( "IgnoreCache" , IgnoreCache )
}
2015-12-03 14:02:38 -05:00
}
if hugoCmdV . Flags ( ) . Lookup ( "noTimes" ) . Changed {
viper . Set ( "NoTimes" , NoTimes )
2015-12-01 11:48:52 -05:00
}
2015-03-11 13:34:57 -04:00
if BaseURL != "" {
if ! strings . HasSuffix ( BaseURL , "/" ) {
BaseURL = BaseURL + "/"
2014-04-05 01:26:43 -04:00
}
2015-03-11 13:34:57 -04:00
viper . Set ( "BaseURL" , BaseURL )
2014-02-17 05:54:15 -05:00
}
2014-04-10 08:10:12 -04:00
2015-05-16 09:42:10 -04:00
if ! viper . GetBool ( "RelativeURLs" ) && viper . GetString ( "BaseURL" ) == "" {
2015-02-06 04:39:54 -05:00
jww . ERROR . Println ( "No 'baseurl' set in configuration or as a flag. Features like page menus will not work without one." )
}
2014-04-10 08:10:12 -04:00
if Theme != "" {
viper . Set ( "theme" , Theme )
}
2014-02-17 05:54:15 -05:00
if Destination != "" {
2014-04-05 01:26:43 -04:00
viper . Set ( "PublishDir" , Destination )
}
if Source != "" {
viper . Set ( "WorkingDir" , Source )
} else {
2014-11-20 19:40:45 -05:00
dir , _ := os . Getwd ( )
2014-04-05 01:26:43 -04:00
viper . Set ( "WorkingDir" , dir )
2014-02-17 05:54:15 -05:00
}
2014-03-31 13:23:34 -04:00
2014-12-26 22:40:10 -05:00
if CacheDir != "" {
if helpers . FilePathSeparator != CacheDir [ len ( CacheDir ) - 1 : ] {
CacheDir = CacheDir + helpers . FilePathSeparator
}
2015-02-02 04:14:59 -05:00
isDir , err := helpers . DirExists ( CacheDir , hugofs . SourceFs )
utils . CheckErr ( err )
if isDir == false {
mkdir ( CacheDir )
}
2014-12-26 22:40:10 -05:00
viper . Set ( "CacheDir" , CacheDir )
} else {
viper . Set ( "CacheDir" , helpers . GetTempDir ( "hugo_cache" , hugofs . SourceFs ) )
}
2014-04-05 01:26:43 -04:00
if VerboseLog || Logging || ( viper . IsSet ( "LogFile" ) && viper . GetString ( "LogFile" ) != "" ) {
if viper . IsSet ( "LogFile" ) && viper . GetString ( "LogFile" ) != "" {
jww . SetLogFile ( viper . GetString ( "LogFile" ) )
2014-03-31 13:23:34 -04:00
} else {
jww . UseTempLogFile ( "hugo" )
}
} else {
jww . DiscardLogging ( )
}
2014-04-05 01:26:43 -04:00
if viper . GetBool ( "verbose" ) {
2014-05-27 18:29:55 -04:00
jww . SetStdoutThreshold ( jww . LevelInfo )
2014-03-31 13:23:34 -04:00
}
if VerboseLog {
2014-05-27 18:29:55 -04:00
jww . SetLogThreshold ( jww . LevelInfo )
2014-03-31 13:23:34 -04:00
}
2014-04-07 23:29:35 -04:00
jww . INFO . Println ( "Using config file:" , viper . ConfigFileUsed ( ) )
2015-04-28 14:39:11 -04:00
2015-07-12 13:25:43 -04:00
themeDir := helpers . GetThemeDir ( )
if themeDir != "" {
if _ , err := os . Stat ( themeDir ) ; os . IsNotExist ( err ) {
2015-12-02 05:42:53 -05:00
return newSystemError ( "Unable to find theme Directory:" , themeDir )
2015-07-12 13:25:43 -04:00
}
}
2015-09-14 11:31:39 -04:00
themeVersionMismatch , minVersion := isThemeVsHugoVersionMismatch ( )
2015-07-12 13:25:43 -04:00
2015-04-28 14:39:11 -04:00
if themeVersionMismatch {
jww . ERROR . Printf ( "Current theme does not support Hugo version %s. Minimum version required is %s\n" ,
helpers . HugoReleaseVersion ( ) , minVersion )
}
2015-12-02 05:42:53 -05:00
return nil
2013-09-29 02:09:03 -04:00
}
2015-11-09 23:31:52 -05:00
func watchConfig ( ) {
viper . WatchConfig ( )
viper . OnConfigChange ( func ( e fsnotify . Event ) {
fmt . Println ( "Config file changed:" , e . Name )
utils . CheckErr ( buildSite ( true ) )
if ! viper . GetBool ( "DisableLiveReload" ) {
// Will block forever trying to write to a channel that nobody is reading if livereload isn't initalized
livereload . ForceRefresh ( )
}
} )
}
2015-12-02 05:42:53 -05:00
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 )
2015-11-16 21:52:37 -05:00
}
2014-02-17 05:54:15 -05:00
watch := false
if len ( watches ) > 0 && watches [ 0 ] {
watch = true
}
2015-12-02 05:42:53 -05:00
if err := buildSite ( BuildWatch || watch ) ; err != nil {
return fmt . Errorf ( "Error building site: %s" , err )
}
2014-02-17 05:54:15 -05:00
if BuildWatch {
2014-04-05 01:26:43 -04:00
jww . FEEDBACK . Println ( "Watching for changes in" , helpers . AbsPathify ( viper . GetString ( "ContentDir" ) ) )
2015-01-29 16:19:12 -05:00
jww . FEEDBACK . Println ( "Press Ctrl+C to stop" )
2014-02-17 05:54:15 -05:00
utils . CheckErr ( NewWatcher ( 0 ) )
}
2015-12-02 05:42:53 -05:00
return nil
2013-09-29 02:09:03 -04:00
}
func copyStatic ( ) error {
2014-04-05 01:26:43 -04:00
publishDir := helpers . AbsPathify ( viper . GetString ( "PublishDir" ) ) + "/"
2014-04-10 08:10:12 -04:00
2015-11-16 21:53:05 -05:00
// If root, remove the second '/'
if publishDir == "//" {
publishDir = "/"
}
2014-11-01 11:57:29 -04:00
syncer := fsync . NewSyncer ( )
2014-11-05 21:13:22 -05:00
syncer . NoTimes = viper . GetBool ( "notimes" )
2014-11-01 11:57:29 -04:00
syncer . SrcFs = hugofs . SourceFs
syncer . DestFs = hugofs . DestinationFS
2015-02-11 14:24:56 -05:00
themeDir , err := helpers . GetThemeStaticDirPath ( )
if err != nil {
2015-12-01 09:53:27 -05:00
jww . WARN . Println ( err )
2015-02-11 14:24:56 -05:00
}
2014-04-10 08:10:12 -04:00
2015-10-15 03:36:27 -04:00
// Copy the theme's static directory
2015-02-14 18:30:15 -05:00
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 ) )
}
2014-04-10 08:10:12 -04:00
2015-10-15 03:36:27 -04:00
// 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
2013-09-29 02:09:03 -04:00
}
2015-03-10 18:55:23 -04:00
// getDirList provides NewWatcher() with a list of directories to watch for changes.
2013-09-29 02:09:03 -04:00
func getDirList ( ) [ ] string {
2014-02-17 05:54:15 -05:00
var a [ ] string
2015-03-12 16:44:36 -04:00
dataDir := helpers . AbsPathify ( viper . GetString ( "DataDir" ) )
2015-07-18 09:14:39 -04:00
layoutDir := helpers . AbsPathify ( viper . GetString ( "LayoutDir" ) )
2014-02-17 05:54:15 -05:00
walker := func ( path string , fi os . FileInfo , err error ) error {
if err != nil {
2015-03-12 16:44:36 -04:00
if path == dataDir && os . IsNotExist ( err ) {
jww . WARN . Println ( "Skip DataDir:" , err )
return nil
2015-07-18 09:14:39 -04:00
}
if path == layoutDir && os . IsNotExist ( err ) {
jww . WARN . Println ( "Skip LayoutDir:" , err )
return nil
2015-03-12 16:44:36 -04:00
}
2014-03-31 13:23:34 -04:00
jww . ERROR . Println ( "Walker: " , err )
2014-02-17 05:54:15 -05:00
return nil
}
2014-12-10 10:48:51 -05:00
if fi . Mode ( ) & os . ModeSymlink == os . ModeSymlink {
2015-02-17 16:21:37 -05:00
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 )
}
2014-12-10 10:48:51 -05:00
return nil
}
2014-02-17 05:54:15 -05:00
if fi . IsDir ( ) {
2015-03-10 18:55:23 -04:00
if fi . Name ( ) == ".git" ||
fi . Name ( ) == "node_modules" || fi . Name ( ) == "bower_components" {
return filepath . SkipDir
}
2014-02-17 05:54:15 -05:00
a = append ( a , path )
}
return nil
}
2015-03-12 16:44:36 -04:00
filepath . Walk ( dataDir , walker )
2014-04-05 01:26:43 -04:00
filepath . Walk ( helpers . AbsPathify ( viper . GetString ( "ContentDir" ) ) , walker )
filepath . Walk ( helpers . AbsPathify ( viper . GetString ( "LayoutDir" ) ) , walker )
filepath . Walk ( helpers . AbsPathify ( viper . GetString ( "StaticDir" ) ) , walker )
2015-02-11 14:24:56 -05:00
if helpers . ThemeSet ( ) {
2014-04-10 08:10:12 -04:00
filepath . Walk ( helpers . AbsPathify ( "themes/" + viper . GetString ( "theme" ) ) , walker )
}
2014-02-17 05:54:15 -05:00
return a
2013-09-29 02:09:03 -04:00
}
2013-10-25 18:03:14 -04:00
func buildSite ( watching ... bool ) ( err error ) {
2014-02-17 05:54:15 -05:00
startTime := time . Now ( )
2014-04-05 01:26:43 -04:00
site := & hugolib . Site { }
2014-02-17 05:54:15 -05:00
if len ( watching ) > 0 && watching [ 0 ] {
site . RunMode . Watching = true
}
err = site . Build ( )
if err != nil {
2014-08-10 12:58:10 -04:00
return err
2014-02-17 05:54:15 -05:00
}
site . Stats ( )
2014-03-31 13:23:34 -04:00
jww . FEEDBACK . Printf ( "in %v ms\n" , int ( 1000 * time . Since ( startTime ) . Seconds ( ) ) )
2014-05-16 11:48:59 -04:00
2014-02-17 05:54:15 -05:00
return nil
2013-09-29 02:09:03 -04:00
}
2013-09-30 22:38:32 -04:00
2015-03-10 11:59:55 -04:00
// NewWatcher creates a new watcher to watch filesystem events.
2013-09-30 22:38:32 -04:00
func NewWatcher ( port int ) error {
2014-02-17 05:54:15 -05:00
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 != "" {
2015-03-10 11:59:55 -04:00
_ = watcher . Add ( d )
2014-02-17 05:54:15 -05:00
}
}
go func ( ) {
for {
select {
2015-03-10 11:59:55 -04:00
case evs := <- watcher . Events :
2014-08-25 13:49:53 -04:00
jww . INFO . Println ( "File System Event:" , evs )
2014-02-17 05:54:15 -05:00
2015-03-06 12:07:50 -05:00
staticChanged := false
dynamicChanged := false
staticFilesChanged := make ( map [ string ] bool )
2014-02-17 05:54:15 -05:00
for _ , ev := range evs {
ext := filepath . Ext ( ev . Name )
2015-03-10 11:59:55 -04:00
istemp := strings . HasSuffix ( ext , "~" ) || ( ext == ".swp" ) || ( ext == ".swx" ) || ( ext == ".tmp" ) || strings . HasPrefix ( ext , ".goutputstream" )
2014-02-17 05:54:15 -05:00
if istemp {
continue
}
// renames are always followed with Create/Modify
2015-03-10 11:59:55 -04:00
if ev . Op & fsnotify . Rename == fsnotify . Rename {
2014-02-17 05:54:15 -05:00
continue
}
2015-11-18 16:59:32 -05:00
// 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
}
2015-06-28 13:27:28 -04:00
isstatic := strings . HasPrefix ( ev . Name , helpers . GetStaticDirPath ( ) ) || ( len ( helpers . GetThemesDirPath ( ) ) > 0 && strings . HasPrefix ( ev . Name , helpers . GetThemesDirPath ( ) ) )
2015-03-06 12:07:50 -05:00
staticChanged = staticChanged || isstatic
dynamicChanged = dynamicChanged || ! isstatic
2014-02-17 05:54:15 -05:00
2014-09-10 06:21:22 -04:00
if isstatic {
if staticPath , err := helpers . MakeStaticPathRelative ( ev . Name ) ; err == nil {
2015-03-06 12:07:50 -05:00
staticFilesChanged [ staticPath ] = true
2014-09-10 06:21:22 -04:00
}
}
2014-02-17 05:54:15 -05:00
// add new directory to watch list
if s , err := os . Stat ( ev . Name ) ; err == nil && s . Mode ( ) . IsDir ( ) {
2015-03-10 11:59:55 -04:00
if ev . Op & fsnotify . Create == fsnotify . Create {
watcher . Add ( ev . Name )
2014-02-17 05:54:15 -05:00
}
}
}
2015-03-06 12:07:50 -05:00
if staticChanged {
2015-04-03 16:18:16 -04:00
jww . FEEDBACK . Printf ( "Static file changed, syncing\n\n" )
2015-11-16 21:52:37 -05:00
err := copyStatic ( )
if err != nil {
fmt . Println ( err )
utils . StopOnErr ( err , fmt . Sprintf ( "Error copying static files to %s" , helpers . AbsPathify ( viper . GetString ( "PublishDir" ) ) ) )
}
2014-05-16 11:48:59 -04:00
2014-10-31 02:13:44 -04:00
if ! BuildWatch && ! viper . GetBool ( "DisableLiveReload" ) {
2014-08-25 13:49:53 -04:00
// Will block forever trying to write to a channel that nobody is reading if livereload isn't initalized
2014-09-10 06:21:22 -04:00
// force refresh when more than one file
2015-03-06 12:07:50 -05:00
if len ( staticFilesChanged ) == 1 {
for path := range staticFilesChanged {
2014-09-10 06:21:22 -04:00
livereload . RefreshPath ( path )
}
} else {
livereload . ForceRefresh ( )
}
2014-08-25 13:49:53 -04:00
}
2014-02-17 05:54:15 -05:00
}
2015-03-06 12:07:50 -05:00
if dynamicChanged {
2014-10-27 17:23:54 -04:00
fmt . Print ( "\nChange detected, rebuilding site\n" )
const layout = "2006-01-02 15:04 -0700"
fmt . Println ( time . Now ( ) . Format ( layout ) )
2015-01-09 04:37:21 -05:00
utils . CheckErr ( buildSite ( true ) )
2014-05-16 11:48:59 -04:00
2014-10-31 02:13:44 -04:00
if ! BuildWatch && ! viper . GetBool ( "DisableLiveReload" ) {
2014-08-25 13:49:53 -04:00
// Will block forever trying to write to a channel that nobody is reading if livereload isn't initalized
livereload . ForceRefresh ( )
}
2014-02-17 05:54:15 -05:00
}
2015-03-10 11:59:55 -04:00
case err := <- watcher . Errors :
2014-02-17 05:54:15 -05:00
if err != nil {
fmt . Println ( "error:" , err )
}
}
}
} ( )
if port > 0 {
2014-05-16 17:49:27 -04:00
if ! viper . GetBool ( "DisableLiveReload" ) {
livereload . Initialize ( )
http . HandleFunc ( "/livereload.js" , livereload . ServeJS )
http . HandleFunc ( "/livereload" , livereload . Handler )
}
2014-02-17 05:54:15 -05:00
go serve ( port )
}
wg . Wait ( )
return nil
2013-09-30 22:38:32 -04:00
}
2015-09-14 11:31:39 -04:00
2015-12-02 13:56:36 -05:00
// isThemeVsHugoVersionMismatch returns whether the current Hugo version is
// less than the theme's min_version.
2015-09-14 11:31:39 -04:00
func isThemeVsHugoVersionMismatch ( ) ( mismatch bool , requiredMinVersion string ) {
if ! helpers . ThemeSet ( ) {
return
}
themeDir := helpers . GetThemeDir ( )
fs := hugofs . SourceFs
path := filepath . Join ( themeDir , "theme.toml" )
exists , err := helpers . Exists ( path , fs )
if err != nil || ! exists {
return
}
f , err := fs . Open ( path )
if err != nil {
return
}
defer f . Close ( )
b , err := ioutil . ReadAll ( f )
if err != nil {
return
}
c , err := parser . HandleTOMLMetaData ( b )
if err != nil {
return
}
config := c . ( map [ string ] interface { } )
if minVersion , ok := config [ "min_version" ] ; ok {
switch minVersion . ( type ) {
case float32 :
return helpers . HugoVersionNumber < minVersion . ( float32 ) , fmt . Sprint ( minVersion )
case float64 :
return helpers . HugoVersionNumber < minVersion . ( float64 ) , fmt . Sprint ( minVersion )
default :
return
}
}
return
}