]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
tocss: Simplify the hugo:vars type handling
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 21 Feb 2023 17:32:09 +0000 (18:32 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Wed, 22 Feb 2023 12:26:10 +0000 (13:26 +0100)
Instead of maintaing a list of all CSS units and functions this commit:

* Uses 3 regexps to detect typed CSS values (e.g. `24px`) + properly handle numeric Go types.
* These regexps may have some false positives -- e.g. strings that needs to be quoted.
* For that rare case, you can mark the string with e.g. `"32xxx" | css.Quoted`
* For the opposite case:  `"32" | css.Unquoted`

Updates #10632

common/types/css/csstypes.go [new file with mode: 0644]
resources/resource_transformers/tocss/dartsass/client.go
resources/resource_transformers/tocss/dartsass/integration_test.go
resources/resource_transformers/tocss/internal/sass/cssValues.go
resources/resource_transformers/tocss/internal/sass/helpers.go
resources/resource_transformers/tocss/internal/sass/helpers_test.go [new file with mode: 0644]
resources/resource_transformers/tocss/scss/client.go
tpl/css/css.go [new file with mode: 0644]
tpl/tplimpl/template_funcs.go

diff --git a/common/types/css/csstypes.go b/common/types/css/csstypes.go
new file mode 100644 (file)
index 0000000..a31df00
--- /dev/null
@@ -0,0 +1,20 @@
+// Copyright 2023 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package css
+
+// QuotedString is a string that needs to be quoted in CSS.
+type QuotedString string
+
+// UnquotedString is a string that does not need to be quoted in CSS.
+type UnquotedString string
index 49cafb52d6b7b18150327dd3247e784514ee4aea..f45e6537abdb744887dc28db847435f94147645b 100644 (file)
@@ -135,7 +135,7 @@ type Options struct {
        // Vars will be available in 'hugo:vars', e.g:
        //     @use "hugo:vars";
        //     $color: vars.$color;
-       Vars map[string]string
+       Vars map[string]any
 }
 
 func decodeOptions(m map[string]any) (opts Options, err error) {
index e7ddd6e6f576cb33a60b67240c2be3e77aa30415..e7432f12bc5cabff36bd1440f5490d16e0b948b5 100644 (file)
@@ -387,6 +387,10 @@ color_hsl = "hsl(0, 0%, 100%)"
 dimension = "24px"
 percentage = "10%"
 flex = "5fr"
+name = "Hugo"
+url = "https://gohugo.io"
+integer = 32
+float = 3.14
 -- assets/scss/main.scss --
 @use "hugo:vars";
 @use "sass:meta";
@@ -397,8 +401,15 @@ flex = "5fr"
 @debug meta.type-of(vars.$dimension);
 @debug meta.type-of(vars.$percentage);
 @debug meta.type-of(vars.$flex);
+@debug meta.type-of(vars.$name);
+@debug meta.type-of(vars.$url);
+@debug meta.type-of(vars.$not_a_number);
+@debug meta.type-of(vars.$integer);
+@debug meta.type-of(vars.$float);
+@debug meta.type-of(vars.$a_number);
 -- layouts/index.html --
 {{ $vars := site.Params.sassvars}}
+{{ $vars = merge $vars (dict "not_a_number" ("32xxx" | css.Quoted) "a_number" ("234" | css.Unquoted) )}}
 {{ $cssOpts := (dict "transpiler" "dartsass" "vars" $vars ) }}
 {{ $r := resources.Get "scss/main.scss" |  toCSS $cssOpts }}
 T1: {{ $r.Content }}
@@ -418,5 +429,11 @@ T1: {{ $r.Content }}
        b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:6:0: number`)
        b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:7:0: number`)
        b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:8:0: number`)
+       b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:9:0: string`)
+       b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:10:0: string`)
+       b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:11:0: string`)
+       b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:12:0: number`)
+       b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:13:0: number`)
+       b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:14:0: number`)
 
 }
index b23c4fc0265db96c2a1e998940216aa076e5699c..410e89e04a62fca4bece4b4bcad93192789b542b 100644 (file)
@@ -15,65 +15,67 @@ package sass
 
 type cssValue struct {
        prefix []string
-       sufix  []string
+       suffix []string
 }
 
 var (
        cssValues = cssValue{
+               // https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
                prefix: []string{
                        "#",
-                       "rgb(",
+                       "attr(",
+                       "calc(",
+                       "clamp(",
                        "hsl(",
                        "hwb(",
-                       "lch(",
                        "lab(",
-                       "calc(",
-                       "min(",
+                       "lch(",
                        "max(",
+                       "min(",
                        "minmax(",
-                       "clamp(",
-                       "attr(",
+                       "rgb(",
                },
-               sufix: []string{
-                       "em",
-                       "ex",
+               // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Values_and_Units#dimensions
+               suffix: []string{
+                       "%",
                        "cap",
                        "ch",
-                       "ic",
-                       "rem",
-                       "lh",
-                       "rlh",
-                       "vw",
-                       "vh",
-                       "vi",
-                       "vb",
-                       "vmin",
-                       "vmax",
-                       "cqw",
+                       "cm",
+                       "cqb",
                        "cqh",
                        "cqi",
-                       "cqb",
-                       "cqmin",
                        "cqmax",
-                       "cm",
-                       "mm",
-                       "Q",
+                       "cqmin",
+                       "cqw",
+                       "deg",
+                       "dpcm",
+                       "dpi",
+                       "dppx",
+                       "em",
+                       "ex",
+                       "fr",
+                       "grad",
+                       "ic",
                        "in",
+                       "lh",
+                       "mm",
+                       "ms",
                        "pc",
                        "pt",
                        "px",
-                       "deg",
-                       "grad",
+                       "Q",
                        "rad",
-                       "turn",
+                       "rem",
+                       "rlh",
                        "s",
-                       "ms",
-                       "fr",
-                       "dpi",
-                       "dpcm",
-                       "dppx",
+                       "turn",
+                       "vb",
+                       "vh",
+                       "vi",
+                       "vmax",
+                       "vmin",
+                       "vw",
                        "x",
-                       "%",
                },
        }
 )
index e6f246b7c8031e5386aa5253a97812da318e17f4..f0425015710a08f9fdded6bbae1552611b3e238e 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright 2022 The Hugo Authors. All rights reserved.
+// Copyright 2023 The Hugo Authors. All rights reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -15,15 +15,18 @@ package sass
 
 import (
        "fmt"
+       "regexp"
        "sort"
        "strings"
+
+       "github.com/gohugoio/hugo/common/types/css"
 )
 
 const (
        HugoVarsNamespace = "hugo:vars"
 )
 
-func CreateVarsStyleSheet(vars map[string]string) string {
+func CreateVarsStyleSheet(vars map[string]any) string {
        if vars == nil {
                return ""
        }
@@ -35,35 +38,56 @@ func CreateVarsStyleSheet(vars map[string]string) string {
                if !strings.HasPrefix(k, "$") {
                        prefix = "$"
                }
-               // These variables can be a combination of Sass identifiers (e.g. sans-serif), which
-               // should not be quoted, and URLs et, which should be quoted.
-               // unquote() is knowing what to do with each.
-               // Use quoteVar() to check if the variables should be quoted or not.
-               if quoteVar(v) {
-                       varsSlice = append(varsSlice, fmt.Sprintf("%s%s: unquote(%q);", prefix, k, v))
-               } else {
-                       varsSlice = append(varsSlice, fmt.Sprintf("%s%s: %s;", prefix, k, v))
+
+               switch v.(type) {
+               case css.QuotedString:
+                       // Marked by the user as a string that needs to be quoted.
+                       varsSlice = append(varsSlice, fmt.Sprintf("%s%s: %q;", prefix, k, v))
+               default:
+                       if isTypedCSSValue(v) {
+                               // E.g. 24px, 1.5rem, 10%, hsl(0, 0%, 100%), calc(24px + 36px), #fff, #ffffff.
+                               varsSlice = append(varsSlice, fmt.Sprintf("%s%s: %v;", prefix, k, v))
+                       } else {
+                               // unquote will preserve quotes around URLs etc. if needed.
+                               varsSlice = append(varsSlice, fmt.Sprintf("%s%s: unquote(%q);", prefix, k, v))
+                       }
                }
        }
        sort.Strings(varsSlice)
        varsStylesheet = strings.Join(varsSlice, "\n")
 
+       fmt.Println(varsStylesheet)
+
+       fmt.Println(varsStylesheet)
+
        return varsStylesheet
 
 }
 
-func quoteVar(v string) bool {
-       v = strings.Trim(v, "\"")
-       for _, p := range cssValues.prefix {
-               if strings.HasPrefix(v, p) {
-                       return false
+var (
+       isCSSColor = regexp.MustCompile(`^#[0-9a-fA-F]{3,6}$`)
+       isCSSFunc  = regexp.MustCompile(`^([a-zA-Z-]+)\(`)
+       isCSSUnit  = regexp.MustCompile(`^([0-9]+)(\.[0-9]+)?([a-zA-Z-%]+)$`)
+)
+
+// isTypedCSSValue returns true if the given string is a CSS value that
+// we should preserve the type of, as in: Not wrap it in quotes.
+func isTypedCSSValue(v any) bool {
+       switch s := v.(type) {
+       case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, css.UnquotedString:
+               return true
+       case string:
+               if isCSSColor.MatchString(s) {
+                       return true
                }
-       }
-       for _, s := range cssValues.sufix {
-               if strings.HasSuffix(v, s) {
-                       return false
+               if isCSSFunc.MatchString(s) {
+                       return true
                }
+               if isCSSUnit.MatchString(s) {
+                       return true
+               }
+
        }
 
-       return true
+       return false
 }
diff --git a/resources/resource_transformers/tocss/internal/sass/helpers_test.go b/resources/resource_transformers/tocss/internal/sass/helpers_test.go
new file mode 100644 (file)
index 0000000..56e7373
--- /dev/null
@@ -0,0 +1,44 @@
+// Copyright 2023 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package sass
+
+import (
+       "testing"
+
+       qt "github.com/frankban/quicktest"
+)
+
+func TestIsUnquotedCSSValue(t *testing.T) {
+       c := qt.New(t)
+
+       for _, test := range []struct {
+               in  any
+               out bool
+       }{
+               {"24px", true},
+               {"1.5rem", true},
+               {"10%", true},
+               {"hsl(0, 0%, 100%)", true},
+               {"calc(24px + 36px)", true},
+               {"24xxx", true}, // a false positive.
+               {123, true},
+               {123.12, true},
+               {"#fff", true},
+               {"#ffffff", true},
+               {"#ffffffff", false},
+       } {
+               c.Assert(isTypedCSSValue(test.in), qt.Equals, test.out)
+       }
+
+}
index 0d027d8880bc1097007755b82623cae5a769501f..2028163ff2e50af538c3733811af684434a42c86 100644 (file)
@@ -63,7 +63,7 @@ type Options struct {
 
        // Vars will be available in 'hugo:vars', e.g:
        //     @import "hugo:vars";
-       Vars map[string]string
+       Vars map[string]any
 }
 
 func DecodeOptions(m map[string]any) (opts Options, err error) {
diff --git a/tpl/css/css.go b/tpl/css/css.go
new file mode 100644 (file)
index 0000000..e178333
--- /dev/null
@@ -0,0 +1,41 @@
+package css
+
+import (
+       "github.com/gohugoio/hugo/common/types/css"
+       "github.com/gohugoio/hugo/deps"
+       "github.com/gohugoio/hugo/tpl/internal"
+       "github.com/spf13/cast"
+)
+
+const name = "css"
+
+// Namespace provides template functions for the "css" namespace.
+type Namespace struct {
+}
+
+// Quoted returns a string that needs to be quoted in CSS.
+func (ns *Namespace) Quoted(v any) css.QuotedString {
+       s := cast.ToString(v)
+       return css.QuotedString(s)
+}
+
+// Unquoted returns a string that does not need to be quoted in CSS.
+func (ns *Namespace) Unquoted(v any) css.UnquotedString {
+       s := cast.ToString(v)
+       return css.UnquotedString(s)
+}
+
+func init() {
+       f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
+               ctx := &Namespace{}
+
+               ns := &internal.TemplateFuncsNamespace{
+                       Name:    name,
+                       Context: func(args ...any) (any, error) { return ctx, nil },
+               }
+
+               return ns
+       }
+
+       internal.AddTemplateFuncsNamespace(f)
+}
index e664bd6c5a904cc4c320141642d7d98d364ed34a..b8102c75d82b5200a3ce0d831201e5f82fad44c1 100644 (file)
@@ -36,6 +36,7 @@ import (
        _ "github.com/gohugoio/hugo/tpl/collections"
        _ "github.com/gohugoio/hugo/tpl/compare"
        _ "github.com/gohugoio/hugo/tpl/crypto"
+       _ "github.com/gohugoio/hugo/tpl/css"
        _ "github.com/gohugoio/hugo/tpl/data"
        _ "github.com/gohugoio/hugo/tpl/debug"
        _ "github.com/gohugoio/hugo/tpl/diagrams"