<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>obsidian on foosel.net</title><link>https://foosel.net/tags/obsidian/</link><description>Recent content in obsidian on foosel.net</description><generator>Hugo</generator><language>en-us</language><copyright>Gina Häußge (foosel)</copyright><lastBuildDate>Fri, 14 Apr 2023 00:00:00 +0000</lastBuildDate><atom:link href="https://foosel.net/tags/obsidian/feed.xml" rel="self" type="application/rss+xml"/><item><title>TIL: How to use Obsidian's Dataview plugin to visualize frontmatter</title><link>https://foosel.net/til/2023-04-14-how-to-use-obsidians-dataview-plugin-to-visualize-frontmatter/</link><pubDate>Fri, 14 Apr 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/2023-04-14-how-to-use-obsidians-dataview-plugin-to-visualize-frontmatter/</guid><description>&lt;p&gt;For every &lt;a href="https://octoprint.org"&gt;OctoPrint&lt;/a&gt; release I run through several update tests: I flash a specific OctoPi version, push it to a specific OctoPrint version, configure the release channel, then see if updating to the newest release (candidate) works fine. I use &lt;a href="https://octoprint.org/blog/2020/07/29/automating-octoprints-release-tests/"&gt;my testrig and its automation scripts&lt;/a&gt; for that and usually go through something between 5 and 10 separate scenarios.&lt;/p&gt;
&lt;p&gt;So far all of these scenarios were noted down as a Markdown table in my release checklist that these days I prepare in my &lt;a href="https://obsidian.md"&gt;Obsidian&lt;/a&gt; vault, including manually adjusting the testrig commands to match the scenario. Having to take care of the latter is something that has been annoying for a long time now, and during the preparation for &lt;a href="https://github.com/OctoPrint/OctoPrint/releases/tag/1.9.0rc5"&gt;yesterday&amp;rsquo;s release candidate&lt;/a&gt; I decided enough is enough and looked into improving my tooling a bit. In the end, I used Obsidian&amp;rsquo;s quite amazing &lt;a href="https://blacksmithgu.github.io/obsidian-dataview/"&gt;Dataview plugin&lt;/a&gt; to query the information about the planned test scenarios from the checklist&amp;rsquo;s frontmatter, build the testrig command from that, then render all of this as a table, complete with some checkboxes for state logging during the tests and a copy button for the command.&lt;/p&gt;</description><content:encoded><![CDATA[<p>For every <a href="https://octoprint.org">OctoPrint</a> release I run through several update tests: I flash a specific OctoPi version, push it to a specific OctoPrint version, configure the release channel, then see if updating to the newest release (candidate) works fine. I use <a href="https://octoprint.org/blog/2020/07/29/automating-octoprints-release-tests/">my testrig and its automation scripts</a> for that and usually go through something between 5 and 10 separate scenarios.</p>
<p>So far all of these scenarios were noted down as a Markdown table in my release checklist that these days I prepare in my <a href="https://obsidian.md">Obsidian</a> vault, including manually adjusting the testrig commands to match the scenario. Having to take care of the latter is something that has been annoying for a long time now, and during the preparation for <a href="https://github.com/OctoPrint/OctoPrint/releases/tag/1.9.0rc5">yesterday&rsquo;s release candidate</a> I decided enough is enough and looked into improving my tooling a bit. In the end, I used Obsidian&rsquo;s quite amazing <a href="https://blacksmithgu.github.io/obsidian-dataview/">Dataview plugin</a> to query the information about the planned test scenarios from the checklist&rsquo;s frontmatter, build the testrig command from that, then render all of this as a table, complete with some checkboxes for state logging during the tests and a copy button for the command.</p>
<p>Here&rsquo;s the format of the frontmatter for the latest release candidate 1.9.0rc5:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">update_test_type</span>: <span style="color:#ae81ff">maintenance</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">update_tests</span>:
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">0.17.0</span>-<span style="color:#ae81ff">py3</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">update_test_type</span>: <span style="color:#ae81ff">simplepip</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">additional</span>: <span style="color:#ae81ff">pip=21.3.1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">0.18.0</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">channel</span>: <span style="color:#ae81ff">maintenance</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">start</span>: <span style="color:#ae81ff">1.8.7</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">0.18.0</span>-<span style="color:#ae81ff">latest</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">channel</span>: <span style="color:#ae81ff">maintenance</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">1.0.0</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">channel</span>: <span style="color:#ae81ff">maintenance</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">start</span>: <span style="color:#ae81ff">1.8.7</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">1.0.0</span>-<span style="color:#ae81ff">latest</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">channel</span>: <span style="color:#ae81ff">stable</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">1.0.0</span>-<span style="color:#ae81ff">latest</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">channel</span>: <span style="color:#ae81ff">maintenance</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">start</span>: <span style="color:#ae81ff">1.9</span><span style="color:#ae81ff">.0rc3</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">1.0.0</span>-<span style="color:#ae81ff">latest</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">channel</span>: <span style="color:#ae81ff">maintenance</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">start</span>: <span style="color:#ae81ff">1.9</span><span style="color:#ae81ff">.0rc4</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span></code></pre></div><p>And this is the <code>dataviewjs</code> query I used:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">createCheckbox</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">checked</span>) =&gt; {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">checkbox</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">container</span>.<span style="color:#a6e22e">createEl</span>(<span style="color:#e6db74">&#34;input&#34;</span>, {<span style="color:#e6db74">&#34;type&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;checkbox&#34;</span>});
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">checkbox</span>.<span style="color:#a6e22e">checked</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">checked</span>;
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">checkbox</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">createDropdown</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">options</span>, <span style="color:#a6e22e">selected</span>) =&gt; {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">dropdown</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">container</span>.<span style="color:#a6e22e">createEl</span>(<span style="color:#e6db74">&#34;select&#34;</span>);
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">options</span>.<span style="color:#a6e22e">map</span>((<span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">idx</span>) =&gt; {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">option</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">dropdown</span>.<span style="color:#a6e22e">createEl</span>(<span style="color:#e6db74">&#34;option&#34;</span>, {<span style="color:#e6db74">&#34;text&#34;</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">value</span>, <span style="color:#e6db74">&#34;value&#34;</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">value</span>});
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">option</span>.<span style="color:#a6e22e">selected</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">value</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">selected</span>);
</span></span><span style="display:flex;"><span>	});
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">dropdown</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">createCopyButton</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">command</span>) =&gt; {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">button</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">container</span>.<span style="color:#a6e22e">createEl</span>(<span style="color:#e6db74">&#34;button&#34;</span>, {<span style="color:#e6db74">&#34;text&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Copy&#34;</span>})
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">button</span>.<span style="color:#a6e22e">addEventListener</span>(<span style="color:#e6db74">&#34;click&#34;</span>, (<span style="color:#a6e22e">evt</span>) =&gt; {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">evt</span>.<span style="color:#a6e22e">preventDefault</span>();
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">navigator</span>.<span style="color:#a6e22e">clipboard</span>.<span style="color:#a6e22e">writeText</span>(<span style="color:#a6e22e">command</span>);
</span></span><span style="display:flex;"><span>	});
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">button</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">DUTS</span> <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#34;pia&#34;</span>, <span style="color:#e6db74">&#34;pib&#34;</span>, <span style="color:#e6db74">&#34;pic&#34;</span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">update_type</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">dv</span>.<span style="color:#a6e22e">current</span>().<span style="color:#a6e22e">update_test_type</span> <span style="color:#f92672">||</span> <span style="color:#e6db74">&#34;maintenance&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">tests</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">dv</span>.<span style="color:#a6e22e">current</span>().<span style="color:#a6e22e">update_tests</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">dv</span>.<span style="color:#a6e22e">table</span>([<span style="color:#e6db74">&#34;ID&#34;</span>, <span style="color:#e6db74">&#34;WIP&#34;</span>, <span style="color:#e6db74">&#34;Done&#34;</span>, <span style="color:#e6db74">&#34;DUT&#34;</span>, <span style="color:#e6db74">&#34;OctoPi&#34;</span>, <span style="color:#e6db74">&#34;Channel&#34;</span>, <span style="color:#e6db74">&#34;Start&#34;</span>, <span style="color:#e6db74">&#34;Command&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>], <span style="color:#a6e22e">tests</span>
</span></span><span style="display:flex;"><span>	.<span style="color:#a6e22e">map</span>((<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">idx</span>) =&gt; {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">dut</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">dut</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">DUTS</span>[<span style="color:#a6e22e">idx</span> <span style="color:#f92672">%</span> <span style="color:#a6e22e">DUTS</span>.<span style="color:#a6e22e">length</span>];
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">version</span> <span style="color:#f92672">=</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">start</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">start</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;stock&#34;</span>) <span style="color:#f92672">?</span> <span style="color:#e6db74">&#34;&#34;</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">`,version=</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">start</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">command</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">switch</span> (<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">update_test_type</span>) {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">case</span> <span style="color:#e6db74">&#34;simplepip&#34;</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">command</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`fab flashhost_flash_and_provision:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">image</span><span style="color:#e6db74">}</span><span style="color:#e6db74"> octopi_test_simplepip`</span>;
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">additional</span>) {
</span></span><span style="display:flex;"><span>					<span style="color:#a6e22e">command</span> <span style="color:#f92672">+=</span> <span style="color:#e6db74">&#34;:&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">additional</span>;
</span></span><span style="display:flex;"><span>				}
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">default</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">command</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`fab flashhost_flash_and_provision:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">image</span><span style="color:#e6db74">}</span><span style="color:#e6db74"> octopi_test_update_</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">update_type</span><span style="color:#e6db74">}</span><span style="color:#e6db74">:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">channel</span><span style="color:#e6db74">}${</span><span style="color:#a6e22e">version</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>;
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">additional</span>) {
</span></span><span style="display:flex;"><span>				  <span style="color:#a6e22e">command</span> <span style="color:#f92672">+=</span> <span style="color:#e6db74">&#34;,&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">additional</span>
</span></span><span style="display:flex;"><span>				}
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">wipCheckbox</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createCheckbox</span>(<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">wip</span>);
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">doneCheckbox</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createCheckbox</span>(<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">done</span>);
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">dutDropdown</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createDropdown</span>(<span style="color:#a6e22e">DUTS</span>, <span style="color:#a6e22e">dut</span>);
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">copyButton</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createCopyButton</span>(<span style="color:#a6e22e">command</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> [<span style="color:#a6e22e">idx</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">wipCheckbox</span>, <span style="color:#a6e22e">doneCheckbox</span>, <span style="color:#a6e22e">dutDropdown</span>, <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">image</span>, <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">channel</span>, <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">start</span> <span style="color:#f92672">?</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">start</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;stock&#34;</span>, <span style="color:#e6db74">&#34;`&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">command</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;`&#34;</span>, <span style="color:#a6e22e">copyButton</span>]
</span></span><span style="display:flex;"><span>	}));
</span></span></code></pre></div><p>This iterates over the list of scenarios, and for each generates a command, assigns one of the DUTs (Device Under Test) of the testrig, creates checkboxes for WIP and Done seeded from the frontmatter data and also a copy button. This then gets rendered as a table.</p>
<p>Throwing that query in a <code>dataviewjs</code> typed markdown code fence yields a nice rendition of the data that allows me to check if I have forgotten anything, track my progress with the checkboxes and also allows me to easily copy the generated command with the click of a button, so I don&rsquo;t have to manually copy paste stuff anymore:</p>
<p><img src="https://foosel.net/til/2023-04-14-how-to-use-obsidians-dataview-plugin-to-visualize-frontmatter/scenario-table.png" alt="A screenshot of a table visualizing the test scenarios. The columns are ID, WIP, Done, DUT, OctoPi, Channel, Start and Command, the rows are the secnarios. Each scenario has a testrig command attached that can be copied with a dedicated button." loading="lazy">
</p>
<p>What I have not yet figured out is how to manipulate the frontmatter directly via the checkboxes and DUT dropdowns (for logging purposes) - right now I still have to edit the state changes manually to persist them. The <a href="https://github.com/chhoumann/MetaEdit">MetaEdit</a> plugin for Obsidian looked promising, but that doesn&rsquo;t seem to be flexible enough to manipulate a list of items (yet).</p>
<p>In any case, this helped me a ton yesterday, and will save me a lot of time with future releases and release candidates!</p>
]]></content:encoded></item></channel></rss>