diff --git a/docs/content/en/functions/bool.md b/docs/content/en/functions/bool.md new file mode 100644 index 000000000..535d72c3b --- /dev/null +++ b/docs/content/en/functions/bool.md @@ -0,0 +1,48 @@ +--- +title: bool +linktitle: bool +description: Creates a `bool` from the argument passed into the function. +date: 2023-01-28 +publishdate: 2023-01-28 +lastmod: 2023-01-28 +categories: [functions] +menu: + docs: + parent: "functions" +keywords: [strings,boolean,bool] +signature: ["bool INPUT"] +workson: [] +hugoversion: +relatedfuncs: [truth] +deprecated: false +aliases: [] +--- + +Useful for turning ints, strings, and nil into booleans. + +``` +{{ bool "true" }} → true +{{ bool "false" }} → false + +{{ bool "TRUE" }} → true +{{ bool "FALSE" }} → false + +{{ truth "t" }} → true +{{ truth "f" }} → false + +{{ truth "T" }} → true +{{ truth "F" }} → false + +{{ bool "1" }} → true +{{ bool "0" }} → false + +{{ bool 1 }} → true +{{ bool 0 }} → false + +{{ bool true }} → true +{{ bool false }} → false + +{{ bool nil }} → false +``` + +This function will throw a type-casting error for most other types or strings. For less strict behavior, see [`truth`](/functions/truth). diff --git a/docs/content/en/functions/truth.md b/docs/content/en/functions/truth.md new file mode 100644 index 000000000..189335df9 --- /dev/null +++ b/docs/content/en/functions/truth.md @@ -0,0 +1,51 @@ +--- +title: truth +linktitle: truth +description: Creates a `bool` from the truthyness of the argument passed into the function +date: 2023-01-28 +publishdate: 2023-01-28 +lastmod: 2023-01-28 +categories: [functions] +menu: + docs: + parent: "functions" +keywords: [strings,boolean,bool,truthy,falsey] +signature: ["truth INPUT"] +workson: [] +hugoversion: +relatedfuncs: [bool] +deprecated: false +aliases: [] +--- + +Useful for turning different types into booleans based on their [truthy-ness](https://developer.mozilla.org/en-US/docs/Glossary/Truthy). + +``` +{{ truth "true" }} → true +{{ truth "false" }} → true + +{{ truth "TRUE" }} → true +{{ truth "FALSE" }} → true + +{{ truth "t" }} → true +{{ truth "f" }} → true + +{{ truth "T" }} → true +{{ truth "F" }} → true + +{{ truth "1" }} → true +{{ truth "0" }} → true + +{{ truth 1 }} → true +{{ truth 0 }} → false + +{{ truth true }} → true +{{ truth false }} → false + +{{ truth nil }} → false + +{{ truth "cheese" }} → true +{{ truth 1.67 }} → true +``` + +This function will not throw an error. For more strict behavior, see [`bool`](/functions/bool). diff --git a/tpl/cast/cast.go b/tpl/cast/cast.go index 1999ddc0a..56637794c 100644 --- a/tpl/cast/cast.go +++ b/tpl/cast/cast.go @@ -17,6 +17,7 @@ package cast import ( "html/template" + "github.com/gohugoio/hugo/common/hreflect" _cast "github.com/spf13/cast" ) @@ -45,6 +46,23 @@ func (ns *Namespace) ToFloat(v any) (float64, error) { return _cast.ToFloat64E(v) } +// ToBool converts v to a boolean. +func (ns *Namespace) ToBool(v any) (bool, error) { + v = convertTemplateToString(v) + result, err := _cast.ToBoolE(v) + if err != nil { + return false, nil + } + return result, nil +} + +// ToTruth yields the same behavior as ToBool when possible. +// If the cast is unsuccessful, ToTruth converts v to a boolean using the JavaScript [definition of truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy). +// Accordingly, it never yields an error, but maintains the signature of other cast methods for consistency. +func (ns *Namespace) ToTruth(v any) (bool, error) { + return hreflect.IsTruthful(v), nil +} + func convertTemplateToString(v any) any { switch vv := v.(type) { case template.HTML: diff --git a/tpl/cast/cast_test.go b/tpl/cast/cast_test.go index 5b4a36c3a..5f2f50241 100644 --- a/tpl/cast/cast_test.go +++ b/tpl/cast/cast_test.go @@ -117,3 +117,90 @@ func TestToFloat(t *testing.T) { c.Assert(result, qt.Equals, test.expect, errMsg) } } + +func TestToBool(t *testing.T) { + t.Parallel() + c := qt.New(t) + ns := New() + + for i, test := range []struct { + v any + expect any + error any + }{ + {"true", true, nil}, + {"false", false, nil}, + {"TRUE", true, nil}, + {"FALSE", false, nil}, + {"t", true, nil}, + {"f", false, nil}, + {"T", true, nil}, + {"F", false, nil}, + {"1", true, nil}, + {"0", false, nil}, + {1, true, nil}, + {0, false, nil}, + {true, true, nil}, + {false, false, nil}, + {nil, false, nil}, + + {"cheese", false, nil}, + {"", false, nil}, + } { + errMsg := qt.Commentf("[%d] %v", i, test.v) + + result, err := ns.ToBool(test.v) + + if b, ok := test.error.(bool); ok && !b { + c.Assert(err, qt.Not(qt.IsNil), errMsg) + continue + } + + c.Assert(err, qt.IsNil, errMsg) + c.Assert(result, qt.Equals, test.expect, errMsg) + } +} + +func TestToTruth(t *testing.T) { + t.Parallel() + c := qt.New(t) + ns := New() + + for i, test := range []struct { + v any + expect any + }{ + {"true", true}, + {"false", true}, + {"TRUE", true}, + {"FALSE", true}, + {"t", true}, + {"f", true}, + {"T", true}, + {"F", true}, + {"1", true}, + {"0", true}, + {1, true}, + {0, false}, + {"cheese", true}, + {"", false}, + {1.67, true}, + {template.HTML("2"), true}, + {template.CSS("3"), true}, + {template.HTMLAttr("4"), true}, + {template.JS("-5.67"), true}, + {template.JSStr("6"), true}, + {t, true}, + {nil, false}, + {"null", true}, + {"undefined", true}, + {"NaN", true}, + } { + errMsg := qt.Commentf("[%d] %v", i, test.v) + + result, err := ns.ToTruth(test.v) + + c.Assert(err, qt.IsNil, errMsg) + c.Assert(result, qt.Equals, test.expect, errMsg) + } +} diff --git a/tpl/cast/init.go b/tpl/cast/init.go index 84211a00b..3acc6423b 100644 --- a/tpl/cast/init.go +++ b/tpl/cast/init.go @@ -52,6 +52,24 @@ func init() { }, ) + ns.AddMethodMapping(ctx.ToBool, + []string{"bool"}, + [][2]string{ + {`{{ "0" | bool | printf "%T" }}`, `bool`}, + {`{{ "true" | bool }}`, `true`}, + {`{{ "false" | bool }}`, `false`}, + }, + ) + + ns.AddMethodMapping(ctx.ToTruth, + []string{"truth"}, + [][2]string{ + {`{{ "1234" | truth | printf "%T" }}`, `bool`}, + {`{{ "1234" | truth }}`, `true`}, + {`{{ "" | truth }}`, `false`}, + }, + ) + return ns }