🎭Expressions
On the previous pages we talked a lot about printable expressions and regular expressions. So let's clarify what exactly an expression is.
An expression is a Go expression that yields a value. Valid Expressions are:
A variable or constant
A Go expression/literal like
"abc"
,123
, or"abc" == "def"
.A function call that returns a single value
Additionally, corgi adds a ternary and a "chain" expression. More on that below. And, of course, string interpolation, which you already learned about in the interpolation chapter.
Implementation Note: While you can use `backtick strings
` you cannot span them over multiple lines like you can in Go. Depending on your use case there are better alternatives.
Printable Types
If your expression gets printed, e.g. the value of an attribute, you are restricted to these printable types:
uint
,uint8
,uint16
,uint32
,uint64
int
,int8
,int16
,int32
,int64
string
bool
corgi.HTML
,corgi.HTMLAttr
,corgi.CSS
,corgi.URL
,corgi.Srcset
,corgi.JS
,corgi.JSStr,
andcorgi.JSAttr
in their appropriate contexts (more on them in the Security and Escaping chapter)Any type implementing
fmt.Stringer
A pointer to the above
A (possibly typed)
nil
which will print nothing
If you try to print something else, the generated function will return with an error.
Ternary Expressions
Aside from the expressions listed above, corgi also supports ternary expressions in the form of a special ternary function.
func ?[T any](cond bool, ifTrue, ifFalse T) T
ifTrue
and ifFalse
can be any Go expression.
cond
can be any Go expression that yields a bool
value.
func Ternary(authenticated, admin bool)
p(class = ?(authenticated, "authed", "anon"))
p(class = ?(authenticated, ?(admin, "admin", "authed"), "anon"))
p(class = ?(authenticated && admin, "admin", "not-admin"))
p= ?(admin, "Hello boss!", "Hello someone.")
p Hello, #{?(admin, "boss", "someone")}.
p #{"Hello " + ?(admin, "boss", "someone")}.
Ternary expressions can be used anywhere in a regular Go expression.
Chain Expressions
You know the problem: You want to access data.MyField.MySlice[3]
, but MyField
could be nil
, and MySlice
might not even be of length 3
. The solution is to add if
s that check nilness, length, and map indexes.
However, checks like these create an enormous bloat in template files. This is why corgi adds a second utility that allows you to perform those kind of checks. You can use a ?
to perform zero value checks, length/index checks, and checked type assertions for any expression that consists of only accessing fields/maps/slices.
// This checks if foo is not zero:
p #{foo?[0]}
// This checks if foo has an index or map entry 0:
p #{foo[0?]}
// This checks if that entry is not zero:
p #{foo[0]?}
// This performs a checked types assertion on that entry:
p #{foo[0].(string)?}
// And this performs all four checks:
p #{foo?[0?]?.(string)?}
You can also add a default if your checks fail by appending a ~
. Defaults can be any Go expression.
p #{foo? ~ "some default value"}
Depending on the context you use chain expressions, they behave differently:
When assigning a value to an attribute or setting the value of a mixin argument, that attribute or mixin arg will only be set if the value passes all checks. For required arguments you need to provide a default.
When used in interpolation and a check fails, nothing will be printed.
When used as condition for an
if
, theif
will only be executed if all checks pass.When used as a range expression in a
for
loop, that loop will only be executed if the value passes all checks.
If the expression yields a pointer that you want to dereference, you can place an *
at the start of the expression to dereference the resolved value. Defaults are assumed to be already dereferenced.
mixin foo(bar string) #{bar}
- s := "foo"
s1 := &s
foo(bar=*s1? ~ "baz")
Here are a couple more examples, just to make sure you undestand everything:
mixin greet(name="world")
Hello #{name}!
- var t struct{F *string}
a(href=*t.F?) Click me!
a(href=*t.F? ~ "bar") Click me!
p #{t.F?}
p #+greet(name=*t.F?)
p #+greet(name=*t.F? ~ "universe")
p The value is: #{t.F?}
p The value is #{t.F? ~ "unknown"}
if t.F?
p The value is: #{t.F}
else
p The value is not accessible.
Last updated
Was this helpful?