diff --git a/hugolib/content_render_hooks_test.go b/hugolib/content_render_hooks_test.go index 5cb79f0506c..bcb18576e54 100644 --- a/hugolib/content_render_hooks_test.go +++ b/hugolib/content_render_hooks_test.go @@ -106,7 +106,7 @@ Inner Link: [Inner Link](https://www.google.com "Google's Homepage") title: With RenderString --- -{{< myshortcode5 >}}Inner Link: [Inner Link](https://www.google.com "Google's Homepage"){{< /myshortcode5 >}} +{{< myshortcode5 >}}Inner Link: [Inner Link](https://www.gohugo.io "Hugo's Homepage"){{< /myshortcode5 >}} `) b.Build(BuildCfg{}) @@ -123,7 +123,7 @@ SHORT3| // The regular markdownify func currently gets regular links. b.AssertFileContent("public/blog/p5/index.html", "Inner Link: Inner Link\n") - b.AssertFileContent("public/blog/p6/index.html", "
Inner Link: With RenderString|https://www.google.com|Title: Google's Homepage|Text: Inner Link|END
\n\nInner Link: With RenderString|https://www.gohugo.io|Title: Hugo's Homepage|Text: Inner Link|END
\n\nEDITED: https://www.google.com|
`, "SHORT3_EDITED|") b.AssertFileContent("public/blog/p2/index.html", `PARTIAL1_EDITED`) b.AssertFileContent("public/blog/p3/index.html", `PARTIAL3_EDITED`) b.AssertFileContent("public/docs/docs1/index.html", `DOCS EDITED: https://www.google.com|`) b.AssertFileContent("public/blog/p4/index.html", `IMAGE EDITED: /images/Dragster.jpg|`) - b.AssertFileContent("public/blog/p6/index.html", "FOO") + b.AssertFileContent("public/blog/p6/index.html", "Inner Link: EDITED: https://www.gohugo.io|
") } diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go index 479ff4bcd2f..2368d7e4f6c 100644 --- a/hugolib/hugo_sites.go +++ b/hugolib/hugo_sites.go @@ -807,43 +807,28 @@ func (h *HugoSites) findPagesByKindIn(kind string, inPages page.Pages) page.Page return h.Sites[0].findPagesByKindIn(kind, inPages) } -func (h *HugoSites) resetPageStateFromEvents(ids identity.Identities) { - idset := ids.ToIdentitySet() - hasIdentify := func(v interface{}) bool { - if id, ok := v.(identity.Provider); ok { - if idset[id.GetIdentity()] { - return true - } - } - if idp, ok := v.(identity.IdentitiesProvider); ok { - for id, _ := range idp.GetIdentities() { - if idset[id.GetIdentity()] { - return true - } - } - } - return false - } +func (h *HugoSites) resetPageStateFromEvents(idset identity.Identities) { for _, s := range h.Sites { PAGES: for _, p := range s.rawAllPages { OUTPUTS: for _, po := range p.pageOutputs { - if c := po.cp; c != nil { - if converted := c.convertedResult; converted != nil { - if hasIdentify(converted) { - c.Reset() - p.forceRender = true - continue OUTPUTS - } + if po.cp == nil { + continue + } + for id, _ := range idset { + if po.cp.dependencyTracker.Search(id) != nil { + po.cp.Reset() + p.forceRender = true + continue OUTPUTS } } } for _, s := range p.shortcodeState.shortcodes { - for _, id := range ids { - if s.info.Search(id.GetIdentity()) != nil { + for id, _ := range idset { + if s.info.Search(id) != nil { for _, po := range p.pageOutputs { if po.cp != nil { po.cp.Reset() diff --git a/hugolib/page.go b/hugolib/page.go index ce28988fff0..d9695fbe7c1 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -567,6 +567,7 @@ func (p *pageState) AlternativeOutputFormats() page.OutputFormats { return o } +// TODO1 option: map: display: inline/block (default inline) func (p *pageState) RenderString(in interface{}) (template.HTML, error) { s, err := cast.ToStringE(in) if err != nil { diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go index 91d5db8d9c8..e94fcb88d3f 100644 --- a/hugolib/page__per_output.go +++ b/hugolib/page__per_output.go @@ -23,6 +23,8 @@ import ( "sync" "unicode/utf8" + "github.com/gohugoio/hugo/identity" + "github.com/gohugoio/hugo/markup/converter/hooks" "github.com/gohugoio/hugo/markup/converter" @@ -60,14 +62,22 @@ var ( } ) +var pageContentOutputDependenciesID = identity.KeyValueIdentity{Key: "pageOutput", Value: "dependencies"} + func newPageContentOutput(p *pageState) func(po *pageOutput) (*pageContentOutput, error) { parent := p.init return func(po *pageOutput) (*pageContentOutput, error) { + var dependencyTracker identity.Manager + if p.s.running() { + dependencyTracker = identity.NewIdentityManager(pageContentOutputDependenciesID) + } + cp := &pageContentOutput{ - p: p, - f: po.f, + dependencyTracker: dependencyTracker, + p: p, + f: po.f, } initContent := func() (err error) { @@ -114,7 +124,8 @@ func newPageContentOutput(p *pageState) func(po *pageOutput) (*pageContentOutput if err != nil { return err } - cp.convertedResult = r + + cp.convertedResult = r // TODO1 avoid storing this cp.workContent = r.Bytes() if _, ok := r.(converter.TableOfContentsProvider); !ok { @@ -238,8 +249,9 @@ type pageContentOutput struct { // Content state - workContent []byte - convertedResult converter.Result + workContent []byte + convertedResult converter.Result + dependencyTracker identity.Manager // Set in server mode. // Temporary storage of placeholders mapped to their content. // These are shortcodes etc. Some of these will need to be replaced @@ -260,7 +272,16 @@ type pageContentOutput struct { readingTime int } +func (p *pageContentOutput) trackDependency(id identity.Provider) { + if p.dependencyTracker != nil { + p.dependencyTracker.Add(id) + } +} + func (p *pageContentOutput) Reset() { + if p.dependencyTracker != nil { + p.dependencyTracker.Reset() + } p.p.initOutputFormats() p.initMain.Reset() p.initPlain.Reset() @@ -351,12 +372,23 @@ func (p *pageContentOutput) setAutoSummary() error { func (cp *pageContentOutput) renderContent(content []byte, renderTOC bool) (converter.Result, error) { c := cp.p.getContentConverter() - return c.Convert( + r, err := c.Convert( converter.RenderContext{ Src: content, RenderTOC: renderTOC, RenderHooks: cp.renderHooks, }) + + if err == nil { + if ids, ok := r.(identity.IdentitiesProvider); ok { + for _, v := range ids.GetIdentities() { + cp.trackDependency(v) + } + } + } + + return r, err + } func (p *pageContentOutput) setWordCounts(isCJKLanguage bool) { diff --git a/hugolib/site.go b/hugolib/site.go index be1d178ba6d..7da1ed5dbcf 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -894,7 +894,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro events = s.filterFileEvents(events) events = s.translateFileEvents(events) - var changeIdentities identity.Identities + changeIdentities := make(identity.Identities) s.Log.DEBUG.Printf("Rebuild for events %q", events) @@ -922,7 +922,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro for _, ev := range events { id, found := s.eventToIdentity(ev) if found { - changeIdentities = append(changeIdentities, id) + changeIdentities[id] = id if assetsFilename := s.BaseFs.Assets.MakePathRelative(ev.Name); assetsFilename != "" { cachePartitions = append(cachePartitions, resources.ResourceKeyPartitions(assetsFilename)...) diff --git a/identity/identity.go b/identity/identity.go index d7be4ebba66..1386b968218 100644 --- a/identity/identity.go +++ b/identity/identity.go @@ -6,62 +6,42 @@ import ( "sync" ) -// NewIdentityManager creates a new Manager starting at root. -func NewIdentityManager(root Provider) Manager { +// NewIdentityManager creates a new Manager starting at id. +func NewIdentityManager(id Provider) Manager { return &identityManager{ - Provider: root, - children: make(Identities, 0), + Provider: id, + ids: Identities{id.GetIdentity(): id}, } } // NewPathIdentity creates a new Identity with the two identifiers // type and path. -func NewPathIdentity(typ, path string) PathIdentity { - path = strings.TrimPrefix(filepath.ToSlash(path), "/") - return PathIdentity{Type: typ, Path: path} +func NewPathIdentity(typ, pat string) PathIdentity { + pat = strings.TrimPrefix(filepath.ToSlash(pat), "/") + return PathIdentity{Type: typ, Path: pat} } // Identities stores identity providers. -type Identities []Provider - -// A set of identities. -type IdentitiesSet map[Identity]bool - -// ToIdentitySet creates a set of these Identities. -func (ids Identities) ToIdentitySet() map[Identity]bool { - m := make(map[Identity]bool) - for _, id := range ids { - m[id.GetIdentity()] = true - } - return m -} +type Identities map[Identity]Provider func (ids Identities) search(id Identity) Provider { + if v, found := ids[id]; found { + return v + } for _, v := range ids { - vid := v.GetIdentity() - - if vid == id { - return v - } - - if idsp, ok := v.(ChildIdentitiesProvider); ok { - if nested := idsp.GetChildIdentities().search(id); nested != nil { + switch t := v.(type) { + case IdentitiesProvider: + if nested := t.GetIdentities().search(id); nested != nil { return nested } } } - return nil } -// IdentitiesProvider provides Identities as a set. +// IdentitiesProvider provides all Identities. type IdentitiesProvider interface { - GetIdentities() IdentitiesSet -} - -// ChildIdentitiesProvider provides child Identities. -type ChildIdentitiesProvider interface { - GetChildIdentities() Identities + GetIdentities() Identities } // Identity represents an thing that can provide an identify. This can be @@ -73,10 +53,11 @@ type Identity interface { // Manager manages identities, and is itself a Provider of Identity. type Manager interface { - ChildIdentitiesProvider + IdentitiesProvider Provider Add(ids ...Provider) Search(id Identity) Provider + Reset() } // A PathIdentity is a common identity identified by a type and a path, e.g. "layouts" and "_default/single.html". @@ -95,6 +76,22 @@ func (id PathIdentity) Name() string { return id.Path } +// A KeyValueIdentity a general purpose identity. +type KeyValueIdentity struct { + Key string + Value string +} + +// GetIdentity returns itself. +func (id KeyValueIdentity) GetIdentity() Identity { + return id +} + +// Name returns the Key. +func (id KeyValueIdentity) Name() string { + return id.Key +} + // Provider provides the hashable Identity. type Provider interface { GetIdentity() Identity @@ -103,26 +100,30 @@ type Provider interface { type identityManager struct { sync.RWMutex Provider - children Identities + ids Identities } func (im *identityManager) Add(ids ...Provider) { im.Lock() - im.children = append(im.children, ids...) + for _, id := range ids { + im.ids[id.GetIdentity()] = id + } + im.Unlock() +} + +func (im *identityManager) Reset() { + im.Lock() + id := im.GetIdentity() + im.ids = Identities{id.GetIdentity(): id} im.Unlock() } -func (im *identityManager) GetChildIdentities() Identities { - return im.children +func (im *identityManager) GetIdentities() Identities { + return im.ids } func (im *identityManager) Search(id Identity) Provider { im.RLock() defer im.RUnlock() - if id == im.GetIdentity() { - return im - } - v := im.children.search(id) - - return v + return im.ids.search(id.GetIdentity()) } diff --git a/markup/converter/hooks/hooks.go b/markup/converter/hooks/hooks.go index c938753a160..de84a0357a6 100644 --- a/markup/converter/hooks/hooks.go +++ b/markup/converter/hooks/hooks.go @@ -31,15 +31,6 @@ type Render struct { ImageRenderer LinkRenderer } -func (h *Render) GetChildIdentities() identity.Identities { - var ids identity.Identities - if h.LinkRenderer != nil { - ids = append(ids, h.LinkRenderer) - } - return ids - -} - type LinkRenderer interface { Render(w io.Writer, ctx LinkContext) error identity.Provider diff --git a/markup/goldmark/convert.go b/markup/goldmark/convert.go index 1586acbb34d..3633fff013e 100644 --- a/markup/goldmark/convert.go +++ b/markup/goldmark/convert.go @@ -151,14 +151,14 @@ var _ identity.IdentitiesProvider = (*converterResult)(nil) type converterResult struct { converter.Result toc tableofcontents.Root - ids identity.IdentitiesSet + ids identity.Identities } func (c converterResult) TableOfContents() tableofcontents.Root { return c.toc } -func (c converterResult) GetIdentities() identity.IdentitiesSet { +func (c converterResult) GetIdentities() identity.Identities { return c.ids } @@ -176,7 +176,7 @@ type renderContextData interface { type renderContextDataHolder struct { rctx converter.RenderContext dctx converter.DocumentContext - ids map[identity.Identity]bool + ids identity.Manager } func (ctx *renderContextDataHolder) RenderContext() converter.RenderContext { @@ -188,11 +188,11 @@ func (ctx *renderContextDataHolder) DocumentContext() converter.DocumentContext } func (ctx *renderContextDataHolder) AddIdentity(id identity.Identity) { - if _, found := ctx.ids[id]; !found { - ctx.ids[id] = true - } + ctx.ids.Add(id) } +var goldmarkConverterIdentity = identity.KeyValueIdentity{Key: "goldmark", Value: "converter"} + func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result converter.Result, err error) { defer func() { if r := recover(); r != nil { @@ -218,7 +218,7 @@ func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result convert rcx := &renderContextDataHolder{ rctx: ctx, dctx: c.ctx, - ids: make(map[identity.Identity]bool), + ids: identity.NewIdentityManager(goldmarkConverterIdentity), } w := renderContext{ @@ -232,7 +232,7 @@ func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result convert return converterResult{ Result: buf, - ids: rcx.ids, + ids: rcx.ids.GetIdentities(), toc: pctx.TableOfContents(), }, nil diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go index 69ca2d800b4..f440c4a2466 100644 --- a/tpl/tplimpl/template.go +++ b/tpl/tplimpl/template.go @@ -200,10 +200,18 @@ func (t *templateHandler) Lookup(name string) (tpl.Template, bool) { } +func (t *templateHandler) getTemplateInfo(name string) (tpl.Info, bool) { + t.mu.Lock() + defer t.mu.Unlock() + info, found := t.templateInfo[name] + return info, found + +} + func (t *templateHandler) applyTemplateInfo(templ tpl.Template, found bool) (tpl.Template, bool) { if adapter, ok := templ.(*tpl.TemplateAdapter); ok { if adapter.Info.IsZero() { - if info, found := t.templateInfo[templ.Name()]; found { + if info, found := t.getTemplateInfo(templ.Name()); found { adapter.Info = info } } @@ -618,6 +626,9 @@ func (t *templateHandler) addTemplate(name, tpl string) error { } func (t *templateHandler) getOrCreateTemplateInfo(name string) tpl.Info { + t.mu.Lock() + defer t.mu.Unlock() + info, found := t.templateInfo[name] if found { return info