var defaultBuild = BuildConfig{
UseResourceCacheWhen: "fallback",
- WriteStats: false,
+ WriteStats: WriteStats{},
CacheBusters: []CacheBuster{
{
// When enabled, will collect and write a hugo_stats.json with some build
// related aggregated data (e.g. CSS class names).
- WriteStats bool
+ // Note that this was a bool <= v0.115.0.
+ WriteStats WriteStats
// Can be used to toggle off writing of the IntelliSense /assets/jsconfig.js
// file.
CacheBusters []CacheBuster
}
+// WriteStats configures what to write to the hugo_stats.json file.
+type WriteStats struct {
+ Tags bool
+ Classes bool
+ IDs bool
+}
+
+func (w WriteStats) Enabled() bool {
+ return w.Tags || w.Classes || w.IDs
+}
+
func (b BuildConfig) clone() BuildConfig {
b.CacheBusters = append([]CacheBuster{}, b.CacheBusters...)
return b
func DecodeBuildConfig(cfg Provider) BuildConfig {
m := cfg.GetStringMap("build")
+
b := defaultBuild.clone()
if m == nil {
return b
}
+ // writeStats was a bool <= v0.115.0.
+ if writeStats, ok := m["writestats"]; ok {
+ if bb, ok := writeStats.(bool); ok {
+ m["writestats"] = WriteStats{
+ Tags: bb,
+ Classes: bb,
+ IDs: bb,
+ }
+ }
+ }
+
err := mapstructure.WeakDecode(m, &b)
if err != nil {
- return defaultBuild
+ return b
}
b.UseResourceCacheWhen = strings.ToLower(b.UseResourceCacheWhen)
if h.ResourceSpec == nil {
panic("h.ResourceSpec is nil")
}
- if !h.ResourceSpec.BuildConfig().WriteStats {
+ if !h.ResourceSpec.BuildConfig().WriteStats.Enabled() {
return nil
}
if match == "" || strings.HasPrefix(match, "#") {
continue
}
+ var negate bool
+ if strings.HasPrefix(match, "! ") {
+ negate = true
+ match = strings.TrimPrefix(match, "! ")
+ }
+ if negate {
+ s.Assert(content, qt.Not(qt.Contains), match, qt.Commentf(m))
+ continue
+ }
s.Assert(content, qt.Contains, match, qt.Commentf(m))
}
}
}
}
+func TestClassCollectorConfigWriteStats(t *testing.T) {
+ r := func(writeStatsConfig string) *IntegrationTestBuilder {
+ files := `
+-- hugo.toml --
+WRITE_STATS_CONFIG
+-- layouts/_default/list.html --
+<div id="myid" class="myclass">Foo</div>
+
+`
+ files = strings.Replace(files, "WRITE_STATS_CONFIG", writeStatsConfig, 1)
+
+ b := NewIntegrationTestBuilder(
+ IntegrationTestConfig{
+ T: t,
+ TxtarString: files,
+ NeedsOsFS: true,
+ },
+ ).Build()
+
+ return b
+ }
+
+ // Legacy config.
+ b := r(`
+[build]
+writeStats = true
+`)
+
+ b.AssertFileContent("hugo_stats.json", "myclass", "div", "myid")
+
+ b = r(`
+[build]
+writeStats = false
+ `)
+
+ b.AssertDestinationExists("hugo_stats.json", false)
+
+ b = r(`
+[build.writeStats]
+tags = true
+classes = true
+ids = true
+ `)
+
+ b.AssertFileContent("hugo_stats.json", "myclass", "div", "myid")
+
+ b = r(`
+[build.writeStats]
+tags = true
+classes = true
+ids = false
+`)
+
+ b.AssertFileContent("hugo_stats.json", "myclass", "div", "! myid")
+
+ b = r(`
+[build.writeStats]
+tags = true
+classes = false
+ids = true
+`)
+
+ b.AssertFileContent("hugo_stats.json", "! myclass", "div", "myid")
+
+ b = r(`
+[build.writeStats]
+tags = false
+classes = true
+ids = true
+ `)
+
+ b.AssertFileContent("hugo_stats.json", "myclass", "! div", "myid")
+
+ b = r(`
+[build.writeStats]
+tags = false
+classes = false
+ids = false
+ `)
+ b.AssertDestinationExists("hugo_stats.json", false)
+
+}
+
func TestClassCollectorStress(t *testing.T) {
statsFilename := "hugo_stats.json"
defer os.Remove(statsFilename)
"golang.org/x/net/html"
+ "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
)
}
)
-func newHTMLElementsCollector() *htmlElementsCollector {
+func newHTMLElementsCollector(conf config.WriteStats) *htmlElementsCollector {
return &htmlElementsCollector{
+ conf: conf,
elementSet: make(map[string]bool),
}
}
}
type htmlElementsCollector struct {
+ conf config.WriteStats
+
// Contains the raw HTML string. We will get the same element
// several times, and want to avoid costly reparsing when this
// is used for aggregated data only.
for _, el := range c.elements {
classes = append(classes, el.Classes...)
ids = append(ids, el.IDs...)
- tags = append(tags, el.Tag)
+ if c.conf.Tags {
+ tags = append(tags, el.Tag)
+ }
}
classes = helpers.UniqueStringsSorted(classes)
}
// Parse each collected element.
- el, err := parseHTMLElement(s)
+ el, err := w.parseHTMLElement(s)
if err != nil {
w.err = err
return resolve
return htmlLexToEndOfComment
}
-func parseHTMLElement(elStr string) (el htmlElement, err error) {
+func (w *htmlElementsCollectorWriter) parseHTMLElement(elStr string) (el htmlElement, err error) {
+ conf := w.collector.conf
+
+ if !conf.IDs && !conf.Classes {
+ // Nothing to do.
+ return
+ }
tagName := parseStartTag(elStr)
switch {
case strings.EqualFold(a.Key, "id"):
// There should be only one, but one never knows...
- el.IDs = append(el.IDs, a.Val)
+ if conf.IDs {
+ el.IDs = append(el.IDs, a.Val)
+ }
default:
+ if !conf.Classes {
+ continue
+ }
if classAttrRe.MatchString(a.Key) {
el.Classes = append(el.Classes, strings.Fields(a.Val)...)
} else {
"testing"
"time"
+ "github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/testconfig"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/minifiers"
} {
c.Run(fmt.Sprintf("%s--minify-%t", test.name, variant.minify), func(c *qt.C) {
- w := newHTMLElementsCollectorWriter(newHTMLElementsCollector())
+ w := newHTMLElementsCollectorWriter(newHTMLElementsCollector(
+ config.WriteStats{
+ Tags: true,
+ Classes: true,
+ IDs: true,
+ },
+ ))
if variant.minify {
if skipMinifyTest[test.name] {
c.Skip("skip minify test")
</html>
`
for i := 0; i < b.N; i++ {
- w := newHTMLElementsCollectorWriter(newHTMLElementsCollector())
+ w := newHTMLElementsCollectorWriter(newHTMLElementsCollector(
+ config.WriteStats{
+ Tags: true,
+ Classes: true,
+ IDs: true,
+ },
+ ))
fmt.Fprint(w, benchHTML)
}
<div class="foo"></div>
`
- w := newHTMLElementsCollectorWriter(newHTMLElementsCollector())
+ w := newHTMLElementsCollectorWriter(newHTMLElementsCollector(
+ config.WriteStats{
+ Tags: true,
+ Classes: true,
+ IDs: true,
+ },
+ ))
for i := 0; i < b.N; i++ {
fmt.Fprint(w, benchHTML)
fs := rs.BaseFs.PublishFs
cfg := rs.Cfg
var classCollector *htmlElementsCollector
- if rs.BuildConfig().WriteStats {
- classCollector = newHTMLElementsCollector()
+ if rs.BuildConfig().WriteStats.Enabled() {
+ classCollector = newHTMLElementsCollector(rs.BuildConfig().WriteStats)
}
pub = DestinationPublisher{fs: fs, htmlElementsCollector: classCollector}
pub.min, err = minifiers.New(mediaTypes, outputFormats, cfg)