Cory LaNou
Wed, 24 Jan 2024

Writing Technical Articles using Hype

Overview

Creating technical articles can be painful when they include code samples and output from running programs. Hype makes this easy to not only create those articles, but ensure that all included content for the code, etc stays up to date. In this article, we will show how to set up Hype locally, create hooks for live reloading and compiling of your documents, as well as show how to dynamically include code and output directly to your documents.

Target Audience

This article is aimed at developers of any experience level with any software language.

In this article, we'll cover the following topics:

  • Talk about what Hype is
  • Cover why Hype was created
  • See how to use Hype and why you want to use it as well!

A Little History

When Gopher Guides originally embarked on our journey to become technical trainers, we knew we wanted to focus on writing content, but not waste time entering the content into bulky editors to present the training material. After looking at several training platforms that already existed, we decided that all of them were not only cumbersome to enter content, but there was no way to get "immediate" feedback of what your material would render as for the final view.

Our first version of our training platform used a custom version of remark.js and a custom go program to render the content. This allowed us to use a custom version of markdown to both write content and show code samples. While this worked, it still had a lot to be desired in terms of quickly including source content, assets such as images, and more.

Within the first year, we wrote our learn platform. This platform was written 100% from scratch. While still using markdown as the primary language to create content, we added the ability for custom tags. These tags could reach into the code examples and pull out specific lines to highlight in the material. Additionally, it automatically deployed all code samples the the go playground so that students could quickly run and work with examples even without having Go installed on their computers.

In addition to now being able to quickly create content, source code examples, and more, it also added the ability to create "modules" or "chapters" for our content, and with those modules, we could now create "courses". This allowed us to customize each training experience to the needs of our students. It also meant that if we made an update to a module (such as arrays), that every course that had the arrays module automatically got these new updates.

While this served us well for many years, we still had issues we wanted to resolve. For instance, every time the go binary was updated, code may compile differently, or the command options may change, etc. As such, we were constantly making small updates to material that while important, was really a waste of content creators time. We needed something that could automatically update when these changes were made without intervention from content creators.

Furthermore, we wanted to make sure that if the code changed, that the material also showed updated output from that code. With the learn platform, this was a manual, and very tedious process.

How Much Content?

Over the past five years, we have generated a lot of go content. Here are some basic statistics:

  • 7 Major Course Tracks
    • Intro to Go
    • Advanced Go Development
    • Testing in Go
    • Profiling and Optimization in Go
    • Web API in Go
    • gRPC In Go
    • Mastering Go
  • 1,549 Markdown Files (the training content)
  • Over 1,000,000 words of training content
  • Over 2,500 source code files for examples, exercises, and solutions
  • Over 175,000 lines of source code for examples, exercises, and solutions

With this much content it had become too difficult to keep everything up to date with the current platform.

Enter Hype

Hype is the next generation in technical content creation. With five years of content creation behind us, and two versions of custom content creation platforms, we had a pretty good idea of what we really needed to move forward.

