2019-01-02 06:33:26 -05:00
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache 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://www.apache.org/licenses/LICENSE-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 (
"fmt"
"path"
2019-03-30 12:08:25 -04:00
"path/filepath"
2019-01-02 06:33:26 -05:00
"regexp"
"strings"
2020-02-18 08:00:58 -05:00
"sync"
2019-01-02 06:33:26 -05:00
"time"
2021-07-27 07:45:05 -04:00
"github.com/gohugoio/hugo/langs"
2021-02-14 12:30:59 -05:00
"github.com/gobuffalo/flect"
2019-08-16 09:55:03 -04:00
"github.com/gohugoio/hugo/markup/converter"
Add Hugo Modules
This commit implements Hugo Modules.
This is a broad subject, but some keywords include:
* A new `module` configuration section where you can import almost anything. You can configure both your own file mounts nd the file mounts of the modules you import. This is the new recommended way of configuring what you earlier put in `configDir`, `staticDir` etc. And it also allows you to mount folders in non-Hugo-projects, e.g. the `SCSS` folder in the Bootstrap GitHub project.
* A module consists of a set of mounts to the standard 7 component types in Hugo: `static`, `content`, `layouts`, `data`, `assets`, `i18n`, and `archetypes`. Yes, Theme Components can now include content, which should be very useful, especially in bigger multilingual projects.
* Modules not in your local file cache will be downloaded automatically and even "hot replaced" while the server is running.
* Hugo Modules supports and encourages semver versioned modules, and uses the minimal version selection algorithm to resolve versions.
* A new set of CLI commands are provided to manage all of this: `hugo mod init`, `hugo mod get`, `hugo mod graph`, `hugo mod tidy`, and `hugo mod vendor`.
All of the above is backed by Go Modules.
Fixes #5973
Fixes #5996
Fixes #6010
Fixes #5911
Fixes #5940
Fixes #6074
Fixes #6082
Fixes #6092
2019-05-03 03:16:58 -04:00
"github.com/gohugoio/hugo/hugofs/files"
2019-04-07 04:22:19 -04:00
"github.com/gohugoio/hugo/common/hugo"
2019-01-02 06:33:26 -05:00
"github.com/gohugoio/hugo/related"
"github.com/gohugoio/hugo/source"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/page/pagemeta"
"github.com/gohugoio/hugo/resources/resource"
"github.com/spf13/cast"
)
var cjkRe = regexp . MustCompile ( ` \p { Han}|\p { Hangul}|\p { Hiragana}|\p { Katakana} ` )
type pageMeta struct {
// kind is the discriminator that identifies the different page types
// in the different page collections. This can, as an example, be used
// to to filter regular pages, find sections etc.
// Kind will, for the pages available to the templates, be one of:
2020-06-16 09:43:50 -04:00
// page, home, section, taxonomy and term.
2019-01-02 06:33:26 -05:00
// It is of string type to make it easy to reason about in
// the templates.
kind string
// This is a standalone page not part of any page collection. These
// include sitemap, robotsTXT and similar. It will have no pageOutputs, but
// a fixed pageOutput.
standalone bool
2019-09-10 05:26:34 -04:00
draft bool // Only published when running with -D flag
buildConfig pagemeta . BuildConfig
bundleType files . ContentClass
2019-01-02 06:33:26 -05:00
// Params contains configuration defined in the params section of page frontmatter.
2022-03-17 17:03:27 -04:00
params map [ string ] any
2019-01-02 06:33:26 -05:00
title string
linkTitle string
2019-04-05 13:11:04 -04:00
summary string
2019-01-02 06:33:26 -05:00
resourcePath string
weight int
markup string
contentType string
// whether the content is in a CJK language.
isCJKLanguage bool
layout string
aliases [ ] string
description string
keywords [ ] string
urlPaths pagemeta . URLPath
resource . Dates
2019-04-22 03:13:47 -04:00
// Set if this page is bundled inside another.
bundled bool
2019-01-02 06:33:26 -05:00
// A key that maps to translation(s) of this page. This value is fetched
// from the page front matter.
translationKey string
// From front matter.
configuredOutputFormats output . Formats
// This is the raw front matter metadata that is going to be assigned to
// the Resources above.
2022-03-17 17:03:27 -04:00
resourcesMetadata [ ] map [ string ] any
2019-01-02 06:33:26 -05:00
f source . File
sections [ ] string
// Sitemap overrides from front matter.
sitemap config . Sitemap
s * Site
2022-05-28 05:01:47 -04:00
contentConverterInit sync . Once
contentConverter converter . Converter
2019-01-02 06:33:26 -05:00
}
func ( p * pageMeta ) Aliases ( ) [ ] string {
return p . aliases
}
func ( p * pageMeta ) Author ( ) page . Author {
2022-04-20 11:08:01 -04:00
helpers . Deprecated ( ".Author" , "Use taxonomies." , false )
2019-01-02 06:33:26 -05:00
authors := p . Authors ( )
for _ , author := range authors {
return author
}
return page . Author { }
}
func ( p * pageMeta ) Authors ( ) page . AuthorList {
2022-04-20 11:08:01 -04:00
helpers . Deprecated ( ".Authors" , "Use taxonomies." , false )
2019-01-02 06:33:26 -05:00
authorKeys , ok := p . params [ "authors" ]
if ! ok {
return page . AuthorList { }
}
authors := authorKeys . ( [ ] string )
if len ( authors ) < 1 || len ( p . s . Info . Authors ) < 1 {
return page . AuthorList { }
}
al := make ( page . AuthorList )
for _ , author := range authors {
a , ok := p . s . Info . Authors [ author ]
if ok {
al [ author ] = a
}
}
return al
}
2019-09-10 05:26:34 -04:00
func ( p * pageMeta ) BundleType ( ) files . ContentClass {
2019-01-02 06:33:26 -05:00
return p . bundleType
}
func ( p * pageMeta ) Description ( ) string {
return p . description
}
func ( p * pageMeta ) Lang ( ) string {
return p . s . Lang ( )
}
func ( p * pageMeta ) Draft ( ) bool {
return p . draft
}
func ( p * pageMeta ) File ( ) source . File {
return p . f
}
func ( p * pageMeta ) IsHome ( ) bool {
return p . Kind ( ) == page . KindHome
}
func ( p * pageMeta ) Keywords ( ) [ ] string {
return p . keywords
}
func ( p * pageMeta ) Kind ( ) string {
return p . kind
}
func ( p * pageMeta ) Layout ( ) string {
return p . layout
}
func ( p * pageMeta ) LinkTitle ( ) string {
if p . linkTitle != "" {
return p . linkTitle
}
return p . Title ( )
}
func ( p * pageMeta ) Name ( ) string {
if p . resourcePath != "" {
return p . resourcePath
}
return p . Title ( )
}
func ( p * pageMeta ) IsNode ( ) bool {
return ! p . IsPage ( )
}
func ( p * pageMeta ) IsPage ( ) bool {
return p . Kind ( ) == page . KindPage
}
// Param is a convenience method to do lookups in Page's and Site's Params map,
// in that order.
//
// This method is also implemented on SiteInfo.
// TODO(bep) interface
2022-03-17 17:03:27 -04:00
func ( p * pageMeta ) Param ( key any ) ( any , error ) {
2019-01-02 06:33:26 -05:00
return resource . Param ( p , p . s . Info . Params ( ) , key )
}
2019-11-21 15:59:38 -05:00
func ( p * pageMeta ) Params ( ) maps . Params {
2019-01-02 06:33:26 -05:00
return p . params
}
func ( p * pageMeta ) Path ( ) string {
2022-01-04 07:07:10 -05:00
if ! p . File ( ) . IsZero ( ) {
const example = `
{ { $ path := "" } }
{ { with . File } }
{ { $ path = . Path } }
{ { else } }
{ { $ path = . Path } }
{ { end } }
`
2022-01-13 06:21:13 -05:00
helpers . Deprecated ( ".Path when the page is backed by a file" , "We plan to use Path for a canonical source path and you probably want to check the source is a file. To get the current behaviour, you can use a construct similar to the one below:\n" + example , false )
2022-01-04 07:07:10 -05:00
}
return p . Pathc ( )
}
// This is just a bridge method, use Path in templates.
func ( p * pageMeta ) Pathc ( ) string {
2019-03-25 13:18:34 -04:00
if ! p . File ( ) . IsZero ( ) {
2019-01-02 06:33:26 -05:00
return p . File ( ) . Path ( )
}
return p . SectionsPath ( )
}
// RelatedKeywords implements the related.Document interface needed for fast page searches.
func ( p * pageMeta ) RelatedKeywords ( cfg related . IndexConfig ) ( [ ] related . Keyword , error ) {
v , err := p . Param ( cfg . Name )
if err != nil {
return nil , err
}
return cfg . ToKeywords ( v )
}
func ( p * pageMeta ) IsSection ( ) bool {
return p . Kind ( ) == page . KindSection
}
func ( p * pageMeta ) Section ( ) string {
if p . IsHome ( ) {
return ""
}
if p . IsNode ( ) {
if len ( p . sections ) == 0 {
// May be a sitemap or similar.
return ""
}
return p . sections [ 0 ]
}
2019-03-25 13:18:34 -04:00
if ! p . File ( ) . IsZero ( ) {
2019-01-02 06:33:26 -05:00
return p . File ( ) . Section ( )
}
panic ( "invalid page state" )
}
func ( p * pageMeta ) SectionsEntries ( ) [ ] string {
return p . sections
}
func ( p * pageMeta ) SectionsPath ( ) string {
return path . Join ( p . SectionsEntries ( ) ... )
}
func ( p * pageMeta ) Sitemap ( ) config . Sitemap {
return p . sitemap
}
func ( p * pageMeta ) Title ( ) string {
return p . title
}
2020-01-26 09:53:42 -05:00
const defaultContentType = "page"
2019-01-02 06:33:26 -05:00
func ( p * pageMeta ) Type ( ) string {
if p . contentType != "" {
return p . contentType
}
2020-01-26 09:53:42 -05:00
if sect := p . Section ( ) ; sect != "" {
return sect
}
return defaultContentType
2019-01-02 06:33:26 -05:00
}
func ( p * pageMeta ) Weight ( ) int {
return p . weight
}
2019-09-10 05:26:34 -04:00
func ( pm * pageMeta ) mergeBucketCascades ( b1 , b2 * pagesMapBucket ) {
if b1 . cascade == nil {
2020-10-05 14:01:52 -04:00
b1 . cascade = make ( map [ page . PageMatcher ] maps . Params )
2019-09-10 05:26:34 -04:00
}
2020-10-05 14:01:52 -04:00
2019-09-10 05:26:34 -04:00
if b2 != nil && b2 . cascade != nil {
for k , v := range b2 . cascade {
2020-10-05 14:01:52 -04:00
vv , found := b1 . cascade [ k ]
if ! found {
2019-09-10 05:26:34 -04:00
b1 . cascade [ k ] = v
2020-10-05 14:01:52 -04:00
} else {
// Merge
for ck , cv := range v {
if _ , found := vv [ ck ] ; ! found {
vv [ ck ] = cv
}
}
2019-09-10 05:26:34 -04:00
}
}
2019-01-02 06:33:26 -05:00
}
2019-09-10 05:26:34 -04:00
}
2019-01-02 06:33:26 -05:00
2022-03-17 17:03:27 -04:00
func ( pm * pageMeta ) setMetadata ( parentBucket * pagesMapBucket , p * pageState , frontmatter map [ string ] any ) error {
2019-11-21 15:59:38 -05:00
pm . params = make ( maps . Params )
2019-01-02 06:33:26 -05:00
2019-09-10 05:26:34 -04:00
if frontmatter == nil && ( parentBucket == nil || parentBucket . cascade == nil ) {
return nil
}
2019-08-09 04:05:22 -04:00
if frontmatter != nil {
// Needed for case insensitive fetching of params values
2021-06-09 04:58:18 -04:00
maps . PrepareParams ( frontmatter )
2019-09-10 05:26:34 -04:00
if p . bucket != nil {
2019-08-09 04:05:22 -04:00
// Check for any cascade define on itself.
if cv , found := frontmatter [ "cascade" ] ; found {
2021-07-09 05:52:03 -04:00
var err error
p . bucket . cascade , err = page . DecodeCascade ( cv )
if err != nil {
return err
2020-10-22 13:14:14 -04:00
}
2019-08-09 04:05:22 -04:00
}
}
} else {
2022-03-17 17:03:27 -04:00
frontmatter = make ( map [ string ] any )
2019-09-10 05:26:34 -04:00
}
2020-10-05 14:01:52 -04:00
var cascade map [ page . PageMatcher ] maps . Params
2019-09-10 05:26:34 -04:00
if p . bucket != nil {
if parentBucket != nil {
// Merge missing keys from parent into this.
pm . mergeBucketCascades ( p . bucket , parentBucket )
}
cascade = p . bucket . cascade
} else if parentBucket != nil {
cascade = parentBucket . cascade
}
2020-10-05 14:01:52 -04:00
for m , v := range cascade {
if ! m . Matches ( p ) {
continue
}
for kk , vv := range v {
if _ , found := frontmatter [ kk ] ; ! found {
frontmatter [ kk ] = vv
}
2019-08-09 04:05:22 -04:00
}
}
2019-01-02 06:33:26 -05:00
var mtime time . Time
2019-08-09 04:05:22 -04:00
var contentBaseName string
if ! p . File ( ) . IsZero ( ) {
contentBaseName = p . File ( ) . ContentBaseName ( )
if p . File ( ) . FileInfo ( ) != nil {
mtime = p . File ( ) . FileInfo ( ) . ModTime ( )
}
2019-01-02 06:33:26 -05:00
}
var gitAuthorDate time . Time
2022-12-30 03:20:58 -05:00
if ! p . gitInfo . IsZero ( ) {
2019-01-02 06:33:26 -05:00
gitAuthorDate = p . gitInfo . AuthorDate
}
descriptor := & pagemeta . FrontMatterDescriptor {
Frontmatter : frontmatter ,
Params : pm . params ,
Dates : & pm . Dates ,
PageURLs : & pm . urlPaths ,
2019-08-09 04:05:22 -04:00
BaseFilename : contentBaseName ,
2019-01-02 06:33:26 -05:00
ModTime : mtime ,
GitAuthorDate : gitAuthorDate ,
2021-07-27 07:45:05 -04:00
Location : langs . GetLocation ( pm . s . Language ( ) ) ,
2019-01-02 06:33:26 -05:00
}
// Handle the date separately
// TODO(bep) we need to "do more" in this area so this can be split up and
// more easily tested without the Page, but the coupling is strong.
err := pm . s . frontmatterHandler . HandleDates ( descriptor )
if err != nil {
2020-10-21 05:17:48 -04:00
p . s . Log . Errorf ( "Failed to handle dates for page %q: %s" , p . pathOrTitle ( ) , err )
2019-01-02 06:33:26 -05:00
}
2019-09-10 05:26:34 -04:00
pm . buildConfig , err = pagemeta . DecodeBuildConfig ( frontmatter [ "_build" ] )
if err != nil {
return err
}
2019-01-02 06:33:26 -05:00
var sitemapSet bool
var draft , published , isCJKLanguage * bool
for k , v := range frontmatter {
loki := strings . ToLower ( k )
if loki == "published" { // Intentionally undocumented
vv , err := cast . ToBoolE ( v )
if err == nil {
published = & vv
}
// published may also be a date
continue
}
if pm . s . frontmatterHandler . IsDateKey ( loki ) {
continue
}
switch loki {
case "title" :
pm . title = cast . ToString ( v )
pm . params [ loki ] = pm . title
case "linktitle" :
pm . linkTitle = cast . ToString ( v )
pm . params [ loki ] = pm . linkTitle
2019-04-05 13:11:04 -04:00
case "summary" :
pm . summary = cast . ToString ( v )
pm . params [ loki ] = pm . summary
2019-01-02 06:33:26 -05:00
case "description" :
pm . description = cast . ToString ( v )
pm . params [ loki ] = pm . description
case "slug" :
// Don't start or end with a -
pm . urlPaths . Slug = strings . Trim ( cast . ToString ( v ) , "-" )
pm . params [ loki ] = pm . Slug ( )
case "url" :
2019-04-07 04:22:19 -04:00
url := cast . ToString ( v )
if strings . HasPrefix ( url , "http://" ) || strings . HasPrefix ( url , "https://" ) {
return fmt . Errorf ( "URLs with protocol (http*) not supported: %q. In page %q" , url , p . pathOrTitle ( ) )
}
lang := p . s . GetLanguagePrefix ( )
2019-04-15 11:09:27 -04:00
if lang != "" && ! strings . HasPrefix ( url , "/" ) && strings . HasPrefix ( url , lang + "/" ) {
2019-04-07 04:22:19 -04:00
if strings . HasPrefix ( hugo . CurrentVersion . String ( ) , "0.55" ) {
// We added support for page relative URLs in Hugo 0.55 and
// this may get its language path added twice.
// TODO(bep) eventually remove this.
2020-10-21 05:17:48 -04:00
p . s . Log . Warnf ( ` Front matter in %q with the url %q with no leading / has what looks like the language prefix added. In Hugo 0.55 we added support for page relative URLs in front matter, no language prefix needed. Check the URL and consider to either add a leading / or remove the language prefix. ` , p . pathOrTitle ( ) , url )
2019-04-07 04:22:19 -04:00
}
2019-01-02 06:33:26 -05:00
}
2019-04-07 04:22:19 -04:00
pm . urlPaths . URL = url
pm . params [ loki ] = url
2019-01-02 06:33:26 -05:00
case "type" :
pm . contentType = cast . ToString ( v )
pm . params [ loki ] = pm . contentType
case "keywords" :
pm . keywords = cast . ToStringSlice ( v )
pm . params [ loki ] = pm . keywords
case "headless" :
2019-09-10 05:26:34 -04:00
// Legacy setting for leaf bundles.
// This is since Hugo 0.63 handled in a more general way for all
// pages.
isHeadless := cast . ToBool ( v )
pm . params [ loki ] = isHeadless
if p . File ( ) . TranslationBaseName ( ) == "index" && isHeadless {
2020-03-20 04:37:21 -04:00
pm . buildConfig . List = pagemeta . Never
2020-10-06 05:19:31 -04:00
pm . buildConfig . Render = pagemeta . Never
2019-01-02 06:33:26 -05:00
}
case "outputs" :
o := cast . ToStringSlice ( v )
if len ( o ) > 0 {
2020-10-05 14:01:52 -04:00
// Output formats are explicitly set in front matter, use those.
2019-01-02 06:33:26 -05:00
outFormats , err := p . s . outputFormatsConfig . GetByNames ( o ... )
if err != nil {
2020-10-21 05:17:48 -04:00
p . s . Log . Errorf ( "Failed to resolve output formats: %s" , err )
2019-01-02 06:33:26 -05:00
} else {
pm . configuredOutputFormats = outFormats
pm . params [ loki ] = outFormats
}
}
case "draft" :
draft = new ( bool )
* draft = cast . ToBool ( v )
case "layout" :
pm . layout = cast . ToString ( v )
pm . params [ loki ] = pm . layout
case "markup" :
pm . markup = cast . ToString ( v )
pm . params [ loki ] = pm . markup
case "weight" :
pm . weight = cast . ToInt ( v )
pm . params [ loki ] = pm . weight
case "aliases" :
pm . aliases = cast . ToStringSlice ( v )
2019-03-30 12:08:25 -04:00
for i , alias := range pm . aliases {
2019-01-02 06:33:26 -05:00
if strings . HasPrefix ( alias , "http://" ) || strings . HasPrefix ( alias , "https://" ) {
2019-03-30 12:08:25 -04:00
return fmt . Errorf ( "http* aliases not supported: %q" , alias )
2019-01-02 06:33:26 -05:00
}
2019-03-30 12:08:25 -04:00
pm . aliases [ i ] = filepath . ToSlash ( alias )
2019-01-02 06:33:26 -05:00
}
pm . params [ loki ] = pm . aliases
case "sitemap" :
2019-11-21 15:59:38 -05:00
p . m . sitemap = config . DecodeSitemap ( p . s . siteCfg . sitemap , maps . ToStringMap ( v ) )
2019-01-02 06:33:26 -05:00
pm . params [ loki ] = p . m . sitemap
sitemapSet = true
case "iscjklanguage" :
isCJKLanguage = new ( bool )
* isCJKLanguage = cast . ToBool ( v )
case "translationkey" :
pm . translationKey = cast . ToString ( v )
pm . params [ loki ] = pm . translationKey
case "resources" :
2022-03-17 17:03:27 -04:00
var resources [ ] map [ string ] any
2019-01-02 06:33:26 -05:00
handled := true
switch vv := v . ( type ) {
2022-03-17 17:03:27 -04:00
case [ ] map [ any ] any :
2019-01-02 06:33:26 -05:00
for _ , vvv := range vv {
2019-11-21 15:59:38 -05:00
resources = append ( resources , maps . ToStringMap ( vvv ) )
2019-01-02 06:33:26 -05:00
}
2022-03-17 17:03:27 -04:00
case [ ] map [ string ] any :
2019-01-02 06:33:26 -05:00
resources = append ( resources , vv ... )
2022-03-17 17:03:27 -04:00
case [ ] any :
2019-01-02 06:33:26 -05:00
for _ , vvv := range vv {
switch vvvv := vvv . ( type ) {
2022-03-17 17:03:27 -04:00
case map [ any ] any :
2019-11-21 15:59:38 -05:00
resources = append ( resources , maps . ToStringMap ( vvvv ) )
2022-03-17 17:03:27 -04:00
case map [ string ] any :
2019-01-02 06:33:26 -05:00
resources = append ( resources , vvvv )
}
}
default :
handled = false
}
if handled {
pm . params [ loki ] = resources
pm . resourcesMetadata = resources
break
}
fallthrough
default :
// If not one of the explicit values, store in Params
switch vv := v . ( type ) {
2023-01-16 10:29:42 -05:00
case [ ] any :
if len ( vv ) > 0 {
allStrings := true
for _ , vvv := range vv {
if _ , ok := vvv . ( string ) ; ! ok {
allStrings = false
break
2019-01-02 06:33:26 -05:00
}
2023-01-16 10:29:42 -05:00
}
if allStrings {
// We need tags, keywords etc. to be []string, not []interface{}.
a := make ( [ ] string , len ( vv ) )
for i , u := range vv {
a [ i ] = cast . ToString ( u )
}
pm . params [ loki ] = a
2019-01-02 06:33:26 -05:00
} else {
2023-01-16 10:29:42 -05:00
pm . params [ loki ] = vv
2019-01-02 06:33:26 -05:00
}
2023-01-16 10:29:42 -05:00
} else {
pm . params [ loki ] = [ ] string { }
2019-01-02 06:33:26 -05:00
}
2023-01-16 10:29:42 -05:00
default :
pm . params [ loki ] = vv
2019-01-02 06:33:26 -05:00
}
}
}
if ! sitemapSet {
pm . sitemap = p . s . siteCfg . sitemap
}
2019-11-06 14:10:47 -05:00
pm . markup = p . s . ContentSpec . ResolveMarkup ( pm . markup )
2019-01-02 06:33:26 -05:00
if draft != nil && published != nil {
pm . draft = * draft
2020-10-21 05:17:48 -04:00
p . m . s . Log . Warnf ( "page %q has both draft and published settings in its frontmatter. Using draft." , p . File ( ) . Filename ( ) )
2019-01-02 06:33:26 -05:00
} else if draft != nil {
pm . draft = * draft
} else if published != nil {
pm . draft = ! * published
}
pm . params [ "draft" ] = pm . draft
if isCJKLanguage != nil {
pm . isCJKLanguage = * isCJKLanguage
2019-08-09 04:05:22 -04:00
} else if p . s . siteCfg . hasCJKLanguage && p . source . parsed != nil {
2019-01-02 06:33:26 -05:00
if cjkRe . Match ( p . source . parsed . Input ( ) ) {
pm . isCJKLanguage = true
} else {
pm . isCJKLanguage = false
}
}
pm . params [ "iscjklanguage" ] = p . m . isCJKLanguage
return nil
}
2020-03-24 06:47:05 -04:00
func ( p * pageMeta ) noListAlways ( ) bool {
return p . buildConfig . List != pagemeta . Always
2020-03-20 04:37:21 -04:00
}
func ( p * pageMeta ) getListFilter ( local bool ) contentTreeNodeCallback {
return newContentTreeFilter ( func ( n * contentNode ) bool {
if n == nil {
return true
}
var shouldList bool
switch n . p . m . buildConfig . List {
case pagemeta . Always :
shouldList = true
case pagemeta . Never :
shouldList = false
case pagemeta . ListLocally :
shouldList = local
}
return ! shouldList
} )
2019-09-10 05:26:34 -04:00
}
func ( p * pageMeta ) noRender ( ) bool {
2020-10-06 05:19:31 -04:00
return p . buildConfig . Render != pagemeta . Always
}
func ( p * pageMeta ) noLink ( ) bool {
return p . buildConfig . Render == pagemeta . Never
2019-09-10 05:26:34 -04:00
}
func ( p * pageMeta ) applyDefaultValues ( n * contentNode ) error {
if p . buildConfig . IsZero ( ) {
p . buildConfig , _ = pagemeta . DecodeBuildConfig ( nil )
}
if ! p . s . isEnabled ( p . Kind ( ) ) {
( & p . buildConfig ) . Disable ( )
}
2019-01-02 06:33:26 -05:00
if p . markup == "" {
2019-03-25 13:18:34 -04:00
if ! p . File ( ) . IsZero ( ) {
// Fall back to file extension
2019-11-06 14:10:47 -05:00
p . markup = p . s . ContentSpec . ResolveMarkup ( p . File ( ) . Ext ( ) )
2019-01-02 06:33:26 -05:00
}
if p . markup == "" {
2019-08-16 09:55:03 -04:00
p . markup = "markdown"
2019-01-02 06:33:26 -05:00
}
}
2019-03-26 09:33:09 -04:00
if p . title == "" && p . f . IsZero ( ) {
2019-01-02 06:33:26 -05:00
switch p . Kind ( ) {
case page . KindHome :
p . title = p . s . Info . title
case page . KindSection :
2019-09-10 05:26:34 -04:00
var sectionName string
if n != nil {
sectionName = n . rootSection ( )
} else {
sectionName = p . sections [ 0 ]
}
sectionName = helpers . FirstUpper ( sectionName )
2019-01-02 06:33:26 -05:00
if p . s . Cfg . GetBool ( "pluralizeListTitles" ) {
2021-02-14 12:30:59 -05:00
p . title = flect . Pluralize ( sectionName )
2019-01-02 06:33:26 -05:00
} else {
p . title = sectionName
}
2020-06-16 09:43:50 -04:00
case page . KindTerm :
2019-09-10 05:26:34 -04:00
// TODO(bep) improve
2019-01-02 06:33:26 -05:00
key := p . sections [ len ( p . sections ) - 1 ]
p . title = strings . Replace ( p . s . titleFunc ( key ) , "-" , " " , - 1 )
2020-06-16 09:43:50 -04:00
case page . KindTaxonomy :
2019-01-02 06:33:26 -05:00
p . title = p . s . titleFunc ( p . sections [ 0 ] )
case kind404 :
p . title = "404 Page not found"
}
}
if p . IsNode ( ) {
Add Hugo Modules
This commit implements Hugo Modules.
This is a broad subject, but some keywords include:
* A new `module` configuration section where you can import almost anything. You can configure both your own file mounts nd the file mounts of the modules you import. This is the new recommended way of configuring what you earlier put in `configDir`, `staticDir` etc. And it also allows you to mount folders in non-Hugo-projects, e.g. the `SCSS` folder in the Bootstrap GitHub project.
* A module consists of a set of mounts to the standard 7 component types in Hugo: `static`, `content`, `layouts`, `data`, `assets`, `i18n`, and `archetypes`. Yes, Theme Components can now include content, which should be very useful, especially in bigger multilingual projects.
* Modules not in your local file cache will be downloaded automatically and even "hot replaced" while the server is running.
* Hugo Modules supports and encourages semver versioned modules, and uses the minimal version selection algorithm to resolve versions.
* A new set of CLI commands are provided to manage all of this: `hugo mod init`, `hugo mod get`, `hugo mod graph`, `hugo mod tidy`, and `hugo mod vendor`.
All of the above is backed by Go Modules.
Fixes #5973
Fixes #5996
Fixes #6010
Fixes #5911
Fixes #5940
Fixes #6074
Fixes #6082
Fixes #6092
2019-05-03 03:16:58 -04:00
p . bundleType = files . ContentClassBranch
2019-01-02 06:33:26 -05:00
} else {
source := p . File ( )
if fi , ok := source . ( * fileInfo ) ; ok {
2021-07-13 05:41:02 -04:00
class := fi . FileInfo ( ) . Meta ( ) . Classifier
Add Hugo Modules
This commit implements Hugo Modules.
This is a broad subject, but some keywords include:
* A new `module` configuration section where you can import almost anything. You can configure both your own file mounts nd the file mounts of the modules you import. This is the new recommended way of configuring what you earlier put in `configDir`, `staticDir` etc. And it also allows you to mount folders in non-Hugo-projects, e.g. the `SCSS` folder in the Bootstrap GitHub project.
* A module consists of a set of mounts to the standard 7 component types in Hugo: `static`, `content`, `layouts`, `data`, `assets`, `i18n`, and `archetypes`. Yes, Theme Components can now include content, which should be very useful, especially in bigger multilingual projects.
* Modules not in your local file cache will be downloaded automatically and even "hot replaced" while the server is running.
* Hugo Modules supports and encourages semver versioned modules, and uses the minimal version selection algorithm to resolve versions.
* A new set of CLI commands are provided to manage all of this: `hugo mod init`, `hugo mod get`, `hugo mod graph`, `hugo mod tidy`, and `hugo mod vendor`.
All of the above is backed by Go Modules.
Fixes #5973
Fixes #5996
Fixes #6010
Fixes #5911
Fixes #5940
Fixes #6074
Fixes #6082
Fixes #6092
2019-05-03 03:16:58 -04:00
switch class {
case files . ContentClassBranch , files . ContentClassLeaf :
p . bundleType = class
2019-01-02 06:33:26 -05:00
}
}
}
2019-11-27 07:42:36 -05:00
return nil
}
2022-05-28 05:01:47 -04:00
func ( p * pageMeta ) newContentConverter ( ps * pageState , markup string ) ( converter . Converter , error ) {
2019-09-10 05:26:34 -04:00
if ps == nil {
panic ( "no Page provided" )
}
2019-11-27 07:42:36 -05:00
cp := p . s . ContentSpec . Converters . Get ( markup )
if cp == nil {
2022-05-02 10:07:52 -04:00
return converter . NopConverter , fmt . Errorf ( "no content renderer found for markup %q" , p . markup )
2019-11-27 07:42:36 -05:00
}
2019-08-16 09:55:03 -04:00
2020-05-24 06:49:45 -04:00
var id string
2020-06-26 07:27:01 -04:00
var filename string
2022-01-28 03:45:11 -05:00
var path string
2020-05-24 06:49:45 -04:00
if ! p . f . IsZero ( ) {
id = p . f . UniqueID ( )
2020-06-26 07:27:01 -04:00
filename = p . f . Filename ( )
2022-01-28 03:45:11 -05:00
path = p . f . Path ( )
} else {
path = p . Pathc ( )
2020-05-24 06:49:45 -04:00
}
2019-11-27 07:42:36 -05:00
cpp , err := cp . New (
converter . DocumentContext {
2022-05-28 05:01:47 -04:00
Document : newPageForRenderHook ( ps ) ,
DocumentID : id ,
DocumentName : path ,
Filename : filename ,
2019-11-27 07:42:36 -05:00
} ,
)
if err != nil {
2020-02-18 08:00:58 -05:00
return converter . NopConverter , err
2019-01-02 06:33:26 -05:00
}
2019-11-27 07:42:36 -05:00
return cpp , nil
2019-01-02 06:33:26 -05:00
}
// The output formats this page will be rendered to.
func ( m * pageMeta ) outputFormats ( ) output . Formats {
if len ( m . configuredOutputFormats ) > 0 {
return m . configuredOutputFormats
}
return m . s . outputFormats [ m . Kind ( ) ]
}
func ( p * pageMeta ) Slug ( ) string {
return p . urlPaths . Slug
}
2022-03-17 17:03:27 -04:00
func getParam ( m resource . ResourceParamsProvider , key string , stringToLower bool ) any {
2019-01-02 06:33:26 -05:00
v := m . Params ( ) [ strings . ToLower ( key ) ]
if v == nil {
return nil
}
switch val := v . ( type ) {
case bool :
return val
case string :
if stringToLower {
return strings . ToLower ( val )
}
return val
case int64 , int32 , int16 , int8 , int :
return cast . ToInt ( v )
case float64 , float32 :
return cast . ToFloat64 ( v )
case time . Time :
return val
case [ ] string :
if stringToLower {
return helpers . SliceToLower ( val )
}
return v
2019-11-21 15:59:38 -05:00
default :
2019-01-02 06:33:26 -05:00
return v
}
}
2022-03-17 17:03:27 -04:00
func getParamToLower ( m resource . ResourceParamsProvider , key string ) any {
2019-01-02 06:33:26 -05:00
return getParam ( m , key , true )
}