I’m ending the year with something non-WordPress. But it has a weird JSON related journey.
I asked three questions at the end of my last post:
- How do I properly mimic a for-loop with range?
- Can I make a responsive gallery out of Hugo?
- Can I power a Hugo site with WordPress JSON?
This is the answer to the first one. However in the solving, I made a breakthrough in my head about how one calls data from JSON and structures it.
But I’m getting ahead of myself, becuase changing a for loop from Jekyll to Hugo was harder than I expected. Way harder.
One of the things I check in some posts is what the rating is. I set a parameter called ‘rating’ and if it’s there, I ran a quick check to determine how many stars to show:
<strong>Rating:</strong> {% if page.rating %}{% for i in (1..page.rating) %}<i style="color:gold;" class="fa fa-star" name="star"></i>{% endfor %}{% if 5 > page.rating %}{% assign greystar = 5 | minus: page.rating %}{% for i in (1..greystar) %}<i style="color:grey;" class="fa fa-star" name="star"></i>{% endfor %}{% endif %}{% else %}<em>Not Available</em>{% endif %}
I was pretty damn proud of that loop. Check the rating, output a gold star for each number over 0 and a grey star for every number between the rating and 5. It’s simple but it’s quick.
Transposing from Liquid to GoLang was not as hard as all that. {% %}
became {{ }}
and {% endfor %}
became {{ end }}
and, at first, that was the easy stuff. The majority of my logic was a one-to-one translation. Change page.rating
to .Params.rating
and so on and so forth.
The order of the if’s was strange to me, in that it became {{ if eq A B }}
(except when it wasn’t) and I was used to thinking {% if A == B %}
— it was fairly easy to overcome. In short order, all my simple if-checks were done.
But those damn loops! I had the ugliest “if A == 1, show 1 gold and 4 grey” configuration. And worse, I had cases where I was checking “If there’s an entry in the Filmography for this show, get the rating from that and not the page itself.” The Filmography file was a straight-forward JSON file, you see (told you JSON was involved).
Back to the first problem. I knew if I wanted a shortcode to say “Get me a list of all pages, by date, where section is news” I did this:
{{ range where $.Page.Site.Pages.ByDate "Section" "news" }}
And that $.Page.Site.Pages.ByDate
call changed depending on what template I was on and what I was calling. For a shortcode I had to pass though page to site to pages. On a template I could do .Data.Pages.ByTitle
(no $
needed either). I’m still at the trial and error stage of figuring out which call and where, but I know I’ll get there. It’s just mastering new syntax and understanding where I’m calling from and what variables are available to me.
That’s really okay. It took me years to visualize the interrelations with WordPress themes and functions and plugins, and even then sometimes I’ll output an array in ugly, unformatted ways just to read through and make sure I understand what I’ve got at my disposal. This is normal for most of us.
And it was fairly clear that if I wanted a shortcode to call the data file and not a list of Section pages, there was code for that: $.Page.Site.Data.filmography
(back to Filmography again). And that worked fine. Right up until I wanted to say “For all values where X equals Foo…” and I was back to my loop-headache.
Now. GoLang does have a for loop!
func main() { sum := 0 for i := 0; i < 10; i++ { sum += i } fmt.Println(sum) }
And I though that I could just use that. Nope! So I asked myself why did {{ for i in (1..Params.rating) }}
fail?
First of all, that ‘in’ should be :=
instead. But even so, when I ran it I got “ERROR: function “for” not defined.” That means there is no for
function. And no loop. After looking at how I was iterating in other places, I did this:
{{ range $index, $element := .Params.rating }} ONE {{ end }}
ERROR: 2015/12/12 template: theme/partials/rating.html:3:26: executing “theme/partials/rating.html” at <.Params.rating>: range can’t iterate over 3 in theme/partials/rating.html
The 3, in that case, had to do with a rating of 3. This makes a little sense, since you’re supposed to use range to iterate over a map, array or slice. A number is none of those.
Thankfully I found a ticket talking about adding a loop function to Hugo that was addressing what I was trying to do and finally I had an answer:
{{ range $star, seq .Params.rating }} <i style="color:gold;" class="fa fa-star" name="star"></i> {{ end }} {{ $greystar := sub 5 .Params.rating }} {{ range $star, seq $greystar }} <i style="color:grey;" class="fa fa-star" name="star"></i> {{ end }}
The idea there is that seq
makes a sequence of the value of .Params.rating
for me. If the value is 3, I get an array of [1,2,3]
to work with. And that’s something range
can itterate over!
Except…
at
<sub 5 .Params.rating>
: error calling sub: Can’t apply the operator to the values in theme/partials/rating.html
Now the amusing thing here is that the code worked! Even if it wasn’t seeing .Params.rating
as an integer, it did the math. I suspect it’s related to this old bug, where variables from front matter are strings and not integers. Except that he said it worked in YAML and that’s what I’m using.
This was the most perplexing issue. How is a number not a number if it’s being numbered? And then I noticed that I was able to make a sequence, so clearly at that point it knew it was a number. And then I did this:
{{ $goldstar := seq .Params.rating }} {{ $goldstar := len $goldstar }}
And yes it works.
I’m basically saying “Hey, how many ones are in the number X?” and saving that as a number. There’s no real reason I can comprehend why it failed to work, but there it is. I’ll file a bug as soon as I can figure out how to explain that in a way that doesn’t make me sound crazy.
How did all this teach me about JSON? You’ll have to wait a few days.