labnotes

Styling Hugo Diffs

Showing just what you changed

tags
hugo

I often want to show small changes I’m making to a file and it would be nice for hugo to support styling patches directly. Lets see what we can do to make this process easier.

Lets take the example of create node package.json file and add the following scripts worflow. How can we say this different than “copy this into your package.json file”?

Create sample steps

Lets first create the file using npm init -y and then immediately cp package.json package.json.base. In this case, we want to add some script attributes, like so:

1
2
3
4
5
  "scripts": {
    "build:eleventy": "eleventy",
    "serve:eleventy": "eleventy --serve",
    "debug:eleventy": "DEBUG=* eleventy"
  },

So we can now look at the differences of the files using the diff command. I’m going to have it user the unified format showing 1 line of code on either side.

1
diff -U 1 package.json.base package.json

Which outputs:

1
2
3
4
5
6
7
8
9
--- package.json.base	2020-01-30 14:38:18.933332377 -0600
+++ package.json	2020-01-30 15:43:37.174332377 -0600
@@ -6,3 +6,5 @@
   "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1"
+    "build:eleventy": "eleventy",
+    "serve:eleventy": "eleventy --serve",
+    "debug:eleventy": "DEBUG=* eleventy"
   },

This seems a little better, but how do we keep it up to date?

Bash script

We are going to write a bash script to parse though a files. First lets do that and make sure that we can pull out the base name of the file.

update_patches.sh.base:

1
2
3
4
5
6
7
#!/bin/bash

for i in *.base
do
    echo $i is the base file
    echo ${i%.base} is final file
done

That seems to work, now lets loop over the files that match the base name, skipping the files that we don’t want. Once we get to the .base file, we know that we are at the end of our patching, and we need to compare the last stage with the original file instead, so we’ll check for that and update the variables so that the stage file is actually the current one.

update_patches.sh.1:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
--- update_patches.sh.base
+++ update_patches.sh.1
@@ -4,4 +4,22 @@
 do
-    echo $i is the base file
-    echo ${i%.base} is final file
+    last_file="$i"
+    final_file="${i%.base}"
+
+    for stage in ${final_file}.*
+    do
+	# Strip out emacs junk
+	if [[ $stage == *~ ]]; then
+	    continue
+	fi
+	
+	# We are at the end!
+	if [[ $stage == *.base ]]; then
+	    stage=$final_file
+	fi
+
+	echo Need to patch $last_file to make $stage
+
+	last_file=$stage
+    done
+    echo
 done

The logic seems to work, so lets add the actual diff creation. But the diff files match our pattern, so we’ll need to add a check to skip over them when we loop over the glob!

update_patches.sh:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
--- update_patches.sh.1
+++ update_patches.sh
@@ -19,3 +19,13 @@
 
-	echo Need to patch $last_file to make $stage
+	# Skip parsing of diff files
+	if [[ $stage == *.diff ]]; then
+	    continue
+	fi
+
+	echo Creating $stage.diff
+
+	# -Z Ignores space
+	# -U 1 sets to the unified patch format with one surrounding lines
+	# Setting --label directly lets use avoid timestamps
+	diff -Z -U 1 --label $last_file --label $stage $last_file $stage > $stage.diff
 

Usage

The idea here is that you are working on an explainable chunk of work. You start by getting a base file that everyone can work off of. For example, run npm init -y and then copy that file to package.json.base. Then make your changes and run update_patches.sh to generate the patch, which will initially just compare package.json.base and package.json

Later you want to make another change to the file. Before you edit package.json make a copy to package.json.1, and make your changes again. Then run update_patches.sh which will regenerate all the diffs for each file. Then write about it, and repeat the process as necessary.

Displaying the patch files easily

What I want to do in while writing markdown is to do something like

If we want to show the base file:

{{ diff “package.json” }}

text here

{{ diff “package.json” “1” }}

text here

{{ diff “package.json” “final” }}

in my content file, and which will figure out the correct diff to inline it.

Here is a hugo short code that you can drop in your /layouts/shortcodes/ folder that will make that possible.

Embedding shortcodes as an example inside of hugo is a pain, so I’ll walk you through the logic but we wont’ be looking at the code.

  1. Pull out the parameters to make it easier to work with.
  2. $diff is going to contain the file contents, initialize it to blank in a scope that everyone can see.
  3. If $stage is set and is final, we append “.diff” to a file – which we assume is in the same directory as the Page that is calling the short code – and readFile.
  4. If $stage is set but not final, append “.$stage.diff” to the filename and load.
  5. If $stage is blank load “$filename.diff”. This is to avoid expanding to ..diff which has an extra ..
  6. Markdownify a link to the file directly and pass to markdownify
  7. Markdownify the actual file, formatting is as a diff

What it looks like in action

Lets first start out by creating an empty project:

  1. mkdir projectname && cd projectname
  2. npm init -y
  3. Add the following script entries to the generated package.json

package.json.1:

1
2
3
4
5
6
7
8
9
--- package.json.base
+++ package.json.1
@@ -6,3 +6,5 @@
   "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1"
+    "build:eleventy": "eleventy",
+    "serve:eleventy": "eleventy --serve",
+    "debug:eleventy": "DEBUG=* eleventy"
   },

Oh, we forgot to install eleventy! Let’s do that now with npm install --save-dev @11ty/eleventy which should modify your package.json like so:

package.json:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
--- package.json.1
+++ package.json
@@ -12,3 +12,6 @@
   "author": "",
-  "license": "ISC"
+  "license": "ISC",
+  "devDependencies": {
+    "@11ty/eleventy": "^0.10.0",
+  }
 }

Previously

howto

Template to setup a linode server with DNS and HTTPS

use terraform to coordinate stuff

tags
scripting
terraform
linode
dns

Next

howto

Playing with tailwindcss

An excersize in minimilism

tags
tailwind