Commandbox-jasper Part Deux

Continuing development on commandbox-jasper code cfml commandbox

July 5, 2022 / Robert Zehnder

Photo by Kaitlyn Baker on Unsplash

I just completed the first iteration of commandbox-jasper, a CommandBox module for generating static sites, and showed what I had to my SSG expert. He asked if it could handle any file structure or if it was tied to the blog format.

I was honest, "Yeah, you are kind of locked in the the blog format at the moment."

He suggested I take a look at how 11ty handles things and try to emulate what they do. That is just what I did.

the blog scaffold

I started cleaning jasper-cli. The eleveny-base-blog seemed like a great place to start so I began reorganizing jasper's directory structure.

The jasperconfig.json file has been moved into the _data folder. Any data files generated by the build process will also be stored here.

The assets/ folder in the root will be copied to the _site folder at build, but that functionality has not been added yet.

The ColdFusion files used to generate the static content have been moved to the _includes directory. I have a very basic layout/view system working with Jasper. If no layout is specified it will default to the main layout.

It is now also possible to use CFML files as templates. You can specify front matter in a CFML file by adding a ColdFusion comment on the first line of the file. The front matter values will be available in the prc scope on the page. Here is an example adding setting YAML in a ColdFusion comment.

<!---
layout: main
something: else
weird:
- an
- array
- of
- stuff
--->
<cfoutput>
<div class="container">
	<cfloop array="#prc.posts#" index="i">
		<a href="/posts/#i.slug#">#i.title#</a><br />
	</cfloop>
</div>
<cfdump var="#prc#" />
</cfoutput>

Here we can see the results. dump

jasper rendering

There are two markdown files in the posts/ folder to getting started, but the new jasper build looks for any valid template in the root directory and subdirectories (currently *.md and *.cfm files), attempts to read the front matter from each file, and write the HTML to disk. Special directorys such as _data and _includes are ignored.

component extends="commandbox.system.BaseCommand" {

	property name="JasperService" inject="JasperService@commandbox-jasper";

	function run() {
		command( "jasper cache build" ).run();

		var conf  = deserializeJSON( fileRead( resolvePath( "_data/jasperconfig.json" ), "utf-8" ) );
		var posts = deserializeJSON( fileRead( resolvePath( "_data/post-cache.json" ), "utf-8" ) );
		var tags  = JasperService.getTags( posts );

		var rootDir = resolvePath( "." );
		rootDir     = left( rootDir, len( rootDir ) - 1 )

		print.line( "Building source directory: " & rootDir );

		var templateList = JasperService.list( rootDir );

		templateList.each( ( template ) => {
			var fragment     = "";
			var content      = "";
			var renderedHTML = "";
			var isCFM        = template.name.findNoCase( ".cfm" ) ? true : false;
			var prc          = {
				"meta"     : {},
				"content"  : "",
				"tagCloud" : tags,
				"type"     : "page",
				"posts"    : posts
			};

			prc.append( conf );
			// Try reading the front matter from the template
			prc.append( JasperService.getPostData( fname = template.directory & "/" & template.name ) );

			// render the view
			if ( prc.keyExists( "type" ) && prc.type == "page" ) {
				if ( isCFM ) {
					// we are rending a CFM file, just include it
					savecontent variable="fragment" {
						include resolvePath( template.directory & "/" & template.name );
					}
				} else {
					// use the page template
					savecontent variable="fragment" {
						include resolvePath( "_includes/page.cfm" );
					}
				}
			} else {
				// use the post template
				savecontent variable="fragment" {
					include resolvePath( "_includes/post.cfm" );
				}
			}

			// content is referenced in the layout
			content = fragment;

			// render the layout
			savecontent variable="fragment" {
				include resolvePath( "_includes/layouts/" & prc.layout & ".cfm" );
			}

			// renderedHTML is the combined view and layout
			renderedHTML = fragment;

			// write the rendered HTML to disk
			var computedPath = template.directory.replace( rootDir, "" );
			try {
				directoryCreate( resolvePath( "_site" & computedPath ) );
				print.line( "Creating " & resolvePath( "_site" & computedPath ) );
			} catch ( any e ) {
				// fail
			}
			if ( prc.keyExists( "type" ) && prc.type == "page" ) {
				fileWrite(
					resolvePath( "_site" & computedPath & "/" & listFirst( template.name, "." ) & ".html" ),
					renderedHTML
				);
				print.line( "_site" & computedPath & "/" & listFirst( template.name, "." ) & ".html" );
			} else {
				print.line( "_site" & computedPath & "/" & prc.slug & ".html" );
				fileWrite( resolvePath( "_site" & computedPath & "/" & prc.slug & ".html" ), renderedHTML );
			}
		} );
	}

}

The jasper builld command has been completely rewritten to support layouts and views, as well as handling *.cfm files.

build

The default front matter has changed a bit, layout and type attribute are required since jasper build uses these values. CFM files that are not in the _includes directory should start with a CFML comment and have a layout set. It should default to main in the front matter, but it might break stuff if you do not have it for the moment.

---
layout: main
type: post
slug: getting-started
title: Getting Started
author: Jasper
description: It is pretty easy to get started with Jasper, here are some steps to help you move forward
tags: 
- Jasper
- Getting Started
image: https://static.kisdigital.com/images/jasper/getting-started-00-cover.jpeg
published: true
publishDate: 2020-05-30
---

I have really enjoyed working with CommandBox so far, the flexibility is amazing.