mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-28 22:21:46 -05:00
Make build.writeStats a struct
So you can do ```toml [build.writeStats] tags = true classes = true ids = false ``` Fixes #11191
This commit is contained in:
parent
da98724bc8
commit
11ecea6106
7 changed files with 166 additions and 14 deletions
|
@ -82,7 +82,7 @@ type LoadConfigResult struct {
|
||||||
|
|
||||||
var defaultBuild = BuildConfig{
|
var defaultBuild = BuildConfig{
|
||||||
UseResourceCacheWhen: "fallback",
|
UseResourceCacheWhen: "fallback",
|
||||||
WriteStats: false,
|
WriteStats: WriteStats{},
|
||||||
|
|
||||||
CacheBusters: []CacheBuster{
|
CacheBusters: []CacheBuster{
|
||||||
{
|
{
|
||||||
|
@ -111,7 +111,8 @@ type BuildConfig struct {
|
||||||
|
|
||||||
// When enabled, will collect and write a hugo_stats.json with some build
|
// When enabled, will collect and write a hugo_stats.json with some build
|
||||||
// related aggregated data (e.g. CSS class names).
|
// 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
|
// Can be used to toggle off writing of the IntelliSense /assets/jsconfig.js
|
||||||
// file.
|
// file.
|
||||||
|
@ -121,6 +122,17 @@ type BuildConfig struct {
|
||||||
CacheBusters []CacheBuster
|
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 {
|
func (b BuildConfig) clone() BuildConfig {
|
||||||
b.CacheBusters = append([]CacheBuster{}, b.CacheBusters...)
|
b.CacheBusters = append([]CacheBuster{}, b.CacheBusters...)
|
||||||
return b
|
return b
|
||||||
|
@ -171,14 +183,26 @@ func (b *BuildConfig) CompileConfig(logger loggers.Logger) error {
|
||||||
|
|
||||||
func DecodeBuildConfig(cfg Provider) BuildConfig {
|
func DecodeBuildConfig(cfg Provider) BuildConfig {
|
||||||
m := cfg.GetStringMap("build")
|
m := cfg.GetStringMap("build")
|
||||||
|
|
||||||
b := defaultBuild.clone()
|
b := defaultBuild.clone()
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return b
|
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)
|
err := mapstructure.WeakDecode(m, &b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return defaultBuild
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
b.UseResourceCacheWhen = strings.ToLower(b.UseResourceCacheWhen)
|
b.UseResourceCacheWhen = strings.ToLower(b.UseResourceCacheWhen)
|
||||||
|
|
|
@ -475,7 +475,7 @@ func (h *HugoSites) writeBuildStats() error {
|
||||||
if h.ResourceSpec == nil {
|
if h.ResourceSpec == nil {
|
||||||
panic("h.ResourceSpec is nil")
|
panic("h.ResourceSpec is nil")
|
||||||
}
|
}
|
||||||
if !h.ResourceSpec.BuildConfig().WriteStats {
|
if !h.ResourceSpec.BuildConfig().WriteStats.Enabled() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -147,6 +147,15 @@ func (s *IntegrationTestBuilder) AssertFileContent(filename string, matches ...s
|
||||||
if match == "" || strings.HasPrefix(match, "#") {
|
if match == "" || strings.HasPrefix(match, "#") {
|
||||||
continue
|
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))
|
s.Assert(content, qt.Contains, match, qt.Commentf(m))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1162,6 +1162,89 @@ Some text.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
func TestClassCollectorStress(t *testing.T) {
|
||||||
statsFilename := "hugo_stats.json"
|
statsFilename := "hugo_stats.json"
|
||||||
defer os.Remove(statsFilename)
|
defer os.Remove(statsFilename)
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
|
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/config"
|
||||||
"github.com/gohugoio/hugo/helpers"
|
"github.com/gohugoio/hugo/helpers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -46,8 +47,9 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func newHTMLElementsCollector() *htmlElementsCollector {
|
func newHTMLElementsCollector(conf config.WriteStats) *htmlElementsCollector {
|
||||||
return &htmlElementsCollector{
|
return &htmlElementsCollector{
|
||||||
|
conf: conf,
|
||||||
elementSet: make(map[string]bool),
|
elementSet: make(map[string]bool),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,6 +95,8 @@ type htmlElement struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type htmlElementsCollector struct {
|
type htmlElementsCollector struct {
|
||||||
|
conf config.WriteStats
|
||||||
|
|
||||||
// Contains the raw HTML string. We will get the same element
|
// Contains the raw HTML string. We will get the same element
|
||||||
// several times, and want to avoid costly reparsing when this
|
// several times, and want to avoid costly reparsing when this
|
||||||
// is used for aggregated data only.
|
// is used for aggregated data only.
|
||||||
|
@ -113,7 +117,9 @@ func (c *htmlElementsCollector) getHTMLElements() HTMLElements {
|
||||||
for _, el := range c.elements {
|
for _, el := range c.elements {
|
||||||
classes = append(classes, el.Classes...)
|
classes = append(classes, el.Classes...)
|
||||||
ids = append(ids, el.IDs...)
|
ids = append(ids, el.IDs...)
|
||||||
tags = append(tags, el.Tag)
|
if c.conf.Tags {
|
||||||
|
tags = append(tags, el.Tag)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
classes = helpers.UniqueStringsSorted(classes)
|
classes = helpers.UniqueStringsSorted(classes)
|
||||||
|
@ -246,7 +252,7 @@ func (w *htmlElementsCollectorWriter) lexElementInside(resolve htmlCollectorStat
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse each collected element.
|
// Parse each collected element.
|
||||||
el, err := parseHTMLElement(s)
|
el, err := w.parseHTMLElement(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.err = err
|
w.err = err
|
||||||
return resolve
|
return resolve
|
||||||
|
@ -363,7 +369,13 @@ func htmlLexToEndOfComment(w *htmlElementsCollectorWriter) htmlCollectorStateFun
|
||||||
return htmlLexToEndOfComment
|
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)
|
tagName := parseStartTag(elStr)
|
||||||
|
|
||||||
|
@ -390,8 +402,13 @@ func parseHTMLElement(elStr string) (el htmlElement, err error) {
|
||||||
switch {
|
switch {
|
||||||
case strings.EqualFold(a.Key, "id"):
|
case strings.EqualFold(a.Key, "id"):
|
||||||
// There should be only one, but one never knows...
|
// 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:
|
default:
|
||||||
|
if !conf.Classes {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if classAttrRe.MatchString(a.Key) {
|
if classAttrRe.MatchString(a.Key) {
|
||||||
el.Classes = append(el.Classes, strings.Fields(a.Val)...)
|
el.Classes = append(el.Classes, strings.Fields(a.Val)...)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/config"
|
||||||
"github.com/gohugoio/hugo/config/testconfig"
|
"github.com/gohugoio/hugo/config/testconfig"
|
||||||
"github.com/gohugoio/hugo/media"
|
"github.com/gohugoio/hugo/media"
|
||||||
"github.com/gohugoio/hugo/minifiers"
|
"github.com/gohugoio/hugo/minifiers"
|
||||||
|
@ -136,7 +137,13 @@ func TestClassCollector(t *testing.T) {
|
||||||
} {
|
} {
|
||||||
|
|
||||||
c.Run(fmt.Sprintf("%s--minify-%t", test.name, variant.minify), func(c *qt.C) {
|
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 variant.minify {
|
||||||
if skipMinifyTest[test.name] {
|
if skipMinifyTest[test.name] {
|
||||||
c.Skip("skip minify test")
|
c.Skip("skip minify test")
|
||||||
|
@ -240,7 +247,13 @@ func BenchmarkElementsCollectorWriter(b *testing.B) {
|
||||||
</html>
|
</html>
|
||||||
`
|
`
|
||||||
for i := 0; i < b.N; i++ {
|
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)
|
fmt.Fprint(w, benchHTML)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -262,7 +275,13 @@ func BenchmarkElementsCollectorWriterPre(b *testing.B) {
|
||||||
<div class="foo"></div>
|
<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++ {
|
for i := 0; i < b.N; i++ {
|
||||||
fmt.Fprint(w, benchHTML)
|
fmt.Fprint(w, benchHTML)
|
||||||
|
|
||||||
|
|
|
@ -81,8 +81,8 @@ func NewDestinationPublisher(rs *resources.Spec, outputFormats output.Formats, m
|
||||||
fs := rs.BaseFs.PublishFs
|
fs := rs.BaseFs.PublishFs
|
||||||
cfg := rs.Cfg
|
cfg := rs.Cfg
|
||||||
var classCollector *htmlElementsCollector
|
var classCollector *htmlElementsCollector
|
||||||
if rs.BuildConfig().WriteStats {
|
if rs.BuildConfig().WriteStats.Enabled() {
|
||||||
classCollector = newHTMLElementsCollector()
|
classCollector = newHTMLElementsCollector(rs.BuildConfig().WriteStats)
|
||||||
}
|
}
|
||||||
pub = DestinationPublisher{fs: fs, htmlElementsCollector: classCollector}
|
pub = DestinationPublisher{fs: fs, htmlElementsCollector: classCollector}
|
||||||
pub.min, err = minifiers.New(mediaTypes, outputFormats, cfg)
|
pub.min, err = minifiers.New(mediaTypes, outputFormats, cfg)
|
||||||
|
|
Loading…
Reference in a new issue