listFiles template function

Regards to everybody.
I’m trying to set up a simple blog publishing workflow that makes use of Caddy templates.

It consists of a single index.html file and a directory containing markdown files with dated blog posts. I already figured out that {{ include "directory/file.md" | markdown }} makes it easy to process an md. marked text into index.html

I can probably make it more detailed with {{ include "directory/file.md" | $parsed.Meta.title }} and {{ include "directory/file.md" | $parsed.Body | markdown }}. Haven’t tried latter though, so correct me if there’s mistake.

But my question is about something that I didn’t manage to find out in documentation and this forum: is it possible to import several files automatically (i.e. without listing each of them manuall)? Can Caddy publish contents of all .md directory wrapping each post title/date in tags? Probably this is possible with some template iteration syntax which I’m not sure I can write myself.

I’ve found a {{listFiles "/mydir"}} action, but it’s obviously not enough. Markdown files that I publish are used twice in my .html template: titles (and dates) listed as <li> elements in <nav> section, and then full text wrapped in <article> tags for each of the posts. Is it possible to make it work with Caddy core template functionality, without rolling out separate plugin?

Code snippet from the official Caddy website:

	{{range $i, $file := (listFiles "/docs/markdown/caddyfile/directives")}}
		{{$directives = append $directives ($file | trimSuffix ".md")}}
	{{end}}

You can iterate over the list of files and do whatever you want with them, including load their front matter for titles etc.

This example is just storing the list of filenames without the file extension in an array to output it to a JS array used for some dynamic logic on page load, but it could just as easily output some HTML for a navigation menu or whatever.

Does that answer your question? If not, I’m not quite sure what you’re asking about.

1 Like

Thank you for you answer, Francis.
Yes you’ve got my point, but I don’t know Golang. I can gues what your code does (probably storing a list of filenames somewhere in RAM), but I don’t know how to edit it to wrap links to each file with html tags.
If someone can code it for me here’s what I have:

Project file structure

|-- index.html
|-- articles/
    |-- first-post.md
    |-- second-post.md
    |-- third-post.md
    |-- last-post.md

Markdown files content example (first-post.md):

     ---
     title: First Post Title
     date: 2023.12.18
     ---
     Body of post, bla-bla.

What I’m trying to get:

<html>
<head>
<title="index.html">
</head>
<body>
<ul>
<!-- These I want somehow to be genetated automatically. -->
<li><a href="#last-post">Last Post Title</a><time datetime="2023-12-20">21-12-2023</time></li>
<li><a href="#third-post">Third Post Title</a><time datetime="2023-12-20">20-12-2023</time></li>
<li><a href="#second-post">Second Post Title</a><time datetime="2023-12-19">19-12-2023</time></li>
<li><a href="#first-post">First Post Title</a><time datetime="2023-12-18">18-12-2023</time></li>
</ul>
<!-- These "include" templates work, but I'd like to strip YAML header out of it and 
also have this list generated based on 'articles' directory contents parsing -->
<section id="first-post">{{ include "/articles/firs-post.md" | markdown }}</section>
<section id="second-post">{{ include "/articles/second-post.md" | markdown }}</section>
<section id="third-post">{{ include "/articles/third-post.md" | markdown }}</section>
<section id="last-post">{{ include "/articles/last-post.md" | markdown }}</section>
</body>
</html>

It’s not Go code, it’s Go template syntax.

Anyway, you’d do something like this:

<ul>
{{range $i, $file := (listFiles "/articles")}}
	{{$md := (include (print "/articles/" $file) | splitFrontMatter)}}
	{{$slug := $file | trimSuffix ".md"}}
	{{$pubDate := toDate $md.Meta.pubDate}}
	<li>
		<a href="#{{$slug}}">{{$md.Meta.title}}</a>
		<time datetime="{{$pubDate | htmlDate}}">{{$pubDate | date "01-02-2006"}}</time>
	</li>
{{end}}
</ul>
{{range $i, $file := (listFiles "/articles")}}
	{{$md := (include (print "/articles/" $file) | splitFrontMatter)}}
	{{$slug := $file | trimSuffix ".md"}}
	<section id="{{ $slug }}">{{ markdown $md.Body }}</section>
{{end}}

