Templater, System Commands, File Lists, Weather, and Git

Today I took a gander at all the forum posts with the Templater tag. Most of them were already closed for replies, but I realized I had some comments that might appply to the following:

As to the question about how to pass a parameter to a User System Command Function in Templater, I don’t think it is possible. However, I stopped using them in favor a User Script Function that allows me to build the command programmatically before passing it to the function to execute.

// In: cmd = a complete command to run in the command line shell. It is up 
//           to the user to make certain the command is properly formatted 
//           for their platform
// Out: an object with two properties:
//      out = the trimmed content the command sent to standard out.
//      err = the trimmed content the command sent to standard error.
async function sh(cmd)
{
   const { promisify } = require('util');
   const exec = promisify(require('child_process').exec);
   const result = await exec(cmd);
   return {out: result.stdout.trim(), err: result.stderr.trim()};
}
module.exports = sh;

With the full power of the terminal (command line) available it opens up a lot of templating opportunities. For instance, one complex template I created built a table of contents using head to extract the first few lines of notes and wc to include their word counts. While that might be a bit complex to present here, I offer some concrete examples of the function in action below.

For the first example, the following template builds a simple list of the markdown files found at the root level of a vault:

<%*
const root = app.vault.adapter.getBasePath();
const escSpace = root.replace(/(\s)/g, '\\$1');  //escape the space characters
const list = await tp.user.sh(`ls ${escSpace}/*.md`);
const files = list.out.split('\n');
files.forEach(file => {tR += `   + ${file}\n`;});
-%>

sample output

   + /Users/ninjineer/Documents/Test/2022.calendar.md
   + /Users/ninjineer/Documents/Test/Book.md
   + /Users/ninjineer/Documents/Test/Counting words.md
   + /Users/ninjineer/Documents/Test/New-Day.md
   + /Users/ninjineer/Documents/Test/TVShow.md
   + /Users/ninjineer/Documents/Test/Terrible Idea 2.md
   + /Users/ninjineer/Documents/Test/Terrible Idea First.md

The above assumes you’re using MacOS or Linux. For Windows, the same template should look something like the following (note this code is untested as I do not have Obsidian running on a Windows computer):

<%*
const root = app.vault.adapter.getBasePath();
const list = await tp.user.sh(`dir "${root}\\*.md" /B`);
const files = list.out.split('\r\n');
files.forEach(file => {tR += `   + ${file}\r\n`;});
-%>

With a little more effort, once could also turn that file list into a list of links, like for a MOC:

<%*
const root = app.vault.adapter.getBasePath();
const escSpace = root.replace(/(\s)/g, '\\$1');
const list = await tp.user.sh(`ls ${escSpace}/*.md`);
const files = list.out.split('\n');
files.forEach(file => {
   const parts = file.split('/');
   const name = parts[parts.length - 1].slice(0,-3)
   tR += `   + [[${name}]]\n`;
});
-%>

sample output

   + [[2022.calendar]]
   + [[Book]]
   + [[Counting words]]
   + [[New-Day]]
   + [[TVShow]]
   + [[Terrible Idea 2]]
   + [[Terrible Idea First]]

Another example comes from the post about inserting weather forcasts. The template for doing that in my location looks like:

<%*
const w = await tp.user.sh(`curl "https://wttr.in/Pohnpei?TuF0"`);
tR += '```\n' + w.out + '\n```\n';
-%>

sample output

Weather report: Pohnpei

                Overcast
       .--.     +78(86) °F     
    .-(    ).   ← 2 mph        
   (___.__)__)  14 mi          
                0.1 in

I’ll leave you with one final example that occurred to me as I wrote this post. Since my vault is also a git repository, I periodically have to jump over to the terminal and commit my changes. With this sh function, I can use a template to automate this task:

<%*
let msg = await tp.system.prompt("Git Commit Message","");
if (msg != null && msg.length > 1)
{
   const root = app.vault.adapter.getBasePath();
   const escSpace = root.replace(/(\s)/g, '\\$1');
   const c = await tp.user.sh(`cd ${escSpace} && git add --all && git commit -m "${msg}"`)
   console.log(c.out);
}
-%>

Note, I directed the output from the command to the console, but I could have just as easily funneled it into a note, if I wanted to keep a log of such commits for some reason.

I hope these examples trigger fresh idea in others. Thanks.

11 Likes

Your solution is really cool, I’m definitely bookmarking it for possible future use. Thank you!

As to the question about how to pass a parameter to a User System Command Function in Templater, I don’t think it is possible.

It is! Sorry, I forgot to post the solution in the forum thread before it got automatically closed. Here is the answer in Obsidian Discord. In short: you use the $parameter syntax on Linux and %parameter% syntax on Windows, and pass the parameter value into the function within the template like this: tp.user.function({ parameter: value }).

Here is actual example of a User System Command Function I named getYoutubeMeta used for getting Youtube video metadata through youtube-dl:

Linux:

youtube-dl $ytLink --skip-download --dump-json

Windows:

youtube-dl %ytLink% --skip-download --dump-json

And that’s how you then call it within a simple example of a note template, together with parsing the resulting JSON to get two example bits of video information (title and channel) into the frontmatter:

<%* let link = await tp.system.prompt("Link");
let youtubeMeta = JSON.parse(await tp.user.getYoutubeMeta({ ytLink: link }));
let title = youtubeMeta.title;
let channel = youtubeMeta.channel;
-%>

---
title: "<% title %>"
channel: "<% channel %>"
---

(The actual getYoutubeMeta function call bit is tp.user.getYoutubeMeta({ ytLink: link }) in case a newbie reading this gets confused. You can get the full list of metadata youtube-dl has access to here.)

2 Likes

This is such a brilliant solution. Thank you so much. Will surely be using it :smile: