URI: 
       Add --printUnusedTemplates - hugo - [fork] hugo port for 9front
  HTML git clone git@git.drkhsh.at/hugo.git
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
   DIR README
   DIR LICENSE
       ---
   DIR commit f2e7b49acfaeab4e1a28cb1096f6461b555900fa
   DIR parent 923419d7fde2056f47668acb0981135bce543b7e
  HTML Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
       Date:   Tue, 15 Feb 2022 15:26:18 +0100
       
       Add --printUnusedTemplates
       
       Fixes #9502
       
       Diffstat:
         M commands/commands.go                |       1 +
         M commands/commands_test.go           |       1 +
         M commands/hugo.go                    |       8 +++++++-
         M docs/content/en/commands/hugo.md    |       1 +
         M docs/content/en/commands/hugo_mod.… |       1 +
         M docs/content/en/commands/hugo_new.… |       1 +
         M docs/content/en/commands/hugo_serv… |       1 +
         M tpl/template.go                     |       5 +++++
         M tpl/template_info.go                |       9 +++++----
         A tpl/tplimpl/integration_test.go     |      61 +++++++++++++++++++++++++++++++
         M tpl/tplimpl/template.go             |      79 +++++++++++++++++++++++++++++--
         M tpl/tplimpl/template_errors.go      |       8 ++++++++
       
       12 files changed, 167 insertions(+), 9 deletions(-)
       ---
   DIR diff --git a/commands/commands.go b/commands/commands.go
       @@ -306,6 +306,7 @@ func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
                cmd.Flags().BoolP("noChmod", "", false, "don't sync permission mode of files")
                cmd.Flags().BoolP("printI18nWarnings", "", false, "print missing translations")
                cmd.Flags().BoolP("printPathWarnings", "", false, "print warnings on duplicate target paths etc.")
       +        cmd.Flags().BoolP("printUnusedTemplates", "", false, "print warnings on unused templates.")
                cmd.Flags().StringVarP(&cc.cpuprofile, "profile-cpu", "", "", "write cpu profile to `file`")
                cmd.Flags().StringVarP(&cc.memprofile, "profile-mem", "", "", "write memory profile to `file`")
                cmd.Flags().BoolVarP(&cc.printm, "printMemoryUsage", "", false, "print memory usage to screen at intervals")
   DIR diff --git a/commands/commands_test.go b/commands/commands_test.go
       @@ -197,6 +197,7 @@ func TestFlags(t *testing.T) {
                                        "--renderToDisk",
                                        "--source=mysource",
                                        "--printPathWarnings",
       +                                "--printUnusedTemplates",
                                },
                                check: func(c *qt.C, sc *serverCmd) {
                                        c.Assert(sc, qt.Not(qt.IsNil))
   DIR diff --git a/commands/hugo.go b/commands/hugo.go
       @@ -31,6 +31,7 @@ import (
                "time"
        
                "github.com/gohugoio/hugo/hugofs/files"
       +        "github.com/gohugoio/hugo/tpl"
        
                "github.com/gohugoio/hugo/common/types"
        
       @@ -217,6 +218,7 @@ func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
                        "force",
                        "gc",
                        "printI18nWarnings",
       +                "printUnusedTemplates",
                        "invalidateCDN",
                        "layoutDir",
                        "logFile",
       @@ -501,7 +503,6 @@ func (c *commandeer) build() error {
                        return err
                }
        
       -        // TODO(bep) Feedback?
                if !c.h.quiet {
                        fmt.Println()
                        c.hugo().PrintProcessingStats(os.Stdout)
       @@ -513,6 +514,11 @@ func (c *commandeer) build() error {
                                        c.logger.Warnln("Duplicate target paths:", dupes)
                                }
                        }
       +
       +                unusedTemplates := c.hugo().Tmpl().(tpl.UnusedTemplatesProvider).UnusedTemplates()
       +                for _, unusedTemplate := range unusedTemplates {
       +                        c.logger.Warnf("Template %s is unused, source file %s", unusedTemplate.Name(), unusedTemplate.Filename())
       +                }
                }
        
                if c.h.buildWatch {
   DIR diff --git a/docs/content/en/commands/hugo.md b/docs/content/en/commands/hugo.md
       @@ -53,6 +53,7 @@ hugo [flags]
              --printI18nWarnings          print missing translations
              --printMemoryUsage           print memory usage to screen at intervals
              --printPathWarnings          print warnings on duplicate target paths etc.
       +      --printUnusedTemplates       print warnings on unused templates.
              --quiet                      build in quiet mode
              --renderToMemory             render to memory (only useful for benchmark testing)
          -s, --source string              filesystem path to read files relative from
   DIR diff --git a/docs/content/en/commands/hugo_mod.md b/docs/content/en/commands/hugo_mod.md
       @@ -49,6 +49,7 @@ See https://gohugo.io/hugo-modules/ for more information.
              --printI18nWarnings      print missing translations
              --printMemoryUsage       print memory usage to screen at intervals
              --printPathWarnings      print warnings on duplicate target paths etc.
       +      --printUnusedTemplates   print warnings on unused templates.
              --templateMetrics        display metrics about template executions
              --templateMetricsHints   calculate some improvement hints when combined with --templateMetrics
          -t, --theme strings          themes to use (located in /themes/THEMENAME/)
   DIR diff --git a/docs/content/en/commands/hugo_new.md b/docs/content/en/commands/hugo_new.md
       @@ -50,6 +50,7 @@ hugo new [path] [flags]
              --printI18nWarnings      print missing translations
              --printMemoryUsage       print memory usage to screen at intervals
              --printPathWarnings      print warnings on duplicate target paths etc.
       +      --printUnusedTemplates   print warnings on unused templates.
              --templateMetrics        display metrics about template executions
              --templateMetricsHints   calculate some improvement hints when combined with --templateMetrics
          -t, --theme strings          themes to use (located in /themes/THEMENAME/)
   DIR diff --git a/docs/content/en/commands/hugo_server.md b/docs/content/en/commands/hugo_server.md
       @@ -63,6 +63,7 @@ hugo server [flags]
              --printI18nWarnings      print missing translations
              --printMemoryUsage       print memory usage to screen at intervals
              --printPathWarnings      print warnings on duplicate target paths etc.
       +      --printUnusedTemplates   print warnings on unused templates.
              --renderToDisk           render to Destination path (default is render to memory & serve from there)
              --templateMetrics        display metrics about template executions
              --templateMetricsHints   calculate some improvement hints when combined with --templateMetrics
   DIR diff --git a/tpl/template.go b/tpl/template.go
       @@ -44,6 +44,11 @@ type TemplateFinder interface {
                TemplateLookupVariant
        }
        
       +// UnusedTemplatesProvider lists unused templates if the build is configured to track those.
       +type UnusedTemplatesProvider interface {
       +        UnusedTemplates() []FileInfo
       +}
       +
        // TemplateHandler finds and executes templates.
        type TemplateHandler interface {
                TemplateFinder
   DIR diff --git a/tpl/template_info.go b/tpl/template_info.go
       @@ -27,6 +27,11 @@ type Info interface {
                identity.Provider
        }
        
       +type FileInfo interface {
       +        Name() string
       +        Filename() string
       +}
       +
        type InfoManager interface {
                ParseInfo() ParseInfo
        
       @@ -65,10 +70,6 @@ func (info ParseInfo) IsZero() bool {
                return info.Config.Version == 0
        }
        
       -// Info holds some info extracted from a parsed template.
       -type Info1 struct {
       -}
       -
        type ParseConfig struct {
                Version int
        }
   DIR diff --git a/tpl/tplimpl/integration_test.go b/tpl/tplimpl/integration_test.go
       @@ -0,0 +1,61 @@
       +package tplimpl_test
       +
       +import (
       +        "path/filepath"
       +        "testing"
       +
       +        qt "github.com/frankban/quicktest"
       +        "github.com/gohugoio/hugo/hugolib"
       +        "github.com/gohugoio/hugo/tpl"
       +)
       +
       +func TestPrintUnusedTemplates(t *testing.T) {
       +        t.Parallel()
       +
       +        files := `
       +-- config.toml --
       +baseURL = 'http://example.com/'
       +printUnusedTemplates=true
       +-- content/p1.md --
       +---
       +title: "P1"
       +---
       +{{< usedshortcode >}}
       +-- layouts/baseof.html --
       +{{ block "main" . }}{{ end }}
       +-- layouts/baseof.json --
       +{{ block "main" . }}{{ end }}
       +-- layouts/index.html --
       +{{ define "main" }}FOO{{ end }}
       +-- layouts/_default/single.json --
       +-- layouts/_default/single.html --
       +{{ define "main" }}MAIN{{ end }}
       +-- layouts/post/single.html --
       +{{ define "main" }}MAIN{{ end }}
       +-- layouts/partials/usedpartial.html --
       +-- layouts/partials/unusedpartial.html --
       +-- layouts/shortcodes/usedshortcode.html --
       +{{ partial "usedpartial.html" }}
       +-- layouts/shortcodes/unusedshortcode.html --
       +
       +        `
       +
       +        b := hugolib.NewIntegrationTestBuilder(
       +                hugolib.IntegrationTestConfig{
       +                        T:           t,
       +                        TxtarString: files,
       +                        NeedsOsFS:   true,
       +                },
       +        )
       +        b.Build()
       +
       +        unused := b.H.Tmpl().(tpl.UnusedTemplatesProvider).UnusedTemplates()
       +
       +        var names []string
       +        for _, tmpl := range unused {
       +                names = append(names, tmpl.Name())
       +        }
       +
       +        b.Assert(names, qt.DeepEquals, []string{"_default/single.json", "baseof.json", "partials/unusedpartial.html", "post/single.html", "shortcodes/unusedshortcode.html"})
       +        b.Assert(unused[0].Filename(), qt.Equals, filepath.Join(b.Cfg.WorkingDir, "layouts/_default/single.json"))
       +}
   DIR diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go
       @@ -67,10 +67,11 @@ var embeddedTemplatesAliases = map[string][]string{
        }
        
        var (
       -        _ tpl.TemplateManager    = (*templateExec)(nil)
       -        _ tpl.TemplateHandler    = (*templateExec)(nil)
       -        _ tpl.TemplateFuncGetter = (*templateExec)(nil)
       -        _ tpl.TemplateFinder     = (*templateExec)(nil)
       +        _ tpl.TemplateManager         = (*templateExec)(nil)
       +        _ tpl.TemplateHandler         = (*templateExec)(nil)
       +        _ tpl.TemplateFuncGetter      = (*templateExec)(nil)
       +        _ tpl.TemplateFinder          = (*templateExec)(nil)
       +        _ tpl.UnusedTemplatesProvider = (*templateExec)(nil)
        
                _ tpl.Template = (*templateState)(nil)
                _ tpl.Info     = (*templateState)(nil)
       @@ -130,6 +131,11 @@ func newTemplateExec(d *deps.Deps) (*templateExec, error) {
                        funcMap[k] = v.Interface()
                }
        
       +        var templateUsageTracker map[string]templateInfo
       +        if d.Cfg.GetBool("printUnusedTemplates") {
       +                templateUsageTracker = make(map[string]templateInfo)
       +        }
       +
                h := &templateHandler{
                        nameBaseTemplateName: make(map[string]string),
                        transformNotFound:    make(map[string]*templateState),
       @@ -146,6 +152,8 @@ func newTemplateExec(d *deps.Deps) (*templateExec, error) {
                        layoutHandler:       output.NewLayoutHandler(),
                        layoutsFs:           d.BaseFs.Layouts.Fs,
                        layoutTemplateCache: make(map[layoutCacheKey]tpl.Template),
       +
       +                templateUsageTracker: templateUsageTracker,
                }
        
                if err := h.loadEmbedded(); err != nil {
       @@ -225,13 +233,72 @@ func (t *templateExec) Execute(templ tpl.Template, wr io.Writer, data interface{
                        defer t.Metrics.MeasureSince(templ.Name(), time.Now())
                }
        
       +        if t.templateUsageTracker != nil {
       +                if ts, ok := templ.(*templateState); ok {
       +                        t.templateUsageTrackerMu.Lock()
       +                        if _, found := t.templateUsageTracker[ts.Name()]; !found {
       +                                t.templateUsageTracker[ts.Name()] = ts.info
       +                        }
       +
       +                        if !ts.baseInfo.IsZero() {
       +                                if _, found := t.templateUsageTracker[ts.baseInfo.name]; !found {
       +                                        t.templateUsageTracker[ts.baseInfo.name] = ts.baseInfo
       +                                }
       +                        }
       +                        t.templateUsageTrackerMu.Unlock()
       +                }
       +        }
       +
                execErr := t.executor.Execute(templ, wr, data)
                if execErr != nil {
                        execErr = t.addFileContext(templ, execErr)
                }
       +
                return execErr
        }
        
       +// TODO1
       +func (t *templateExec) UnusedTemplates() []tpl.FileInfo {
       +        if t.templateUsageTracker == nil {
       +                return nil
       +        }
       +        var unused []tpl.FileInfo
       +
       +        for _, ti := range t.needsBaseof {
       +                if _, found := t.templateUsageTracker[ti.name]; !found {
       +                        unused = append(unused, ti)
       +                }
       +        }
       +
       +        for _, ti := range t.baseof {
       +                if _, found := t.templateUsageTracker[ti.name]; !found {
       +                        unused = append(unused, ti)
       +                }
       +        }
       +
       +        for _, ts := range t.main.templates {
       +                ti := ts.info
       +                if strings.HasPrefix(ti.name, "_internal/") {
       +                        continue
       +                }
       +                if strings.HasPrefix(ti.name, "partials/inline/pagination") {
       +                        // TODO(bep) we need to fix this. These are internal partials, but
       +                        // they may also be defined in the project, which currently could
       +                        // lead to some false negatives.
       +                        continue
       +                }
       +                if _, found := t.templateUsageTracker[ti.name]; !found {
       +                        unused = append(unused, ti)
       +                }
       +        }
       +
       +        sort.Slice(unused, func(i, j int) bool {
       +                return unused[i].Name() < unused[j].Name()
       +        })
       +
       +        return unused
       +}
       +
        func (t *templateExec) GetFunc(name string) (reflect.Value, bool) {
                v, found := t.funcs[name]
                return v, found
       @@ -285,6 +352,10 @@ type templateHandler struct {
                // Note that for shortcodes that same information is embedded in the
                // shortcodeTemplates type.
                templateInfo map[string]tpl.Info
       +
       +        // May be nil.
       +        templateUsageTracker   map[string]templateInfo
       +        templateUsageTrackerMu sync.Mutex
        }
        
        // AddTemplate parses and adds a template to the collection.
   DIR diff --git a/tpl/tplimpl/template_errors.go b/tpl/tplimpl/template_errors.go
       @@ -34,6 +34,14 @@ type templateInfo struct {
                realFilename string
        }
        
       +func (t templateInfo) Name() string {
       +        return t.name
       +}
       +
       +func (t templateInfo) Filename() string {
       +        return t.realFilename
       +}
       +
        func (t templateInfo) IsZero() bool {
                return t.name == ""
        }