Untested, YMMV. You’d need to define pubDate in your front matter in each file.

2 Likes

It works, only that I had to change the fifth line in your code to
{{$pubDate := mustToDate "2006-01-02 15:04" $md.Meta.date}}
and change the way I set post date in YAML header:

---
title: Install Jekyll on Mac OS
date: 2017-09-29 17:42
---

toDate function required second argument and while diggin in I’ve found mustToDate more strict and informative.

Since I’m going to use this template for rss.xml as well I think proper date handling is critical.

Francis, you’re my savior, this is for you.

2 Likes

Francis, can you please tell me why the same template tags do not work for the .xml file in the same directory? index.html is served as supposed with all {{tags}} processed. And the feed.xml is given out as a raw text. I’ve tried to set

	templates {
		"mime_types":["text/html"], ["text/xml"], ["text/plain"], ["text/markdown"]
	}

in Caddyfile, but it doesn’t help.

This isn’t proper Caddyfile syntax.

The Caddyfile is not JSON. It doesn’t have the : nor [ ]. I recommend you go through this page of the docs:

The correct syntax for what you’re trying to do is this:

templates {
	mime_types "text/html" "text/xml" "text/plain" "text/markdown"
}
1 Like

Thank you Mohammed. I was repeating literally what I saw here.
I’ve changed my Caddyfile as you suggested, but unfortunately it didn’t solve the problem. feed.xml file still is served as raw text. will go through docs once again…

Yeah, that’s JSON docs, not Caddyfile docs. Look at templates (Caddyfile directive) — Caddy Documentation

The config option is mime, not mime_types.

templates {
	mime text/html text/xml text/plain text/markdown
}
1 Like

Francis, you did it again. It works. Wish you an awesome 2024.
For now my localhost Caddyfile looks very succinct:

:1313
root * /home/quicknquiet/Public/site
file_server
templates {
	mime text/html text/xml text/plain text/markdown
}

My publishing environment consists now of five files: caddy.exe, Caddyfile, index.html, feed.xml, favicon.ico and two folders: /articles, /pictures. And it’s crossplatform. Most portable and minimal blogging way ever. For the curious ones: have a look at this html/css template.

It is almost done. A feed should have an <updated>2006-01-02T15:04:05-0700</updated> tag. And I’m trying to think out the way to autofill it. Actually it is a pubDate of the latest article from above. But I can’t get it out of the cycle. Is there a way to force Caddy to look through directory for a most recent updated file and give back the date of its last change?

Or it could be the time/date of the last index.html edit. If it wasn’t generated everytime on request. So may be the date of the previous cached index.html file? It would give a second post date but it’s better than nothing.

1 Like

OK, so I’m making this in the same shell that runs Caddy:

export MYDATE=`ls /path/to/articles -rto --time-style=+%Y-%m-%dT%T.%9N%:z | awk 'END{print $5}'`

checking it with echo $MYDATE and it works, giving out proper formatted date of a recent file in a directory
Then I try to use this part of doc and put system variable $MYDATE into my feed.xml markup. If I wrap it in single pair of curly braces it is given out literally as a raw text, if I wrap it in double curly braces I get an “undefined variable” error.

How are you actually running Caddy? Env vars will only be available to your current shell with export.

You need to use {{env "VAR_NAME"}} template syntax to get the value of an env var.

Keep in mind that the env var will not automatically update itself, it doesn’t store a command, it only stores the value at the time you can that command.

My testing environment on local machines is a project directory with the Caddyfile in it. So I just open console at this very directory and caddy run. Public VPS that is used for publishing runs on Debian. Caddy is installed with root privileges and runs with systemctl.

I’ve changed my one-liner a bit, used your snippet and suddenly it works. I was also worrying about variable update. Probably the variable updates at logon (since ‘export VAR=…’ is added to proper login files) and then it’s static.

So either I find out how to get a date of most recent file in directory with the help of go templates or I have to think about this variable update every time I publish.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.