We started with (but certainly aren't done) the following criteria:

  • Continue to use markdown as it allows for non-technical content creators
  • Create small, manageable, topical content modules
    • Keep source code and assets specific to these modules in the same directory
    • Custom tags/helpers to easily generate complex content from source code including:
      • Source code partials
      • Runnable links to source code
      • Links to download full source code
      • Output from source code (automatically updated any time the source code changes)
  • Be able to easily create courses from different modules
  • Courses should be able to adapt to anything from a one hour boot camp to a twelve week university style class
  • Able to render or deploy any course to a pdf, online platform, etc
  • No technical knowledge needed for rendering or deployment
  • Pull request friendly
  • Live reload for virtually instant feedback of changes
    • Caching built in to assist in fast rebuilds of content
  • Instant validation of source code (never show code that doesn't compile)
  • Tag source validation (if you include a link to code or asset such as an image, we validate that asset exists and report an error if it doesn't)
  • Run commands
    • Quickly show the output for common commands such as using tree to show a directory structure of your code example. This also updates automatically any time the code structure changes
    • Run ANY command and include the output in your content.
    • Validates exit status and errors out if statuses don't match
    • Shows the actual command that was run
  • Supports most programming languages already (that's right, this isn't just for creating Go content!)
  • Support for i18n
  • Support for binding variables so content will refer to itself as a Chapter for a book, vs. a Module for a training course. You can create as many bindings as needed for any Course, such as Title, etc.
  • Video embedding (from S3, youtube, vimeo, etc.)

Style Guide

The following examples are all part of what we would refer to as a style guide. Each example will show the markdown used for the example, followed by the content generated by that markdown.

Including Markdown

To include a markdown file, use the include tag. This will run that markdown file through the hype.Parser being used and append the results to the current document.

The paths specified in the src attribute of the include are relative to the markdown file they are used in. This allows you to move entire directory structures around in your project without having to change references within the documents themselves.

The following code will parse the code/code.md and sourceable/sourceable.md documents and append them to the end of the document they were included in.

<include src="code/code.md"></include>

<include src="sourceable/sourceable.md"></include>

Including Code Examples

Source code can be included in the documentation using the <code> tag.

<code src="src/snippets.go"></code>

Including Snippets

Code snippets can be included in the documentation using the snippet attribute of the <code> tag.

<code src="src/snippets.go" snippet="goodbye"></code>

Executing Commands

You can also execute any command and have it include the output in your docuemnt as well.

The following code:

<cmd exec="cat snippets.go" src="src"></cmd>

will result in the following output being compiled and included in your document automatically:

$ cat snippets.go

package main

import "fmt"

// snippet: hello
func Hello() {
	// snippet: indent
	fmt.Println("Hello, World!")
	// snippet: indent
}

// snippet: hello

// snippet: goodbye
func Goodbye() {
	fmt.Println("Goodbye, World!")
}

// snippet: goodbye

Snippet Support for Other Programming Languages

Snippet support can easily be added for any language. Currently support languages are:

  • Go
  • Ruby
  • HTML
  • JavaScript
  • Markdown

Ruby

<code src="src/snippets.rb" snippet="goodbye"></code>
def goodbye
  puts "Goodbye, World!"
end

Source Files and Assets

All source files and assets are stored relative to the root of the markdown file they are used in.

<TODO exec="tree" src="."></TODO>
$ tree

.
├── assets
│   └── logo.png
├── sourceable.md
└── src
    ├── file.md
    ├── filegroup.md
    ├── image.md
    ├── snippets.go
    ├── snippets.js
    ├── snippets.rb
    ├── source-code.md
    └── tree.md

2 directories, 10 files

Images

<img src="assets/logo.png">

Source Code

<code src="src/snippets.go"></code>
package main

import "fmt"

func Hello() {
	fmt.Println("Hello, World!")
}


func Goodbye() {
	fmt.Println("Goodbye, World!")
}

Go Specific Commands

There are a number of Go specific commands you can run as well. Anything from executing the code and showing the output, to including go doc (from the standard library or your own source code), etc.

Running Go Code

The following command will include the go source code, run it, and include the output of the program as well:

<go src="src/hello" run="."></go>

Here is the result that will be included in your document from the above command:

package main

import "fmt"

func main() {
	fmt.Println("Hello World")
}


$ go run .

Hello World

--------------------------------------------------------------------------------
Go Version: go1.23.0

Including a snippet

You may not want to include the entire source code of the file. In that case, you can use the code tag to specify both the file, and the snippet. The snippet is identified by using the # symbol in the file name:

<go src="src/hello" run="." code="main.go#example"></go>

In the source file, add the following comment before and after the code you want to be included for that snippet:

package main

import "fmt"

// snippet: example
func main() {
	fmt.Println("Hello World")
}

// snippet: example

This will result in only the following code being included in your document:

func main() {
	fmt.Println("Hello World")
}

$ go run .

Hello World

--------------------------------------------------------------------------------
Go Version: go1.23.0

Including output only

To only include the output, you can omit the code tag.

<go src="src/hello" run="."></go>
$ go run .

Hello World

--------------------------------------------------------------------------------
Go Version: go1.23.0

Annotating with Figure

You may want to annotate the code listing and example. You can do that by wrapping it in a figure tag and adding an optional figcaption tag.

<figure id="hello-world" type="listing">

<go src="src/hello" run="." code="main.go#example"></go>

<figcaption>Your first Go program!</figcaption>

</figure>

func main() {
	fmt.Println("Hello World")
}

$ go run .

Hello World

--------------------------------------------------------------------------------
Go Version: go1.23.0
Listing 1.1: Your first Go program!

Figure Styles

<figure id="1">
<figcaption>caption</figcaption>
</figure>

<figure id="2" type="listing">
<figcaption>caption</figcaption>
</figure>

<figure id="3" type="table">
<figcaption>caption</figcaption>
</figure>

<figure id="4" type="listing">
<figcaption>caption</figcaption>
</figure>

<figure id="5">
<figcaption>caption</figcaption>
</figure>

<figure id="6" type="table">
<figcaption>caption</figcaption>
</figure>

<figure id="7" type="listing">
<figcaption>caption</figcaption>
</figure>

## Figure Refs

* <ref id="1"></ref>
* <ref id="5"></ref>

## Listing Refs

* <ref id="2"></ref>
* <ref id="4"></ref>
* <ref id="7"></ref>

## Table Refs

* <ref id="3"></ref>
* <ref id="6"></ref>

Output:

Figure 1.1: caption
Listing 1.2: caption
Table 1.1: caption
Listing 1.3: caption
Figure 1.2: caption
Table 1.2: caption
Listing 1.4: caption

Figure Refs

Listing Refs

Table Refs

Godoc

There are also several godoc commands that allow you to quickly insert your own documentation dynamically. As your code and docs update, so will your documents.

Here is the basic usage first:

<go doc="-short context"></go>
$ go doc -short context

var Canceled = errors.New("context canceled")
var DeadlineExceeded error = deadlineExceededError{}
func AfterFunc(ctx Context, f func()) (stop func() bool)
func Cause(c Context) error
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc)
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)
func WithDeadlineCause(parent Context, d time.Time, cause error) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithTimeoutCause(parent Context, timeout time.Duration, cause error) (Context, CancelFunc)
type CancelCauseFunc func(cause error)
type CancelFunc func()
type Context interface{ ... }
    func Background() Context
    func TODO() Context
    func WithValue(parent Context, key, val any) Context
    func WithoutCancel(parent Context) Context

--------------------------------------------------------------------------------
Go Version: go1.23.0

Godoc For a Specific Symbol

You can also be more specific.

<go doc="-short context.WithCancel"></go>
$ go doc -short context.WithCancel

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
    WithCancel returns a copy of parent with a new Done channel. The returned
    context's Done channel is closed when the returned cancel function is called
    or when the parent context's Done channel is closed, whichever happens
    first.

    Canceling this context releases resources associated with it, so code should
    call cancel as soon as the operations running in this Context complete.

--------------------------------------------------------------------------------
Go Version: go1.23.0

Godoc links

You an also link withing your content to any godoc easily with the following tag:

Here is the code to create a dynamic link to the online documentation for `context.

Here is a link to <godoc>context</godoc>.

This is a link to the <godoc>context#Context</godoc> type.

Which results in the following output:

Here is a link to context.

This is a link to the context.Context type.

Summary

There are a lot of things we still haven't covered, but hopefully this gives a baseline overview of the things you can accomplish with Hype. Keep an eye on the repo as we continue to update documentation and functionality.

Want More?

If you've enjoyed reading this article, you may find these articles interesting as well:

More Articles

Quick Tips: Pointer Optimizations in Go

Overview

This article explores important performance considerations when working with pointers in Go. We'll cover key topics like returning pointers to local variables, choosing between pointer and value receivers for methods, and how to properly measure and optimize pointer-related performance using Go's built-in tools. Whether you're new to Go or an experienced developer, these tips will help you write more efficient and maintainable code.

Learn more

Hype Quick Start Guide

Overview

This article covers the basics of quickly writing a technical article using Hype.

Learn more

Go (golang) Slog Package

Overview

In Go (golang) release 1.21, the slog package will be added to the standard library. It includes many useful features such as structured logging as well as level logging. In this article, we will talk about the history of logging in Go, the challenges faced, and how the new slog package will help address those challenges.

Learn more