Half-Elf on Tech

Thoughts From a Professional Lesbian

Tag: hugo

  • A Simplier Hugo Deploy

    A Simplier Hugo Deploy

    I have a Hugo site that I’ve been deploying by running Hugo on the server. But this isn’t the only way about it.

    If you use Git and it’s on the same server as your site, and owned by the same user, it’s remarkably easy to do this.

    First make sure the public folder in your Hugo repository is being tracked. Yes, this can make your repository a little large but that’s not something to worry about too much. Space is cheap, or it should be. Next make a folder in tmp – I called mine library – to store the Git output in.

    The new post-update code then looks like this:

    #!/bin/sh
    
    SRC_DIR=$HOME/tmp/library/public/
    DST_DIR=$HOME/public_html/library/
    
    export GIT_WORK_TREE=$HOME/tmp/library/
    git checkout -f
    
    rsync -a --delete $SRC_DIR $DST_DIR 
    
    exit
    

    What this does is checkout the Git repository and then copy it over. The format of the sync will delete anything not found. Done.

    The benefit of this method is that you don’t need to install GoLang or Hugo on your server, and everything is pure and simple Git and copy. Rsync is a delightful way to copy everything over as well. You can delete the temp folder when you’re done, but the checkout process handles things for you. Another nice trick is you can specify what branch to checkout, so if you have a special one for publishing, just use that.

    But could this be even easier? Yes and no. You see, what I’m going is checking out the whole thing and then copying over folders. What if I could tell Git to just checkout the code in that one folder?

    There’s a think called a ‘sparse checkout’ where in I can tell Git “Only checkout this folder.” Then all I have to do is go into that folder and checkout the content I wanted. The problem there is it literally checked out the folder ‘public’ and what I wanted was the content of the public folder. Which means while it’s ‘easier’ in that I’ve only checked out the code I need, I can’t just checkout it out into where I want. I will always have to have a little extra move.

    To set up my folder, I did this:

    cd ~/tmp/library/
    git init
    git remote add -f origin ~/repositories/library.git
    git config core.sparsecheckout true
    echo public/ >> .git/info/sparse-checkout
    git checkout master
    

    And then my script remains the same. But! This is going to be a faster checkout since it’s only ever going to be exporting and seeing the folders it needs.

  • WP JSON API Challenges

    WP JSON API Challenges

    In my frustration of my static process, I ripped everything out by it’s roots and decided to take things in a strict order.

    Decision: All-In-One or Separate?

    The first question was do I want to download everything as one big JSON file and split it out later, or do I want to, from the start, download each post as it’s own file? The time to download shouldn’t change, and if I do it right, I could script it to check if the date stamp on my downloaded file was older than the post date in WP. That means I could have this logic:

    if ( ! post file exists || post file date < post datestamp ) {
        download JSON for this post
    }
    

    That made it an easy win. Let’s split away from the original plan. Now my goal is to extract all the posts from my WP site in JSON format.

    Remember the goal here is to run a static website with WP. There are plugins that convert WP to static HTML, which you can then use to power a site, and this isn’t a bad idea.

    How To: Get all the posts from a WP site via the JSON API?

    This sounded easy. http://local.wordpress.dev//wp-json/wp/v2/posts listed posts, and the v1 comparison chart says that you can list all posts, it stands to reason that I can easily get all my posts.

    In a way, this is a WordPress problem. WP doesn’t want you to show all posts. It would make your site very slow if you had, say, 10,000 posts. Which means that the claim that the JSON API can lists all posts is true, it just doesn’t do them all at once.

    I happen to have 40 published posts on my test site, so when I GET with this URL, it loads all of them: http://local.wordpress.dev//wp-json/wp/v2/posts?per_page=100

    But that isn’t sustainable. And the old trick of making per_page equal to -1 didn’t work.

    I did determine that the JSON API actually knows what the amount of posts is!

    JSON reports X-WP-Total which, in my case is 40

    The header “X-WP-Total: 40” is what I needed. Of course, that is rather weird to get. It means to get all the files, I have to write ‘something’ that does this:

    1. Get http://local.wordpress.dev//wp-json/wp/v2/posts and parse it to get the value of X-WP-Total
    2. Get http://local.wordpress.dev//wp-json/wp/v2/posts?per_page=X-WP-Total

    Okay. So how?

    Well remember how I mentioned a plugin that made a static site already? I was talking about things like Really Static. Save a post, it saves the JSON file! Why not write this plugin?

    Why not use “Really Static”? Well I don’t want to have WP doing more than be my content generator. I want to separate content (the blog) from WP. And that means I will end up writing a plugin…

    Am I In Too Deep? Or Am I Over My Head?

    So far, this has been an incredible amount of work. It’s been a lot of two steps forward and three steps back, over and over and over. And yes, it’s been very frustrating. I’ve given up many times and, if my posts about multiple approaches are any indication, I’ve deleted and restarted many, many times.

    And I have to be honest here… I’m giving up on this overly ambitious plan right now.

    This marks two major failures (three if I could how insane the entire ‘use Jekyll/Hugo to run a gallery’ was). First, I’ve failed to sensibly convert JSON files to MD in a scriptable and reproducible way. Second, I’ve failed to export JSON from WordPress in a simple way. Both those failures can be worked around. I can build a plugin that exports the JSON. It’s not trivial, but having done similar things I’m sure I can do it. The issue is ‘is it worth it’? And right now I think the answer is no.

    I’m back to the drawing board right now, sketching up new plans and ideas. I want to eliminate as many steps as possible. I’d like to leave WordPress alone, let it be WordPress, and similarly let my static generator be static. These failures have taught me more about the interdependencies than I expected, and I’ve learned more from them than I may have if I’d had outright success from the start.

    While I’m starting over, again, I feel hopeful and less ‘I hate you, JSON’ than I thought I would. I now understand things more clearly and I see how I made things far, far more complicated for myself.

    The future of this project is, weirdly, bright.

  • Not Ditching ZenPhoto After All

    Not Ditching ZenPhoto After All

    When we last left our heros… We had taken a SQL database, converted it to JSON, split it into 885 separate JSON files, renamed and moved them based on the folder path previously assigned.

    That took a while, but now we’re ready for the fun!

    Convert them to MD

    I already know how to do this one. Run grunt-mustache-render!

    One problem. It didn’t like recursive folders. Pause for laughter. Thankfully I had done a cp and not an mv on my files with Mike’s script, so I just changed that to handle moving .md files instead of .json and it all went fine.

    Turn on Hugo

    I had a theme already so I copied it over and then started making my changes. I needed the layout to be a little different.

    First I needed a list of all the sections and files and I needed to grab all the items that were an ‘Index’ file and format them specially:

    <div id="albums">
    {{ range $section, $taxonomy := .Site.Sections }}{{ range $taxonomy.Pages }}{{if eq .Type "index" }}
    		<div class="indexalbum">
    			<div class="thumb"><a href="{{ .Permalink}}" title="View album: {{ replace $section "-" " " | title }}">IMAGE HERE</a></div>
    			<div class="albumdesc"><h3><a href="{{ .Permalink}}" title="View album: {{ replace $section "-" " " | title }}">{{ replace $section "-" " " | title }}</a></h3>
                {{ with .Params.desc }} {{ . | safeHTML}} {{ end }}</div>
    			<p style="clear: both; "></p>
    		</div>
    {{ end }}{{ end }}{{ end }}
    </div>
    

    I felt very pleased with myself at this point. Obviously I would need to replace “IMAGE HERE” with an image, but I had the basic idea down.

    Inside each post, there was a shortcode mentioned before, {{< gallery >}}, and this is where my drama started. What I wanted I thought would be (fairly) easy. Get a list of all the folders where this index.md file was and list them similar to what I had done before. They were sub-sections (in Hugo Terms), but as it happens, Hugo doesn’t have a way to handle those! There are no templates for subsections. Now this in and of itself wasn’t a deal breaker. I could made do with that shortcode except…

    Limitations

    The thing I really do like about ZenPhoto is that I can drop my new media in a folder and magically knows what’s up. Add a new folder? It knows it’s there. With Hugo (or Jekyll) I found it was incredible hard to do something as ‘simple’ as getting a list of all the items in a folder. With Jekyll I’d need a plugin (which I am certainly not adverse to). Hugo has a readDir call, but it’s limited to the working directory and I wanted to read the images in a CDN folder (on the same server, but still).

    Perhaps ironically, it’s easier to run a gallery with Hugo and Jekyll if you host the images off your own domain. It goes against my goal of having a self-hosted site, though. But for now, it’s not possibly to do what I wanted.

    Do I feel like I wasted time?

    No! In fact, I’ve learned a lot of things that tell me where the foibles are in my plans.

    ustwo actually did make a React powered site and in looking at their source code I see they did it by making the site ‘all one page.’ This is something I can understand today and wrap my head around. Because the alternative would be something like this:

    1. Write a post
    2. Run a script to grab all the pages via the JSON API as individual .json files
    3. Run a script to convert them into .md
    4. Build static site (test and then deploy)

    It’s not actually that horrible when I put it that way. It means everyone could write on WordPress and, when they’re ready to deploy, I just run my script, push to my dev site, validate, and push to live.

    But. That loses a lot of what makes WordPress cool. Editing posts on the fly, pushing live updates, and scheduling posts all becomes a nightmare. For my Gallery idea, it means that WordPress remains a viable option except for the fact that I can’t easily move from ZenPhoto to WordPress and WordPress behaves like a blog when I don’t want it to be one.

    I’m all about using WordPress for things other than blogging but the sad truth is it’s still structured like a blog. Custom Post Types are doing a great job, and a lot of what I do on my estore site is similar to how I’d want to handle a gallery. I could do posts as posts and organize them as categories, for example, and structure the whole theme to handle that…

    Or I could just stay on ZenPhoto, which does all that out of the box.

    And yes. That’s what I’m doing.

  • Ditching ZenPhoto for Hugo?

    Ditching ZenPhoto for Hugo?

    This started out as a 230 word bullet point list of how to do things. It’s now two entire posts!

    (Almost) Leaving ZenPhoto

    I have to preface this by telling you that I didn’t in the end. Yeah. I know. I planned to. And here’s why:

    I like ZenPhoto a lot. It’s a great Gallery tool, and after Gallery2 went to Gallery3 and I hated it, I was quite in love with them. But. They’ve started to inch towards being a general CMS and the whole ZenPhoto/ZenPhoto20 split left a weird taste in my mouth.

    Plus the DB I have is already 65 megs, which has started to behave a little oddly. Now I have 38,534 images in 885 albums, and to a degree that makes sense. If you think about it in WordPress terms, I have 885 taxonomies (tags and categories), and 38,534 posts. Given that each post in this example would be a sentence at most (describing the image), the WordPress DB would be around 15megs based on some napkin math I did. Validating this, there’s a WordPress Joke DB with 40k Jokes and it’s only 14 megs.

    Oh and did I mention that ZenPhoto has no export feature? I guess there’s a reason the first PHP script I wrote was a simple Gallery. All I really want is to display images in a structured format.

    But. As I said before, I did not leave ZenPhoto! Why not? Because I was unable to do what I wanted to in pure Hugo, and because porting everything over to WordPress is incredibly hard and complex.

    Why Not WordPress?

    WordPress has a lot of awesome advantages. It auto generates thumbnails for one, which Hugo does not, so porting the gallery over would save me one level of complication. But I have 38,534 images to import. No matter how you slice that, I’d have to import them and associate them with the right post. Probably manually.

    With a static generator, I can very easily assign a variable for each album to say ‘get images from this folder and display them.’ And if there are folders, list each folder as a link to another page. By contrast, I would have to use a plugin like NextGen gallery to do this in WordPress, and while I do like the plugin, it’s got the same issue that ZenPhoto has for me. It’s doing too much. And, to be honest, part of this process is to limit my potential security issues. Adding in really big, really complex plugins is the opposite of that, no matter how rigorously reviewed they are.

    Besides, if I hated it, I knew I could import MD files into WordPress. Someone already did it with Jekyll after all.

    Get and Grep the Data

    No matter what I needed the data. Thankfully I knew how to do this with phpMyAdmin already. I dropped the _albums table in JSON format. Why JSON? Well as I’d already learned, I could generate a Markdown file from a JSON pretty easily. Of course, I got stumped on having it loop through the JSON file, but I have a plan for this.

    On export, albums.json was 643 KB while images.json was 61.4 MB. And they were all one line. I’m not using images.json just yet.

    I tossed it into BBedit and scrubbed the data, removing everything I didn’t need. This did not change the file size at first, since I also made the JSON human readable. There was a lot of gripping here, but I took each entry from 16-22 items down to six: tags, categories, folder path, title, description, date. And the file went down to 143 KB. One-sixth, more or less.

    Finally I had to split each entry because, as I’d learned, trying to loop through a nested JSON array in grunt is complicated. So fine, I’ll do it manually. But how? Answer: By loving linux. You can split files!

    $ split -a 6 -l 8 ../albums.json album-
    

    There’s also csplit, which is contextual split. Either way, I now have 885 album-xxx.json files. Whew.

    Rename and Move the Files

    I messed around with linux commands to move files based on their folder paths and rename them based on the last variable. I dreaded having to just move them and then going to mess with the renames. So after two days, I asked Twitter and was saved by Mike Little and Kailey Lampert. Between them I learned that there was a jq tool for messing with JSON (homebrew install it) and that a shell script is a godsend.

    #! /bin/sh
    
    # awk script:
    # set field seperator to :
    # strip comma from field 2
    # strip quotes from field 2
    # strip lead space from field 2
    # strip trail space from field 2
    # print field 2
    
    for FILE in `ls *.json` ; do
        NAME=`cat $FILE | grep folder | awk 'BEGIN { FS= ":" } { 
            gsub( ",", "", $2 );   \
            gsub( "\"", "", $2 );  \
            gsub(/^[ \t]+/,"",$2); \
            gsub(/[ \t]+$/,"",$2); \
            print $2 
        }'`
        
        mkdir -p $NAME;
        cp "$FILE" "$NAME/index.json"
    done
    

    I ended up changing it to reduce future broken URLs. But basically everything except the mkdir comes from Mike. He’s my hero.

    To Be Continued

    Once I had all the files, it was time to change them all to Markdown files and whip out my templates!

    But you’ll need to wait for that because this post got really long.

  • Using JSON with Hugo

    Using JSON with Hugo

    I know I’ve been talking about Hugo a lot, but the whole reason I say and wrapped my head around it was that I wanted to make a website using JSON.

    Making something out of the JSON API in WordPress isn’t easy, and I decided to start with something hard. I thought I would make a Multisite network to be the back end of my site, and then call the JSON API to generate the content.

    Why?

    It doesn’t make my site (much) faster, though it makes it more cacheable. Mostly what it does is lets me separate church and state. I can use WordPress to write content, and then call it however I want. That would be Hugo. If I build the site via Hugo locally, then every time I generate the files it can pull in the data on the fly.

    Enable the API

    This is easy. Install WordPress 4.4 and install v2 of the Rest API plugin. I also installed Jetpack for reasons of using Calypso.

    You absolutely have to use the Rest API plugin. WordPress 4.4 adds in some of the bits, but the API plugin gives you the actual endpoints. You’ll need these later.

    Call the API

    This was also easy. Kind of. Making a jquery file that called the content was fairly straightforward but… Making it responsive and reactive was hard.

    What I really wanted to do was have some web app, js powered without node, that just called the data and kept the url structure. And for that what I need is a JSON client.

    It was incredibly hard to find how to make a static HTML website powered by JSON. I’d see Rachel Baker’s presentation on making a WordPress theme with the JSON API but that wasn’t what I wanted to do. I mean, in a way it was. The theme itself was what I wanted, but I didn’t want to run it with WordPress.

    I did a lot of research and finally stumbled onto restful.js by marmelab. I read their blog post on the product and it looked like what I wanted. I needed something that would run without Node.js since I wanted to do this on Apache.

    Consuming a RESTful Web Service Dynamically

    And this is when I starting banging my head against a table. None of the solutions I’d found were working for me. Nothing. Rachel was right. I was in a bit over my head. Oh she was nice enough not to say it that way, but they way she looked at me was very Jewish Mom of her.

    In order to stop feeling useless, I decided to learn Hugo. It had come up a few times on my search for a dynamic static site. Hugo has LiveReload which, if you’re familiar with most static generators, is pretty cool. The idea is the pages rebuild as you edit the content (or theme) files.

    If you run hugo server locally on your laptop, you can see this today. Hugo upped the ante by tossing in the ability to run it on the server and the ability to watch the data files. It also has the ability to call JSON remotely.

    I feel it’s important to note that you cannot trigger that LiveReload when you’re using external URLs. But anytime you edit a file and LiveReload is triggered, Hugo will read the URL content from your cache. If you’ve disabled cache, it will download it fresh. Don’t disable cache unless you have an unlimited API. And no, I don’t know how long the cache is good for.

    Sadly, OAuth or other authentication methods are not working yet, but for now that’s okay because I’d been in over my head for so long, I stepped back.

    Understanding JSON First

    Okay fine. Once I realized I’d tried to do too much at first, I went back to that static filmography I had.

      {
      	"role": "actor",
        "title": "White Christmas",
        "slug": "white-christmas",
        "type": "Movie",
        "character": "Phil Davis",
        "notes": null,
        "dates": 1954
      }
    

    This is basic stuff. A simple chunk by chunk. And to call the file in a shortcode, I did this:

    {{ range $.Page.Site.Data.filmography }}
    	{{ if eq $role .role }}
    		<li>{{ .title }}: <strong>{{ .character }}</strong> </li>
    	{{ end }}
    {{ end }}
    

    Actually I did a lot more but you get the idea. And if you’re wondering about the if statement at the top, it’s so I can pass a parameter to my shortcode:

    {{< filmography role="actor" >}}

    It took me longer than I’d like to figure that out. At first I had multiple filmography files (actor, writer, producer) and I was trying to look through them all. In the end, I realized this simple JSON and a simple call was better.

    A Little More JSON

    There was another case, though. I had lists of episodes compiled into separate JSON files, one per show. This is logical and maintainable after all, but it meant I couldn’t use the same logic loop in the same way.

    What I really needed was to loop through $.Page.Site.Data.episodes.SHOWNAME in order to get the data from that show. And I couldn’t add on a variable to my range call.

    But as it happens, Hugo is clever and if I say {{ range $.Page.Site.Data.episodes }} it will give me the file episodes.json if that exists. But if there happens to be a folder called episodes then it will give me all the JSON files in there.

    I read through the data documentation on Hugo a few times to understand the hierarchy but it breaks down really logically:

    ├── data
    |   ├── episodes
    |   |   ├── continum.json
    |   |   └── fringe.json
    |   |   └── sense8.json
    |   ├── movies.json
    |   ├── musicals.html
    

    Data {dot} episodes {dot} continum

    And if I ran this:

    {{ range $.Page.Site.Data.episodes }}
    	<li>{{ .show }}<li>
    {{ end }}
    

    Then I got this:

    • continum
    • fringe
    • sense8

    I already knew I could do a range within a range, and finally I came up with this:

    {{ $show := $.Page.Params.show }}
    
    {{ range $.Page.Site.Data.episodes }}
    	{{ if eq .show $show }}
    		<li>{{ .title }}</li>
    	{{ end }}
    {{ end}}
    

    The shortcode checks the post it’s on and, if the Front Matter has ‘show’ defined, it will give me a list of all titles (episode titles) for that show.

    I added in an extra check for the season parameter. This one is passed through the shortcode:

    {{< episodelist season="1" >}}

    And that, in turn is called in the shortcode above the output for title:

    {{ range where .episodes "season" "==" $season }}

    So nothing will display unless the show in the JSON file matches the show on the page with the shortcode, and the season matches the one defined in the shortcode.

    Now I actually have a more robust check. If no season is defined, it shows all episodes (useful for one-season shows, right?). But you get the idea.

    How Did This Help?

    Remember my JSON example before? Here’s what it looks like for Fringe:

    {
    "show" : "fringe",
    "episodes": [
      {
        "epnum": "1",
        "season": "1",
        "title": "Pilot",
        "slug": "pilot",
        "airdate": "2008-09-08T22:00:00.000Z",
        "rating": "5",
        "summary": "Walter gets a cow named Gene to experiment on ethically."
      },
      ...
    ]}
    

    Instead of just started with those naked chunks, I have a definition of the show name (fringe – lowercase because I want to compare it to other lowercase things and I’m lazy) and the definition of a section for episodes. That’s the whole reason why that call for seasons works the way I want it to.

    This helped me to understand and visualize JSON. By taking a simple JSON list (the filmography) I learned the basics of connecting code and content. Then by taking the more complex scenario, where I needed nested data, I understood that relationship.

    The next step will be making a simple WordPress site and calling the JSON remotely. Obviously I have to figure out how to trick it into making fake permalinks, but baby steps. Baby steps.

  • Looping Hugo

    Looping Hugo

    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:

    1. How do I properly mimic a for-loop with range?
    2. Can I make a responsive gallery out of Hugo?
    3. 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.