2014-04-10 08:10:12 -04:00
// Copyright © 2013-14 Steve Francia <spf@spf13.com>.
2013-07-04 11:32:55 -04:00
//
// 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 hugolib
import (
2014-01-29 17:50:31 -05:00
"bytes"
2014-09-14 07:23:03 -04:00
"errors"
2014-01-29 17:50:31 -05:00
"fmt"
"html/template"
"io"
Provide (relative) reference funcs & shortcodes.
- `.Ref` and `.RelRef` take a reference (the logical filename for a
page, including extension and/or a document fragment ID) and return
a permalink (or relative permalink) to the referenced document.
- If the reference is a page name (such as `about.md`), the page
will be discovered and the permalink will be returned: `/about/`
- If the reference is a page name with a fragment (such as
`about.md#who`), the page will be discovered and used to add the
`page.UniqueID()` to the resulting fragment and permalink:
`/about/#who:deadbeef`.
- If the reference is a fragment and `.*Ref` has been called from
a `Node` or `SiteInfo`, it will be returned as is: `#who`.
- If the reference is a fragment and `.*Ref` has been called from
a `Page`, it will be returned with the page’s unique ID:
`#who:deadbeef`.
- `.*Ref` can be called from either `Node`, `SiteInfo` (e.g.,
`Node.Site`), `Page` objects, or `ShortcodeWithPage` objects in
templates.
- `.*Ref` cannot be used in content, so two shortcodes have been
created to provide the functionality to content: `ref` and `relref`.
These are intended to be used within markup, like `[Who]({{% ref
about.md#who %}})` or `<a href="{{% ref about.md#who %}}">Who</a>`.
- There are also `ref` and `relref` template functions (used to create
the shortcodes) that expect a `Page` or `Node` object and the
reference string (e.g., `{{ relref . "about.md" }}` or `{{
"about.md" | ref . }}`). It actually looks for `.*Ref` as defined on
`Node` or `Page` objects.
- Shortcode handling had to use a *differently unique* wrapper in
`createShortcodePlaceholder` because of the way that the `ref` and
`relref` are intended to be used in content.
2014-11-24 01:15:34 -05:00
"net/url"
2014-01-29 17:50:31 -05:00
"os"
2014-08-29 13:40:21 -04:00
"strconv"
2014-01-29 17:50:31 -05:00
"strings"
2014-03-05 19:07:39 -05:00
"sync"
2014-01-29 17:50:31 -05:00
"time"
2014-03-31 13:23:34 -04:00
"bitbucket.org/pkg/inflect"
2014-04-23 02:55:43 -04:00
"github.com/spf13/cast"
2014-03-31 13:23:34 -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/source"
"github.com/spf13/hugo/target"
2014-11-20 12:32:21 -05:00
"github.com/spf13/hugo/tpl"
2014-03-31 13:23:34 -04:00
"github.com/spf13/hugo/transform"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/nitro"
2014-04-05 01:26:43 -04:00
"github.com/spf13/viper"
2013-07-04 11:32:55 -04:00
)
2013-11-05 00:28:08 -05:00
var _ = transform . AbsURL
2013-10-03 09:44:45 -04:00
var DefaultTimer * nitro . B
2013-08-07 20:21:22 -04:00
2013-10-17 23:57:25 -04:00
// Site contains all the information relevant for constructing a static
2013-09-01 00:13:04 -04:00
// site. The basic flow of information is as follows:
//
// 1. A list of Files is parsed and then converted into Pages.
//
// 2. Pages contain sections (based on the file they were generated from),
// aliases and slugs (included in a pages frontmatter) which are the
2013-11-18 04:35:56 -05:00
// various targets that will get generated. There will be canonical
// listing. The canonical path can be overruled based on a pattern.
2013-09-01 00:13:04 -04:00
//
2014-04-08 23:15:57 -04:00
// 3. Taxonomies are created via configuration and will present some aspect of
2013-09-01 00:13:04 -04:00
// the final page and typically a perm url.
//
// 4. All Pages are passed through a template based on their desired
2013-11-18 04:35:56 -05:00
// layout based on numerous different elements.
2013-09-01 00:13:04 -04:00
//
// 5. The entire collection of files is written to disk.
2013-07-04 11:32:55 -04:00
type Site struct {
2014-08-25 13:13:11 -04:00
Pages Pages
2014-10-20 20:15:33 -04:00
Files [ ] * source . File
2014-11-20 12:32:21 -05:00
Tmpl tpl . Template
2014-08-25 13:13:11 -04:00
Taxonomies TaxonomyList
Source source . Input
Sections Taxonomy
Info SiteInfo
Shortcodes map [ string ] ShortcodeFunc
Menus Menus
timer * nitro . B
2014-11-04 00:39:37 -05:00
Targets targetList
2014-08-25 13:13:11 -04:00
Completed chan bool
RunMode runmode
params map [ string ] interface { }
draftCount int
2014-08-20 11:09:35 -04:00
futureCount int
2013-07-04 11:32:55 -04:00
}
2014-11-04 00:39:37 -05:00
type targetList struct {
Page target . Output
File target . Output
Alias target . AliasPublisher
}
2013-07-04 11:32:55 -04:00
type SiteInfo struct {
2014-04-23 02:52:01 -04:00
BaseUrl template . URL
Taxonomies TaxonomyList
2014-12-09 13:33:55 -05:00
Authors AuthorList
Social SiteSocial
2014-04-23 02:52:01 -04:00
Indexes * TaxonomyList // legacy, should be identical to Taxonomies
2014-05-13 10:46:04 -04:00
Sections Taxonomy
2014-09-05 06:57:32 -04:00
Pages * Pages
2014-10-20 20:15:33 -04:00
Files [ ] * source . File
2014-09-05 06:57:32 -04:00
Recent * Pages // legacy, should be identical to Pages
2014-04-23 02:59:19 -04:00
Menus * Menus
2014-04-23 02:52:01 -04:00
Title string
2014-10-31 23:58:14 -04:00
Author map [ string ] interface { }
2014-04-23 02:52:01 -04:00
LanguageCode string
DisqusShortname string
Copyright string
LastChange time . Time
Permalinks PermalinkOverrides
Params map [ string ] interface { }
2014-09-10 22:34:00 -04:00
BuildDrafts bool
2014-04-23 02:52:01 -04:00
}
2014-04-23 02:55:43 -04:00
2014-12-09 13:33:55 -05:00
// SiteSocial is a place to put social details on a site level. These are the
// standard keys that themes will expect to have available, but can be
// expanded to any others on a per site basis
// github
// facebook
// facebook_admin
// twitter
// twitter_domain
// googleplus
// pinterest
// instagram
// youtube
// linkedin
type SiteSocial map [ string ] string
2014-04-23 02:55:43 -04:00
func ( s * SiteInfo ) GetParam ( key string ) interface { } {
v := s . Params [ strings . ToLower ( key ) ]
if v == nil {
return nil
}
switch v . ( type ) {
case bool :
return cast . ToBool ( v )
case string :
return cast . ToString ( v )
case int64 , int32 , int16 , int8 , int :
return cast . ToInt ( v )
case float64 , float32 :
return cast . ToFloat64 ( v )
case time . Time :
return cast . ToTime ( v )
case [ ] string :
return v
}
return nil
2013-07-04 11:32:55 -04:00
}
Provide (relative) reference funcs & shortcodes.
- `.Ref` and `.RelRef` take a reference (the logical filename for a
page, including extension and/or a document fragment ID) and return
a permalink (or relative permalink) to the referenced document.
- If the reference is a page name (such as `about.md`), the page
will be discovered and the permalink will be returned: `/about/`
- If the reference is a page name with a fragment (such as
`about.md#who`), the page will be discovered and used to add the
`page.UniqueID()` to the resulting fragment and permalink:
`/about/#who:deadbeef`.
- If the reference is a fragment and `.*Ref` has been called from
a `Node` or `SiteInfo`, it will be returned as is: `#who`.
- If the reference is a fragment and `.*Ref` has been called from
a `Page`, it will be returned with the page’s unique ID:
`#who:deadbeef`.
- `.*Ref` can be called from either `Node`, `SiteInfo` (e.g.,
`Node.Site`), `Page` objects, or `ShortcodeWithPage` objects in
templates.
- `.*Ref` cannot be used in content, so two shortcodes have been
created to provide the functionality to content: `ref` and `relref`.
These are intended to be used within markup, like `[Who]({{% ref
about.md#who %}})` or `<a href="{{% ref about.md#who %}}">Who</a>`.
- There are also `ref` and `relref` template functions (used to create
the shortcodes) that expect a `Page` or `Node` object and the
reference string (e.g., `{{ relref . "about.md" }}` or `{{
"about.md" | ref . }}`). It actually looks for `.*Ref` as defined on
`Node` or `Page` objects.
- Shortcode handling had to use a *differently unique* wrapper in
`createShortcodePlaceholder` because of the way that the `ref` and
`relref` are intended to be used in content.
2014-11-24 01:15:34 -05:00
func ( s * SiteInfo ) refLink ( ref string , page * Page , relative bool ) ( string , error ) {
var refUrl * url . URL
var err error
refUrl , err = url . Parse ( ref )
if err != nil {
return "" , err
}
var target * Page = nil
var link string = ""
if refUrl . Path != "" {
var target * Page
for _ , page := range [ ] * Page ( * s . Pages ) {
if page . Source . Path ( ) == refUrl . Path || page . Source . LogicalName ( ) == refUrl . Path {
target = page
break
}
}
if target == nil {
return "" , errors . New ( fmt . Sprintf ( "No page found with path or logical name \"%s\".\n" , refUrl . Path ) )
}
if relative {
link , err = target . RelPermalink ( )
} else {
link , err = target . Permalink ( )
}
if err != nil {
return "" , err
}
}
if refUrl . Fragment != "" {
link = link + "#" + refUrl . Fragment
if refUrl . Path != "" {
link = link + ":" + target . UniqueId ( )
} else if page != nil {
link = link + ":" + page . UniqueId ( )
}
}
return link , nil
}
func ( s * SiteInfo ) Ref ( ref string , page * Page ) ( string , error ) {
return s . refLink ( ref , page , false )
}
func ( s * SiteInfo ) RelRef ( ref string , page * Page ) ( string , error ) {
return s . refLink ( ref , page , true )
}
2013-10-25 18:03:14 -04:00
type runmode struct {
2014-01-29 17:50:31 -05:00
Watching bool
2013-10-25 18:03:14 -04:00
}
func ( s * Site ) Running ( ) bool {
2014-01-29 17:50:31 -05:00
return s . RunMode . Watching
2013-10-25 18:03:14 -04:00
}
2013-10-03 09:44:45 -04:00
func init ( ) {
2014-01-29 17:50:31 -05:00
DefaultTimer = nitro . Initalize ( )
2013-10-03 09:44:45 -04:00
}
2013-08-07 20:21:22 -04:00
func ( s * Site ) timerStep ( step string ) {
2014-01-29 17:50:31 -05:00
if s . timer == nil {
s . timer = DefaultTimer
}
s . timer . Step ( step )
2013-07-04 11:32:55 -04:00
}
2013-08-31 20:29:19 -04:00
func ( s * Site ) Build ( ) ( err error ) {
2014-01-29 17:50:31 -05:00
if err = s . Process ( ) ; err != nil {
return
}
if err = s . Render ( ) ; err != nil {
2014-03-31 13:23:34 -04:00
jww . ERROR . Printf ( "Error rendering site: %s\nAvailable templates:\n" , err )
2014-01-29 17:50:31 -05:00
for _ , template := range s . Tmpl . Templates ( ) {
2014-03-31 13:23:34 -04:00
jww . ERROR . Printf ( "\t%s\n" , template . Name ( ) )
2014-01-29 17:50:31 -05:00
}
return
}
return nil
2013-07-04 11:32:55 -04:00
}
2013-08-31 20:29:19 -04:00
func ( s * Site ) Analyze ( ) {
2014-01-29 17:50:31 -05:00
s . Process ( )
s . ShowPlan ( os . Stdout )
2013-07-04 11:32:55 -04:00
}
2013-08-31 20:47:21 -04:00
func ( s * Site ) prepTemplates ( ) {
2014-11-24 17:10:38 -05:00
s . Tmpl = tpl . InitializeT ( )
2014-01-29 17:50:31 -05:00
s . Tmpl . LoadTemplates ( s . absLayoutDir ( ) )
2014-04-10 08:10:12 -04:00
if s . hasTheme ( ) {
s . Tmpl . LoadTemplatesWithPrefix ( s . absThemeDir ( ) + "/layouts" , "theme" )
}
2013-08-31 20:47:21 -04:00
}
func ( s * Site ) addTemplate ( name , data string ) error {
2014-01-29 17:50:31 -05:00
return s . Tmpl . AddTemplate ( name , data )
2013-08-31 20:47:21 -04:00
}
2013-08-31 20:29:19 -04:00
func ( s * Site ) Process ( ) ( err error ) {
2014-01-29 17:50:31 -05:00
if err = s . initialize ( ) ; err != nil {
return
}
s . prepTemplates ( )
s . timerStep ( "initialize & template prep" )
if err = s . CreatePages ( ) ; err != nil {
return
}
s . setupPrevNext ( )
s . timerStep ( "import pages" )
if err = s . BuildSiteMeta ( ) ; err != nil {
return
}
2014-04-08 23:15:57 -04:00
s . timerStep ( "build taxonomies" )
2014-01-29 17:50:31 -05:00
return
2013-07-04 11:32:55 -04:00
}
2013-09-20 20:03:43 -04:00
func ( s * Site ) setupPrevNext ( ) {
2014-01-29 17:50:31 -05:00
for i , page := range s . Pages {
if i < len ( s . Pages ) - 1 {
page . Next = s . Pages [ i + 1 ]
}
2013-09-20 20:03:43 -04:00
2014-01-29 17:50:31 -05:00
if i > 0 {
page . Prev = s . Pages [ i - 1 ]
}
}
2013-09-20 20:03:43 -04:00
}
2013-08-31 20:29:19 -04:00
func ( s * Site ) Render ( ) ( err error ) {
2014-01-29 17:50:31 -05:00
if err = s . RenderAliases ( ) ; err != nil {
return
}
s . timerStep ( "render and write aliases" )
2014-04-08 23:15:57 -04:00
if err = s . RenderTaxonomiesLists ( ) ; err != nil {
2014-01-29 17:50:31 -05:00
return
}
2014-04-08 23:15:57 -04:00
s . timerStep ( "render and write taxonomies" )
s . RenderListsOfTaxonomyTerms ( )
s . timerStep ( "render & write taxonomy lists" )
if err = s . RenderSectionLists ( ) ; err != nil {
2014-01-29 17:50:31 -05:00
return
}
s . timerStep ( "render and write lists" )
if err = s . RenderPages ( ) ; err != nil {
return
}
s . timerStep ( "render and write pages" )
if err = s . RenderHomePage ( ) ; err != nil {
return
}
s . timerStep ( "render and write homepage" )
2014-05-06 06:50:23 -04:00
if err = s . RenderSitemap ( ) ; err != nil {
return
}
s . timerStep ( "render and write Sitemap" )
2014-01-29 17:50:31 -05:00
return
2013-07-04 11:32:55 -04:00
}
2013-08-31 20:29:19 -04:00
func ( s * Site ) checkDescriptions ( ) {
2014-01-29 17:50:31 -05:00
for _ , p := range s . Pages {
if len ( p . Description ) < 60 {
2014-10-16 20:20:09 -04:00
jww . FEEDBACK . Println ( p . Source . Path ( ) + " " )
2014-01-29 17:50:31 -05:00
}
}
2013-07-04 11:32:55 -04:00
}
2014-05-01 13:20:58 -04:00
func ( s * Site ) Initialise ( ) ( err error ) {
return s . initialize ( )
}
2013-09-25 00:24:49 -04:00
func ( s * Site ) initialize ( ) ( err error ) {
2014-01-29 17:50:31 -05:00
if err = s . checkDirectories ( ) ; err != nil {
return err
}
2013-07-04 11:32:55 -04:00
2014-04-05 01:26:43 -04:00
staticDir := helpers . AbsPathify ( viper . GetString ( "StaticDir" ) + "/" )
2013-08-15 14:58:34 -04:00
2014-01-29 17:50:31 -05:00
s . Source = & source . Filesystem {
AvoidPaths : [ ] string { staticDir } ,
Base : s . absContentDir ( ) ,
}
2013-07-04 11:32:55 -04:00
2014-04-23 02:59:19 -04:00
s . Menus = Menus { }
2014-01-29 17:50:31 -05:00
s . initializeSiteInfo ( )
2013-09-12 19:17:53 -04:00
2014-01-29 17:50:31 -05:00
s . Shortcodes = make ( map [ string ] ShortcodeFunc )
return
2013-09-12 19:17:53 -04:00
}
func ( s * Site ) initializeSiteInfo ( ) {
2014-05-28 18:37:59 -04:00
params := viper . GetStringMap ( "Params" )
2014-04-05 01:26:43 -04:00
2014-05-10 20:27:49 -04:00
permalinks := make ( PermalinkOverrides )
for k , v := range viper . GetStringMapString ( "Permalinks" ) {
permalinks [ k ] = PathPattern ( v )
2014-04-05 01:26:43 -04:00
}
2014-04-24 12:18:47 -04:00
s . Info = SiteInfo {
2014-04-23 02:52:01 -04:00
BaseUrl : template . URL ( helpers . SanitizeUrl ( viper . GetString ( "BaseUrl" ) ) ) ,
Title : viper . GetString ( "Title" ) ,
2014-10-31 23:58:14 -04:00
Author : viper . GetStringMap ( "author" ) ,
2014-04-23 02:52:01 -04:00
LanguageCode : viper . GetString ( "languagecode" ) ,
Copyright : viper . GetString ( "copyright" ) ,
DisqusShortname : viper . GetString ( "DisqusShortname" ) ,
2014-09-10 22:34:00 -04:00
BuildDrafts : viper . GetBool ( "BuildDrafts" ) ,
2014-09-05 06:57:32 -04:00
Pages : & s . Pages ,
2014-04-23 02:52:01 -04:00
Recent : & s . Pages ,
2014-04-23 02:59:19 -04:00
Menus : & s . Menus ,
2014-04-23 02:52:01 -04:00
Params : params ,
Permalinks : permalinks ,
2014-01-29 17:50:31 -05:00
}
2013-07-04 11:32:55 -04:00
}
2014-04-10 08:10:12 -04:00
func ( s * Site ) hasTheme ( ) bool {
return viper . GetString ( "theme" ) != ""
}
func ( s * Site ) absThemeDir ( ) string {
return helpers . AbsPathify ( "themes/" + viper . GetString ( "theme" ) )
}
2013-08-07 20:21:22 -04:00
func ( s * Site ) absLayoutDir ( ) string {
2014-04-05 01:26:43 -04:00
return helpers . AbsPathify ( viper . GetString ( "LayoutDir" ) )
2013-08-07 20:21:22 -04:00
}
func ( s * Site ) absContentDir ( ) string {
2014-04-05 01:26:43 -04:00
return helpers . AbsPathify ( viper . GetString ( "ContentDir" ) )
2013-08-07 20:21:22 -04:00
}
func ( s * Site ) absPublishDir ( ) string {
2014-04-05 01:26:43 -04:00
return helpers . AbsPathify ( viper . GetString ( "PublishDir" ) )
2013-08-07 20:21:22 -04:00
}
2013-09-25 00:24:49 -04:00
func ( s * Site ) checkDirectories ( ) ( err error ) {
2014-11-01 11:57:29 -04:00
if b , _ := helpers . DirExists ( s . absContentDir ( ) , hugofs . SourceFs ) ; ! b {
2014-01-29 17:50:31 -05:00
return fmt . Errorf ( "No source directory found, expecting to find it at " + s . absContentDir ( ) )
}
return
2013-07-04 11:32:55 -04:00
}
2014-09-05 06:57:32 -04:00
type pageResult struct {
2014-08-29 13:40:21 -04:00
page * Page
err error
}
func ( s * Site ) CreatePages ( ) error {
2014-01-29 17:50:31 -05:00
if s . Source == nil {
panic ( fmt . Sprintf ( "s.Source not set %s" , s . absContentDir ( ) ) )
}
if len ( s . Source . Files ( ) ) < 1 {
2014-08-29 13:40:21 -04:00
return nil
2014-01-29 17:50:31 -05:00
}
2014-08-29 13:40:21 -04:00
files := s . Source . Files ( )
2014-01-29 17:50:31 -05:00
2014-10-20 17:42:16 -04:00
results := make ( chan HandledResult )
2014-09-05 06:57:32 -04:00
filechan := make ( chan * source . File )
2014-01-29 17:50:31 -05:00
2014-08-29 13:40:21 -04:00
procs := getGoMaxProcs ( )
2014-03-05 19:07:39 -05:00
2014-08-29 13:40:21 -04:00
wg := & sync . WaitGroup { }
2014-03-05 19:07:39 -05:00
2014-11-24 17:51:47 -05:00
wg . Add ( procs * 4 )
2014-08-30 01:15:20 -04:00
for i := 0 ; i < procs * 4 ; i ++ {
2014-10-16 20:20:09 -04:00
go sourceReader ( s , filechan , results , wg )
2014-08-29 13:40:21 -04:00
}
2014-03-05 19:07:39 -05:00
2014-08-29 13:40:21 -04:00
errs := make ( chan error )
2014-08-25 13:13:11 -04:00
2014-08-29 13:40:21 -04:00
// we can only have exactly one result collator, since it makes changes that
// must be synchronized.
2014-09-05 06:57:32 -04:00
go readCollator ( s , results , errs )
2014-08-20 11:09:35 -04:00
2014-09-05 06:57:32 -04:00
for _ , file := range files {
filechan <- file
2014-01-29 17:50:31 -05:00
}
2014-09-05 06:57:32 -04:00
close ( filechan )
2014-08-29 13:40:21 -04:00
2014-03-05 19:07:39 -05:00
wg . Wait ( )
2014-08-29 13:40:21 -04:00
close ( results )
2014-09-05 06:57:32 -04:00
readErrs := <- errs
2014-10-20 17:42:16 -04:00
results = make ( chan HandledResult )
2014-11-04 00:36:05 -05:00
pageChan := make ( chan * Page )
fileConvChan := make ( chan * source . File )
2014-09-05 06:57:32 -04:00
wg = & sync . WaitGroup { }
2014-11-24 17:51:47 -05:00
wg . Add ( 2 * procs * 4 )
2014-10-20 20:15:33 -04:00
for i := 0 ; i < procs * 4 ; i ++ {
2014-11-04 00:36:05 -05:00
go fileConverter ( s , fileConvChan , results , wg )
go pageConverter ( s , pageChan , results , wg )
2014-09-05 06:57:32 -04:00
}
2014-09-14 07:01:40 -04:00
go converterCollator ( s , results , errs )
2014-09-05 06:57:32 -04:00
for _ , p := range s . Pages {
2014-11-04 00:36:05 -05:00
pageChan <- p
2014-09-05 06:57:32 -04:00
}
2014-10-20 20:15:33 -04:00
for _ , f := range s . Files {
2014-11-04 00:36:05 -05:00
fileConvChan <- f
2014-10-20 20:15:33 -04:00
}
2014-11-04 00:36:05 -05:00
close ( pageChan )
close ( fileConvChan )
2014-09-05 06:57:32 -04:00
wg . Wait ( )
close ( results )
renderErrs := <- errs
if renderErrs == nil && readErrs == nil {
return nil
}
if renderErrs == nil {
return readErrs
}
if readErrs == nil {
return renderErrs
}
return fmt . Errorf ( "%s\n%s" , readErrs , renderErrs )
2014-08-29 13:40:21 -04:00
}
2014-10-20 17:42:16 -04:00
func sourceReader ( s * Site , files <- chan * source . File , results chan <- HandledResult , wg * sync . WaitGroup ) {
2014-09-14 07:01:40 -04:00
defer wg . Done ( )
2014-09-05 06:57:32 -04:00
for file := range files {
2014-11-20 12:39:09 -05:00
h := NewMetaHandler ( file . Extension ( ) )
2014-10-17 16:57:48 -04:00
if h != nil {
2014-10-20 17:51:53 -04:00
h . Read ( file , s , results )
2014-10-17 16:57:48 -04:00
} else {
jww . ERROR . Println ( "Unsupported File Type" , file . Path ( ) )
}
2014-09-05 06:57:32 -04:00
}
}
2014-08-29 13:40:21 -04:00
2014-10-20 17:42:16 -04:00
func pageConverter ( s * Site , pages <- chan * Page , results HandleResults , wg * sync . WaitGroup ) {
2014-09-14 07:01:40 -04:00
defer wg . Done ( )
2014-09-05 06:57:32 -04:00
for page := range pages {
2014-11-20 12:39:09 -05:00
var h * MetaHandle
2014-11-04 00:44:30 -05:00
if page . Markup != "" {
2014-11-20 12:39:09 -05:00
h = NewMetaHandler ( page . Markup )
2014-11-04 00:44:30 -05:00
} else {
2014-11-20 12:39:09 -05:00
h = NewMetaHandler ( page . File . Extension ( ) )
2014-11-04 00:44:30 -05:00
}
2014-10-20 20:15:33 -04:00
if h != nil {
h . Convert ( page , s , results )
}
}
}
2014-08-29 13:40:21 -04:00
2014-10-20 20:15:33 -04:00
func fileConverter ( s * Site , files <- chan * source . File , results HandleResults , wg * sync . WaitGroup ) {
defer wg . Done ( )
for file := range files {
2014-11-20 12:39:09 -05:00
h := NewMetaHandler ( file . Extension ( ) )
2014-10-20 20:15:33 -04:00
if h != nil {
h . Convert ( file , s , results )
2014-08-29 13:40:21 -04:00
}
}
}
2014-10-20 17:42:16 -04:00
func converterCollator ( s * Site , results <- chan HandledResult , errs chan <- error ) {
2014-09-05 06:57:32 -04:00
errMsgs := [ ] string { }
for r := range results {
if r . err != nil {
errMsgs = append ( errMsgs , r . err . Error ( ) )
continue
}
}
if len ( errMsgs ) == 0 {
errs <- nil
return
}
errs <- fmt . Errorf ( "Errors rendering pages: %s" , strings . Join ( errMsgs , "\n" ) )
}
2014-10-20 17:42:16 -04:00
func readCollator ( s * Site , results <- chan HandledResult , errs chan <- error ) {
2014-08-29 13:40:21 -04:00
errMsgs := [ ] string { }
for r := range results {
if r . err != nil {
2014-11-20 12:39:09 -05:00
errMsgs = append ( errMsgs , r . Error ( ) )
2014-08-29 13:40:21 -04:00
continue
}
2014-10-20 20:15:33 -04:00
// !page == file
if r . page == nil {
s . Files = append ( s . Files , r . file )
} else {
if r . page . ShouldBuild ( ) {
s . Pages = append ( s . Pages , r . page )
}
2014-08-29 13:40:21 -04:00
2014-10-20 20:15:33 -04:00
if r . page . IsDraft ( ) {
s . draftCount += 1
}
2014-08-29 13:40:21 -04:00
2014-10-20 20:15:33 -04:00
if r . page . IsFuture ( ) {
s . futureCount += 1
}
2014-08-29 13:40:21 -04:00
}
}
2014-10-20 20:15:33 -04:00
2014-01-29 17:50:31 -05:00
s . Pages . Sort ( )
2014-08-29 13:40:21 -04:00
if len ( errMsgs ) == 0 {
errs <- nil
2014-09-05 06:57:32 -04:00
return
2014-08-29 13:40:21 -04:00
}
2014-09-05 06:57:32 -04:00
errs <- fmt . Errorf ( "Errors reading pages: %s" , strings . Join ( errMsgs , "\n" ) )
2013-07-04 11:32:55 -04:00
}
2013-08-01 14:55:18 -04:00
func ( s * Site ) BuildSiteMeta ( ) ( err error ) {
2014-04-23 02:59:19 -04:00
s . assembleMenus ( )
if len ( s . Pages ) == 0 {
return
}
s . assembleTaxonomies ( )
s . assembleSections ( )
s . Info . LastChange = s . Pages [ 0 ] . Date
return
}
2014-04-24 18:11:08 -04:00
func ( s * Site ) getMenusFromConfig ( ) Menus {
ret := Menus { }
if menus := viper . GetStringMap ( "menu" ) ; menus != nil {
for name , menu := range menus {
m , err := cast . ToSliceE ( menu )
if err != nil {
jww . ERROR . Printf ( "unable to process menus in site config\n" )
jww . ERROR . Println ( err )
} else {
for _ , entry := range m {
jww . DEBUG . Printf ( "found menu: %q, in site config\n" , name )
menuEntry := MenuEntry { Menu : name }
ime , err := cast . ToStringMapE ( entry )
if err != nil {
jww . ERROR . Printf ( "unable to process menus in site config\n" )
jww . ERROR . Println ( err )
}
menuEntry . MarshallMap ( ime )
2014-10-18 14:25:10 -04:00
if strings . HasPrefix ( menuEntry . Url , "/" ) {
2014-12-10 23:33:40 -05:00
// make it absolute so it matches the nodes
menuEntry . Url = s . permalinkStr ( menuEntry . Url )
2014-10-18 14:25:10 -04:00
}
2014-04-24 18:11:08 -04:00
if ret [ name ] == nil {
ret [ name ] = & Menu { }
}
* ret [ name ] = ret [ name ] . Add ( & menuEntry )
}
}
}
return ret
}
return ret
}
2014-04-23 02:59:19 -04:00
func ( s * Site ) assembleMenus ( ) {
type twoD struct {
MenuName , EntryName string
}
flat := map [ twoD ] * MenuEntry { }
children := map [ twoD ] Menu { }
2014-04-24 18:11:08 -04:00
menuConfig := s . getMenusFromConfig ( )
for name , menu := range menuConfig {
for _ , me := range * menu {
2014-05-14 18:01:13 -04:00
flat [ twoD { name , me . KeyName ( ) } ] = me
2014-04-24 18:11:08 -04:00
}
}
2014-04-23 02:59:19 -04:00
//creating flat hash
for _ , p := range s . Pages {
for name , me := range p . Menus ( ) {
2014-05-14 18:01:13 -04:00
if _ , ok := flat [ twoD { name , me . KeyName ( ) } ] ; ok {
jww . ERROR . Printf ( "Two or more menu items have the same name/identifier in %q Menu. Identified as %q.\n Rename or set a unique identifier. \n" , name , me . KeyName ( ) )
}
flat [ twoD { name , me . KeyName ( ) } ] = me
2014-04-23 02:59:19 -04:00
}
}
// Create Children Menus First
for _ , e := range flat {
if e . Parent != "" {
children [ twoD { e . Menu , e . Parent } ] = children [ twoD { e . Menu , e . Parent } ] . Add ( e )
}
}
// Placing Children in Parents (in flat)
for p , childmenu := range children {
_ , ok := flat [ twoD { p . MenuName , p . EntryName } ]
if ! ok {
// if parent does not exist, create one without a url
flat [ twoD { p . MenuName , p . EntryName } ] = & MenuEntry { Name : p . EntryName , Url : "" }
}
flat [ twoD { p . MenuName , p . EntryName } ] . Children = childmenu
}
// Assembling Top Level of Tree
for menu , e := range flat {
if e . Parent == "" {
_ , ok := s . Menus [ menu . MenuName ]
if ! ok {
s . Menus [ menu . MenuName ] = & Menu { }
}
* s . Menus [ menu . MenuName ] = s . Menus [ menu . MenuName ] . Add ( e )
}
}
}
func ( s * Site ) assembleTaxonomies ( ) {
2014-04-08 23:15:57 -04:00
s . Taxonomies = make ( TaxonomyList )
s . Sections = make ( Taxonomy )
2014-01-29 17:50:31 -05:00
2014-04-08 23:15:57 -04:00
taxonomies := viper . GetStringMapString ( "Taxonomies" )
jww . INFO . Printf ( "found taxonomies: %#v\n" , taxonomies )
2014-04-07 23:29:35 -04:00
2014-04-08 23:15:57 -04:00
for _ , plural := range taxonomies {
s . Taxonomies [ plural ] = make ( Taxonomy )
2014-01-29 17:50:31 -05:00
for _ , p := range s . Pages {
vals := p . GetParam ( plural )
weight := p . GetParam ( plural + "_weight" )
if weight == nil {
weight = 0
}
if vals != nil {
2014-09-05 09:29:01 -04:00
if v , ok := vals . ( [ ] string ) ; ok {
2014-01-29 17:50:31 -05:00
for _ , idx := range v {
x := WeightedPage { weight . ( int ) , p }
2014-04-08 23:15:57 -04:00
s . Taxonomies [ plural ] . Add ( idx , x )
2014-01-29 17:50:31 -05:00
}
2014-09-05 09:29:01 -04:00
} else if v , ok := vals . ( string ) ; ok {
x := WeightedPage { weight . ( int ) , p }
s . Taxonomies [ plural ] . Add ( v , x )
2014-01-29 17:50:31 -05:00
} else {
2014-10-16 20:20:09 -04:00
jww . ERROR . Printf ( "Invalid %s in %s\n" , plural , p . File . Path ( ) )
2014-01-29 17:50:31 -05:00
}
}
}
2014-04-08 23:15:57 -04:00
for k := range s . Taxonomies [ plural ] {
s . Taxonomies [ plural ] [ k ] . Sort ( )
2014-01-29 17:50:31 -05:00
}
}
2014-04-23 02:59:19 -04:00
s . Info . Taxonomies = s . Taxonomies
s . Info . Indexes = & s . Taxonomies
2014-05-13 10:46:04 -04:00
s . Info . Sections = s . Sections
2014-04-23 02:59:19 -04:00
}
func ( s * Site ) assembleSections ( ) {
2014-01-29 17:50:31 -05:00
for i , p := range s . Pages {
2014-10-16 20:20:09 -04:00
s . Sections . Add ( p . Section ( ) , WeightedPage { s . Pages [ i ] . Weight , s . Pages [ i ] } )
2014-01-29 17:50:31 -05:00
}
for k := range s . Sections {
s . Sections [ k ] . Sort ( )
}
2013-07-04 11:32:55 -04:00
}
2014-04-08 23:15:57 -04:00
func ( s * Site ) possibleTaxonomies ( ) ( taxonomies [ ] string ) {
2014-01-29 17:50:31 -05:00
for _ , p := range s . Pages {
for k := range p . Params {
2014-10-20 20:15:33 -04:00
if ! helpers . InStringArray ( taxonomies , k ) {
2014-04-08 23:15:57 -04:00
taxonomies = append ( taxonomies , k )
2014-01-29 17:50:31 -05:00
}
}
}
return
2013-08-13 13:46:05 -04:00
}
2014-04-08 23:15:57 -04:00
// Render shell pages that simply have a redirect in the header
2013-08-10 10:35:34 -04:00
func ( s * Site ) RenderAliases ( ) error {
2014-01-29 17:50:31 -05:00
for _ , p := range s . Pages {
for _ , a := range p . Aliases {
plink , err := p . Permalink ( )
if err != nil {
return err
}
2014-11-04 00:39:37 -05:00
if err := s . WriteDestAlias ( a , template . HTML ( plink ) ) ; err != nil {
2014-01-29 17:50:31 -05:00
return err
}
}
}
return nil
2013-08-10 10:35:34 -04:00
}
2014-04-08 23:15:57 -04:00
// Render pages each corresponding to a markdown file
2014-09-14 07:23:03 -04:00
func ( s * Site ) RenderPages ( ) error {
2014-09-14 07:01:40 -04:00
results := make ( chan error )
pages := make ( chan * Page )
procs := getGoMaxProcs ( )
wg := & sync . WaitGroup { }
for i := 0 ; i < procs * 4 ; i ++ {
2014-03-05 19:07:39 -05:00
wg . Add ( 1 )
2014-09-14 07:01:40 -04:00
go pageRenderer ( s , pages , results , wg )
}
errs := make ( chan error )
2014-09-14 07:23:03 -04:00
go errorCollator ( results , errs )
2014-01-29 17:50:31 -05:00
2014-09-14 07:01:40 -04:00
for _ , page := range s . Pages {
pages <- page
2014-03-05 19:07:39 -05:00
}
2014-09-14 07:01:40 -04:00
close ( pages )
2014-03-05 19:07:39 -05:00
wg . Wait ( )
2014-09-14 07:01:40 -04:00
close ( results )
2014-09-14 07:23:03 -04:00
err := <- errs
if err != nil {
return fmt . Errorf ( "Error(s) rendering pages: %s" , err )
}
return nil
2014-09-14 07:01:40 -04:00
}
func pageRenderer ( s * Site , pages <- chan * Page , results chan <- error , wg * sync . WaitGroup ) {
defer wg . Done ( )
for p := range pages {
var layouts [ ] string
if ! p . IsRenderable ( ) {
self := "__" + p . TargetPath ( )
_ , err := s . Tmpl . New ( self ) . Parse ( string ( p . Content ) )
if err != nil {
results <- err
continue
}
layouts = append ( layouts , self )
} else {
layouts = append ( layouts , p . Layout ( ) ... )
layouts = append ( layouts , "_default/single.html" )
}
2014-11-04 00:39:37 -05:00
b , err := s . renderPage ( "page " + p . FullFilePath ( ) , p , s . appendThemeTemplates ( layouts ) ... )
if err != nil {
results <- err
} else {
results <- s . WriteDestPage ( p . TargetPath ( ) , b )
}
2014-01-29 17:50:31 -05:00
}
2014-09-14 07:01:40 -04:00
}
2014-09-14 07:23:03 -04:00
func errorCollator ( results <- chan error , errs chan <- error ) {
2014-09-14 07:01:40 -04:00
errMsgs := [ ] string { }
for err := range results {
if err != nil {
errMsgs = append ( errMsgs , err . Error ( ) )
}
}
if len ( errMsgs ) == 0 {
errs <- nil
2014-09-14 07:23:03 -04:00
} else {
errs <- errors . New ( strings . Join ( errMsgs , "\n" ) )
2014-09-14 07:01:40 -04:00
}
2014-09-14 07:23:03 -04:00
close ( errs )
2013-07-04 11:32:55 -04:00
}
2014-04-10 08:10:12 -04:00
func ( s * Site ) appendThemeTemplates ( in [ ] string ) [ ] string {
if s . hasTheme ( ) {
out := [ ] string { }
// First place all non internal templates
for _ , t := range in {
2014-11-14 12:14:52 -05:00
if ! strings . HasPrefix ( t , "_internal/" ) {
2014-04-10 08:10:12 -04:00
out = append ( out , t )
}
}
// Then place theme templates with the same names
for _ , t := range in {
2014-11-14 12:14:52 -05:00
if ! strings . HasPrefix ( t , "_internal/" ) {
2014-04-10 08:10:12 -04:00
out = append ( out , "theme/" + t )
}
}
2014-11-14 12:14:52 -05:00
2014-04-10 08:10:12 -04:00
// Lastly place internal templates
for _ , t := range in {
2014-11-14 12:14:52 -05:00
if strings . HasPrefix ( t , "_internal/" ) {
out = append ( out , t )
2014-04-10 08:10:12 -04:00
}
}
return out
} else {
return in
}
}
2014-09-14 07:23:03 -04:00
type taxRenderInfo struct {
key string
pages WeightedPages
singular string
plural string
}
2014-04-08 23:15:57 -04:00
// Render the listing pages based on the meta data
// each unique term within a taxonomy will have a page created
2014-09-14 07:23:03 -04:00
func ( s * Site ) RenderTaxonomiesLists ( ) error {
wg := & sync . WaitGroup { }
2014-03-05 19:07:39 -05:00
2014-09-14 07:23:03 -04:00
taxes := make ( chan taxRenderInfo )
results := make ( chan error )
2014-04-07 23:29:35 -04:00
2014-09-14 07:23:03 -04:00
procs := getGoMaxProcs ( )
for i := 0 ; i < procs * 4 ; i ++ {
wg . Add ( 1 )
go taxonomyRenderer ( s , taxes , results , wg )
}
errs := make ( chan error )
go errorCollator ( results , errs )
taxonomies := viper . GetStringMapString ( "Taxonomies" )
for singular , plural := range taxonomies {
for key , pages := range s . Taxonomies [ plural ] {
taxes <- taxRenderInfo { key , pages , singular , plural }
2014-01-29 17:50:31 -05:00
}
}
2014-09-14 07:23:03 -04:00
close ( taxes )
2014-03-05 19:07:39 -05:00
wg . Wait ( )
2014-09-14 07:23:03 -04:00
close ( results )
err := <- errs
if err != nil {
return fmt . Errorf ( "Error(s) rendering taxonomies: %s" , err )
}
2014-01-29 17:50:31 -05:00
return nil
2013-07-04 11:32:55 -04:00
}
2014-10-18 14:25:10 -04:00
func ( s * Site ) newTaxonomyNode ( t taxRenderInfo ) ( * Node , string ) {
base := t . plural + "/" + t . key
n := s . NewNode ( )
n . Title = strings . Replace ( strings . Title ( t . key ) , "-" , " " , - 1 )
s . setUrls ( n , base )
if len ( t . pages ) > 0 {
n . Date = t . pages [ 0 ] . Page . Date
}
n . Data [ t . singular ] = t . pages
2014-11-01 23:44:21 -04:00
n . Data [ "Singular" ] = t . singular
n . Data [ "Plural" ] = t . plural
2014-10-18 14:25:10 -04:00
n . Data [ "Pages" ] = t . pages . Pages ( )
return n , base
}
2014-09-14 07:23:03 -04:00
func taxonomyRenderer ( s * Site , taxes <- chan taxRenderInfo , results chan <- error , wg * sync . WaitGroup ) {
defer wg . Done ( )
for t := range taxes {
2014-10-18 14:25:10 -04:00
n , base := s . newTaxonomyNode ( t )
2014-09-14 07:23:03 -04:00
layouts := [ ] string { "taxonomy/" + t . singular + ".html" , "indexes/" + t . singular + ".html" , "_default/taxonomy.html" , "_default/list.html" }
2014-11-04 00:39:37 -05:00
b , err := s . renderPage ( "taxononomy " + t . singular , n , s . appendThemeTemplates ( layouts ) ... )
2014-09-14 07:23:03 -04:00
if err != nil {
results <- err
continue
2014-11-04 00:39:37 -05:00
} else {
err := s . WriteDestPage ( base + ".html" , b )
if err != nil {
results <- err
}
2014-09-14 07:23:03 -04:00
}
if ! viper . GetBool ( "DisableRSS" ) {
// XML Feed
2014-11-16 09:41:07 -05:00
n . Url = s . permalinkStr ( base + "/index.xml" )
n . Permalink = s . permalink ( base )
2014-09-14 07:23:03 -04:00
rssLayouts := [ ] string { "taxonomy/" + t . singular + ".rss.xml" , "_default/rss.xml" , "rss.xml" , "_internal/_default/rss.xml" }
2014-11-04 00:39:37 -05:00
b , err := s . renderXML ( "taxonomy " + t . singular + " rss" , n , s . appendThemeTemplates ( rssLayouts ) ... )
2014-09-14 07:23:03 -04:00
if err != nil {
results <- err
continue
2014-11-04 00:39:37 -05:00
} else {
2014-11-16 09:41:07 -05:00
err := s . WriteDestFile ( base + "/index.xml" , b )
2014-11-04 00:39:37 -05:00
if err != nil {
results <- err
}
2014-09-14 07:23:03 -04:00
}
}
}
}
2014-04-08 23:15:57 -04:00
// Render a page per taxonomy that lists the terms for that taxonomy
func ( s * Site ) RenderListsOfTaxonomyTerms ( ) ( err error ) {
2014-04-10 08:10:12 -04:00
taxonomies := viper . GetStringMapString ( "Taxonomies" )
for singular , plural := range taxonomies {
n := s . NewNode ( )
n . Title = strings . Title ( plural )
s . setUrls ( n , plural )
n . Data [ "Singular" ] = singular
n . Data [ "Plural" ] = plural
n . Data [ "Terms" ] = s . Taxonomies [ plural ]
// keep the following just for legacy reasons
n . Data [ "OrderedIndex" ] = n . Data [ "Terms" ]
n . Data [ "Index" ] = n . Data [ "Terms" ]
layouts := [ ] string { "taxonomy/" + singular + ".terms.html" , "_default/terms.html" , "indexes/indexes.html" }
layouts = s . appendThemeTemplates ( layouts )
if s . layoutExists ( layouts ... ) {
2014-11-04 00:39:37 -05:00
b , err := s . renderPage ( "taxonomy terms for " + singular , n , layouts ... )
2014-04-07 23:29:35 -04:00
if err != nil {
return err
2014-01-29 17:50:31 -05:00
}
2014-11-04 00:39:37 -05:00
if err := s . WriteDestPage ( plural + "/index.html" , b ) ; err != nil {
return err
}
2014-01-29 17:50:31 -05:00
}
}
2014-04-10 08:10:12 -04:00
2014-01-29 17:50:31 -05:00
return
2013-08-03 03:09:28 -04:00
}
2014-04-08 23:15:57 -04:00
// Render a page for each section
func ( s * Site ) RenderSectionLists ( ) error {
2014-01-29 17:50:31 -05:00
for section , data := range s . Sections {
n := s . NewNode ( )
2014-06-24 17:44:16 -04:00
if viper . GetBool ( "PluralizeListTitles" ) {
n . Title = strings . Title ( inflect . Pluralize ( section ) )
} else {
n . Title = strings . Title ( section )
}
2014-02-27 18:32:09 -05:00
s . setUrls ( n , section )
2014-01-29 17:50:31 -05:00
n . Date = data [ 0 ] . Page . Date
n . Data [ "Pages" ] = data . Pages ( )
2014-04-10 08:10:12 -04:00
layouts := [ ] string { "section/" + section + ".html" , "_default/section.html" , "_default/list.html" , "indexes/" + section + ".html" , "_default/indexes.html" }
2014-01-29 17:50:31 -05:00
2014-11-04 00:39:37 -05:00
b , err := s . renderPage ( "section " + section , n , s . appendThemeTemplates ( layouts ) ... )
2014-01-29 17:50:31 -05:00
if err != nil {
return err
}
2014-11-04 00:39:37 -05:00
if err := s . WriteDestPage ( section , b ) ; err != nil {
return err
}
2014-01-29 17:50:31 -05:00
2014-11-16 09:41:07 -05:00
if ! viper . GetBool ( "DisableRSS" ) && section != "" {
2014-01-29 17:50:31 -05:00
// XML Feed
2014-11-16 09:41:07 -05:00
n . Url = s . permalinkStr ( section + "/index.xml" )
n . Permalink = s . permalink ( section )
2014-04-10 08:10:12 -04:00
rssLayouts := [ ] string { "section/" + section + ".rss.xml" , "_default/rss.xml" , "rss.xml" , "_internal/_default/rss.xml" }
2014-11-04 00:39:37 -05:00
b , err = s . renderXML ( "section " + section + " rss" , n , s . appendThemeTemplates ( rssLayouts ) ... )
2014-01-29 17:50:31 -05:00
if err != nil {
return err
}
2014-11-16 09:41:07 -05:00
if err := s . WriteDestFile ( section + "/index.xml" , b ) ; err != nil {
2014-11-04 00:39:37 -05:00
return err
}
2014-01-29 17:50:31 -05:00
}
}
return nil
2013-07-04 11:32:55 -04:00
}
2014-10-18 14:25:10 -04:00
func ( s * Site ) newHomeNode ( ) * Node {
2014-01-29 17:50:31 -05:00
n := s . NewNode ( )
n . Title = n . Site . Title
2014-02-27 18:32:09 -05:00
s . setUrls ( n , "/" )
2014-01-29 17:50:31 -05:00
n . Data [ "Pages" ] = s . Pages
2014-10-18 14:25:10 -04:00
return n
}
func ( s * Site ) RenderHomePage ( ) error {
n := s . newHomeNode ( )
2014-05-13 17:07:50 -04:00
layouts := [ ] string { "index.html" , "_default/list.html" , "_default/single.html" }
2014-11-04 00:39:37 -05:00
b , err := s . renderPage ( "homepage" , n , s . appendThemeTemplates ( layouts ) ... )
2014-01-29 17:50:31 -05:00
if err != nil {
return err
}
2014-11-04 00:39:37 -05:00
if err := s . WriteDestPage ( "/" , b ) ; err != nil {
return err
}
2014-01-29 17:50:31 -05:00
2014-04-09 17:45:34 -04:00
if ! viper . GetBool ( "DisableRSS" ) {
2014-01-29 17:50:31 -05:00
// XML Feed
2014-11-16 09:41:07 -05:00
n . Url = s . permalinkStr ( "index.xml" )
n . Title = ""
2014-01-29 17:50:31 -05:00
high := 50
if len ( s . Pages ) < high {
high = len ( s . Pages )
}
n . Data [ "Pages" ] = s . Pages [ : high ]
if len ( s . Pages ) > 0 {
n . Date = s . Pages [ 0 ] . Date
}
2014-04-10 08:10:12 -04:00
2014-11-16 09:41:07 -05:00
rssLayouts := [ ] string { "rss.xml" , "_default/rss.xml" , "_internal/_default/rss.xml" }
b , err := s . renderXML ( "homepage rss" , n , s . appendThemeTemplates ( rssLayouts ) ... )
if err != nil {
return err
}
if err := s . WriteDestFile ( "index.xml" , b ) ; err != nil {
return err
2014-08-20 22:57:51 -04:00
}
}
2014-06-26 15:41:29 -04:00
n . Url = helpers . Urlize ( "404.html" )
n . Title = "404 Page not found"
n . Permalink = s . permalink ( "404.html" )
nfLayouts := [ ] string { "404.html" }
2014-11-04 00:39:37 -05:00
b , nfErr := s . renderPage ( "404 page" , n , s . appendThemeTemplates ( nfLayouts ) ... )
2014-06-26 15:41:29 -04:00
if nfErr != nil {
return nfErr
2014-01-29 17:50:31 -05:00
}
2014-11-04 00:39:37 -05:00
if err := s . WriteDestFile ( "404.html" , b ) ; err != nil {
return err
}
2014-01-29 17:50:31 -05:00
return nil
2013-07-04 11:32:55 -04:00
}
2014-05-06 06:50:23 -04:00
func ( s * Site ) RenderSitemap ( ) error {
if viper . GetBool ( "DisableSitemap" ) {
return nil
}
2014-05-06 11:02:56 -04:00
sitemapDefault := parseSitemap ( viper . GetStringMap ( "Sitemap" ) )
2014-05-06 06:50:23 -04:00
optChanged := false
n := s . NewNode ( )
2014-05-06 11:02:56 -04:00
// Prepend homepage to the list of pages
pages := make ( Pages , 0 )
page := & Page { }
2014-05-10 04:45:33 -04:00
page . Date = s . Info . LastChange
2014-05-28 19:11:54 -04:00
page . Site = & s . Info
2014-05-06 11:02:56 -04:00
page . Url = "/"
pages = append ( pages , page )
pages = append ( pages , s . Pages ... )
n . Data [ "Pages" ] = pages
for _ , page := range pages {
if page . Sitemap . ChangeFreq == "" {
page . Sitemap . ChangeFreq = sitemapDefault . ChangeFreq
}
if page . Sitemap . Priority == - 1 {
page . Sitemap . Priority = sitemapDefault . Priority
}
}
2014-05-06 06:50:23 -04:00
// Force `UglyUrls` option to force `sitemap.xml` file name
2014-11-04 00:39:37 -05:00
switch s . PageTarget ( ) . ( type ) {
2014-05-06 06:50:23 -04:00
case * target . Filesystem :
2014-11-04 00:39:37 -05:00
s . PageTarget ( ) . ( * target . PagePub ) . UglyUrls = true
2014-05-06 06:50:23 -04:00
optChanged = true
}
smLayouts := [ ] string { "sitemap.xml" , "_default/sitemap.xml" , "_internal/_default/sitemap.xml" }
2014-11-04 00:39:37 -05:00
b , err := s . renderXML ( "sitemap" , n , s . appendThemeTemplates ( smLayouts ) ... )
2014-05-06 06:50:23 -04:00
if err != nil {
return err
}
2014-11-04 00:39:37 -05:00
if err := s . WriteDestFile ( "sitemap.xml" , b ) ; err != nil {
return err
}
2014-05-06 06:50:23 -04:00
if optChanged {
2014-11-04 00:39:37 -05:00
s . PageTarget ( ) . ( * target . PagePub ) . UglyUrls = viper . GetBool ( "UglyUrls" )
2014-05-06 06:50:23 -04:00
}
return nil
}
2013-07-04 11:32:55 -04:00
func ( s * Site ) Stats ( ) {
2014-08-20 11:09:35 -04:00
jww . FEEDBACK . Println ( s . draftStats ( ) )
jww . FEEDBACK . Println ( s . futureStats ( ) )
2014-08-25 13:13:11 -04:00
jww . FEEDBACK . Printf ( "%d pages created \n" , len ( s . Pages ) )
2014-04-05 01:26:43 -04:00
2014-04-08 23:15:57 -04:00
taxonomies := viper . GetStringMapString ( "Taxonomies" )
2014-04-05 01:26:43 -04:00
2014-04-08 23:15:57 -04:00
for _ , pl := range taxonomies {
jww . FEEDBACK . Printf ( "%d %s created\n" , len ( s . Taxonomies [ pl ] ) , pl )
2014-01-29 17:50:31 -05:00
}
2013-07-04 11:32:55 -04:00
}
2014-02-27 18:32:09 -05:00
func ( s * Site ) setUrls ( n * Node , in string ) {
n . Url = s . prepUrl ( in )
n . Permalink = s . permalink ( n . Url )
n . RSSLink = s . permalink ( in + ".xml" )
}
2013-09-25 00:24:49 -04:00
2014-02-27 18:32:09 -05:00
func ( s * Site ) permalink ( plink string ) template . HTML {
2014-10-18 14:25:10 -04:00
return template . HTML ( s . permalinkStr ( plink ) )
}
func ( s * Site ) permalinkStr ( plink string ) string {
return helpers . MakePermalink ( string ( viper . GetString ( "BaseUrl" ) ) , s . prepUrl ( plink ) ) . String ( )
2014-02-27 18:32:09 -05:00
}
func ( s * Site ) prepUrl ( in string ) string {
2014-12-16 06:08:16 -05:00
return s . PrettifyUrl ( helpers . Urlize ( in ) )
2014-02-27 18:32:09 -05:00
}
func ( s * Site ) PrettifyUrl ( in string ) string {
2014-04-05 01:26:43 -04:00
return helpers . UrlPrep ( viper . GetBool ( "UglyUrls" ) , in )
2014-02-27 18:32:09 -05:00
}
2013-09-25 00:24:49 -04:00
2014-02-27 18:32:09 -05:00
func ( s * Site ) PrettifyPath ( in string ) string {
2014-04-05 01:26:43 -04:00
return helpers . PathPrep ( viper . GetBool ( "UglyUrls" ) , in )
2013-08-31 20:47:21 -04:00
}
func ( s * Site ) NewNode ( ) * Node {
2014-01-29 17:50:31 -05:00
return & Node {
Data : make ( map [ string ] interface { } ) ,
2014-05-28 19:11:54 -04:00
Site : & s . Info ,
2014-01-29 17:50:31 -05:00
}
2013-07-04 11:32:55 -04:00
}
2014-04-08 23:15:57 -04:00
func ( s * Site ) layoutExists ( layouts ... string ) bool {
_ , found := s . findFirstLayout ( layouts ... )
return found
}
2014-11-04 00:39:37 -05:00
func ( s * Site ) renderXML ( name string , d interface { } , layouts ... string ) ( io . Reader , error ) {
renderBuffer := s . NewXMLBuffer ( )
err := s . render ( name , d , renderBuffer , layouts ... )
return renderBuffer , err
}
2013-10-01 22:58:15 -04:00
2014-11-04 00:39:37 -05:00
func ( s * Site ) renderPage ( name string , d interface { } , layouts ... string ) ( io . Reader , error ) {
renderBuffer := new ( bytes . Buffer )
err := s . render ( name , d , renderBuffer , layouts ... )
var outBuffer = new ( bytes . Buffer )
2014-01-29 17:50:31 -05:00
transformLinks := transform . NewEmptyTransforms ( )
2014-04-05 01:26:43 -04:00
if viper . GetBool ( "CanonifyUrls" ) {
absURL , err := transform . AbsURL ( viper . GetString ( "BaseUrl" ) )
2014-01-29 17:50:31 -05:00
if err != nil {
2014-11-04 00:39:37 -05:00
return nil , err
2014-01-29 17:50:31 -05:00
}
transformLinks = append ( transformLinks , absURL ... )
}
2014-05-16 17:49:27 -04:00
if viper . GetBool ( "watch" ) && ! viper . GetBool ( "DisableLiveReload" ) {
transformLinks = append ( transformLinks , transform . LiveReloadInject )
}
2014-01-29 17:50:31 -05:00
transformer := transform . NewChain ( transformLinks ... )
2014-11-04 00:39:37 -05:00
transformer . Apply ( outBuffer , renderBuffer )
return outBuffer , err
}
2014-01-29 17:50:31 -05:00
2014-11-04 00:39:37 -05:00
func ( s * Site ) render ( name string , d interface { } , renderBuffer * bytes . Buffer , layouts ... string ) error {
layout , found := s . findFirstLayout ( layouts ... )
if found == false {
jww . WARN . Printf ( "Unable to locate layout for %s: %s\n" , name , layouts )
return nil
2014-01-29 17:50:31 -05:00
}
2014-11-04 00:39:37 -05:00
if err := s . renderThing ( d , layout , renderBuffer ) ; err != nil {
2014-01-29 17:50:31 -05:00
// Behavior here should be dependent on if running in server or watch mode.
2014-09-29 09:19:39 -04:00
jww . ERROR . Println ( fmt . Errorf ( "Error while rendering %s: %v" , name , err ) )
2014-01-29 17:50:31 -05:00
if ! s . Running ( ) {
os . Exit ( - 1 )
}
}
2014-11-04 00:39:37 -05:00
return nil
2013-10-01 22:58:15 -04:00
}
2014-04-08 23:15:57 -04:00
func ( s * Site ) findFirstLayout ( layouts ... string ) ( string , bool ) {
for _ , layout := range layouts {
2014-01-29 17:50:31 -05:00
if s . Tmpl . Lookup ( layout ) != nil {
2014-04-08 23:15:57 -04:00
return layout , true
2014-01-29 17:50:31 -05:00
}
}
2014-04-08 23:15:57 -04:00
return "" , false
2013-07-04 11:32:55 -04:00
}
2013-11-05 00:28:08 -05:00
func ( s * Site ) renderThing ( d interface { } , layout string , w io . Writer ) error {
2014-01-29 17:50:31 -05:00
// If the template doesn't exist, then return, but leave the Writer open
if s . Tmpl . Lookup ( layout ) == nil {
return fmt . Errorf ( "Layout not found: %s" , layout )
}
return s . Tmpl . ExecuteTemplate ( w , layout , d )
2013-08-27 05:09:50 -04:00
}
2013-11-05 00:28:08 -05:00
func ( s * Site ) NewXMLBuffer ( ) * bytes . Buffer {
2014-01-29 17:50:31 -05:00
header := "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>\n"
return bytes . NewBufferString ( header )
2013-07-04 11:32:55 -04:00
}
2014-11-04 00:39:37 -05:00
func ( s * Site ) PageTarget ( ) target . Output {
if s . Targets . Page == nil {
s . Targets . Page = & target . PagePub {
2014-01-29 17:50:31 -05:00
PublishDir : s . absPublishDir ( ) ,
2014-04-05 01:26:43 -04:00
UglyUrls : viper . GetBool ( "UglyUrls" ) ,
2014-01-29 17:50:31 -05:00
}
}
2014-11-04 00:39:37 -05:00
return s . Targets . Page
2013-09-03 23:52:50 -04:00
}
2014-11-04 00:39:37 -05:00
func ( s * Site ) FileTarget ( ) target . Output {
if s . Targets . File == nil {
s . Targets . File = & target . Filesystem {
PublishDir : s . absPublishDir ( ) ,
}
}
return s . Targets . File
2013-07-04 11:32:55 -04:00
}
2013-09-12 19:17:53 -04:00
2014-11-04 00:39:37 -05:00
func ( s * Site ) AliasTarget ( ) target . AliasPublisher {
if s . Targets . Alias == nil {
s . Targets . Alias = & target . HTMLRedirectAlias {
2014-01-29 17:50:31 -05:00
PublishDir : s . absPublishDir ( ) ,
}
2014-11-04 00:39:37 -05:00
2014-01-29 17:50:31 -05:00
}
2014-11-04 00:39:37 -05:00
return s . Targets . Alias
}
2014-01-29 17:50:31 -05:00
2014-11-04 00:39:37 -05:00
func ( s * Site ) WriteDestFile ( path string , reader io . Reader ) ( err error ) {
jww . DEBUG . Println ( "creating file:" , path )
return s . FileTarget ( ) . Publish ( path , reader )
}
func ( s * Site ) WriteDestPage ( path string , reader io . Reader ) ( err error ) {
jww . DEBUG . Println ( "creating page:" , path )
return s . PageTarget ( ) . Publish ( path , reader )
}
2014-01-29 17:50:31 -05:00
2014-11-04 00:39:37 -05:00
func ( s * Site ) WriteDestAlias ( path string , permalink template . HTML ) ( err error ) {
jww . DEBUG . Println ( "alias created at:" , path )
return s . AliasTarget ( ) . Publish ( path , permalink )
2013-09-12 19:17:53 -04:00
}
2014-08-20 11:09:35 -04:00
func ( s * Site ) draftStats ( ) string {
var msg string
switch s . draftCount {
case 0 :
return "0 draft content "
case 1 :
msg = "1 draft rendered "
default :
msg = fmt . Sprintf ( "%d drafts rendered" , s . draftCount )
}
if viper . GetBool ( "BuildDrafts" ) {
return fmt . Sprintf ( "%d of " , s . draftCount ) + msg
2014-08-25 13:13:11 -04:00
}
2014-08-20 11:09:35 -04:00
return "0 of " + msg
}
func ( s * Site ) futureStats ( ) string {
2014-08-25 13:13:11 -04:00
var msg string
switch s . futureCount {
case 0 :
return "0 future content "
case 1 :
msg = "1 future rendered "
default :
msg = fmt . Sprintf ( "%d future rendered" , s . draftCount )
}
if viper . GetBool ( "BuildFuture" ) {
return fmt . Sprintf ( "%d of " , s . futureCount ) + msg
}
return "0 of " + msg
2014-08-20 11:09:35 -04:00
}
2014-08-29 13:40:21 -04:00
func getGoMaxProcs ( ) int {
if gmp := os . Getenv ( "GOMAXPROCS" ) ; gmp != "" {
if p , err := strconv . Atoi ( gmp ) ; err != nil {
return p
}
}
return 1
}