Hugo: Add reflection-related template functions

Created on 14 Nov 2017  路  11Comments  路  Source: gohugoio/hugo

Most helpful comment

Proposal for reflect template functions

@bep @moorereason

Here's a very rough proposal. For many Hugo users, they would generally be interested in knowing if a variable is a string (doesn't matter if it is string, or template.HTML, or template.URL, ..), or a number (doesn't matter int vs int64 vs float64 vs ..), or a slice (so that range can be used), or a map (so that range $i, $j := .. can be used), or just knowing that something is a struct or an interface (to know enough that range cannot be used).

To a user wanting to know these things in a template, it's not important (please comment below if this is wrong) to know a specific Type and Kind. The functions added to Hugo template functions are user-level functions, and not for internal Go debugging.. it's just to debug the Hugo layout templates and such.

With that introduction, I would propose adding these functions:

  • reflect.IsBool
  • reflect.IsNumber (if this is too coarse, then may be reflect.IsInteger and reflect.IsFloat?)
  • reflect.IsString
  • reflect.IsTime
  • reflect.IsSlice
  • reflect.IsMap
  • reflect.IsStruct
  • reflect.IsInterface

All of these functions should return a boolean value.

All 11 comments

This issue has been automatically marked as stale because it has not had recent activity. The resources of the Hugo team are limited, and so we are asking for your help.
If this is a bug and you can still reproduce this error on the master branch, please reply with all of the information you have about it in order to keep the issue open.
If this is a feature request, and you feel that it is still relevant and valuable, please tell us why.
This issue will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions.

Oh no! Please keep this.

I have to resort to this:

{{ $value              := . }}
{{ $type               := (printf "%T" $value) }}
{{ $typeIsBool         := (eq "bool" $type) }}
{{ $typeIsNumber       := (or (eq "int" $type) (eq "int64" $type) (eq "float64" $type)) }}
{{ $typeIsString       := (eq "string" $type) }}
{{ $typeIsTime         := (eq "time.Time" $type) }}
{{ $typeIsSlice        := (findRE "^([[][]]|.*TaxonomyList|output\\.Formats|resource\\.Resources|\\*?hugolib\\.Menu$|\\*?hugolib\\.Pages$)" $type) }} <!-- match ^[] -->
{{ $typeIsMap          := (findRE "^(map[[].+[]]|.*SiteSocial|\\*hugolib\\.Menus|hugolib\\.AuthorList)" $type) }} <!-- match ^map[*] -->

{{ $typeIsSiteInfo     := (eq "*hugolib.SiteInfo" $type) }}
{{ $typeIsGitInfo      := (findRE "^.*gitmap\\.GitInfo" $type) }}
{{ $typeIsOutputFormat := (eq "output.Format" $type) }}
{{ $typeIsResource     := (findRE "^\\*resource" $type) }}
{{ $typeIsPage         := (findRE "^\\*hugolib\\.Page" $type) }}
{{ $typeIsFileInfo     := (findRE "^\\*(hugolib|source)\\.[fF]ileInfo$" $type) }} <!-- terms pages for e.g. uses source.FileInfo, regular pages using hugolib.fileInfo -->
{{ $typeIsMenuEntry    := (eq "*hugolib.MenuEntry" $type) }}
{{ $typeIsStruct       := (or $typeIsSiteInfo $typeIsGitInfo $typeIsOutputFormat $typeIsFileInfo $typeIsPage $typeIsMenuEntry) }}
{{ $typeIsInterface := (or $typeIsResource) }}

My current solution (above) is very hacky. As you see, there's no easy way to figure out if a variable is a slice/map/struct/interface.


Full code

Proposal for reflect template functions

@bep @moorereason

Here's a very rough proposal. For many Hugo users, they would generally be interested in knowing if a variable is a string (doesn't matter if it is string, or template.HTML, or template.URL, ..), or a number (doesn't matter int vs int64 vs float64 vs ..), or a slice (so that range can be used), or a map (so that range $i, $j := .. can be used), or just knowing that something is a struct or an interface (to know enough that range cannot be used).

To a user wanting to know these things in a template, it's not important (please comment below if this is wrong) to know a specific Type and Kind. The functions added to Hugo template functions are user-level functions, and not for internal Go debugging.. it's just to debug the Hugo layout templates and such.

With that introduction, I would propose adding these functions:

  • reflect.IsBool
  • reflect.IsNumber (if this is too coarse, then may be reflect.IsInteger and reflect.IsFloat?)
  • reflect.IsString
  • reflect.IsTime
  • reflect.IsSlice
  • reflect.IsMap
  • reflect.IsStruct
  • reflect.IsInterface

All of these functions should return a boolean value.

That sounds great, but couldn't we have a function returning the type? (similar to javascript's typeof).

This means we could both "retrieve" the type and test it in conjunction with eq. And this limit the addition to one single template function.

```
{{ with .Params.stuff }}
{{ if eq (getType .) "slice" }}
{{ range . }}
that code
{{ end }}
{{ end }}
{{ end}}

@regisphilibert That would be fine too, but I would still weigh on the reflect.Is.. functions as they look sweeter (I meant, a better syntactic sugar 馃槃).

It would be nicer to see {{ if reflect.IsSlice . }} instead of {{ if eq (getType .) "slice" }}. There too, if we go down the getType route, it would be good to add the reflect namespace, to prevent confusion with the Hugo types used in layouts and the variable Type (slice, map, etc.). But then it becomes even wordier..

{{ if reflect.IsSlice . }}

vs

{{ if eq (reflect.getType .) "slice" }}

Also, there are only handful data Types that we care about here.. so the effort to get all those reflect.Is.. should not be too bad.

@regisphilibert you can do similar with printf today, e.g. {{ if eq (printf "%T" .) "string" }}. But I think you will get a little dizzy about the myriad of types out there ...

As @kaushalmodi points out, you really do not care too much if it is a "slice of apples", just that it is a slice that you can iterate over, a map with keys/values etc.

But I think you will get a little dizzy about the myriad of types out there

He probably meant a "getKind", again not to confuse with page kinds :)

But yes, talking about the myriad of types, I am probably covering a veery small fraction of all Hugo types here ;-)

Yeah, I in the end, all we really need is a CanWeRangeOnThis function.

I agree the boolean version of @kaushalmodi is cleaner and I'd happily settle for a reflect.getType.

I have a subset of @kaushalmodi's proposal ready.

I'm omitting IsTime because I'm sticking with Kind tests, and that would be a Type test. Use printf for Type tests.

I'm omitting IsInterface because I'm not yet convinced of its usefulness. How would this be useful?

I currently have IsBool, IsMap, IsNumber, IsSlice, IsString, and IsStruct.

Any comments before I submit a PR?

Cc: @bep

IsSlice and IsMap have clear use cases. I'm not convinced about the "general usefulness" of the others? And note that @kaushalmodi 's "debug printer" does not fall into the "general useful" category.

What is IsStruct used for?

Was this page helpful?
0 / 5 - 0 ratings