mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
parent
21d9057dbf
commit
639073e4fe
18 changed files with 229 additions and 120 deletions
|
@ -18,9 +18,11 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/types"
|
"github.com/gohugoio/hugo/common/types"
|
||||||
"github.com/gohugoio/hugo/hugofs/files"
|
"github.com/gohugoio/hugo/hugofs/files"
|
||||||
|
"github.com/gohugoio/hugo/identity"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultPathParser PathParser
|
var defaultPathParser PathParser
|
||||||
|
@ -50,19 +52,42 @@ func NormalizePathStringBasic(s string) string {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseIdentity parses component c with path s into a StringIdentity.
|
||||||
|
func (pp *PathParser) ParseIdentity(c, s string) identity.StringIdentity {
|
||||||
|
s = NormalizePathStringBasic(s)
|
||||||
|
p := getPath()
|
||||||
|
p.component = c
|
||||||
|
defer putPath(p)
|
||||||
|
p, err := pp.doParse(c, s, p)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return identity.StringIdentity(p.IdentifierBase())
|
||||||
|
}
|
||||||
|
|
||||||
// Parse parses component c with path s into Path using Hugo's content path rules.
|
// Parse parses component c with path s into Path using Hugo's content path rules.
|
||||||
func (parser PathParser) Parse(c, s string) *Path {
|
func (pp *PathParser) Parse(c, s string) *Path {
|
||||||
p, err := parser.parse(c, s)
|
p, err := pp.parse(c, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pp *PathParser) newPath(component string) *Path {
|
||||||
|
return &Path{
|
||||||
|
component: component,
|
||||||
|
posContainerLow: -1,
|
||||||
|
posContainerHigh: -1,
|
||||||
|
posSectionHigh: -1,
|
||||||
|
posIdentifierLanguage: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (pp *PathParser) parse(component, s string) (*Path, error) {
|
func (pp *PathParser) parse(component, s string) (*Path, error) {
|
||||||
ss := NormalizePathStringBasic(s)
|
ss := NormalizePathStringBasic(s)
|
||||||
|
|
||||||
p, err := pp.doParse(component, ss)
|
p, err := pp.doParse(component, ss, pp.newPath(component))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -70,7 +95,7 @@ func (pp *PathParser) parse(component, s string) (*Path, error) {
|
||||||
if s != ss {
|
if s != ss {
|
||||||
var err error
|
var err error
|
||||||
// Preserve the original case for titles etc.
|
// Preserve the original case for titles etc.
|
||||||
p.unnormalized, err = pp.doParse(component, s)
|
p.unnormalized, err = pp.doParse(component, s, pp.newPath(component))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -82,15 +107,7 @@ func (pp *PathParser) parse(component, s string) (*Path, error) {
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *PathParser) doParse(component, s string) (*Path, error) {
|
func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
|
||||||
p := &Path{
|
|
||||||
component: component,
|
|
||||||
posContainerLow: -1,
|
|
||||||
posContainerHigh: -1,
|
|
||||||
posSectionHigh: -1,
|
|
||||||
posIdentifierLanguage: -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
hasLang := pp.LanguageIndex != nil
|
hasLang := pp.LanguageIndex != nil
|
||||||
hasLang = hasLang && (component == files.ComponentFolderContent || component == files.ComponentFolderLayouts)
|
hasLang = hasLang && (component == files.ComponentFolderContent || component == files.ComponentFolderLayouts)
|
||||||
|
|
||||||
|
@ -220,6 +237,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Path struct {
|
type Path struct {
|
||||||
|
// Note: Any additions to this struct should also be added to the pathPool.
|
||||||
s string
|
s string
|
||||||
|
|
||||||
posContainerLow int
|
posContainerLow int
|
||||||
|
@ -239,6 +257,31 @@ type Path struct {
|
||||||
unnormalized *Path
|
unnormalized *Path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pathPool = &sync.Pool{
|
||||||
|
New: func() any {
|
||||||
|
return &Path{}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPath() *Path {
|
||||||
|
return pathPool.Get().(*Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func putPath(p *Path) {
|
||||||
|
p.s = ""
|
||||||
|
p.posContainerLow = -1
|
||||||
|
p.posContainerHigh = -1
|
||||||
|
p.posSectionHigh = -1
|
||||||
|
p.component = ""
|
||||||
|
p.bundleType = 0
|
||||||
|
p.identifiers = p.identifiers[:0]
|
||||||
|
p.posIdentifierLanguage = -1
|
||||||
|
p.disabled = false
|
||||||
|
p.trimLeadingSlash = false
|
||||||
|
p.unnormalized = nil
|
||||||
|
pathPool.Put(p)
|
||||||
|
}
|
||||||
|
|
||||||
// TrimLeadingSlash returns a copy of the Path with the leading slash removed.
|
// TrimLeadingSlash returns a copy of the Path with the leading slash removed.
|
||||||
func (p Path) TrimLeadingSlash() *Path {
|
func (p Path) TrimLeadingSlash() *Path {
|
||||||
p.trimLeadingSlash = true
|
p.trimLeadingSlash = true
|
||||||
|
@ -254,7 +297,7 @@ func (p *Path) norm(s string) string {
|
||||||
|
|
||||||
// IdentifierBase satifies identity.Identity.
|
// IdentifierBase satifies identity.Identity.
|
||||||
func (p *Path) IdentifierBase() string {
|
func (p *Path) IdentifierBase() string {
|
||||||
return p.Base()[1:]
|
return p.Base()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Component returns the component for this path (e.g. "content").
|
// Component returns the component for this path (e.g. "content").
|
||||||
|
|
|
@ -349,3 +349,9 @@ func TestHasExt(t *testing.T) {
|
||||||
c.Assert(HasExt("/a/b/c"), qt.IsFalse)
|
c.Assert(HasExt("/a/b/c"), qt.IsFalse)
|
||||||
c.Assert(HasExt("/a/b.c/d"), qt.IsFalse)
|
c.Assert(HasExt("/a/b.c/d"), qt.IsFalse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkParseIdentity(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
testParser.ParseIdentity(files.ComponentFolderAssets, "/a/b.css")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -663,7 +663,7 @@ type Configs struct {
|
||||||
// All below is set in Init.
|
// All below is set in Init.
|
||||||
Languages langs.Languages
|
Languages langs.Languages
|
||||||
LanguagesDefaultFirst langs.Languages
|
LanguagesDefaultFirst langs.Languages
|
||||||
ContentPathParser paths.PathParser
|
ContentPathParser *paths.PathParser
|
||||||
|
|
||||||
configLangs []config.AllProvider
|
configLangs []config.AllProvider
|
||||||
}
|
}
|
||||||
|
@ -735,7 +735,7 @@ func (c *Configs) Init() error {
|
||||||
c.Languages = languages
|
c.Languages = languages
|
||||||
c.LanguagesDefaultFirst = languagesDefaultFirst
|
c.LanguagesDefaultFirst = languagesDefaultFirst
|
||||||
|
|
||||||
c.ContentPathParser = paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet(), IsLangDisabled: c.Base.IsLangDisabled}
|
c.ContentPathParser = &paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet(), IsLangDisabled: c.Base.IsLangDisabled}
|
||||||
|
|
||||||
c.configLangs = make([]config.AllProvider, len(c.Languages))
|
c.configLangs = make([]config.AllProvider, len(c.Languages))
|
||||||
for i, l := range c.LanguagesDefaultFirst {
|
for i, l := range c.LanguagesDefaultFirst {
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"github.com/gohugoio/hugo/common/paths"
|
"github.com/gohugoio/hugo/common/paths"
|
||||||
"github.com/gohugoio/hugo/common/urls"
|
"github.com/gohugoio/hugo/common/urls"
|
||||||
"github.com/gohugoio/hugo/config"
|
"github.com/gohugoio/hugo/config"
|
||||||
|
"github.com/gohugoio/hugo/identity"
|
||||||
"github.com/gohugoio/hugo/langs"
|
"github.com/gohugoio/hugo/langs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -42,7 +43,7 @@ func (c ConfigLanguage) LanguagesDefaultFirst() langs.Languages {
|
||||||
return c.m.LanguagesDefaultFirst
|
return c.m.LanguagesDefaultFirst
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c ConfigLanguage) PathParser() paths.PathParser {
|
func (c ConfigLanguage) PathParser() *paths.PathParser {
|
||||||
return c.m.ContentPathParser
|
return c.m.ContentPathParser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,6 +134,13 @@ func (c ConfigLanguage) Watching() bool {
|
||||||
return c.m.Base.Internal.Watch
|
return c.m.Base.Internal.Watch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c ConfigLanguage) NewIdentityManager(name string) identity.Manager {
|
||||||
|
if !c.Watching() {
|
||||||
|
return identity.NopManager
|
||||||
|
}
|
||||||
|
return identity.NewManager(name)
|
||||||
|
}
|
||||||
|
|
||||||
// GetConfigSection is mostly used in tests. The switch statement isn't complete, but what's in use.
|
// GetConfigSection is mostly used in tests. The switch statement isn't complete, but what's in use.
|
||||||
func (c ConfigLanguage) GetConfigSection(s string) any {
|
func (c ConfigLanguage) GetConfigSection(s string) any {
|
||||||
switch s {
|
switch s {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"github.com/gohugoio/hugo/common/paths"
|
"github.com/gohugoio/hugo/common/paths"
|
||||||
"github.com/gohugoio/hugo/common/types"
|
"github.com/gohugoio/hugo/common/types"
|
||||||
"github.com/gohugoio/hugo/common/urls"
|
"github.com/gohugoio/hugo/common/urls"
|
||||||
|
"github.com/gohugoio/hugo/identity"
|
||||||
"github.com/gohugoio/hugo/langs"
|
"github.com/gohugoio/hugo/langs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ type AllProvider interface {
|
||||||
LanguagePrefix() string
|
LanguagePrefix() string
|
||||||
BaseURL() urls.BaseURL
|
BaseURL() urls.BaseURL
|
||||||
BaseURLLiveReload() urls.BaseURL
|
BaseURLLiveReload() urls.BaseURL
|
||||||
PathParser() paths.PathParser
|
PathParser() *paths.PathParser
|
||||||
Environment() string
|
Environment() string
|
||||||
IsMultihost() bool
|
IsMultihost() bool
|
||||||
IsMultiLingual() bool
|
IsMultiLingual() bool
|
||||||
|
@ -57,6 +58,7 @@ type AllProvider interface {
|
||||||
BuildDrafts() bool
|
BuildDrafts() bool
|
||||||
Running() bool
|
Running() bool
|
||||||
Watching() bool
|
Watching() bool
|
||||||
|
NewIdentityManager(name string) identity.Manager
|
||||||
FastRenderMode() bool
|
FastRenderMode() bool
|
||||||
PrintUnusedTemplates() bool
|
PrintUnusedTemplates() bool
|
||||||
EnableMissingTranslationPlaceholders() bool
|
EnableMissingTranslationPlaceholders() bool
|
||||||
|
|
|
@ -241,7 +241,7 @@ type ComponentFsOptions struct {
|
||||||
DefaultContentLanguage string
|
DefaultContentLanguage string
|
||||||
|
|
||||||
// The parser used to parse paths provided by this filesystem.
|
// The parser used to parse paths provided by this filesystem.
|
||||||
PathParser paths.PathParser
|
PathParser *paths.PathParser
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *componentFs) Open(name string) (afero.File, error) {
|
func (fs *componentFs) Open(name string) (afero.File, error) {
|
||||||
|
|
|
@ -93,8 +93,8 @@ func (r *resourceSource) GetIdentity() identity.Identity {
|
||||||
return r.path
|
return r.path
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *resourceSource) ForEeachIdentity(f func(identity.Identity) bool) {
|
func (r *resourceSource) ForEeachIdentity(f func(identity.Identity) bool) bool {
|
||||||
f(r.GetIdentity())
|
return f(r.GetIdentity())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *resourceSource) Path() string {
|
func (r *resourceSource) Path() string {
|
||||||
|
@ -142,14 +142,15 @@ func (n resourceSources) GetIdentity() identity.Identity {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n resourceSources) ForEeachIdentity(f func(identity.Identity) bool) {
|
func (n resourceSources) ForEeachIdentity(f func(identity.Identity) bool) bool {
|
||||||
for _, r := range n {
|
for _, r := range n {
|
||||||
if r != nil {
|
if r != nil {
|
||||||
if f(r.GetIdentity()) {
|
if f(r.GetIdentity()) {
|
||||||
return
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg contentMapConfig) getTaxonomyConfig(s string) (v viewName) {
|
func (cfg contentMapConfig) getTaxonomyConfig(s string) (v viewName) {
|
||||||
|
|
|
@ -605,12 +605,15 @@ func (n contentNodeIs) GetIdentity() identity.Identity {
|
||||||
return n[0].GetIdentity()
|
return n[0].GetIdentity()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n contentNodeIs) ForEeachIdentity(f func(identity.Identity) bool) {
|
func (n contentNodeIs) ForEeachIdentity(f func(identity.Identity) bool) bool {
|
||||||
for _, nn := range n {
|
for _, nn := range n {
|
||||||
if nn != nil {
|
if nn != nil {
|
||||||
nn.ForEeachIdentity(f)
|
if nn.ForEeachIdentity(f) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n contentNodeIs) resetBuildState() {
|
func (n contentNodeIs) resetBuildState() {
|
||||||
|
@ -1151,7 +1154,7 @@ func (h *HugoSites) resolveAndResetDependententPageOutputs(ctx context.Context,
|
||||||
// First check the top level dependency manager.
|
// First check the top level dependency manager.
|
||||||
for _, id := range changes {
|
for _, id := range changes {
|
||||||
checkedCounter.Add(1)
|
checkedCounter.Add(1)
|
||||||
if r := depsFinder.Contains(id, p.dependencyManager, 100); r > identity.FinderFoundOneOfManyRepetition {
|
if r := depsFinder.Contains(id, p.dependencyManager, 2); r > identity.FinderFoundOneOfManyRepetition {
|
||||||
for _, po := range p.pageOutputs {
|
for _, po := range p.pageOutputs {
|
||||||
resetPo(po, r)
|
resetPo(po, r)
|
||||||
}
|
}
|
||||||
|
@ -1167,7 +1170,7 @@ func (h *HugoSites) resolveAndResetDependententPageOutputs(ctx context.Context,
|
||||||
}
|
}
|
||||||
for _, id := range changes {
|
for _, id := range changes {
|
||||||
checkedCounter.Add(1)
|
checkedCounter.Add(1)
|
||||||
if r := depsFinder.Contains(id, po.dependencyManagerOutput, 2); r > identity.FinderFoundOneOfManyRepetition {
|
if r := depsFinder.Contains(id, po.dependencyManagerOutput, 50); r > identity.FinderFoundOneOfManyRepetition {
|
||||||
resetPo(po, r)
|
resetPo(po, r)
|
||||||
continue OUTPUTS
|
continue OUTPUTS
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,8 +120,8 @@ func (p *pageState) GetIdentity() identity.Identity {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pageState) ForEeachIdentity(f func(identity.Identity) bool) {
|
func (p *pageState) ForEeachIdentity(f func(identity.Identity) bool) bool {
|
||||||
f(p)
|
return f(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pageState) GetDependencyManager() identity.Manager {
|
func (p *pageState) GetDependencyManager() identity.Manager {
|
||||||
|
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/hugofs/files"
|
"github.com/gohugoio/hugo/hugofs/files"
|
||||||
"github.com/gohugoio/hugo/identity"
|
|
||||||
"github.com/gohugoio/hugo/resources"
|
"github.com/gohugoio/hugo/resources"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/maps"
|
"github.com/gohugoio/hugo/common/maps"
|
||||||
|
@ -160,12 +159,6 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var dependencyManager identity.Manager = identity.NopManager
|
|
||||||
|
|
||||||
if m.s.conf.Internal.Watch {
|
|
||||||
dependencyManager = identity.NewManager(m.Path())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the rest of the page content.
|
// Parse the rest of the page content.
|
||||||
m.content, err = m.newCachedContent(h, pi)
|
m.content, err = m.newCachedContent(h, pi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -178,7 +171,7 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
|
||||||
pageOutputTemplateVariationsState: &atomic.Uint32{},
|
pageOutputTemplateVariationsState: &atomic.Uint32{},
|
||||||
resourcesPublishInit: &sync.Once{},
|
resourcesPublishInit: &sync.Once{},
|
||||||
Staler: m,
|
Staler: m,
|
||||||
dependencyManager: dependencyManager,
|
dependencyManager: m.s.Conf.NewIdentityManager(m.Path()),
|
||||||
pageCommon: &pageCommon{
|
pageCommon: &pageCommon{
|
||||||
FileProvider: m,
|
FileProvider: m,
|
||||||
AuthorProvider: m,
|
AuthorProvider: m,
|
||||||
|
|
|
@ -51,11 +51,6 @@ func newPageOutput(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var dependencyManager identity.Manager = identity.NopManager
|
|
||||||
if ps.s.conf.Internal.Watch {
|
|
||||||
dependencyManager = identity.NewManager(ps.Path() + "/" + f.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
providers := struct {
|
providers := struct {
|
||||||
page.PaginatorProvider
|
page.PaginatorProvider
|
||||||
resource.ResourceLinksProvider
|
resource.ResourceLinksProvider
|
||||||
|
@ -75,7 +70,7 @@ func newPageOutput(
|
||||||
TableOfContentsProvider: page.NopPage,
|
TableOfContentsProvider: page.NopPage,
|
||||||
render: render,
|
render: render,
|
||||||
paginator: pag,
|
paginator: pag,
|
||||||
dependencyManagerOutput: dependencyManager,
|
dependencyManagerOutput: ps.s.Conf.NewIdentityManager((ps.Path() + "/" + f.Name)),
|
||||||
}
|
}
|
||||||
|
|
||||||
return po
|
return po
|
||||||
|
|
|
@ -1131,7 +1131,7 @@ Single.
|
||||||
Running: true,
|
Running: true,
|
||||||
NeedsOsFS: true,
|
NeedsOsFS: true,
|
||||||
NeedsNpmInstall: true,
|
NeedsNpmInstall: true,
|
||||||
// LogLevel: logg.LevelTrace,
|
// LogLevel: logg.LevelDebug,
|
||||||
},
|
},
|
||||||
).Build()
|
).Build()
|
||||||
|
|
||||||
|
@ -1261,3 +1261,35 @@ func BenchmarkRebuildContentFileChange(b *testing.B) {
|
||||||
// fmt.Println(bb.LogString())
|
// fmt.Println(bb.LogString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRebuildConcat(t *testing.T) {
|
||||||
|
files := `
|
||||||
|
-- hugo.toml --
|
||||||
|
baseURL = "https://example.com"
|
||||||
|
disableLiveReload = true
|
||||||
|
disableKinds = ["taxonomy", "term", "sitemap", "robotsTXT", "404", "rss"]
|
||||||
|
-- assets/a.css --
|
||||||
|
a
|
||||||
|
-- assets/b.css --
|
||||||
|
b
|
||||||
|
-- assets/c.css --
|
||||||
|
c
|
||||||
|
-- assets/common/c1.css --
|
||||||
|
c1
|
||||||
|
-- assets/common/c2.css --
|
||||||
|
c2
|
||||||
|
-- layouts/index.html --
|
||||||
|
{{ $a := resources.Get "a.css" }}
|
||||||
|
{{ $b := resources.Get "b.css" }}
|
||||||
|
{{ $common := resources.Match "common/*.css" | resources.Concat "common.css" | minify }}
|
||||||
|
{{ $ab := slice $a $b $common | resources.Concat "ab.css" }}
|
||||||
|
all: {{ $ab.RelPermalink }}
|
||||||
|
`
|
||||||
|
b := TestRunning(t, files)
|
||||||
|
|
||||||
|
b.AssertFileContent("public/ab.css", "abc1c2")
|
||||||
|
b.EditFileReplaceAll("assets/common/c2.css", "c2", "c2 edited").Build()
|
||||||
|
b.AssertFileContent("public/ab.css", "abc1c2 edited")
|
||||||
|
b.AddFiles("assets/common/c3.css", "c3").Build()
|
||||||
|
b.AssertFileContent("public/ab.css", "abc1c2 editedc3")
|
||||||
|
}
|
||||||
|
|
|
@ -169,12 +169,7 @@ func (f *Finder) checkManager(sid *searchID, m Manager, level int) FinderResult
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
ids := m.getIdentities()
|
r = f.search(sid, m, level)
|
||||||
if len(ids) == 0 {
|
|
||||||
r = FinderNotFound
|
|
||||||
} else {
|
|
||||||
r = f.search(sid, ids, level)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r == FinderFoundOneOfMany {
|
if r == FinderFoundOneOfMany {
|
||||||
// Don't cache this one.
|
// Don't cache this one.
|
||||||
|
@ -270,11 +265,7 @@ func (f *Finder) doCheckOne(sid *searchID, v Identity, depth int) FinderResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
// search searches for id in ids.
|
// search searches for id in ids.
|
||||||
func (f *Finder) search(sid *searchID, ids Identities, depth int) FinderResult {
|
func (f *Finder) search(sid *searchID, m Manager, depth int) FinderResult {
|
||||||
if len(ids) == 0 {
|
|
||||||
return FinderNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
id := sid.id
|
id := sid.id
|
||||||
|
|
||||||
if id == Anonymous {
|
if id == Anonymous {
|
||||||
|
@ -285,19 +276,24 @@ func (f *Finder) search(sid *searchID, ids Identities, depth int) FinderResult {
|
||||||
return FinderNotFound
|
return FinderNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
for v := range ids {
|
var r FinderResult
|
||||||
r := f.checkOne(sid, v, depth)
|
m.forEeachIdentity(
|
||||||
if r > 0 {
|
func(v Identity) bool {
|
||||||
return r
|
if r > 0 {
|
||||||
}
|
panic("should be terminated")
|
||||||
|
}
|
||||||
m := GetDependencyManager(v)
|
r = f.checkOne(sid, v, depth)
|
||||||
if r := f.checkManager(sid, m, depth+1); r > 0 {
|
if r > 0 {
|
||||||
return r
|
return true
|
||||||
}
|
}
|
||||||
}
|
m := GetDependencyManager(v)
|
||||||
|
if r = f.checkManager(sid, m, depth+1); r > 0 {
|
||||||
return FinderNotFound
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// FinderConfig provides configuration for the Finder.
|
// FinderConfig provides configuration for the Finder.
|
||||||
|
|
|
@ -55,8 +55,8 @@ func NewManager(name string, opts ...ManagerOption) Manager {
|
||||||
// CleanString cleans s to be suitable as an identifier.
|
// CleanString cleans s to be suitable as an identifier.
|
||||||
func CleanString(s string) string {
|
func CleanString(s string) string {
|
||||||
s = strings.ToLower(s)
|
s = strings.ToLower(s)
|
||||||
s = strings.TrimPrefix(filepath.ToSlash(s), "/")
|
s = strings.Trim(filepath.ToSlash(s), "/")
|
||||||
return path.Clean(s)
|
return "/" + path.Clean(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanStringIdentity cleans s to be suitable as an identifier and wraps it in a StringIdentity.
|
// CleanStringIdentity cleans s to be suitable as an identifier and wraps it in a StringIdentity.
|
||||||
|
@ -77,23 +77,6 @@ func GetDependencyManager(v any) Manager {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDependencyManagerForScope returns the DependencyManager for the given scope from v or nil if none found.
|
|
||||||
// Note that it will fall back to an unscoped manager if none found for the given scope.
|
|
||||||
func GetDependencyManagerForScope(v any, scope int) Manager {
|
|
||||||
switch vv := v.(type) {
|
|
||||||
case DependencyManagerScopedProvider:
|
|
||||||
return vv.GetDependencyManagerForScope(scope)
|
|
||||||
case types.Unwrapper:
|
|
||||||
return GetDependencyManagerForScope(vv.Unwrapv(), scope)
|
|
||||||
case Manager:
|
|
||||||
return vv
|
|
||||||
case DependencyManagerProvider:
|
|
||||||
return vv.GetDependencyManager()
|
|
||||||
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FirstIdentity returns the first Identity in v, Anonymous if none found
|
// FirstIdentity returns the first Identity in v, Anonymous if none found
|
||||||
func FirstIdentity(v any) Identity {
|
func FirstIdentity(v any) Identity {
|
||||||
var result Identity = Anonymous
|
var result Identity = Anonymous
|
||||||
|
@ -169,7 +152,15 @@ type DependencyManagerScopedProvider interface {
|
||||||
type ForEeachIdentityProvider interface {
|
type ForEeachIdentityProvider interface {
|
||||||
// ForEeachIdentityProvider calls cb for each Identity.
|
// ForEeachIdentityProvider calls cb for each Identity.
|
||||||
// If cb returns true, the iteration is terminated.
|
// If cb returns true, the iteration is terminated.
|
||||||
ForEeachIdentity(cb func(id Identity) bool)
|
// The return value is whether the iteration was terminated.
|
||||||
|
ForEeachIdentity(cb func(id Identity) bool) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForEeachIdentityProviderFunc is a function that implements the ForEeachIdentityProvider interface.
|
||||||
|
type ForEeachIdentityProviderFunc func(func(id Identity) bool) bool
|
||||||
|
|
||||||
|
func (f ForEeachIdentityProviderFunc) ForEeachIdentity(cb func(id Identity) bool) bool {
|
||||||
|
return f(cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForEeachIdentityByNameProvider provides a way to look up identities by name.
|
// ForEeachIdentityByNameProvider provides a way to look up identities by name.
|
||||||
|
@ -279,9 +270,10 @@ type IsProbablyDependencyProvider interface {
|
||||||
type Manager interface {
|
type Manager interface {
|
||||||
Identity
|
Identity
|
||||||
AddIdentity(ids ...Identity)
|
AddIdentity(ids ...Identity)
|
||||||
|
AddIdentityForEach(ids ...ForEeachIdentityProvider)
|
||||||
GetIdentity() Identity
|
GetIdentity() Identity
|
||||||
Reset()
|
Reset()
|
||||||
getIdentities() Identities
|
forEeachIdentity(func(id Identity) bool) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type ManagerOption func(m *identityManager)
|
type ManagerOption func(m *identityManager)
|
||||||
|
@ -301,8 +293,9 @@ type identityManager struct {
|
||||||
|
|
||||||
// mu protects _changes_ to this manager,
|
// mu protects _changes_ to this manager,
|
||||||
// reads currently assumes no concurrent writes.
|
// reads currently assumes no concurrent writes.
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
ids Identities
|
ids Identities
|
||||||
|
forEachIds []ForEeachIdentityProvider
|
||||||
|
|
||||||
// Hooks used in debugging.
|
// Hooks used in debugging.
|
||||||
onAddIdentity func(id Identity)
|
onAddIdentity func(id Identity)
|
||||||
|
@ -312,7 +305,7 @@ func (im *identityManager) AddIdentity(ids ...Identity) {
|
||||||
im.mu.Lock()
|
im.mu.Lock()
|
||||||
|
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
if id == Anonymous {
|
if id == nil || id == Anonymous {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, found := im.ids[id]; !found {
|
if _, found := im.ids[id]; !found {
|
||||||
|
@ -325,6 +318,12 @@ func (im *identityManager) AddIdentity(ids ...Identity) {
|
||||||
im.mu.Unlock()
|
im.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (im *identityManager) AddIdentityForEach(ids ...ForEeachIdentityProvider) {
|
||||||
|
im.mu.Lock()
|
||||||
|
im.forEachIds = append(im.forEachIds, ids...)
|
||||||
|
im.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
func (im *identityManager) ContainsIdentity(id Identity) FinderResult {
|
func (im *identityManager) ContainsIdentity(id Identity) FinderResult {
|
||||||
if im.Identity != Anonymous && id == im.Identity {
|
if im.Identity != Anonymous && id == im.Identity {
|
||||||
return FinderFound
|
return FinderFound
|
||||||
|
@ -355,10 +354,20 @@ func (im *identityManager) String() string {
|
||||||
return fmt.Sprintf("IdentityManager(%s)", im.name)
|
return fmt.Sprintf("IdentityManager(%s)", im.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(bep) these identities are currently only read on server reloads
|
func (im *identityManager) forEeachIdentity(fn func(id Identity) bool) bool {
|
||||||
// so there should be no concurrency issues, but that may change.
|
// The absense of a lock here is debliberate. This is currently opnly used on server reloads
|
||||||
func (im *identityManager) getIdentities() Identities {
|
// in a single-threaded context.
|
||||||
return im.ids
|
for id := range im.ids {
|
||||||
|
if fn(id) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, fe := range im.forEachIds {
|
||||||
|
if fe.ForEeachIdentity(fn) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
type nopManager int
|
type nopManager int
|
||||||
|
@ -366,6 +375,9 @@ type nopManager int
|
||||||
func (m *nopManager) AddIdentity(ids ...Identity) {
|
func (m *nopManager) AddIdentity(ids ...Identity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *nopManager) AddIdentityForEach(ids ...ForEeachIdentityProvider) {
|
||||||
|
}
|
||||||
|
|
||||||
func (m *nopManager) IdentifierBase() string {
|
func (m *nopManager) IdentifierBase() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -377,8 +389,8 @@ func (m *nopManager) GetIdentity() Identity {
|
||||||
func (m *nopManager) Reset() {
|
func (m *nopManager) Reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *nopManager) getIdentities() Identities {
|
func (m *nopManager) forEeachIdentity(func(id Identity) bool) bool {
|
||||||
return nil
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns whether further walking should be terminated.
|
// returns whether further walking should be terminated.
|
||||||
|
@ -401,11 +413,9 @@ func walkIdentities(v any, level int, deep bool, seen map[Identity]bool, cb func
|
||||||
|
|
||||||
if deep {
|
if deep {
|
||||||
if m := GetDependencyManager(id); m != nil {
|
if m := GetDependencyManager(id); m != nil {
|
||||||
for id2 := range m.getIdentities() {
|
m.forEeachIdentity(func(id2 Identity) bool {
|
||||||
if walkIdentitiesShallow(id2, level+1, cbRecursive) {
|
return walkIdentitiesShallow(id2, level+1, cbRecursive)
|
||||||
return true
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -420,6 +430,9 @@ func walkIdentitiesShallow(v any, level int, cb func(level int, id Identity) boo
|
||||||
if id == Anonymous {
|
if id == Anonymous {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if id == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return cb(level, id)
|
return cb(level, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -171,7 +171,7 @@ func (fd *ResourceSourceDescriptor) init(r *Spec) error {
|
||||||
fd.MediaType = mediaType
|
fd.MediaType = mediaType
|
||||||
|
|
||||||
if fd.DependencyManager == nil {
|
if fd.DependencyManager == nil {
|
||||||
fd.DependencyManager = identity.NopManager
|
fd.DependencyManager = r.Cfg.NewIdentityManager("resource")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -39,7 +39,7 @@ func newResourceCache(rs *Spec, memCache *dynacache.Cache) *ResourceCache {
|
||||||
cacheResources: dynacache.GetOrCreatePartition[string, resource.Resources](
|
cacheResources: dynacache.GetOrCreatePartition[string, resource.Resources](
|
||||||
memCache,
|
memCache,
|
||||||
"/ress",
|
"/ress",
|
||||||
dynacache.OptionsPartition{ClearWhen: dynacache.ClearOnChange, Weight: 40},
|
dynacache.OptionsPartition{ClearWhen: dynacache.ClearOnRebuild, Weight: 40},
|
||||||
),
|
),
|
||||||
cacheResourceTransformation: dynacache.GetOrCreatePartition[string, *resourceAdapterInner](
|
cacheResourceTransformation: dynacache.GetOrCreatePartition[string, *resourceAdapterInner](
|
||||||
memCache,
|
memCache,
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/hugio"
|
"github.com/gohugoio/hugo/common/hugio"
|
||||||
|
"github.com/gohugoio/hugo/identity"
|
||||||
"github.com/gohugoio/hugo/media"
|
"github.com/gohugoio/hugo/media"
|
||||||
"github.com/gohugoio/hugo/resources"
|
"github.com/gohugoio/hugo/resources"
|
||||||
"github.com/gohugoio/hugo/resources/resource"
|
"github.com/gohugoio/hugo/resources/resource"
|
||||||
|
@ -86,13 +87,32 @@ func (c *Client) Concat(targetPath string, r resource.Resources) (resource.Resou
|
||||||
|
|
||||||
// The given set of resources must be of the same Media Type.
|
// The given set of resources must be of the same Media Type.
|
||||||
// We may improve on that in the future, but then we need to know more.
|
// We may improve on that in the future, but then we need to know more.
|
||||||
for i, r := range r {
|
for i, rr := range r {
|
||||||
if i > 0 && r.MediaType().Type != resolvedm.Type {
|
if i > 0 && rr.MediaType().Type != resolvedm.Type {
|
||||||
return nil, fmt.Errorf("resources in Concat must be of the same Media Type, got %q and %q", r.MediaType().Type, resolvedm.Type)
|
return nil, fmt.Errorf("resources in Concat must be of the same Media Type, got %q and %q", rr.MediaType().Type, resolvedm.Type)
|
||||||
}
|
}
|
||||||
resolvedm = r.MediaType()
|
resolvedm = rr.MediaType()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
idm := c.rs.Cfg.NewIdentityManager("concat")
|
||||||
|
// Add the concatenated resources as dependencies to the composite resource
|
||||||
|
// so that we can track changes to the individual resources.
|
||||||
|
idm.AddIdentityForEach(identity.ForEeachIdentityProviderFunc(
|
||||||
|
func(f func(identity.Identity) bool) bool {
|
||||||
|
var terminate bool
|
||||||
|
for _, rr := range r {
|
||||||
|
identity.WalkIdentitiesShallow(rr, func(depth int, id identity.Identity) bool {
|
||||||
|
terminate = f(id)
|
||||||
|
return terminate
|
||||||
|
})
|
||||||
|
if terminate {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return terminate
|
||||||
|
},
|
||||||
|
))
|
||||||
|
|
||||||
concatr := func() (hugio.ReadSeekCloser, error) {
|
concatr := func() (hugio.ReadSeekCloser, error) {
|
||||||
var rcsources []hugio.ReadSeekCloser
|
var rcsources []hugio.ReadSeekCloser
|
||||||
for _, s := range r {
|
for _, s := range r {
|
||||||
|
@ -136,6 +156,7 @@ func (c *Client) Concat(targetPath string, r resource.Resources) (resource.Resou
|
||||||
LazyPublish: true,
|
LazyPublish: true,
|
||||||
OpenReadSeekCloser: concatr,
|
OpenReadSeekCloser: concatr,
|
||||||
TargetPath: targetPath,
|
TargetPath: targetPath,
|
||||||
|
DependencyManager: idm,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -63,13 +63,6 @@ func (c *Client) Copy(r resource.Resource, targetPath string) (resource.Resource
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) newDependencyManager() identity.Manager {
|
|
||||||
if c.rs.Cfg.Running() {
|
|
||||||
return identity.NewManager("resources")
|
|
||||||
}
|
|
||||||
return identity.NopManager
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get creates a new Resource by opening the given pathname in the assets filesystem.
|
// Get creates a new Resource by opening the given pathname in the assets filesystem.
|
||||||
func (c *Client) Get(pathname string) (resource.Resource, error) {
|
func (c *Client) Get(pathname string) (resource.Resource, error) {
|
||||||
pathname = path.Clean(pathname)
|
pathname = path.Clean(pathname)
|
||||||
|
@ -79,7 +72,8 @@ func (c *Client) Get(pathname string) (resource.Resource, error) {
|
||||||
// The resource file will not be read before it gets used (e.g. in .Content),
|
// The resource file will not be read before it gets used (e.g. in .Content),
|
||||||
// so we need to check that the file exists here.
|
// so we need to check that the file exists here.
|
||||||
filename := filepath.FromSlash(pathname)
|
filename := filepath.FromSlash(pathname)
|
||||||
if _, err := c.rs.BaseFs.Assets.Fs.Stat(filename); err != nil {
|
fi, err := c.rs.BaseFs.Assets.Fs.Stat(filename)
|
||||||
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -87,14 +81,16 @@ func (c *Client) Get(pathname string) (resource.Resource, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pi := fi.(hugofs.FileMetaInfo).Meta().PathInfo
|
||||||
|
|
||||||
return c.rs.NewResource(resources.ResourceSourceDescriptor{
|
return c.rs.NewResource(resources.ResourceSourceDescriptor{
|
||||||
LazyPublish: true,
|
LazyPublish: true,
|
||||||
OpenReadSeekCloser: func() (hugio.ReadSeekCloser, error) {
|
OpenReadSeekCloser: func() (hugio.ReadSeekCloser, error) {
|
||||||
return c.rs.BaseFs.Assets.Fs.Open(filename)
|
return c.rs.BaseFs.Assets.Fs.Open(filename)
|
||||||
},
|
},
|
||||||
GroupIdentity: identity.StringIdentity(key),
|
Path: pi,
|
||||||
DependencyManager: c.newDependencyManager(),
|
GroupIdentity: pi,
|
||||||
TargetPath: pathname,
|
TargetPath: pathname,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue