Hugo Shortcodes for Coding Blogs

Published August 23, 2022

Tags: Cloud-Resume PyScript Python

As I've been diving deep into PyScript the past couple months, I've developed a handful of shortcodes for Hugo that make it quicker and easier to display code snippets on the page in a nicely formatted way.

We'll use the following Python code as the source to be formatted. The code below is displayed by simply wrapped it in a pair of <pre></pre> tags:

class hello_sayer:
    def __init__(self, greeting = "Hello"):
        self.greeting = greeting
    def say_hello(self, name):
        print(f"{self.greeting}, {name}!")

if __name__ == "__main__":
    h = hello_sayer("Good morning")
    h.say_hello("Jeff")

This really isn't a bad place to start, but now let's make it better.

All of the following assumes the monokai syntax highlighting theme and use of the tailwind css framework. The custom css-classes used on this page are included at the end.

Built-in {{< highlight >}}

We can use Hugo's built-in {{< highlight >}} shortcode to highlight our code. This works fairly-well for simple code examples that you're copying and pasting into the document - it's also the default behavior if you're relyingon Markdown code fences to do highlighting for you.

Code

{{ highlight python }}
    class hello_sayer:
        def __init__(self, greeting = "Hello"):
            self.greeting = greeting
        def say_hello(self, name):
            print(f"{self.greeting}, {name}!")

    if __name__ == "__main__":
        h = hello_sayer("Good morning")
        h.say_hello("Jeff")
{{ /highlight }}

Result

1
2
3
4
5
6
7
8
9
class hello_sayer:
    def __init__(self, greeting = "Hello"):
        self.greeting = greeting
    def say_hello(self, name):
        print(f"{self.greeting}, {name}!")

if __name__ == "__main__":
    h = hello_sayer("Good morning")
    h.say_hello("Jeff")

Import Code with loadcode

The first and simplest shortcode I wrote I call loadcode, which simply takes the contents of an external file and drops it into the current file. The source is:

layouts/shortcodes/loadcode.html

1
{{ os.ReadFile (.Get 0) | htmlUnescape | safeHTML}}

If we move our "hello_saver" code into a separate file called hellosayer.py, we can use loadcode to dynamically load the text into our page. This allows for using an external code editor to work on the code itself (with autocompletion, linting etc) while keeping the display correct on the page.

Code

{{< highlight python >}}
{{< loadcode "post/Cloud-Resume-Challenge-Shortcodes/hellosayer.py" >}}
{{< /highlight >}}

Result

1
2
3
4
5
6
7
8
9
class hello_sayer:
    def __init__(self, greeting = "Hello"):
        self.greeting = greeting
    def say_hello(self, name):
        print(f"{self.greeting}, {name}!")

if __name__ == "__main__":
    h = hello_sayer("Good morning")
    h.say_hello("Jeff")

Pleasing Code Displays with {{< showcode >}}

Of course when writing about code, it's nice for me to have a fairly standard format that code blocks are displayed in, including the title of the included file. The showcode shortcode accomplishes this: it adds a nicely beveled header tag to the top of the code block, and ensures that if the code is over 20 lines, we prevent it from getting any longer and add a scrollbar. The second and third arguments to showcode are the language to try to highlight and the highlight options:

layouts/shortcodes/showcode.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<div>
    <p class="code-title">{{ path.Base (.Get 0) }}</p>
    {{ $lines :=  len (split (os.ReadFile (.Get 0)) "\n") }}
    {{ if ge $lines 21 }}
    <div class="overflow-y-scroll h-124">
    {{ end }}
    {{ highlight (os.ReadFile (.Get 0) | htmlUnescape | safeHTML) (.Get 1) (.Get 2) }}
    {{ if ge $lines 21 }}
    </div>
    <p class="post-img-caption">Scroll to see complete code</p>
    {{ end }}
</div>

Applying this to our hellosayer.py example, we get:

Code

{{< showcode "post/Cloud-Resume-Challenge-Shortcodes/hellosayer.py" "python" >}}

Result

hellosayer.py

1
2
3
4
5
6
7
8
9
class hello_sayer:
    def __init__(self, greeting = "Hello"):
        self.greeting = greeting
    def say_hello(self, name):
        print(f"{self.greeting}, {name}!")

if __name__ == "__main__":
    h = hello_sayer("Good morning")
    h.say_hello("Jeff")

Displaying and Running PyScript Code with {{< showandrun >}}

For an upcoming post about integrating the Rich terminal formatting library with PyScript, I'd like to be able to run a piece of Python code in PyScript on the page and display its source code adjacent to it. For this, I use the showandrun shortcode:

showandrun creates a div which displays the source much like showcode does; however, for my purposes, since it's intended specifically to run Python code and be formatted a specific way, the language is hardcoded to "python3" and the options are not passed from the shortcode.

The lengthy tailwind class list in the first div ensures that the code and results live side-by-side on large screens, but get stacked one-over-the-other on smaller/mobile screens. We also accept a "flip" parameter which swaps the placement of the code and results, if desired.

layouts/shortcodes/shownadrun.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<div class="flex items-stretch flex-col-reverse space-y-2 {{ if (.Get "direction") }} {{ if eq (.Get "direction") "flip" }} md:flex-row-reverse md:space-x-2 md:space-x-reverse {{ end }} {{ else }} md:flex-row md:space-x-2{{ end }}">
    <div class="flex flex-col items-stretch w-full md:w-1/2">
        <div class="flex-none w-full italic h-7">Live PyScript Results:</div>
        <div class="flex-auto w-full px-2 overflow-y-auto bg-gray-200 border-2 border-gray-400 max-h-124">
            <py-script class="px-2" src="{{ path.Base (.Get "file") }}"></py-script>
            <div id="{{ strings.TrimSuffix ".py" (path.Base (.Get "file")) }}-output"></div>
        </div>
    </div>
    <div class="w-full md:w-1/2">
        <p class="code-title">{{ path.Base (.Get "file") }}</p>
        {{ $lines :=  len (split (os.ReadFile (.Get "file")) "\n") }}
        {{ if ge $lines 21 }}
        <div class="overflow-y-scroll h-124">
        {{ end }}
        {{ highlight (os.ReadFile (.Get "file") | htmlUnescape | safeHTML) "python3" "" }}
        {{ if ge $lines 21 }}
        </div>
        <p class="post-img-caption">Scroll to see complete code</p>
        {{ end }}
    </div>
</div>

The result, when applied to hellosayer.py, is:

Code

{{< showandrun file="post/Cloud-Resume-Challenge-Shortcodes/hellosayer.py" direction="flip" >}}

Result

Live PyScript Results:

hellosayer.py

1
2
3
4
5
6
7
8
9
class hello_sayer:
    def __init__(self, greeting = "Hello"):
        self.greeting = greeting
    def say_hello(self, name):
        print(f"{self.greeting}, {name}!")

if __name__ == "__main__":
    h = hello_sayer("Good morning")
    h.say_hello("Jeff")

CSS Classes

Many of the examples above make use of a couple of 'css-classes' in the tailwind style that I've defined for ease of use. Their definitions (in both tailwind classes and raw css) are:

/* in tailwind styles: */
.code-title {
    @apply table-cell px-6 py-1 font-semibold leading-tight text-center text-white rounded-t-xl;
    background-color: #f0f3f3;
}

/* in css: */
.code-title {
    display: table-cell;
    padding-top: 0.25rem;
    padding-bottom: 0.25rem;
    padding-left: 1.5rem;
    padding-right: 1.5rem;
    color: #ffffff;
    font-weight: 600;
    line-height: 1.25;
    text-align: center;
    border-top-left-radius: 0.75rem;
    border-top-right-radius: 0.75rem;
    background-color: #f0f3f3;
}

/* in tailwind styles: */
.post-img-caption {
    @apply w-auto m-auto italic text-center;
    }

/* in css: */
.post-img-caption {
    font-style: italic;
    text-align: center;
    width: auto;
}

Scroll to see complete code