<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Zikani's Blog]]></title><description><![CDATA[Zikani's Blog]]></description><link>https://zikani.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Fri, 23 Feb 2024 07:25:10 GMT</lastBuildDate><atom:link href="https://zikani.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><atom:link rel="next" href="https://zikani.hashnode.dev/rss.xml?page=1"/><item><title><![CDATA[Generating random project names using Chichewa words]]></title><description><![CDATA[<p>Modern day developers are no strangers to automatically generated project names, these have become common and you may run into them if you use for example:</p><ul><li><p>Vercel for deploying projects</p></li><li><p>Fly.io</p></li><li><p>Docker (Desktop)</p></li><li><p>... and many other modern tools that generate names for things</p></li></ul><p>I was thinking about this and figured it would be an interesting undertaking to try to generate some names from Chichewa words.</p><p>First of all, where to get the words. The internet is full of Chichewa speaking folks nowadays, and I guess I could just <a target="_blank" href="https://github.com/zikani03/articulated">scrape a bunch of news sites</a> and social media platforms for Chichewa content, but for this tiny experiment that's overkill. I thought we could mostly make use of <a target="_blank" href="https://github.com/ceekays/chichewa">this old-but-gold resource</a> by my friend, Edmond Kachale, which he worked on years ago with his colleague Prof. Kevin Scannel.</p><h2 id="heading-the-name-gen-algorithm">The name gen algorithm</h2><blockquote><p>The algorithm is embarrassingly simple, we just shuffle the list of words and pick the first N-words from the shuffled list and join the words with a hyphen ( - )</p></blockquote><p>Initially, I had tried to come up with something that picked random nouns from the Chichewa project I mentioned above but the output wasn't appealing at all. I tried to change the code to try to include the adjectives, adverbs, pronouns, and nouns in different combinations but it ended up being an exercise in linguistics which I am not qualified to perform.</p><p>In the end, I settled on a simple "algorithm" - the algorithm is embarrassingly simple, we just shuffle the list of words and pick the first N-words from the shuffled list and join the words with a hyphen ( - ). I went further to modify the code to add a suffix extracted from a Base64 encoded string to ensure that names remain somewhat unique in the face of collisions due to repetitions of the "random" order we pick words from.</p><p>I decided this would work better if I came up with my own list of words for the corpus.</p><pre><code class="lang-java"><span class="hljs-keyword">package</span> main;<span class="hljs-keyword">import</span> java.nio.file.*;<span class="hljs-keyword">import</span> java.util.*;<span class="hljs-keyword">import</span> java.security.*;<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ChichewaNameGen</span> </span>{    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> SecureRandom rng = <span class="hljs-keyword">new</span> SecureRandom();    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String... args)</span> <span class="hljs-keyword">throws</span> Exception </span>{        rng.setSeed(System.currentTimeMillis());        <span class="hljs-keyword">var</span> lines = Files.readAllLines(Paths.get(<span class="hljs-string">"./words.txt"</span>));        <span class="hljs-keyword">var</span> words = <span class="hljs-keyword">new</span> ArrayList&lt;&gt;(lines);        <span class="hljs-keyword">var</span> N = Integer.parseInt(args.length &gt;= <span class="hljs-number">1</span> ? args[<span class="hljs-number">0</span>] : <span class="hljs-string">"3"</span>);        <span class="hljs-keyword">var</span> k = Integer.parseInt(args.length &gt;= <span class="hljs-number">2</span> ? args[<span class="hljs-number">1</span>] : <span class="hljs-string">"10"</span>);        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; k; i++) {            System.out.println(generate(words, N));        }    }    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> String <span class="hljs-title">generate</span><span class="hljs-params">(List&lt;String&gt; words, <span class="hljs-keyword">int</span> maxWords)</span> </span>{        Collections.shuffle(words);            <span class="hljs-keyword">var</span> w = String.join(<span class="hljs-string">"-"</span>, words.subList(<span class="hljs-number">0</span>, maxWords))                .replace(<span class="hljs-string">"'"</span>, <span class="hljs-string">""</span>).toLowerCase();        <span class="hljs-keyword">return</span> w + <span class="hljs-string">"-"</span> + randomHash(w);    }    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> String <span class="hljs-title">randomHash</span><span class="hljs-params">(String w)</span> </span>{        <span class="hljs-keyword">var</span> alpha = Base64.getEncoder()                .encodeToString((System.currentTimeMillis()+ w)                        .getBytes()).replace(<span class="hljs-string">"="</span>, <span class="hljs-string">""</span>);        <span class="hljs-keyword">return</span> (alpha).substring(rng.nextInt(<span class="hljs-number">0</span>, alpha.length()-<span class="hljs-number">9</span>))                .substring(<span class="hljs-number">0</span>, <span class="hljs-number">9</span>);    }}</code></pre><h2 id="heading-creating-a-chichewa-word-list-for-quirkier-phrases">Creating a Chichewa word list for quirkier phrases</h2><p>I wanted to have a list of words that generates names that roll off the tongue somewhat nicely or are at least quirky enough to be mildly interesting. I came up with a 100 random but relatable <a target="_blank" href="https://gist.github.com/zikani03/be019504cc1827e951986d83488d90da">Chichewa words</a>. I then placed these in the file where the code above looks. I tried several runs of tests and found the results were more pleasing.</p><p>Below is an example with a 5 random names, with length of 3 words from the list:</p><pre><code class="lang-bash">$ java ChichewaNameGen.java 3 5  <span class="hljs-comment"># output of the random words with a substring from a base64 string</span>fotokoza-ndemanga-tilipo-XRpbGlwbwzikwanje-malonje-malemba-WFsZW1iYQmatumba-chiyambi-mwamva-tbXdhbXZhbasiketi-kufika-lolemba-sb2xlbWJhkufika-chitedze-makope-1tYWtvcGU</code></pre><p>I am confident that those are somewhat quirky phrases. I am not going to try to translate. Okay, I can't resist with the first one ;)</p><p>Roughly <code>(fotokoza)-(ndemanga)-(tilipo)</code> -&gt; <code>(explain)-(your-thoughts/opinions)-(we-are-here/around)</code></p><h2 id="heading-how-many-names-can-we-generate">How many names can we generate</h2><p>The number of random names/phrases we can generate greatly depends on the number of words in the dataset. With 100 random words this approach should give more than 700 thousand random project names if we set N = 3, roughly (<code>100 * 99 * 98 )</code>.</p><p>The last part of the string is a 9 character long sub-string from a Base64 string and it's this bit that will help greatly with reducing collisions if we ever need to generate more names for example in some kind of service with millions of users. That should make us safe-ish from running out of random names in most applications of this kind of thing. Collisions would still occur, but we have more of a leeway for generating new names/phrases without running out of options quickly.</p><h2 id="heading-conclusion">Conclusion</h2><p>This was an interesting little experiment, I was pleased with the results and plan on using this somewhere in a future project. I might come back to this idea later and try to use the Chichewa dataset properly to create words that are built up grammatically but for now a random list of words works. Here is a link to the Gist with the <a target="_blank" href="https://gist.github.com/zikani03/be019504cc1827e951986d83488d90da">100 random chichewa words</a>  </p><p>Thanks to <a target="_blank" href="https://sethbonnie.com/blog/">Seth</a>, <a target="_blank" href="https://codeslayermw.hashnode.dev/">Chimwemwe</a> and <a target="_blank" href="https://ntumbuka.me/">Walter</a> for reviewing drafts of this post.</p>]]></description><link>https://zikani.hashnode.dev/generating-random-project-names-using-chichewa-words</link><guid isPermaLink="true">https://zikani.hashnode.dev/generating-random-project-names-using-chichewa-words</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Fri, 16 Feb 2024 22:00:00 GMT</pubDate></item><item><title><![CDATA[Is the Kitchen Sink the best place to think?]]></title><description><![CDATA[<p>This article was authored at the kitchen sink.</p><p>As a young Malawian bachelor (some rude people would say senior bachelor), I often find myself at the kitchen sink doing dishes. Not only because it's the one skill I am confident at in the kitchen when I compare myself to my house mate, particularly his cooking skills, but also because it's the one activity I have found that somehow helps me think a lot more than other activities.</p><p>There are few places in a house where one can retreat to have some time to think. Many of us retreat to the revered W.C. 🚽, others will walk about in a garden if they have one, others laze around the couch or if they are fancy on a Hammock, still more others lay in bed looking up at the ceiling as they gather their thoughts. (Side note: I intend to try to be fancy one day because of <a target="_blank" href="https://www.youtube.com/watch?v=f84n5oFoZBc">this talk by Rich Hickey</a>).</p><p>As for me, I long for dirty dishes and some time at the kitchen sink.</p><h2 id="heading-the-kitchen-sink-theory">The Kitchen Sink Theory</h2><p>Why is the Kitchen Sink so amenable to thinking? I have thought about this a bit (no doubt at the Kitchen Sink) but can only offer a theory: Washing dishes is an activity that has a predictable, well-defined process and outcome that it can be done almost mindlessly, but not completely. You cannot mindlessly handle a knife, soapy ceramic plate or glass tumbler. Even though it's a somewhat mindless activity, it leaves one room to spend time thinking about other things throughout the activity. Washing dishes is one of the most delightful activities because it leaves you with a clean kitchen sink, countertop, clean dishes, and a sense of completion - if done right.</p><h2 id="heading-what-to-think-about-at-the-kitchen-sink">What to think about at the Kitchen Sink ?</h2><p>I tend to think about about life, relationships, programming, work related issues or new ideas. One limitation of working the mind at the kitchen sink is that my hands are arrested by the very dishes, cutlery, water and dish washing liquid and I cannot write down what I am thinking of immediately. Taking a break to write would disrupt the flow of cleaning and thinking so I usually delegate that to later, if I don't get distracted by another thought by the time I am leaving the kitchen. Most of the time the thoughts do stick or lead to some action; whether finding something to read, research on, write down or code up.</p><h2 id="heading-there-arent-enough-dirty-dishes-i-need-an-alternative">There aren't enough dirty dishes, I need an alternative.</h2><p>Despite the fact that this works for me, the most glaring limitation is that once you are done washing dishes, that's the end of the session. It makes no sense to wash dishes for the sake of washing or just as an excuse to think (though I get tempted to do so every now and then).</p><p>Therefore I am looking for an alternative activity, something similarly mindless, that will not wait on me to have dirty dishes at my disposal.</p><p>What would you suggest ?</p><hr /><p> Thank you to my lovely fiance for helping me edit this post. Thanks for being patient when I'm doing dishes. </p>]]></description><link>https://zikani.hashnode.dev/is-the-kitchen-sink-the-best-place-to-think</link><guid isPermaLink="true">https://zikani.hashnode.dev/is-the-kitchen-sink-the-best-place-to-think</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Tue, 30 Jan 2024 22:16:30 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1706647204597/80c32b54-6b23-4d02-b5d2-5699778708c1.png</cover_image></item><item><title><![CDATA[Generating PDF from Markdown with Go]]></title><description><![CDATA[<p>In this quick-n-dirty article, we will see how to generate a PDF from a user-submitted markdown with Go. We will build a simple service using the standard library's <code>net/http</code> for the backend and EasyMDE, a browser editor, for handling markdown on the front-end.</p><p>Markdown is a popular authoring format and has gained wide adoption in many different places. Markdown is generally converted to HTML, but there do exist tools like <a target="_blank" href="https://pandoc.org/installing.html">Pandoc</a> which can additionally convert markdown to different formats like .docx documents, PDF etc...</p><p>In the Go ecosystem, a popular library for working with Markdown is <a target="_blank" href="https://github.com/yuin/goldmark">goldmark</a> - we which we will use to process user-submitted markdown text and render it to a PDF file. You can imagine this being used for basic authoring of content including letters, articles, simple newsletters, basic reports etc...</p><h2 id="heading-markdown-pdf-generator-backend">Markdown PDF generator backend</h2><p>For us to process Markdown, we will rely on the popular library <a target="_blank" href="https://github.com/yuin/goldmark">goldmark</a> which has some community-built extensions, the one we are particularly interested in is the <a target="_blank" href="http://github.com/stephenafamo/goldmark-pdf">goldmark-pdf</a> library which will allow us to render Goldmark's Markdown AST into PDF. Firstly, we will create a bare-bones (i.e. not production-grade) web server using the standard library's <code>net/http</code>.</p><p>The complete server code is shown below:</p><pre><code class="lang-go"><span class="hljs-keyword">package</span> main<span class="hljs-keyword">import</span> (    <span class="hljs-string">"bytes"</span>    <span class="hljs-string">"context"</span>    <span class="hljs-string">"encoding/base64"</span>    <span class="hljs-string">"fmt"</span>    <span class="hljs-string">"io/ioutil"</span>    <span class="hljs-string">"net/http"</span>    <span class="hljs-string">"os"</span>    <span class="hljs-string">"text/template"</span>    pdf <span class="hljs-string">"github.com/stephenafamo/goldmark-pdf"</span>    <span class="hljs-string">"github.com/yuin/goldmark"</span>)<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {    markdown := goldmark.New(        goldmark.WithRenderer(            pdf.New(                pdf.WithTraceWriter(os.Stdout),                pdf.WithContext(context.Background()),                pdf.WithImageFS(os.DirFS(<span class="hljs-string">"."</span>)),                pdf.WithHeadingFont(pdf.GetTextFont(<span class="hljs-string">"Arial"</span>, pdf.FontCourier)),                pdf.WithBodyFont(pdf.GetTextFont(<span class="hljs-string">"Arial"</span>, pdf.FontCourier)),                pdf.WithCodeFont(pdf.GetCodeFont(<span class="hljs-string">"Arial"</span>, pdf.FontCourier)),            ),        ),    )    mux := http.NewServeMux()    mux.HandleFunc(<span class="hljs-string">"/"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {        w.Header().Add(<span class="hljs-string">"Cache-Control"</span>, <span class="hljs-string">"cache"</span>)        indexHtmlTemplate := template.Must(template.ParseFiles(<span class="hljs-string">"index.html"</span>))        indexHtmlTemplate.ExecuteTemplate(w, <span class="hljs-string">"index.html"</span>, <span class="hljs-literal">nil</span>)    })    mux.HandleFunc(<span class="hljs-string">"/generate"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {        source, err := ioutil.ReadAll(r.Body)        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {            http.Error(w, err.Error(), http.StatusInternalServerError)            <span class="hljs-keyword">return</span>        }        <span class="hljs-keyword">var</span> buf bytes.Buffer        <span class="hljs-keyword">if</span> err := markdown.Convert([]<span class="hljs-keyword">byte</span>(source), &amp;buf); err != <span class="hljs-literal">nil</span> {            http.Error(w, <span class="hljs-string">"failed to generate pdf"</span>, http.StatusInternalServerError)            <span class="hljs-keyword">return</span>        }        base64Encoded := base64.StdEncoding.EncodeToString(buf.Bytes())        dataURL := fmt.Sprintf(<span class="hljs-string">"data:application/octet-stream;base64,%s"</span>, base64Encoded)        w.Write([]<span class="hljs-keyword">byte</span>(dataURL))    })    http.ListenAndServe(<span class="hljs-string">":8001"</span>, mux)}</code></pre><h2 id="heading-the-frontend-adding-an-in-browser-editor">The frontend - adding an in-browser editor</h2><p>We are going to complete our simple service by adding a frontend to it which is going to be a simple Markdown Editor with a button for Generating and Downloading the PDF. We will use EasyMDE for this - I went through several different options for an editor and at the moment I like EasyMDE, especially for how easy it is to add extra functionality to the Editor. Will will be extending EasyMDE to enable downloading the generated PDF by adding a button with a PDF icon for the user to generate the PDF.</p><p>The complete code for the <code>index.html</code> page is shown below</p><pre><code class="lang-xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"X-UA-Compatible"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"IE=edge"</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Markdown to PDF<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">base</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>        <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"</span> <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">"anonymous"</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/default.min.css"</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"</span>        <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="</span> <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">"anonymous"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.bundle.min.js"</span>        <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha384-pjaaA8dDz/5BgdFUPX6M/9SUZv4d12SUPF0axWc+VRZkx5xU3daN+lYb49+Ax+Tl"</span>        <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">"anonymous"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/highlight.min.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span>hljs.initHighlightingOnLoad();<span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">defer</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://use.fontawesome.com/releases/v5.4.1/js/all.js"</span>        <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha384-L469/ELG4Bg9sDQbl0hvjMq8pOcqFgkSpwhwnslzvVVGpDjYJ6wJJyYjvG3u8XW7"</span>        <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">"anonymous"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://unpkg.com/axios@0.26.0/dist/axios.min.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://unpkg.com/easymde@2.18.0/dist/easymde.min.css"</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://unpkg.com/easymde@2.18.0/dist/easymde.min.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"content"</span>&gt;</span>        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"app"</span>&gt;</span>            <span class="hljs-tag">&lt;<span class="hljs-name">textarea</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"editor"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">textarea</span>&gt;</span>            <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"downloadEl"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"display: none;"</span> <span class="hljs-attr">download</span>=<span class="hljs-string">"Document.pdf"</span>&gt;</span>Download PDF<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/javascript"</span>&gt;</span><span class="javascript">        <span class="hljs-keyword">let</span> downloadEl = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'downloadEl'</span>)        <span class="hljs-keyword">new</span> EasyMDE({            <span class="hljs-attr">autoDownloadFontAwesome</span>: <span class="hljs-literal">false</span>,            <span class="hljs-attr">toolbar</span>: [            <span class="hljs-string">"bold"</span>, <span class="hljs-string">"italic"</span>, <span class="hljs-string">"heading"</span>, <span class="hljs-string">"|"</span>, <span class="hljs-string">"quote"</span>, <span class="hljs-string">'strikethrough'</span>, <span class="hljs-string">'code'</span>, <span class="hljs-string">'heading'</span>,<span class="hljs-string">'|'</span>, <span class="hljs-string">'undo'</span>,<span class="hljs-string">'redo'</span>            ,<span class="hljs-string">'|'</span>, { <span class="hljs-comment">// Separator</span>                <span class="hljs-attr">name</span>: <span class="hljs-string">"generate-pdf"</span>,                <span class="hljs-attr">action</span>: <span class="hljs-function">(<span class="hljs-params">editor</span>) =&gt;</span> {                    axios.post(<span class="hljs-string">"/generate"</span>, editor.value())                    .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> {                        <span class="hljs-keyword">let</span> pdfDataURL = response.data                        <span class="hljs-keyword">if</span> (pdfDataURL.indexOf(<span class="hljs-string">"base64"</span>) &gt; <span class="hljs-number">0</span>) {                            downloadEl.setAttribute(<span class="hljs-string">'href'</span>, pdfDataURL)                            downloadEl.click()                        }                    })                },                <span class="hljs-attr">className</span>: <span class="hljs-string">"fa fa-file-pdf"</span>,                <span class="hljs-attr">title</span>: <span class="hljs-string">"Download PDF"</span>,            }, <span class="hljs-string">'|'</span>, {                <span class="hljs-attr">name</span>: <span class="hljs-string">"link"</span>,                <span class="hljs-attr">action</span>: <span class="hljs-string">'https://blog.nndi.cloud'</span>,                <span class="hljs-attr">className</span>: <span class="hljs-string">"fa fa-globe"</span>,                <span class="hljs-attr">title</span>: <span class="hljs-string">"Go to the NNDI Blog"</span>            }],            <span class="hljs-attr">element</span>: <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'editor'</span>),            <span class="hljs-attr">initialValue</span>: <span class="hljs-string">'## Welcome to Markdown to PDF using Go.\nType some markdown in here and then click the PDF button to render the markdown to PDF'</span>        });    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></code></pre><p>With those two bits of code done, we can now build and run our service. Make sure you copy the Go code into a file named <code>main.go</code> and the HTML code in a file named <code>index.html</code> in the same directory - you can then run the following commands:</p><pre><code class="lang-bash">$ go mod init markdown-pdf$ go mod tidy$ go run main.go</code></pre><p>The last command should start a server on http://localhost:8001 visit that in your browser and try it out.</p><p>Thanks for reading.</p>]]></description><link>https://zikani.hashnode.dev/generating-pdf-from-markdown-with-go</link><guid isPermaLink="true">https://zikani.hashnode.dev/generating-pdf-from-markdown-with-go</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Sat, 21 Oct 2023 22:00:00 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1698334233098/6833b004-fcdd-4bc8-9e99-de7cf272b6b5.webp</cover_image></item><item><title><![CDATA[Implementing a Custom ID generator for Hibernate entities in Java]]></title><description><![CDATA[<p>If you have used Hibernate before you know that you can specify that an Entity class should have it's <code>@Id</code> fields set via <code>@GeneratedValue</code> - typically you use either a sequence or identity strategy to automatically generate, say, auto-incrementing integer IDs (also take note that Hibernate also provides a <code>@UuidGenerator</code>).</p><p>However, Hibernate also allows you to create custom implementations of a generator which you can use to create a custom ID generation scheme - which can be useful for generating Transaction IDs for example.</p><p>In this article, I will show you how to implement such a custom generator that will be used to generate user-friendly, somewhat predictable, IDs. The IDs will have the following form, <code>FAN-00YY&lt;Month-Letter&gt;&lt;Day-Letter&gt;#&lt;counter&gt;</code> which would generate a value that looks something like this<code>FAN-0023JL#3642</code>. Of course, you can amend the solution presented here to match your own needs for the format of the IDs.</p><p>Let's get coding. To enable us to test the solution easily, we will use hibernate withing a Spring Boot project - so create a new Spring Boot project with Web, JPA and Postgres as dependencies. Done? Okay, great.</p><p>Let's assume we have an SQL table to keep track of Fans (whether the electric kind or the eccentric kind is up to you). As you can see, we have defined its id column as text and as a primary key which means we must provide unique values for that field every time we want to insert/create a record.</p><pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> fans (  <span class="hljs-keyword">id</span> <span class="hljs-built_in">text</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span> primary <span class="hljs-keyword">key</span>,  <span class="hljs-keyword">name</span> <span class="hljs-built_in">text</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,  created_at timestamptz);</code></pre><p>The associated entity class may look like this:</p><pre><code class="lang-java"><span class="hljs-keyword">import</span> jakarta.persistence.Column;<span class="hljs-keyword">import</span> jakarta.persistence.Entity;<span class="hljs-keyword">import</span> jakarta.persistence.Id;<span class="hljs-keyword">import</span> jakarta.persistence.Table;<span class="hljs-keyword">import</span> java.time.OffsetDateTime;<span class="hljs-meta">@Entity</span><span class="hljs-meta">@Table(name = "fans")</span><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Fan</span> </span>{    <span class="hljs-meta">@Id</span>    <span class="hljs-meta">@FanId</span>    <span class="hljs-keyword">private</span> String id;    <span class="hljs-meta">@Column(name = "name")</span>    <span class="hljs-keyword">private</span> String name;    <span class="hljs-meta">@Column(name="created_at")</span>    <span class="hljs-keyword">private</span> OffsetDateTime createdAt;    <span class="hljs-comment">// getters and setters omitted...</span>}</code></pre><p>The attentive reader will observe that we have added a non-standard annotation <code>@FanId</code> to the id field. This is our custom annotation that will be used to drive the custom ID generation. Lets create the custom annotation as shown below:-</p><pre><code class="lang-java"><span class="hljs-keyword">import</span> org.hibernate.annotations.IdGeneratorType;<span class="hljs-keyword">import</span> java.lang.annotation.Retention;<span class="hljs-keyword">import</span> java.lang.annotation.RetentionPolicy;<span class="hljs-keyword">import</span> java.lang.annotation.Target;<span class="hljs-keyword">import</span> <span class="hljs-keyword">static</span> java.lang.annotation.ElementType.FIELD;<span class="hljs-keyword">import</span> <span class="hljs-keyword">static</span> java.lang.annotation.ElementType.METHOD;<span class="hljs-meta">@IdGeneratorType( FanIdGenerator.class )</span><span class="hljs-meta">@Retention(RetentionPolicy.RUNTIME)</span><span class="hljs-meta">@Target({ FIELD, METHOD })</span><span class="hljs-keyword">public</span> <span class="hljs-meta">@interface</span> FanId {    <span class="hljs-function"><span class="hljs-keyword">boolean</span> <span class="hljs-title">usePersistentCounter</span><span class="hljs-params">()</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">false</span></span>;}</code></pre><h2 id="heading-the-id-generator-class">The ID Generator class</h2><p>This is the main class that handles the heavy lifting of the ID generation process. It is implemented as a class that implements the <code>BeforeExecutionGenerator</code> which means we can perform some actions before lifecycle events like Entity#save.</p><p>The generator defines an interface <code>CounterService</code> to which it delegates the generation of an automatically incrementing counter which forms the last part of our custom ID. In the constructor, we check if the annotation we are processing is set to use persistent counters, if so - we use a specific implementation of the counter service that stores the counters in the database - more on that below.</p><p>As you can see in the code below, we specify that we only want this class to be executed on INSERT operations/events.</p><pre><code class="lang-java"><span class="hljs-keyword">import</span> org.hibernate.HibernateException;<span class="hljs-keyword">import</span> org.hibernate.engine.spi.SharedSessionContractImplementor;<span class="hljs-keyword">import</span> org.hibernate.generator.BeforeExecutionGenerator;<span class="hljs-keyword">import</span> org.hibernate.generator.EventType;<span class="hljs-keyword">import</span> org.hibernate.generator.EventTypeSets;<span class="hljs-keyword">import</span> org.hibernate.id.factory.spi.CustomIdGeneratorCreationContext;<span class="hljs-keyword">import</span> java.lang.reflect.Member;<span class="hljs-keyword">import</span> java.time.OffsetDateTime;<span class="hljs-keyword">import</span> java.util.EnumSet;<span class="hljs-keyword">import</span> <span class="hljs-keyword">static</span> org.hibernate.internal.util.ReflectHelper.getPropertyType;<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FanIdGenerator</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">BeforeExecutionGenerator</span> </span>{    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> CounterService DEFAULT_IN_PROCESS_COUNTER = <span class="hljs-keyword">new</span> InMemoryCounterService();    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">char</span>[] CHARS = <span class="hljs-keyword">new</span> <span class="hljs-keyword">char</span>[]{            <span class="hljs-string">'A'</span>, <span class="hljs-string">'B'</span>, <span class="hljs-string">'C'</span>, <span class="hljs-string">'D'</span>, <span class="hljs-string">'E'</span>, <span class="hljs-string">'F'</span>, <span class="hljs-string">'G'</span>,            <span class="hljs-string">'H'</span>, <span class="hljs-string">'I'</span>, <span class="hljs-string">'J'</span>, <span class="hljs-string">'K'</span>, <span class="hljs-string">'L'</span>, <span class="hljs-string">'M'</span>, <span class="hljs-string">'N'</span>,            <span class="hljs-string">'O'</span>, <span class="hljs-string">'P'</span>, <span class="hljs-string">'Q'</span>, <span class="hljs-string">'R'</span>, <span class="hljs-string">'S'</span>, <span class="hljs-string">'T'</span>, <span class="hljs-string">'U'</span>,            <span class="hljs-string">'V'</span>, <span class="hljs-string">'W'</span>, <span class="hljs-string">'X'</span>, <span class="hljs-string">'Y'</span>, <span class="hljs-string">'Z'</span>, <span class="hljs-string">'1'</span>, <span class="hljs-string">'2'</span>,            <span class="hljs-string">'3'</span>, <span class="hljs-string">'4'</span>, <span class="hljs-string">'5'</span>, <span class="hljs-string">'6'</span>, <span class="hljs-string">'7'</span>, <span class="hljs-string">'8'</span>, <span class="hljs-string">'9'</span>    };    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> CounterService counterService;    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">FanIdGenerator</span><span class="hljs-params">(            FanId config,            Member idMember,            CustomIdGeneratorCreationContext creationContext)</span> </span>{        <span class="hljs-keyword">final</span> Class&lt;?&gt; propertyType = getPropertyType(idMember);        <span class="hljs-keyword">if</span> (config.usePersistentCounter()) {            counterService = <span class="hljs-keyword">new</span> PersistentCounterService();        } <span class="hljs-keyword">else</span> {            counterService = DEFAULT_IN_PROCESS_COUNTER;        }        <span class="hljs-keyword">if</span> (!String.class.isAssignableFrom(propertyType)) {            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> HibernateException(<span class="hljs-string">"Unanticipated return type ["</span> + propertyType.getName() + <span class="hljs-string">"] for FanId conversion"</span>);        }    }    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> String <span class="hljs-title">formatYear</span><span class="hljs-params">(<span class="hljs-keyword">int</span> year)</span> </span>{        <span class="hljs-keyword">return</span> String.format(<span class="hljs-string">"00%d"</span>, year % <span class="hljs-number">100</span>);    }    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">char</span> <span class="hljs-title">formatMonth</span><span class="hljs-params">(<span class="hljs-keyword">int</span> dayValue)</span> </span>{        <span class="hljs-keyword">return</span> CHARS[dayValue - <span class="hljs-number">1</span>];    }    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">char</span> <span class="hljs-title">formatDay</span><span class="hljs-params">(<span class="hljs-keyword">int</span> monthValue)</span> </span>{        <span class="hljs-keyword">return</span> CHARS[monthValue - <span class="hljs-number">1</span>];    }    <span class="hljs-meta">@Override</span>    <span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">generate</span><span class="hljs-params">(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType)</span> </span>{        OffsetDateTime now = OffsetDateTime.now();        <span class="hljs-keyword">return</span> String.format(<span class="hljs-string">"FAN-%s%s%s#%s"</span>,                formatYear(now.getYear()),                formatMonth(now.getMonthValue()),                formatDay(now.getDayOfMonth()),                counterService.nextInteger(session, now)        );    }    <span class="hljs-meta">@Override</span>    <span class="hljs-function"><span class="hljs-keyword">public</span> EnumSet&lt;EventType&gt; <span class="hljs-title">getEventTypes</span><span class="hljs-params">()</span> </span>{        <span class="hljs-keyword">return</span> EventTypeSets.INSERT_ONLY;    }    <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">CounterService</span> </span>{        <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">nextInteger</span><span class="hljs-params">(SharedSessionContractImplementor session, OffsetDateTime onDate)</span></span>;    }}</code></pre><blockquote><p>The ID generation scheme was inspired by some code shared and implemented by <a target="_blank" href="https://ntumbuka.me/">a friend</a> for a health information system used widely in Malawi, the code for that <a target="_blank" href="https://github.com/EGPAFMalawiHIS/his_emr_api_lab/blob/2bf27234045499d67f84739790a5ef8f42222130/app/services/lab/accession_number_service.rb#L11">is here</a>.</p></blockquote><h2 id="heading-integer-counter-generator-using-in-memory-counter">Integer Counter generator using in-memory counter</h2><p>With this simplest implementation, the counter service uses a variable to keep track of the values generated. We use an AtomicInteger for this to enable some kind of thread safety, in case that's required. The implementation is fairly straightforward:-</p><pre><code class="lang-java"><span class="hljs-keyword">import</span> org.hibernate.engine.spi.SharedSessionContractImplementor;<span class="hljs-keyword">import</span> java.time.OffsetDateTime;<span class="hljs-keyword">import</span> java.util.concurrent.atomic.AtomicInteger;<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InMemoryCounterService</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">FanIdGenerator</span>.<span class="hljs-title">CounterService</span> </span>{    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> AtomicInteger id = <span class="hljs-keyword">new</span> AtomicInteger(<span class="hljs-number">1000</span>);    <span class="hljs-meta">@Override</span>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">nextInteger</span><span class="hljs-params">(SharedSessionContractImplementor session, OffsetDateTime onDate)</span> </span>{        <span class="hljs-keyword">return</span> id.incrementAndGet();    }}</code></pre><p>The above approach works but has the limitation that the counter of the IDs will be reset every time the service is restarted which may lead to errors since that introduces the potential of generating duplicate keys (remember we want the IDs to be unique to use them as primary keys). We can do better by storing the counter's latest value in the database, indexed by date - this way we can be sure that we can always continue from where we left off despite restarts.</p><h2 id="heading-integer-counter-generator-using-a-value-stored-in-a-database-table">Integer Counter Generator using a value stored in a database table</h2><p>Below is the implementation of the CounterService interface, named <code>PersistentCounterService</code>, which stores counter values in a database table to allow us to fetch easily the latest counter value to try to avoid collisions. Each date has its counter that gets incremented every time we receive a request to generate an ID for that day.</p><p>As you can see in the code, the table that keeps track of the counters is named <code>fan_id_counters</code> and we try to create it every time, this allows our ID Generator to be re-usable without any upfront configuration - the only implication is that the user connecting to the database should have appropriate permissions to create tables.</p><p>You could always change the implementation to remove the create call and manually create your counter-tracking table ahead of time.</p><pre><code class="lang-java"><span class="hljs-keyword">import</span> org.hibernate.engine.spi.SharedSessionContractImplementor;<span class="hljs-keyword">import</span> java.time.LocalDate;<span class="hljs-keyword">import</span> java.time.OffsetDateTime;<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PersistentCounterService</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">FanIdGenerator</span>.<span class="hljs-title">CounterService</span> </span>{    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> DEFAULT_COUNTER_START = <span class="hljs-number">1000</span>;    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String SQL_CREATE_COUNTER_TABLE = <span class="hljs-string">""</span><span class="hljs-string">"            CREATE TABLE IF NOT EXISTS fan_id_counters (                entry_date date primary key not null,                counter_value integer not null default 1000            );            "</span><span class="hljs-string">""</span>;    <span class="hljs-meta">@Override</span>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">nextInteger</span><span class="hljs-params">(SharedSessionContractImplementor session, OffsetDateTime onDate)</span> </span>{        <span class="hljs-keyword">final</span> <span class="hljs-keyword">var</span> today = LocalDate.now();        <span class="hljs-keyword">var</span> statelessSession = session.isStatelessSession() ? session.asStatelessSession() : session.getSession();        statelessSession.createNativeMutationQuery(SQL_CREATE_COUNTER_TABLE)                .executeUpdate();        <span class="hljs-keyword">var</span> list = statelessSession.createNativeQuery(<span class="hljs-string">"SELECT counter_value FROM fan_id_counters WHERE entry_date = :date FOR UPDATE LIMIT 1;"</span>, Integer.class)                .setParameter("date", today)                .setFetchSize(<span class="hljs-number">1</span>)                .getResultList();        <span class="hljs-keyword">if</span> (list.isEmpty()) {            statelessSession.createNativeMutationQuery(<span class="hljs-string">"INSERT INTO fan_id_counters(entry_date, counter_value) VALUES (:date , :counter)"</span>)                    .setParameter(<span class="hljs-string">"date"</span>, today)                    .setParameter(<span class="hljs-string">"counter"</span>, DEFAULT_COUNTER_START + <span class="hljs-number">1</span>)                    .executeUpdate();            <span class="hljs-keyword">return</span> DEFAULT_COUNTER_START;        }        statelessSession.createNativeMutationQuery(<span class="hljs-string">"UPDATE fan_id_counters SET counter_value = counter_value + 1 WHERE entry_date = :date ;"</span>)                .setParameter(<span class="hljs-string">"date"</span>, today)                .executeUpdate();        <span class="hljs-keyword">return</span> list.get(<span class="hljs-number">0</span>);    }}</code></pre><h2 id="heading-lets-test-this-thing">Let's test this thing</h2><p>Now that we have the ID generator setup, we can implement a simple controller to allow us to create Fan entities to ensure that the generator does its job and also do a little bit of load testing to ensure that the generator doesn't trip up when there is a large number of requests, as one use-case would be to use it to generate Transaction IDs or Transaction Reference numbers for example.</p><p>First, let's create a simple controller that allows us to list and create Fans</p><pre><code class="lang-java"><span class="hljs-meta">@RestController</span><span class="hljs-meta">@RequestMapping("/api/v1/fans")</span><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FanController</span> </span>{    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> FanRepository fanRepository;    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">FanController</span><span class="hljs-params">(FanRepository fanRepository)</span> </span>{        <span class="hljs-keyword">this</span>.fanRepository = fanRepository;    }    <span class="hljs-meta">@GetMapping</span>    <span class="hljs-keyword">public</span> ResponseEntity&lt;List&lt;Fan&gt;&gt; createFan(Pageable pageable) {        <span class="hljs-keyword">return</span> ResponseEntity.ok(fanRepository.findAll(pageable).toList());    }    <span class="hljs-meta">@PostMapping</span>    <span class="hljs-function"><span class="hljs-keyword">public</span> ResponseEntity&lt;Fan&gt; <span class="hljs-title">createFan</span><span class="hljs-params">(<span class="hljs-meta">@RequestParam("name")</span> String name)</span> </span>{        Fan fan = <span class="hljs-keyword">new</span> Fan();        fan.setName(name);        fan.setCreatedAt(OffsetDateTime.now());        fanRepository.saveAndFlush(fan);        <span class="hljs-keyword">return</span> ResponseEntity.ok(fan);    }}</code></pre><p>We can use the following cURL request to create a Fan</p><pre><code class="lang-bash">curl -X POST <span class="hljs-string">"http://localhost:8080/api/v1/fans?name=James"</span></code></pre><p>This could return something like this</p><pre><code class="lang-json">{    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"FAN-0023JL#1000"</span>,    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"James"</span>,    <span class="hljs-attr">"createdAt"</span>: <span class="hljs-string">"2023-08-12T21:04:44.135036Z"</span>},</code></pre><h3 id="heading-load-testing-the-id-generator">Load testing the ID Generator</h3><p>We can now use a load testing tool like <a target="_blank" href="https://github.com/rakyll/hey">Hey</a> to load test the server - which will send about 1000 requests with the command below.</p><pre><code class="lang-bash">hey -n 1000 -m POST <span class="hljs-string">"http://localhost:8080/api/v1/fans?name=James"</span></code></pre><p>Here is a screenshot that shows a sample run on my environment, not so bad :) -</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697172409868/173bc51c-0ddc-44e9-8ccf-1b58ee0e83f2.png" alt class="image--center mx-auto" /></p><p>We can check in the database and verify that each fan has a uniquely generated ID.</p><h2 id="heading-conclusion">Conclusion</h2><p>In this article, we have seen how to implement a custom ID generator with Hibernate that uses a custom format that includes an auto-incrementing counter that has two implementations, one in-memory and another that stores the most recent counter in a database table indexed by calendar day. You can adapt the code here to implement your ID generator which can be useful for things like Transaction IDs or Transaction References that are somewhat more user friendly.</p><p>Thank you for reading.</p>]]></description><link>https://zikani.hashnode.dev/implementing-a-custom-id-generator-for-hibernate-entities-in-java</link><guid isPermaLink="true">https://zikani.hashnode.dev/implementing-a-custom-id-generator-for-hibernate-entities-in-java</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Sun, 08 Oct 2023 22:00:00 GMT</pubDate></item><item><title><![CDATA[A few PostgreSQL tricks]]></title><description><![CDATA[<p>In this article, we will look at some cool, practical query tricks you can use in projects that use PostgreSQL as the DBMS:</p><h2 id="heading-order-results-but-select-a-specific-first-row">Order results but select a specific first-row</h2><p>This trick allows you to write a query with an order-by clause which allows you to choose which row should come first based on some condition, despite the presence of another order-by clause.</p><p>See the example below which will always return the company with ID = 2 as the first row despite how the other order by clause is written.</p><pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> companies_test (  <span class="hljs-keyword">id</span> <span class="hljs-built_in">serial</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,  company_name <span class="hljs-built_in">text</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,  created_at <span class="hljs-built_in">timestamp</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">now</span>());<span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> companies_test (company_name)<span class="hljs-keyword">VALUES</span> (<span class="hljs-string">'Company A'</span>), (<span class="hljs-string">'Company B'</span>),(<span class="hljs-string">'Company C'</span>);<span class="hljs-keyword">select</span> <span class="hljs-keyword">id</span>, company_name, created_at <span class="hljs-keyword">from</span> companies_test<span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> (<span class="hljs-keyword">id</span> = <span class="hljs-number">2</span>) <span class="hljs-keyword">desc</span>, created_at <span class="hljs-keyword">asc</span></code></pre><h2 id="heading-using-string-aggregates-to-return-arrays-for-related-records">Using string aggregates to return arrays for related records</h2><p>The <code>string_agg</code> function is a good function in Postgres that allows you to aggregate columns after a group by operations.</p><p>Let's say you have a database of individuals who share the same first and last names and you want to capture all the dates of birth linked to that name in the database, (sorry if this is a quirky example, could only think of it as it's something I faced) you could do something like this:</p><pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> individuals_test (  <span class="hljs-keyword">id</span> <span class="hljs-built_in">serial</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,  first_name <span class="hljs-built_in">text</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,  last_name <span class="hljs-built_in">text</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,  date_of_birth <span class="hljs-built_in">date</span>,  created_at <span class="hljs-built_in">timestamp</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">now</span>());<span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> individuals_test (first_name, last_name, date_of_birth)<span class="hljs-keyword">VALUES</span> (<span class="hljs-string">'John'</span>, <span class="hljs-string">'Doe'</span>, <span class="hljs-string">'1970-01-01'</span>), (<span class="hljs-string">'John'</span>, <span class="hljs-string">'Doe'</span>, <span class="hljs-string">'2003-10-01'</span>),(<span class="hljs-string">'John'</span>, <span class="hljs-string">'Doe'</span>, <span class="hljs-string">'1993-04-01'</span>); <span class="hljs-keyword">SELECT</span>    <span class="hljs-keyword">concat</span>(first_name, <span class="hljs-string">' '</span>, last_name) <span class="hljs-keyword">as</span> full_name,    string_agg(date_of_birth::<span class="hljs-built_in">text</span>, <span class="hljs-string">';'</span>) <span class="hljs-keyword">as</span> dates_of_birth  <span class="hljs-keyword">FROM</span> individuals_test  <span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> <span class="hljs-keyword">concat</span>(first_name, <span class="hljs-string">' '</span>, last_name) <span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> full_name</code></pre><p>The result will look something like</p><pre><code class="lang-markdown">| full<span class="hljs-emphasis">_name | dates_</span>of<span class="hljs-emphasis">_birth                   || --------- | -------------------------------- || John Doe  | 1970-01-01;2003-10-01;1993-04-01 |</span></code></pre><h2 id="heading-checking-if-a-record-exists-with-a-true-or-false-result">Checking if a record exists with a true or false result</h2><p>Most times we have some parts in our code where we would just like to know if a record exists. Here is a pattern for a query that always guarantees a result that will be either <code>true</code> or <code>false</code> depending on the result of the subquery - this way you don't have to check if the resultset from your query is empty or not:</p><pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">exists</span> (<span class="hljs-keyword">select</span> <span class="hljs-keyword">from</span> <span class="hljs-keyword">users</span> <span class="hljs-keyword">where</span> username = :username);</code></pre><h2 id="heading-check-constraint-to-validate-a-json-column">CHECK constraint to validate a JSON column</h2><p>Some days you may find yourself storing JSON in a database column - now the thing about JSON is that you will have to deal with a Schema somewhere, usually in your application code. Here is a simple approach to check that the JSON has at least the required top-level keys as a simple validation to ensure incorrectly formatted JSON isn't entered in your columns. You can go crazy with this and validate against a whole JSON Schema - take a look at <a target="_blank" href="https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation">pg_jsonschema</a> by the Supabase team</p><pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> user_app_settings (  username <span class="hljs-built_in">text</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">null</span>,  <span class="hljs-keyword">settings</span> jsonb);<span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> user_app_settings <span class="hljs-keyword">ADD</span> <span class="hljs-keyword">CONSTRAINT</span> check_settings_fields      <span class="hljs-keyword">CHECK</span> (<span class="hljs-keyword">settings</span>::jsonb ?&amp; <span class="hljs-built_in">array</span>[<span class="hljs-string">'cover_photo'</span>, <span class="hljs-string">'current_theme'</span>, <span class="hljs-string">'toolbar_x'</span>, <span class="hljs-string">'toolbar_y'</span>, <span class="hljs-string">'version'</span>]); <span class="hljs-comment">-- this insert will work because the JSON is well-formed according to our rules</span> <span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> user_app_settings <span class="hljs-keyword">VALUES</span> (<span class="hljs-string">'zikani03'</span>, <span class="hljs-string">'{"version": "v1.0-beta", "cover_photo": "https://some.site/pic/zikani03", "current_theme":"nord", "toolbar_x": 354.5, "toolbar_y": 100}'</span>::jsonb); <span class="hljs-comment">-- this insert below should fail since we don't have the "version" field </span> <span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> user_app_settings <span class="hljs-keyword">VALUES</span> (<span class="hljs-string">'john'</span>, <span class="hljs-string">'{"no_version": "v1.0-beta", "cover_photo": "https://some.site/pic/john", "current_theme":"nord", "toolbar_x": 354.5, "toolbar_y": 100}'</span>::jsonb);</code></pre><h2 id="heading-conclusion">Conclusion</h2><p>In this article, I shared a few tricks you can use in your projects that use Postgresql, though some of them would also likely work in other DBMSs if you found the equivalent functions. I hope to share more as I keep learning...</p><p>Thanks for reading.</p>]]></description><link>https://zikani.hashnode.dev/postgresql-tricks-01</link><guid isPermaLink="true">https://zikani.hashnode.dev/postgresql-tricks-01</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Sun, 08 Oct 2023 10:00:00 GMT</pubDate></item><item><title><![CDATA[Keeping @JsonProperty and Bean Validation field error messages in sync in a Spring Boot project]]></title><description><![CDATA[<p>Damn, this one bit me very recently. So imagine you have a Java API and because of whatever reasons you agreed that the JSON properties for the requests should be in <code>snake_case_form</code>. Cool, cool, that just wants a simple Jackson <code>@JsonProperty</code> annotation on a POJO (Plain Old Java Object) field. Now imagine you are sending validation error messages to the client, and they discover that the validation messages refer to the fields using <code>camelCaseForm</code> - now you have to reassure them that their request with snake_case_form fields is supposed to be that way, except for there being validation errors, of course.</p><p>This very thing happened to me recently and I wanted to document solutions to the problem, I will tell you which I went with at the end.</p><p>Let's create a new Spring Boot project to get a better picture of the issue at hand.</p><p>Let's add a Person class that looks like this. Notice that we have thrown in Hibernate Validator annotations to validate the data, like the good backend developers we are.</p><pre><code class="lang-java"><span class="hljs-keyword">import</span> com.fasterxml.jackson.annotation.JsonProperty;<span class="hljs-keyword">import</span> jakarta.validation.constraints.Email;<span class="hljs-keyword">import</span> jakarta.validation.constraints.NotEmpty;<span class="hljs-keyword">import</span> org.hibernate.validator.constraints.Length;<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span> </span>{    <span class="hljs-meta">@NotEmpty</span>    <span class="hljs-meta">@Length(min = 2, max = 120)</span>    <span class="hljs-meta">@JsonProperty("full_name")</span>    <span class="hljs-keyword">private</span> String fullName;    <span class="hljs-meta">@Email</span>    <span class="hljs-meta">@JsonProperty("email")</span>    <span class="hljs-keyword">private</span> String email;    <span class="hljs-meta">@NotEmpty</span>    <span class="hljs-meta">@Length(min = 8, max = 50)</span>    <span class="hljs-meta">@JsonProperty("phone_number")</span>    <span class="hljs-keyword">private</span> String phoneNumber;    <span class="hljs-meta">@JsonProperty("country_iso")</span>    <span class="hljs-keyword">private</span> String countryISO;    <span class="hljs-comment">// getters and setters omitted ...</span>}</code></pre><p>And a Spring Boot controller that just validates the data and returns it</p><pre><code class="lang-java"><span class="hljs-comment">// imports omitted for brevity</span><span class="hljs-meta">@RestController</span><span class="hljs-meta">@RequestMapping("/people")</span><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PeopleController</span> </span>{    <span class="hljs-meta">@PostMapping</span>    <span class="hljs-function"><span class="hljs-keyword">public</span> ResponseEntity&lt;Person&gt; <span class="hljs-title">handlePost</span><span class="hljs-params">(<span class="hljs-meta">@Validated</span> <span class="hljs-meta">@RequestBody</span> Person request)</span> </span>{        <span class="hljs-keyword">return</span> ResponseEntity.ok(request);    }}</code></pre><p>We want our clients to send us data that looks like this</p><pre><code class="lang-json">{  <span class="hljs-attr">"full_name"</span>: <span class="hljs-string">"John Banda"</span>,  <span class="hljs-attr">"phone_number"</span>: <span class="hljs-string">""</span>,  <span class="hljs-attr">"email"</span>: <span class="hljs-string">"john.banda@example.com"</span>,  <span class="hljs-attr">"country_iso"</span>: <span class="hljs-string">"MWI"</span>}</code></pre><p>Now the client sends a request with invalid data (let's assume it's missing the <code>full_name</code> and <code>phone_number</code> fields) and the API, like any good API, returns a good ol' <code>400 Bad Request</code> to let them know the error of their ways. To figure out how they may have screwed that request up, the client checks the response body and discovers the following:</p><pre><code class="lang-json">{    <span class="hljs-attr">"timestamp"</span>: <span class="hljs-string">"2023-09-27T20:22:29.627+00:00"</span>,    <span class="hljs-attr">"status"</span>: <span class="hljs-number">400</span>,    <span class="hljs-attr">"error"</span>: <span class="hljs-string">"Bad Request"</span>,    <span class="hljs-attr">"path"</span>: <span class="hljs-string">"/people"</span>}</code></pre><p>From the status code, they can guess it's an error on their end, but the response doesn't show if it's a validation error or something else wrong with the request data. Now the client isn't sure if they sent the request with the rightly named fields in the first place or if it's our API that's gone off the Rails (if there is a pun here, it is intended ;).</p><h2 id="heading-sending-validation-error-response">Sending validation error response</h2><p>We would rather give the client a bit more information about why their request is bad. A good way to do that is to catch the validation errors and return a <code>400 BadRequest</code> with actual details about the validation errors. Let's create a ValidationErrorsResponse class with the following code:</p><pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ValidationErrorsResponse</span> </span>{    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> code;    <span class="hljs-keyword">private</span> String message;    <span class="hljs-keyword">private</span> Map&lt;String, List&lt;String&gt;&gt; validationErrors;    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ValidationErrorsResponse</span><span class="hljs-params">(<span class="hljs-keyword">int</span> code, String message)</span> </span>{        <span class="hljs-keyword">this</span>.code = code;        <span class="hljs-keyword">this</span>.message = message;        <span class="hljs-keyword">this</span>.validationErrors = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();    }    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">put</span><span class="hljs-params">(String field, String error)</span> </span>{        validationErrors.computeIfAbsent(field, s -&gt; <span class="hljs-keyword">new</span> ArrayList&lt;&gt;()).add(error);    }    <span class="hljs-comment">// getters and setters omitted ...   </span>}</code></pre><p>Now we will need to register an Exception handler for the validation error which we can register against a <code>MethodArgumentNotValidException</code></p><pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExceptionHandlers</span> </span>{    <span class="hljs-meta">@ResponseStatus(HttpStatus.BAD_REQUEST)</span>    <span class="hljs-meta">@ExceptionHandler(MethodArgumentNotValidException.class)</span>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> ResponseEntity&lt;ValidationErrorsResponse&gt; <span class="hljs-title">handleExceptions</span><span class="hljs-params">(MethodArgumentNotValidException ex)</span> </span>{        <span class="hljs-keyword">final</span> List&lt;FieldError&gt; fieldErrors = ex.getBindingResult().getFieldErrors();        ValidationErrorsResponse errorResponse = <span class="hljs-keyword">new</span> ValidationErrorsResponse(                HttpStatus.BAD_REQUEST.value(),                HttpStatus.BAD_REQUEST.getReasonPhrase()        );        <span class="hljs-keyword">for</span> (FieldError fieldError : fieldErrors) {            errorResponse.put(fieldError.getField(), fieldError.getDefaultMessage());        }        <span class="hljs-keyword">return</span> ResponseEntity.badRequest().body(errorResponse);    }}</code></pre><p>It's at this point that if we try to send a POST with an empty or invalid object <code>{}</code> to the <code>/people</code> endpoint, we will get a response similar to the one below, which as you can see shows the fields in <code>camelCase</code> form.</p><pre><code class="lang-json">{    <span class="hljs-attr">"code"</span>: <span class="hljs-number">400</span>,    <span class="hljs-attr">"message"</span>: <span class="hljs-string">"Bad Request"</span>,    <span class="hljs-attr">"validationErrors"</span>: {        <span class="hljs-attr">"fullName"</span>: [            <span class="hljs-string">"must not be empty"</span>        ],        <span class="hljs-attr">"phoneNumber"</span>: [            <span class="hljs-string">"must not be empty"</span>        ]    }}</code></pre><p>While this works and in other situations would be sufficient, it is not great for us because the client will now get confused about the naming of the fields; the validation error response shows the fields named in <code>camelCaseForm</code> whereas the request data is expected to be sent in <code>snake_case_form</code>. The very problem we want to avoid!</p><p>We have some possible solutions to fix this issue, explained below.</p><h2 id="heading-solution-1-rename-the-class-fields-to-the-snake-case-form">Solution 1: Rename the class fields to the snake case form</h2><p>So one quick solution to this problem is the naming of the fields, you can just change it to match the <code>@JsonProperty</code> value. This has the downside that your linter or co-worker may shout at you for naming fields "out-of-standard" but fixes the issue without any over-engineering required. The con is that you have to name all the fields in all your other API objects in this way, so don't forget!</p><pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Person</span> </span>{    <span class="hljs-meta">@NotEmpty</span>    <span class="hljs-meta">@Length(min = 2, max = 120)</span>    <span class="hljs-meta">@JsonProperty("full_name")</span>    <span class="hljs-keyword">private</span> String full_name;    <span class="hljs-meta">@Email</span>    <span class="hljs-meta">@JsonProperty("email")</span>    <span class="hljs-keyword">private</span> String email;    <span class="hljs-meta">@NotEmpty</span>    <span class="hljs-meta">@Length(min = 8, max = 50)</span>    <span class="hljs-meta">@JsonProperty("phone_number")</span>    <span class="hljs-keyword">private</span> String phone_number;    <span class="hljs-meta">@JsonProperty("country_iso")</span>    <span class="hljs-keyword">private</span> String country_iso;    <span class="hljs-comment">// getters and setters omitted ...</span>}</code></pre><h2 id="heading-solution-2-improve-the-exception-handler-to-maintain-standards">Solution 2: Improve the exception handler to maintain standards</h2><p>Another approach to this is to do a little "over-engineering" to solve the problem in a way that maintains your strongly held opinions and standards while ensuring that the client will never be confused about what's what. This approach involves putting a bit more code in the exception handler to check if the field we are dealing with has a <code>@JsonProperty</code> annotation and then uses the <code>value()</code> from that annotation as the field name when building up validation errors, if we can't find the field, we fall back to sending the regular Bean name of the field/property.</p><p>We don't have to make any changes to the names of the fields on the Person or any other class. Here is how we could solve the problem with this approach:-</p><pre><code class="lang-java">    <span class="hljs-meta">@ResponseStatus(HttpStatus.BAD_REQUEST)</span>    <span class="hljs-meta">@ExceptionHandler(MethodArgumentNotValidException.class)</span>    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> ResponseEntity&lt;ValidationErrorsResponse&gt; <span class="hljs-title">handleExceptions</span><span class="hljs-params">(MethodArgumentNotValidException ex)</span> </span>{        <span class="hljs-keyword">final</span> List&lt;FieldError&gt; fieldErrors = ex.getBindingResult().getFieldErrors();        ValidationErrorsResponse errorResponse = <span class="hljs-keyword">new</span> ValidationErrorsResponse(                HttpStatus.BAD_REQUEST.value(),                HttpStatus.BAD_REQUEST.getReasonPhrase()        );        <span class="hljs-keyword">for</span> (FieldError fieldError : fieldErrors) {            <span class="hljs-keyword">try</span> {                <span class="hljs-keyword">var</span> beanClazz = ex.getTarget().getClass();                <span class="hljs-keyword">var</span> field = beanClazz.getDeclaredField(fieldError.getField());                <span class="hljs-keyword">var</span> jsonPropertyAnnotation = field.getAnnotation(JsonProperty.class);                <span class="hljs-keyword">if</span> (jsonPropertyAnnotation != <span class="hljs-keyword">null</span>) {                    errorResponse.put(jsonPropertyAnnotation.value(), fieldError.getDefaultMessage());                } <span class="hljs-keyword">else</span> {                    LoggerFactory.getLogger(getClass()).debug(<span class="hljs-string">"Failed to get annotated field at {}"</span>, fieldError.getField());                    errorResponse.put(fieldError.getField(), fieldError.getDefaultMessage());                }            } <span class="hljs-keyword">catch</span> (NoSuchFieldException e) {                LoggerFactory.getLogger(getClass()).error(<span class="hljs-string">"Failed to get annotated field"</span>, e);                errorResponse.put(fieldError.getField(), fieldError.getDefaultMessage());            }        }        <span class="hljs-keyword">return</span> ResponseEntity.badRequest().body(errorResponse);    }</code></pre><h2 id="heading-solution-3-use-a-consistent-naming-convention-ie-camelcase">Solution 3: Use a consistent naming convention (i.e. camelCase)</h2><p>Another option is to update your API data fields + docs to use <code>camelCasedFields</code> as a standard and avoid compromising on Java bean standards and introducing inconsistencies. This is probably not the most practical solution if your API is already used by clients in production unless you manage the change/upgrade to a new version very well. You may also face resistance from API clients who insist on dealing with fields named in <code>snake_case_form</code>.</p><p>There is no code for this section because the solution requires communication and coordination with your teams and clients, which you cannot code around. YMMV.</p><hr /><p>Thanks for reading.</p><p>Ohh, I promised to tell you which solution I went with. I went for Solution 1 as a quick fix <s>but after reviewing the other API objects + endpoints in our code, I applied Solution 2 which helped avoid renaming a whole bunch of API object classes</s>. Solution 2 works well with simple flat objects and isn't robust for nested structures. Will write a follow-up soon.</p>]]></description><link>https://zikani.hashnode.dev/jsonproperty-bean-validation-field-error-messages-in-spring-boot</link><guid isPermaLink="true">https://zikani.hashnode.dev/jsonproperty-bean-validation-field-error-messages-in-spring-boot</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Sat, 23 Sep 2023 22:00:00 GMT</pubDate></item><item><title><![CDATA[Hiding sensitive information when the screen loses focus with React]]></title><description><![CDATA[<p>I don't remember what prompted me to think of this, but basically, I was uncomfortable that sometimes people leave their screens unlocked with potentially sensitive information displayed on their screens. As a developer, I set out to think of a solution that could address this.</p><p>Although I appreciate that it will most likely not be implemented in a lot of places it did give me an excuse to try something sort of new to me; I had never built a standalone React library, so it was a good excuse to finally do that.</p><h2 id="heading-enter-react-frostedglass">Enter <code>react-frostedglass</code></h2><p>You may forgive the corny name. <a target="_blank" href="https://github.com/zikani03/react-frostedglass">react-frostedglass</a> is a React library that enables developers to hide potentially sensitive information when the window is not in focus.</p><p>It uses a Layout Effect to apply a blur to components when the window/tab is not in focus. Of course, this does not apply to all the components automatically, rather you as the user of the library have to wrap your components in a <code>FrostedContext</code> provider and use the <code>"frosted"</code> variants of common HTML components.</p><h3 id="heading-example-usage">Example Usage</h3><p>Let's create a simple react project with Vite to see how the library can be used, first create a Vite project and then install the library using the following commands:</p><pre><code class="lang-bash">$ npm create vite@latest app -- --template react$ <span class="hljs-built_in">cd</span> app$ npm install https://github.com/zikani03/react-frostedglass.git</code></pre><p>Once that is done, replace the <code>App.jsx</code> with the following content:</p><pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span><span class="hljs-keyword">import</span> { FrostedContext, useFrostedEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'@zikani03/react-frostedglass'</span><span class="hljs-keyword">import</span> <span class="hljs-string">'./App.css'</span><span class="hljs-keyword">import</span> Account <span class="hljs-keyword">from</span> <span class="hljs-string">'./Account'</span><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{  <span class="hljs-keyword">const</span> focusCheckInterval = <span class="hljs-number">500</span>;  <span class="hljs-keyword">const</span> [blurSize, isFrosted] = useFrostedEffect(focusCheckInterval, <span class="hljs-string">'0.3em'</span>)  <span class="hljs-keyword">return</span> (    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">FrostedContext.Provider</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">blurSize:</span> <span class="hljs-attr">blurSize</span> }}&gt;</span>      <span class="hljs-tag">&lt;<span class="hljs-name">Account</span> /&gt;</span>    <span class="hljs-tag">&lt;/<span class="hljs-name">FrostedContext.Provider</span>&gt;</span></span>  )}<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App</code></pre><p>As you can see above, we use the <code>useFrostedEffect</code> hook and we pass it two parameters, the interval (in milliseconds) to be used to check if the window is in focus and the size of the blur - a valid value for this is anything you can use in the <code>filter: blur()</code> CSS property.</p><p>Now create a new component in a file named <code>Account.jsx</code>. Use the code listed below:</p><pre><code class="lang-javascript"><span class="hljs-keyword">import</span> {FrostedDiv, FrostedSpan, FrostedLabel, withFrost} <span class="hljs-keyword">from</span> <span class="hljs-string">'@zikani03/react-frostedglass'</span><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Account</span>(<span class="hljs-params"></span>) </span>{    <span class="hljs-keyword">const</span> user = {        <span class="hljs-attr">email</span>: <span class="hljs-string">'user@example.com'</span>,        <span class="hljs-attr">phone</span>: <span class="hljs-string">'+265-xxx-xxx-xxx'</span>,        <span class="hljs-attr">address</span>: <span class="hljs-string">'Address 1, City, Country'</span>    };    <span class="hljs-keyword">return</span> (        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"field"</span>&gt;</span>                <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>Email<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>                <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>                    <span class="hljs-tag">&lt;<span class="hljs-name">FrostedSpan</span>&gt;</span>{user.email}<span class="hljs-tag">&lt;/<span class="hljs-name">FrostedSpan</span>&gt;</span>                <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>                <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>Phone<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>                <span class="hljs-tag">&lt;<span class="hljs-name">FrostedDiv</span>&gt;</span>{user.phone}<span class="hljs-tag">&lt;/<span class="hljs-name">FrostedDiv</span>&gt;</span>                <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>Address<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>                <span class="hljs-tag">&lt;<span class="hljs-name">FrostedDiv</span>&gt;</span>{user.address}<span class="hljs-tag">&lt;/<span class="hljs-name">FrostedDiv</span>&gt;</span>            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>            <span class="hljs-tag">&lt;<span class="hljs-name">section</span> {<span class="hljs-attr">...withFrost</span>({ <span class="hljs-attr">blurSize:</span> '<span class="hljs-attr">0.3em</span>' })}&gt;</span>                Some section that's frosted by default...            <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>    )}</code></pre><p>As you can see above, the Account component uses FrostedDiv, FrostedSpan for the content that we want to blur when the window is out of focus</p><h3 id="heading-lets-see-how-frosted-components-look">Let's see how Frosted components look</h3><p>We can now run the development server to see how all this looks in action</p><pre><code class="lang-bash">$ npm run dev</code></pre><p>When we focus on the page we will see everything displaying as normal, except the <code>section</code> element we set to be blurred by default:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693150744691/15fa103c-8b7c-4312-b81b-24a2af539eec.png" alt class="image--center mx-auto" /></p><p>Now try opening or focusing on another window and you will observe that the elements become "frosted". How cool is that? 🥶</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693150800304/5798971a-edce-4a54-81b4-199f0aef8760.png" alt class="image--center mx-auto" /></p><h2 id="heading-using-the-library">Using the library</h2><p>As of the time of writing, I have not published a package to NPM. So if you want to use the library you can install the module directly from GitHub using npm</p><pre><code class="lang-bash">npm install --save https://github.com/zikani03/react-frostedglass.git</code></pre><h2 id="heading-observations-and-lessons-learned">Observations and lessons learned</h2><p>As mentioned previously, one of my objectives was to learn or at least figure out how to go about creating a standalone React library from scratch. I am glad I was able to accomplish that and here are a few observations:</p><ul><li><p>Coming up with a nice API/library surface is not always easy, I enjoy creating developer tools and although I have written a <a target="_blank" href="https://github.com/creditdatamw/zerocell">few</a> <a target="_blank" href="https://github.com/zikani03/pgadvisorylock">libraries</a> in various languages I was reminded that picking the right abstractions, and naming things (functions, class etc...) requires lots of thought.</p></li><li><p>I came to appreciate the use of Context Providers and Layout Effect hooks better</p></li><li><p>I was able to configure Vite for building the library "from scratch", that is without depending on a template repository or some kind of boilerplate.</p></li><li><p>Related to the point above, I was able to customize the TypeScript compilation options - of course, I had done this before for another <a target="_blank" href="https://github.com/zikani03/ika/">JavaScript library</a> I built (you should check that out too, by the way!)</p></li></ul><h2 id="heading-conclusion">Conclusion</h2><p>In this article, we have seen how you can use <code>react-frostedglass</code>, a React library I created, to blur components when the screen loses focus. Although this might not be the most practical approach to the problem, it did allow me to figure out how to publish a standalone React library and appreciate the process of building one.</p><p>Thank you for reading.</p>]]></description><link>https://zikani.hashnode.dev/hiding-sensitive-information-with-react</link><guid isPermaLink="true">https://zikani.hashnode.dev/hiding-sensitive-information-with-react</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Sat, 19 Aug 2023 22:00:00 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1693150302680/74694ca3-35fc-4934-ae81-bb2e2180949d.png</cover_image></item><item><title><![CDATA[Are you a software glutton too?]]></title><description><![CDATA[<p>I just realized I may be a glutton for software libraries/tools. I don't yet know if that's a bad thing. Basically what I am referring to is my interest in trying and tinkering with as many software libraries as possible. I love to try things and every time I discover a (new) library or tool I try to make time to test it and see what it offers; whether it deserves a space on my infinite canvas of software tools. Of course, I do prioritize tools and libraries that seem to solve the problem at hand as soon as possible if it's work-related. Some factors I consider in such cases include popularity, documentation, usability, stability, maintenance history etc...</p><p>However, for a glutton - on personal projects and when tinkering, this is not enough. They want to be able to know for sure what the differences are and what attributes make two or more solutions different. Sometimes, they may publish these comparisons - this can be seen as a benefit of such gluttony, a public service - saving some other soul some time at the expense of your own.</p><p><code>Note: Maybe gluttony is not the right word choice but it's all I could think of.</code></p><p>Unfortunately for the glutton, software is growing at a rapid rate, and developers keep coming up with new ideas, spin-offs and takes of their own on problems or tools that have been done before. So many damn GitHub repositories. If you don't limit yourself on what you consume you may find yourself feeling anxiety as a result of FOMO (Fear Of Missing Out) which can affect you negatively in the long run. Maybe this is why the old timers and sages retreat to their small set of tools they know very well and ignore the rest of the world only to take a peek now and then until something catches their attention enough to bring them out of the cocoon of "what I have works well enough". I don't know.</p><p>Is this what they mean when they say software will eat the world? /s</p>]]></description><link>https://zikani.hashnode.dev/software-glutton</link><guid isPermaLink="true">https://zikani.hashnode.dev/software-glutton</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Wed, 31 May 2023 10:31:00 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1685528200348/ec4e871e-0195-416d-9880-849a8701729c.png</cover_image></item><item><title><![CDATA[Convert database fields to uppercase automatically with GORM Callbacks]]></title><description><![CDATA[<p>Sometimes I would like to convert the fields for an entity to uppercase every time the record is saved or updated automatically, without littering the code with code to perform those uppercase conversions and risk forgetting to place them elsewhere. Here is a simple callback function for use with GORM to achieve that:</p><pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">WithToUpperCaseCallback</span><span class="hljs-params">(db *gorm.DB, table <span class="hljs-keyword">string</span>, fields ...<span class="hljs-keyword">string</span>)</span></span> {    cb := UpperCaseCallback(fields...)    db.Callback().Create().Before(<span class="hljs-string">"*"</span>).Register(<span class="hljs-string">"create:uppercase:"</span> + table, cb)     db.Callback().Update().Before(<span class="hljs-string">"*"</span>).Register(<span class="hljs-string">"update:uppercase:"</span> + table, cb)}<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">UpperCaseCallback</span><span class="hljs-params">(fields ...<span class="hljs-keyword">string</span>)</span> <span class="hljs-title">func</span><span class="hljs-params">(db *gorm.DB)</span></span> {    <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(db *gorm.DB)</span></span> {        <span class="hljs-keyword">if</span> db.Statement.Schema == <span class="hljs-literal">nil</span> {            <span class="hljs-keyword">return</span>        }        <span class="hljs-comment">// Only process statements that operate on a table</span>        <span class="hljs-keyword">if</span> db.Statement.Schema.Table == <span class="hljs-string">""</span> {            <span class="hljs-keyword">return</span>        }        <span class="hljs-keyword">for</span> _, argField := <span class="hljs-keyword">range</span> fields {            arr := strings.SplitN(argField, <span class="hljs-string">"."</span>, <span class="hljs-number">2</span>)            tableName, desiredFieldName := arr[<span class="hljs-number">0</span>], arr[<span class="hljs-number">1</span>]            <span class="hljs-keyword">if</span> tableName != db.Statement.Schema.Table {                <span class="hljs-keyword">continue</span>            }            field := db.Statement.Schema.LookUpField(desiredFieldName)            <span class="hljs-keyword">if</span> field == <span class="hljs-literal">nil</span> {                <span class="hljs-keyword">continue</span>            }            <span class="hljs-keyword">if</span> db.Statement.ReflectValue.Kind() != reflect.String {                <span class="hljs-keyword">continue</span>            }            <span class="hljs-keyword">if</span> fieldValue, isZero := field.ValueOf(db.Statement.Context, db.Statement.ReflectValue); !isZero {                <span class="hljs-keyword">if</span> actualStr, ok := fieldValue.(<span class="hljs-keyword">string</span>); ok {                    db.AddError(field.Set(db.Statement.Context, db.Statement.ReflectValue, strings.ToUpper(actualStr)))                }            }        }    }}</code></pre><p>You can register the call back as shown below - this will enable gorm to convert your fields to upper case (<em>in this case first_name and last_name on the "people" table</em>) whenever the entity is saved or updated by gorm, automatically.</p><pre><code class="lang-go"><span class="hljs-keyword">type</span> Person <span class="hljs-keyword">struct</span> {    ID        <span class="hljs-keyword">int64</span>  <span class="hljs-string">`gorm:"`</span>    FirstName <span class="hljs-keyword">string</span> <span class="hljs-string">`gorm:""`</span>    LastName  <span class="hljs-keyword">string</span> <span class="hljs-string">`gorm:""`</span>}<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {    db, err := gorm.Open(postgres.Open(<span class="hljs-string">"..."</span>), &amp;gorm.Config{})    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {        <span class="hljs-built_in">panic</span>(<span class="hljs-string">"failed to connect database"</span>)    }    db.Callback().Create().Before(<span class="hljs-string">"*"</span>).Register(<span class="hljs-string">"create:uppercase"</span>, UpperCaseCallback(<span class="hljs-string">"people.first_name"</span>, <span class="hljs-string">"people.last_name"</span>))    db.Callback().Update().Before(<span class="hljs-string">"*"</span>).Register(<span class="hljs-string">"update:uppercase"</span>, UpperCaseCallback(<span class="hljs-string">"people.first_name"</span>, <span class="hljs-string">"people.last_name"</span>))    <span class="hljs-comment">// or using utility function</span>    WithToUpperCaseCallback(db, <span class="hljs-string">"people"</span>, <span class="hljs-string">"first_name"</span>, <span class="hljs-string">"last_name"</span>)    <span class="hljs-comment">// the code below would result in first_name and last_name being capitalized to "JOHN" and "BANDA", respectively before being saved</span>    person := &amp;Person{FirstName: <span class="hljs-string">"john"</span>, LastName: <span class="hljs-string">"banda"</span>}    tx = s.db.Save(person)    <span class="hljs-keyword">if</span> tx.Error != <span class="hljs-literal">nil</span> {        <span class="hljs-built_in">panic</span>(<span class="hljs-string">"failed to save"</span>)    }}</code></pre><h2 id="heading-conclusion">Conclusion</h2><p>As you can see, it is pretty straightforward to augment GORM with callbacks to achieve the behavior we wanted. However, use this judiciously, as it adds a layer of "magic" to your codebase - one day you may be left scratching your head as to why some fields are appearing as uppercase when you didn't input them as such.</p><p>Funny enough, I have written various similar versions of this functionality in different languages/frameworks, e.g. one for JDBI/Java is [here](<a target="_blank" href="https://github.com/nndi-oss/jdbi-utils/blob/main/jdbi3-utils/src/main/java/cloud/nndi/oss/jdbi/customizers/CapitalizeCustomizer.java">https://github.com/nndi-oss/jdbi-utils/blob/main/jdbi3-utils/src/main/java/cloud/nndi/oss/jdbi/customizers/CapitalizeCustomizer.java</a>)</p><p>I hope you found this useful.</p>]]></description><link>https://zikani.hashnode.dev/gorm-uppercase-callback</link><guid isPermaLink="true">https://zikani.hashnode.dev/gorm-uppercase-callback</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Sun, 07 May 2023 02:03:21 GMT</pubDate></item><item><title><![CDATA[Simple setup for deploying a Vue or React frontend  to CPanel-based hosting]]></title><description><![CDATA[<p>So I have several little tools, or apps that I create for convenience and most of them tend to be Single Page applications written with either Vue or React. Sometimes I will make a backend for them and others don't require it. Either way, I sometimes decide to host the front end on a CPanel-based hosting and the back end on a VPS or cloud service somewhere else. As a result, I often have to find a way of "deploying" these applications to CPanel which means uploading the built files via FTP. I have several toy projects and so it became boring to have to upload files via some FTP client, so in this article, I will document one way I have found to ease the drudgery.</p><p>It relies on NodeJS, which is typically okay for my project since am most likely using something like Vue or React with Node/npm build system anyways. We only need one extra devDependency: <strong>ftpdeploy</strong></p><pre><code class="lang-bash">$ npm install --save-dev ftp-deploy</code></pre><p>Once we have that installed, we can now add the config file for it in <code>ftpdeploy.js</code> which can be setup like below, obviously you would change according to your site details.</p><pre><code class="lang-javascript"><span class="hljs-keyword">import</span> FtpDeploy <span class="hljs-keyword">from</span> <span class="hljs-string">"ftp-deploy"</span>;<span class="hljs-keyword">import</span> { cwd } <span class="hljs-keyword">from</span> <span class="hljs-string">'node:process'</span>;<span class="hljs-keyword">import</span> { join } <span class="hljs-keyword">from</span> <span class="hljs-string">'node:path'</span>;<span class="hljs-keyword">const</span> ftpDeploy = <span class="hljs-keyword">new</span> FtpDeploy();<span class="hljs-keyword">const</span> config = {    <span class="hljs-attr">user</span>: <span class="hljs-string">"mycpaneluser"</span>,    <span class="hljs-comment">// Password optional, prompted if none given</span>    <span class="hljs-attr">password</span>: <span class="hljs-literal">null</span>,    <span class="hljs-attr">host</span>: <span class="hljs-string">"ftp.example.com"</span>,    <span class="hljs-attr">port</span>: <span class="hljs-number">21</span>,    <span class="hljs-attr">localRoot</span>: join(cwd(), <span class="hljs-string">"./dist"</span>),    <span class="hljs-attr">remoteRoot</span>: <span class="hljs-string">"/someapp.example.com/"</span>,    <span class="hljs-comment">// include: ["*", "**/*"],      // this would upload everything except dot files</span>    <span class="hljs-attr">include</span>: [<span class="hljs-string">"*"</span>, <span class="hljs-string">"dist/*"</span>, <span class="hljs-string">".*"</span>],    <span class="hljs-comment">// e.g. exclude sourcemaps, and ALL files in node_modules (including dot files)</span>    <span class="hljs-attr">exclude</span>: [        <span class="hljs-comment">// "dist/**/*.map",</span>        <span class="hljs-string">"node_modules/**"</span>,        <span class="hljs-string">"node_modules/**/.*"</span>,        <span class="hljs-string">".git/**"</span>    ],    <span class="hljs-comment">// delete ALL existing files at destination before uploading, if true</span>    <span class="hljs-attr">deleteRemote</span>: <span class="hljs-literal">false</span>,    <span class="hljs-comment">// Passive mode is forced (EPSV command is not sent)</span>    <span class="hljs-attr">forcePasv</span>: <span class="hljs-literal">true</span>,    <span class="hljs-comment">// use sftp or ftp</span>    <span class="hljs-attr">sftp</span>: <span class="hljs-literal">false</span>,};ftpDeploy    .deploy(config)    .then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"finished:"</span>, res))    .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> <span class="hljs-built_in">console</span>.log(err));</code></pre><p>This script can then be run using NodeJS via <code>node ftpdeploy.js</code>Or you can add that to your package.json under scripts, something like:</p><pre><code class="lang-json">  <span class="hljs-string">"scripts"</span>: {    ...    <span class="hljs-attr">"deploy"</span>: <span class="hljs-string">"npm run build &amp;&amp; node ftpdeploy.js"</span>  },</code></pre><p>Which makes deploying or pushing updates as simple as</p><pre><code class="lang-bash">$ npm run deploy</code></pre><p>That's it, I guess ideally, one should have a nice CI/CD process and better tooling for deploying <em>real-world</em> projects but this setup serves me well for some small toy projects, especially on utilizing my CPanel hosting plan.</p><h3 id="heading-notes">Notes</h3><ul><li><p>I recommend trying GitHub actions for a CI/CD process that can deploy sites when you push a tag/release.</p></li><li><p>My friend, Walter, reminded me that we had agreed to atleast publish one post - this post probably wasn't going to get published if it wasn't for his reminder. Checkout his blog <a target="_blank" href="https://ntumbuka.me/">https://ntumbuka.me/</a></p></li></ul>]]></description><link>https://zikani.hashnode.dev/simple-setup-for-deploying-a-vue-or-react-frontend-to-cpanel-based-hosting</link><guid isPermaLink="true">https://zikani.hashnode.dev/simple-setup-for-deploying-a-vue-or-react-frontend-to-cpanel-based-hosting</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Sun, 30 Apr 2023 21:57:26 GMT</pubDate></item><item><title><![CDATA[promptato: prompt yourself to think of things]]></title><description><![CDATA[<p>It is important to remember or reflect on certain things now and then. With how busy life gets it's possible to forget and only think about certain things if they occur again or if something triggers the thought. I have certain things that I'd like to think about often but don't want to commit them on my TODO list because of the clutter and task maintenance burden that would come from that.</p><p>Enter <strong><em>promptato</em></strong> a simple command-line tool that periodically prompts you without cluttering your to-do list app.</p><pre><code class="lang-bash">./promptato -every 15m -f prompts-1.txt -reload</code></pre><h2 id="heading-how-it-works">How it works</h2><p>The program prompts you to think about something every interval and sends a Desktop Notification with the prompt as the text. The prompt items are chosen at random. The source of the prompts is a text file, where each prompt or question is on a separate line. You can also specify how often you want the prompts, the default is every 42 minutes.</p><p>For example, my prompts text file looks like this:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670503155111/oOxbuO5iA.jpg" alt="Prompts file example" /></p><p>And I get desktop notifications every few minutes like this:</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670503144615/A7LYJspx2.jpg" alt="Example notification" /></p><p>After running it like so</p><pre><code class="lang-bash">./promptato -every 15m -f prompts-1.txt -reload</code></pre><p><em>Pro-tip: If you are feeling adventurous, you can trigger your own anxiety by having a prompt file with just one line "Where are you with that task?" and run it every 5 minutes - which could simulate the behavior of some micro-managing managers.</em></p><h2 id="heading-building-and-using">Building and Using</h2><p>To use this you will need to have Go installed and use the following steps to create your build of the program. I may create a GitHub repository and pre-built some binaries for it if people are interested, but the following instructions can help you get it up and running for yourself.</p><p>Step 1: Create a go project and create an empty file named <code>main.go</code></p><pre><code class="lang-bash">$ mkdir promptato$ <span class="hljs-built_in">cd</span> promptato$ go mod init promptato$ touch main.go</code></pre><p>Step 2: Copy the full code below into <code>main.go</code></p><p>Step 3: Get dependencies and compile, if you are on windows this will create a <code>promptato.exe</code> and on other OS it will create a binary (in this case one named <code>promptato</code>)</p><pre><code class="lang-bash">$ go mod tidy$ go build</code></pre><h2 id="heading-the-full-code">The full code</h2><p>The full program is below, please note it's a very simple no frills, offline program - it could be made better (see below for ideas), below is the Minimum Useable Thing:</p><pre><code class="lang-go"><span class="hljs-keyword">package</span> main<span class="hljs-keyword">import</span> (    <span class="hljs-string">"flag"</span>    <span class="hljs-string">"io/ioutil"</span>    <span class="hljs-string">"log"</span>    <span class="hljs-string">"math/rand"</span>    <span class="hljs-string">"strings"</span>    <span class="hljs-string">"time"</span>    <span class="hljs-string">"github.com/gen2brain/beeep"</span>    <span class="hljs-string">"github.com/go-co-op/gocron"</span>    <span class="hljs-string">"github.com/mroth/weightedrand"</span>)<span class="hljs-keyword">var</span> every <span class="hljs-keyword">string</span><span class="hljs-keyword">var</span> promptFile <span class="hljs-keyword">string</span><span class="hljs-keyword">var</span> reloadFile <span class="hljs-keyword">bool</span><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">init</span><span class="hljs-params">()</span></span> {    flag.StringVar(&amp;every, <span class="hljs-string">"every"</span>, <span class="hljs-string">"42m"</span>, <span class="hljs-string">"Every specifies the duration and supports formats like 5s, 42m, 1h "</span>)    flag.StringVar(&amp;promptFile, <span class="hljs-string">"f"</span>, <span class="hljs-string">"./prompts.txt"</span>, <span class="hljs-string">"Which file to load prompts from"</span>)    flag.BoolVar(&amp;reloadFile, <span class="hljs-string">"reload"</span>, <span class="hljs-literal">false</span>, <span class="hljs-string">"Whether to reload prompts file on each run..."</span>)}<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {    rand.Seed(time.Now().UTC().UnixNano()) <span class="hljs-comment">// always seed random!</span>    flag.Parse()    preloadedChoices := loadChoices(promptFile)    s := gocron.NewScheduler(time.UTC)    s.Every(every).Do(<span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span></span> {        <span class="hljs-keyword">var</span> chooser *weightedrand.Chooser        <span class="hljs-keyword">if</span> reloadFile {            choices := loadChoices(promptFile)            chooser, _ = weightedrand.NewChooser(choices...)        } <span class="hljs-keyword">else</span> {            chooser, _ = weightedrand.NewChooser(preloadedChoices...)        }        result := chooser.Pick()        err := beeep.Notify(<span class="hljs-string">"Promptato"</span>, result.(<span class="hljs-keyword">string</span>), <span class="hljs-string">"question.png"</span>)        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {            log.Fatalf(<span class="hljs-string">"failed to notify user, got %v"</span>, err)        }    })    <span class="hljs-comment">// starts the scheduler and blocks current execution path</span>    s.StartBlocking()}<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">loadChoices</span><span class="hljs-params">(sourceFile <span class="hljs-keyword">string</span>)</span> []<span class="hljs-title">weightedrand</span>.<span class="hljs-title">Choice</span></span> {    data, err := ioutil.ReadFile(sourceFile)    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {        log.Fatalf(<span class="hljs-string">"failed to load choices file, got %v"</span>, err)        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>    }    lines := strings.Split(<span class="hljs-keyword">string</span>(data), <span class="hljs-string">"\n"</span>)    choices := <span class="hljs-built_in">make</span>([]weightedrand.Choice, <span class="hljs-built_in">len</span>(lines))    <span class="hljs-keyword">for</span> _, line := <span class="hljs-keyword">range</span> lines {        trimmed := strings.TrimSpace(strings.TrimPrefix(line, <span class="hljs-string">"- "</span>))        choices = <span class="hljs-built_in">append</span>(choices, weightedrand.NewChoice(trimmed, <span class="hljs-number">1</span>))    }    <span class="hljs-keyword">return</span> choices}</code></pre><p><strong>Ideas for improvement</strong></p><p>This code represents a small useable thing but could be improved further with:</p><ul><li><p>making it a background service</p></li><li><p>having a way to weight the prompts (already using weightedrand library, just needs a way to define weights)</p></li><li><p>Having some kind of jitter to randomize the schedule of the prompts</p></li><li><p>Allowing users to interact with the prompts to indicate if they don't want it to come up again (this would necessitate re-implementing this with maybe a GUI toolkit)</p></li></ul><p>Anyways, I may or may not implement these ideas. For now, I just needed this for reminding me of some things as the year comes to an end as I meditate on the next year.</p><h2 id="heading-conclusion">Conclusion</h2><p>In this post, we have seen how a simple command-line tool built with Go can help you remember to think of things without having to add that as a task to your to-do app. I hope you found this at least mildly interesting.</p>]]></description><link>https://zikani.hashnode.dev/promptato</link><guid isPermaLink="true">https://zikani.hashnode.dev/promptato</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Sun, 04 Dec 2022 12:22:24 GMT</pubDate></item><item><title><![CDATA[A GitHub Action to check  if a repo conforms to some directory structure]]></title><description><![CDATA[<p>GitHub Actions rock. I had always wanted to create a re-usable action for others to benefit from. An opportunity came up as I was thinking about making some more Pull-Requests to <a target="_blank" href="https://github.com/sethbonnie/dblstd">dblstd</a>.</p><h2 id="heading-what-the-heck-is-this-dblstd">What the heck is this <code>dblstd</code> ?</h2><p><code>dblstd</code> is a command-line tool that aims to helps developers/teams check their project structure to verify that some required files/directories are included in a project. The goal is to enforce standard project structures for teams.</p><h2 id="heading-the-dblstd-action">The <code>dblstd-action</code></h2><p>The dblstd action basically uses dblstd to check the structure of the repository and can be configured to run on every pull request or tag. It's a simple action that literally doesn't have any code. It just clones the dblstd binary and runs it on the repo with option for which shape file to use to check the repo.</p><p>You can checkout the action here: https://github.com/zikani03/dblstd-action</p>]]></description><link>https://zikani.hashnode.dev/dblstd-github-action</link><guid isPermaLink="true">https://zikani.hashnode.dev/dblstd-github-action</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Fri, 09 Sep 2022 05:36:33 GMT</pubDate></item><item><title><![CDATA[neria: An AWS Lambda function for Named Entity Recognition from any website]]></title><description><![CDATA[<p><strong>TLDR;</strong> I developed an AWS Lambda Function URL which can be used for performing basic Named Entity Recognition i.e. detecting names of people and places from text from <em>any website</em>. It requires a URL and an JQuery-like selector which specifies which part of the page to extract the text from.</p><h2 id="heading-background">Background</h2><p>I work on/off on a Java toy project, <a target="_blank" href="https://github.com/zikani03/articulated">articulated</a>, which scrapes articles from a few Malawian news sites. I use it mostly to play with different technologies, ideas and to try to keep up with the local news. One of the features of that project is performing basic Named Entity Recognition which I decided to build as a separate service in Go after facing issues with Java libraries for similar functionality, but that's a story for another day.</p><h2 id="heading-building-an-aws-lambda-function-for-ner">Building an AWS Lambda Function for NER</h2><p>I decided to re-implement that service as a Lambda function because it seemed like the kind of thing that would <em>make sense</em> to have a lambda function for. As I was going about that, it struck me that the service could be made more useful if it was generic enough to be used with any website.</p><p>The result is a project dubbed <code>neria-lambda</code> which can be deployed on AWSs Lambda platform. You send a request with a URL to a website and a jQuery-like selector which specifies which DOM elements (i.e. part of the page) to extract the text from. After the text is fetched, the service uses <a target="_blank" href="https://github.com/jdkato/prose">prose</a> to extract Named Entities from the text, which basically means it detects names of people or places from the text. (Side note: turns out the <a target="_blank" href="https://github.com/jdkato/prose">prose library</a> was archived by the maintainer at some point)</p><p>Here is an example in Insomnia</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662699352656/aGRmVDt3B.png" alt="neria-00.png" /></p><h2 id="heading-deploying-the-lambda-function">Deploying the Lambda Function</h2><p>So as much as I love having things in production, I don't like bleeding money so I won't share the Function URLs public URL - this section will explain how you can deploy your own instance of the lambda on AWS.</p><p><strong>Step 1</strong>: Clone the repository, compile the go program and create a zip file named <code>main.zip</code>. The instructions are below:</p><pre><code class="lang-bash">$ git <span class="hljs-built_in">clone</span> https://github.com/zikani03/neria$ <span class="hljs-built_in">cd</span> neria/neria-lambda$ GOOS=linux GOARCH=amd64 go build -o main main.go$ zip main.zip main</code></pre><blockquote><p>NOTE: Windows users should use the <code>build.ps1</code> powershell script for the process</p></blockquote><p><strong>Step 2</strong>: Login to the AWS console and create a Lambda function</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662698962247/fsQ8FO33m.png" alt="neria-01.png" /></p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662698970273/tIiEt6IzX.png" alt="neria-02.png" /></p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662698977119/rKd9-E7dJ.png" alt="neria-03.png" /></p><p><strong>Step 3</strong>: Once the Lambda Function is created, upload the <code>main.zip</code> from step 1.</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662698984617/3CHQBudcD.png" alt="neria-04.png" /></p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662698991601/mxsyebR8J.png" alt="neria-05.png" /></p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662698999006/_UGs41tqz.png" alt="neria-06.png" /></p><p><strong>Step 4:</strong> Create a Function URL - which will enable you to access the lambda via a public URL (of course you can configure it to be accessible to certain IAM roles, etc.. - I'm assuming you know your way around this AWS stuff)</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662699007826/-y8PuvSud.png" alt="neria-07.png" /></p><p><strong>Step 5:</strong> Once you create the Function URL you can test the lambda with cURL, Postman or Insomnia with the following example request:</p><pre><code class="lang-json">{    <span class="hljs-attr">"Url"</span>: <span class="hljs-string">"https://www.nyasatimes.com/chilima-says-malawi-is-a-best-investment-place-in-sadc-region-and-beyond/"</span>,    <span class="hljs-attr">"Selector"</span>: <span class="hljs-string">"#content div.nyasa-content"</span>,    <span class="hljs-attr">"Text"</span>: <span class="hljs-string">""</span>}</code></pre><h3 id="heading-experience-of-deploying-a-lambda-function-url">Experience of deploying a Lambda Function URL</h3><p>I found the experience of deploying a Lambda Function URL an interesting one, it was easy to follow the AWS documentation and was especially useful to reference their samples from the GitHub repos.</p><p>What I found really interesting is how deployment of the function is done via uploading a zip (with an option to get that from S3). I expected a Heroku-like experience for the process but was reminded that sometimes simple approaches go a long way.</p><h2 id="heading-conclusion">Conclusion</h2><p>In this article I described my experience building a useful Lambda function which you can also deploy for your own use. The project could improve in how it does named entity recognition but since I'm using a library that's since been archived I can't really say much on when and how. It was an interesting experience and one that's given me a couple of ideas (one for a PaaS platform).</p><p>Thanks for reading. Feel free to share feedback on Twitter - <a target="_blank" href="https://twitter.com/zikani03">@zikani03</a></p>]]></description><link>https://zikani.hashnode.dev/neria-an-aws-lambda-function-for-named-entity-recognition-from-any-website</link><guid isPermaLink="true">https://zikani.hashnode.dev/neria-an-aws-lambda-function-for-named-entity-recognition-from-any-website</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Sun, 28 Aug 2022 05:22:21 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1662699468760/lPbSg_iU4.png</cover_image></item><item><title><![CDATA[Passing data to JavaScript within a Server-Side Rendered template]]></title><description><![CDATA[<p>Web developers often need to add JavaScript for interactivity to their pages, and some times that interaction needs to be kicked off by some (initial) data from the backend. In this article I will show you an approach for passing data between a server-side template and JavaScript which is cleaner that what I've seen many people do in their projects.</p><blockquote><p>MVC is dead, long live MVC!</p></blockquote><p> In projects where the frontend is completely handled by a Single Page Application initial data may be fetched using an API call on the components "onMount" event. However, there are still a lot of (MVC) projects which just sprinkle in JavaScript in a Server Side Rendered page for extra functionality like client-side validation. </p><p>Please note by server-side rendered here I mean that the page is being rendered by say, PHP (regular PHP, Twig, Blade template etc..), Rails templates, Java with JSP, Thymeleaf, Velocity etc.. , .NET with ASP.NET pages, Razor templates, Django templates or whatever language/framework you fancy that allows you to render to HTML via some <em>template engine</em>.</p><p>In these kinds of projects it is not uncommon to find code that mixes in the template rendering logic with the JavaScript code on the page in a way that results in hard to read or maintain code.</p><blockquote><p>NOTE: I am using PHP in these examples but I have seen this pattern in Spring Boot projects, and the solution can be applied to any framework that offers server side rendering of templates</p></blockquote><p>Below is an example of code you may find in some projects when passing data from server-side template to JavaScript. (Raise your hand if you have done something like this before, I see you).</p><pre><code class="lang-php">&lt;script type=<span class="hljs-string">"text/javascript"</span>&gt;    <span class="hljs-keyword">const</span> individuals = [        <span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">foreach</span>($people <span class="hljs-keyword">as</span> $p): <span class="hljs-meta">?&gt;</span>        { <span class="hljs-string">"firstName"</span>: <span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">echo</span> <span class="hljs-string">'"'</span> . $p-&gt;firstName . <span class="hljs-string">'"'</span> <span class="hljs-meta">?&gt;</span>, <span class="hljs-string">"lastName"</span>: <span class="hljs-meta">&lt;?=</span> <span class="hljs-string">'"'</span> . $p-&gt;lastName . <span class="hljs-string">'"'</span> <span class="hljs-meta">?&gt;</span> },        <span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">endforeach</span> <span class="hljs-meta">?&gt;</span>    ];    <span class="hljs-keyword">const</span> selectOptions = {        <span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">echo</span> <span class="hljs-string">'"'</span> . ORGANIZER_CONSTANT . <span class="hljs-string">'"'</span><span class="hljs-meta">?&gt;</span> : <span class="hljs-string">"Organizer"</span>,        <span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">echo</span> <span class="hljs-string">'"'</span> . SPEAKER_CONSTANT . <span class="hljs-string">'"'</span><span class="hljs-meta">?&gt;</span> : <span class="hljs-string">"Speaker"</span>,        <span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">echo</span> <span class="hljs-string">'"'</span> . ATTENDEE_CONSTANT . <span class="hljs-string">'"'</span><span class="hljs-meta">?&gt;</span> : <span class="hljs-string">"Attendee"</span>,        <span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">echo</span> <span class="hljs-string">'"'</span> . SPONSOR_CONSTANT . <span class="hljs-string">'"'</span><span class="hljs-meta">?&gt;</span> : <span class="hljs-string">"Sponsor"</span>    }&lt;/script&gt;</code></pre><p>Which would render to something like:</p><pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">    <span class="hljs-keyword">const</span> individuals = [        { <span class="hljs-string">"firstName"</span>: <span class="hljs-string">"John"</span>, <span class="hljs-string">"lastName"</span>: <span class="hljs-string">"Banda"</span> },        { <span class="hljs-string">"firstName"</span>: <span class="hljs-string">"Mary"</span>, <span class="hljs-string">"lastName"</span>: <span class="hljs-string">"Banda"</span> },        { <span class="hljs-string">"firstName"</span>: <span class="hljs-string">"Joseph"</span>, <span class="hljs-string">"lastName"</span>: <span class="hljs-string">"Gondwe"</span> },        { <span class="hljs-string">"firstName"</span>: <span class="hljs-string">"Jane"</span>, <span class="hljs-string">"lastName"</span>: <span class="hljs-string">"Phiri"</span> },    ];    <span class="hljs-keyword">const</span> selectOptions = {        <span class="hljs-string">"organizer"</span>: <span class="hljs-string">"Organizer"</span>,        <span class="hljs-string">"speaker"</span>: <span class="hljs-string">"Speaker"</span>,        <span class="hljs-string">"attendee"</span>: <span class="hljs-string">"Attendee"</span>,        <span class="hljs-string">"sponsor"</span>: <span class="hljs-string">"Sponsor"</span>    }</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></code></pre><p>This example is trivial, but I have seen situations where lots of data was serialized this way. As you can see this approach of serializing data is messy and can indeed make code hard to follow and also lead to subtle bugs:</p><h2 id="heading-a-better-way-to-pass-data-to-javascript">A better way to pass data to JavaScript</h2><p>We can do better by using an approach that I think is better, cleaner, separates the concerns between server-rendered template and JavaScript as well as opening up possibilities for improving testing the JavaScript code. </p><p>The approach basically works as follows:</p><ol><li>Put all the data you need on the JavaScript side into a "root" object(s) within the server-side template (note: you can use an <code>array</code> in PHP, a <code>Map</code> in Java/C# or similar languages, hashmap/dictionary in Python etc..) </li><li>Encode the data as JSON using the server-side template capabilities (using <code>json_encode</code> in PHP, <code>objectMapper#writeAsString</code> or <code>gson#toJson</code> in a Java / Spring Boot codebase, <code>json.dumps</code> or <code>to_json</code> in  Python etc..)</li><li>Use <code>JSON.parse</code> to decode the JSON into a global object under the toplevel <code>window</code>, typically named the same as the root object from step 1</li></ol><p>See the example below...</p><pre><code class="lang-php">&lt;!-- Step <span class="hljs-number">1</span>--&gt;<span class="hljs-meta">&lt;?php</span>     $pageData = [        <span class="hljs-string">"individuals"</span> =&gt; $people,        <span class="hljs-string">"selectOptions"</span> =&gt; [            ORGANIZER_CONSTANT =&gt; <span class="hljs-string">"Organizer"</span>,            SPEAKER_CONSTANT =&gt; <span class="hljs-string">"Speaker"</span>,            ATTENDEE_CONSTANT =&gt; <span class="hljs-string">"Attendee"</span>,            SPONSOR_CONSTANT =&gt; <span class="hljs-string">"Sponsor"</span>        ],    ];<span class="hljs-meta">?&gt;</span>&lt;!-- Step <span class="hljs-number">2</span> <span class="hljs-keyword">and</span> Step <span class="hljs-number">3</span> --&gt;&lt;script type=<span class="hljs-string">"text/javascript"</span> id=<span class="hljs-string">"pageDataScript"</span>&gt;window.__pageData = JSON.parse(<span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">echo</span> json_encode($pageData) <span class="hljs-meta">?&gt;</span>);<span class="hljs-keyword">const</span> individuals = window.__pageData.individuals;<span class="hljs-keyword">const</span> selectOptions = window.__pageData.selectOptions;&lt;/script&gt;</code></pre><p>It seems like an obvious approach in hindsight.</p><p>If you are using a template engine like Twig you can use the <code>raw</code> function to avoid the JSON data from being rendered using HTML escapes.</p><pre><code class="lang-php">&lt;script type=<span class="hljs-string">"text/javascript"</span> id=<span class="hljs-string">"pageDataScript"</span>&gt;window.__pageData = JSON.parse( {{ json_encode(pageData) | raw }} );&lt;/script&gt;</code></pre><p>Benefits of this approach.</p><ol><li>Cleaner - better separation of concerns as the server side code doesn't 'bleed' into the JavaScript code too much.</li><li>Easier to reason about the structure of the data passed to the JavaScript handling code </li><li>Makes it easy to introduce a library like VueJS or React as you could just fetch the data from the __pageData object "onMount"</li></ol><h2 id="heading-extension-of-the-approach">Extension of the approach</h2><p>An extension to the above approach enables you to render/write the data separately in a script element and then use another script element to read the data. You will note in the example below that the first <code>&lt;script&gt;</code> element has type <code>application/json</code> - this allows us to place data in there that the Browser safely ignores, and will not execute as JavaScript.</p><p>This may seem like overkill but could provide you with flexibility for testing if you have issues with getting the data from the backend since you can easily place random JSON that follows the proper structure in the <code>initialPageData</code> element.</p><pre><code class="lang-php">&lt;script type=<span class="hljs-string">"application/json"</span> id=<span class="hljs-string">"initialPageData"</span>&gt;<span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">echo</span> json_encode($pageData) &lt;/script&gt;&lt;script type=<span class="hljs-string">"text/javascript"</span> id=<span class="hljs-string">"pageDataScript"</span>&gt;window.__pageData = JSON.parse(document.getElementById(<span class="hljs-string">"initialPageData"</span>).innerHTML);&lt;/script&gt;</code></pre><h2 id="heading-conclusion">Conclusion</h2><p>In this article we have seen that with a few changes it's possible for us to make it cleaner to pass data from server-side rendered template to JavaScript to enhance the experience on pages of your web applications.</p><p>Thanks for reading. If you have questions, comments or suggestions, reach out on Twitter @zikani03.</p>]]></description><link>https://zikani.hashnode.dev/passing-data-to-javascript-within-a-server-side-rendered-template</link><guid isPermaLink="true">https://zikani.hashnode.dev/passing-data-to-javascript-within-a-server-side-rendered-template</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Sun, 14 Aug 2022 16:01:39 GMT</pubDate></item><item><title><![CDATA[Embedding  a PDF viewer in a Wails application]]></title><description><![CDATA[<p>I am enjoying using Wails, a Go UI framework which allows you to <em>build desktop applications using Go &amp; Web Technologies.</em> So I thought it would be interesting to try to embed <a target="_blank" href="https://github.com/mozilla/pdf.js">PDF.js</a> in a Wails application. After spending some time working with PDF.js directly and getting it to work, I decided to try <a target="_blank" href="https://github.com/wojtekmaj/react-pdf">React-PDF</a> since I am most likely to build something with React or Vue anyways.</p><p>This article will go over how to embed a basic PDF viewer in a Wails application starting from the Wails' React template. It is not meant to be a comprehensive tutorial so I won't go into styling the PDF reader or anything.</p><p>I have put the complete code in a repository <a target="_blank" href="https://github.com/nndi-oss/blog-tutorials/tree/master/pdf-in-wails">here</a></p><blockquote><p>NOTE: this article is using Wails v2.0.0-beta.35</p></blockquote><h3 id="heading-setup-wails-project">Setup Wails Project</h3><pre><code class="lang-sh">$ wails init -n pdf-in-wails -t react</code></pre><h3 id="heading-install-react-and-react-pdf">Install React and React-PDF</h3><pre><code class="lang-sh">$ <span class="hljs-built_in">cd</span> pdf-in-wails$ <span class="hljs-built_in">cd</span> frontend$ npm install --save react react-pdf fast-base64$ <span class="hljs-built_in">cd</span> ..</code></pre><h2 id="heading-read-a-document-from-the-file-system-with-go">Read a Document from the File System with Go</h2><p>We are going to use the Go backend to read the PDF from the file system. In order to do so we will use the Wails' runtime package which provides a function to <a target="_blank" href="https://wails.io/docs/reference/runtime/dialog#opendialogoptions">Open a file dialog</a> and then use the standard <code>ioutil.ReadAll</code> function to read the file into a byte slice.</p><p>In the <code>app.go</code> file in the project add this content</p><pre><code class="lang-go"><span class="hljs-keyword">package</span> main<span class="hljs-keyword">import</span> (    <span class="hljs-string">"context"</span>    <span class="hljs-string">"fmt"</span>    <span class="hljs-string">"io/ioutil"</span>    <span class="hljs-string">"github.com/wailsapp/wails/v2/pkg/runtime"</span>)<span class="hljs-comment">// App struct</span><span class="hljs-keyword">type</span> App <span class="hljs-keyword">struct</span> {    ctx context.Context}<span class="hljs-comment">// NewApp creates a new App application struct</span><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">NewApp</span><span class="hljs-params">()</span> *<span class="hljs-title">App</span></span> {    <span class="hljs-keyword">return</span> &amp;App{}}<span class="hljs-comment">// Greet returns a greeting for the given name</span><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(a *App)</span> <span class="hljs-title">Greet</span><span class="hljs-params">(name <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">string</span></span> {    <span class="hljs-keyword">return</span> fmt.Sprintf(<span class="hljs-string">"Hello %s, It's show time!"</span>, name)}<span class="hljs-comment">// startup is called at application startup</span><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(a *App)</span> <span class="hljs-title">startup</span><span class="hljs-params">(ctx context.Context)</span></span> {    <span class="hljs-comment">// Perform your setup here</span>    a.ctx = ctx}<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(a *App)</span> <span class="hljs-title">OpenAndGetPDFData</span><span class="hljs-params">()</span> <span class="hljs-params">([]<span class="hljs-keyword">byte</span>, error)</span></span> {    filters := []runtime.FileFilter{        {DisplayName: <span class="hljs-string">"PDF Documents (*.pdf)"</span>, Pattern: <span class="hljs-string">"*.pdf"</span>},    }    filePath, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{        Filters:         filters,        ShowHiddenFiles: <span class="hljs-literal">false</span>,    })    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err    }    <span class="hljs-keyword">return</span> ioutil.ReadFile(filePath)}</code></pre><h3 id="heading-register-the-onstartup-function-in-maingo">Register the <code>OnStartup</code> function in <code>main.go</code></h3><pre><code class="lang-go"> <span class="hljs-comment">// ... other lines ignored</span><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {    <span class="hljs-comment">// Create an instance of the app structure</span>    app := NewApp()    <span class="hljs-comment">// Create application with options</span>    err := wails.Run(&amp;options.App{        Title:     <span class="hljs-string">"pdf-in-wails"</span>,        Width:     <span class="hljs-number">1024</span>,        Height:    <span class="hljs-number">768</span>,        Assets:    assets,        OnStartup: app.startup,        Bind: []<span class="hljs-keyword">interface</span>{}{            app,        },    })    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {        <span class="hljs-built_in">println</span>(<span class="hljs-string">"Error:"</span>, err)    }}</code></pre><h3 id="heading-adding-a-button-to-trigger-the-action-with-react">Adding a button to trigger the action with React</h3><p>Since Wails User Interfaces are driven by JavaScript, we need to add some way to triggger the function we just created. </p><p>You will notice the import for <code>OpenAndGetPDFData</code> - this function binding is generated automatically by Wails when you run <code>wails build</code> or <code>wails dev</code> or you can run it directly with <code>wails generate module</code>.</p><p>Replace the <code>App.jsx</code> with the following.</p><pre><code class="lang-jsx"><span class="hljs-keyword">import</span> <span class="hljs-string">'./App.css'</span>;<span class="hljs-keyword">import</span> {useState} <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;<span class="hljs-keyword">import</span> {Document, Page, pdfjs} <span class="hljs-keyword">from</span> <span class="hljs-string">'react-pdf'</span>;<span class="hljs-keyword">import</span> {toBytes} <span class="hljs-keyword">from</span> <span class="hljs-string">'fast-base64'</span>;<span class="hljs-keyword">import</span> {OpenAndGetPDFData} <span class="hljs-keyword">from</span> <span class="hljs-string">"../wailsjs/go/main/App"</span>;<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{    pdfjs.GlobalWorkerOptions.workerSrc = <span class="hljs-string">`//cdnjs.cloudflare.com/ajax/libs/pdf.js/<span class="hljs-subst">${pdfjs.version}</span>/pdf.worker.min.js`</span>;    <span class="hljs-keyword">const</span> [pdfDocument, setPdfDocument] = useState(<span class="hljs-literal">null</span>)    <span class="hljs-keyword">const</span> [numPages, setNumPages] = useState(<span class="hljs-literal">null</span>);    <span class="hljs-keyword">const</span> [pageNumber, setPageNumber] = useState(<span class="hljs-number">1</span>);    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onDocumentLoadSuccess</span>(<span class="hljs-params">{ numPages }</span>) </span>{      setNumPages(numPages);    }    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">openPDF</span>(<span class="hljs-params"></span>) </span>{        <span class="hljs-keyword">try</span> {            <span class="hljs-keyword">const</span> pdfFile = <span class="hljs-keyword">await</span> OpenAndGetPDFData()            <span class="hljs-keyword">const</span> pdfBytes = <span class="hljs-keyword">await</span> toBytes(pdfFile)            setPdfDocument({<span class="hljs-attr">data</span>: pdfBytes })        } <span class="hljs-keyword">catch</span>(e) {            <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Failed to open pdf"</span>, e)        }    }    <span class="hljs-keyword">return</span> (        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"App"</span>&gt;</span>            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"btn"</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{openPDF}</span>&gt;</span>Open PDF<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>            <span class="hljs-tag">&lt;<span class="hljs-name">Document</span> <span class="hljs-attr">file</span>=<span class="hljs-string">{pdfDocument}</span>                     <span class="hljs-attr">onLoadSuccess</span>=<span class="hljs-string">{onDocumentLoadSuccess}</span>                     <span class="hljs-attr">onLoadError</span>=<span class="hljs-string">{(error)</span> =&gt;</span> alert('Error while loading document! ' + error.message)}                    onSourceError={(error) =&gt; alert('Error while retrieving document source! ' + error.message)}&gt;                <span class="hljs-tag">&lt;<span class="hljs-name">Page</span> <span class="hljs-attr">pageNumber</span>=<span class="hljs-string">{pageNumber}</span> /&gt;</span>            <span class="hljs-tag">&lt;/<span class="hljs-name">Document</span>&gt;</span>            <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>                Page {pageNumber} of {numPages}            <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>    )}<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App</code></pre><h4 id="heading-wails-sends-byte-as-a-base64-encoded-string">Wails sends <code>[]byte</code> as a Base64 encoded string</h4><p>One thing you will note is that we are using the <code>toBytes</code> function from the <code>fast-base64</code> library to get the data of the PDF file on the JavaScript side. This is because Wails serializes the byte slice as a Base64 encoded string when sending the data from the Go side. Other data structures are serialized as JavaScript objects appropriately.</p><h3 id="heading-run-it">Run it</h3><p>You can then run the application using Wails dev which will startup a webserver with hot reloading and open a New Window with a running instance of the application.</p><p>In order to build the application as a binary you need only to run <code>wails build</code></p><pre><code class="lang-sh">$ wails dev</code></pre><p>Here are screenshots from my build of the same</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653231715381/9-QDCXNgC.png" alt="Screenshot 2022-05-22 163122.png" /></p><p>With a PDF opened</p><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653231723722/fiIp-Zl9k.png" alt="Screenshot 2022-05-22 162817.png" /></p><h2 id="heading-conclusion">Conclusion</h2><p>Wails is an interesting technology for building desktop applications which is worth a look if you want to keep using web technologies to build desktop applications and as an alternative to something like Electron. We have seen how we can embed a PDF viewer in Wails easily using React PDF library (though you can do it without React).</p><p>Thanks for reading.</p>]]></description><link>https://zikani.hashnode.dev/embedding-a-pdf-viewer-in-a-wails-application</link><guid isPermaLink="true">https://zikani.hashnode.dev/embedding-a-pdf-viewer-in-a-wails-application</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Sun, 22 May 2022 15:10:14 GMT</pubDate></item><item><title><![CDATA[A solution to serving static files from an embedded file system in Go using Fiber web framework]]></title><description><![CDATA[<p>I really like using Fiber but sometimes have to find solutions for problems that could be solved in a somewhat <em>straightforward</em> way using other frameworks like <a target="_blank" href="https://gin-gonic.com">gin</a> or <a target="_blank" href="https://go-chi.io">go-chi</a>. Anyways, I do enjoy trying to find solutions even though they sometimes <a target="_blank" href="https://github.com/zikani03/quick-dirty-fiber-github-auth">feel hacky</a>.</p><blockquote><p>UPDATE: So it seems I missed the memo and didn't know that the Fiber team already had the functionality in v2. So please see <a target="_blank" href="https://github.com/gofiber/fiber/tree/master/middleware/filesystem#embed">this page</a> for more on THE RIGHT WAY</p></blockquote><h3 id="heading-serving-files-from-embedfs-in-fiber-not-straightfoward">Serving files from <code>embed.FS</code> in Fiber not straightfoward</h3><p>One of the problems that I ran into is serving static assets from an embedded file system or <code>embed.FS</code>. While Fiber does allow for serving static assets it only allows you to pass the path to a directory which is read from the "real" FileSystem. </p><p>This may not be ideal as deploying the program now requires both the binary and the files to be placed/uploaded on  a server <em>(<strong>NOTE</strong>: we are assuming we aren't deploying using containers which could solve this problem easily)</em>. </p><h4 id="heading-what-is-embedfs-anyways">What is embed.FS anyways?</h4><p>The <a target="_blank" href="https://pkg.go.dev/embed">embed package</a>, a feature of Go since sometime in Go 1.16, enables us to pack files in the binary - this is useful as we can just ship the binary and not worry about forgetting to copy &amp; upload the right files.  For more information on <code>//go:embed</code> see <a target="_blank" href="https://blog.jetbrains.com/go/2021/06/09/how-to-use-go-embed-in-go-1-16/">this blog post</a> on the JetBrains GoLand blog.</p><p>So we want to be able to serve files that are embedded in the Go program itself. How can we do that?</p><h3 id="heading-using-rebed-to-work-with-embedded-file-system-alongside-fiber">Using rebed  to work with embedded file system alongside Fiber</h3><p>Enter <a target="_blank" href="https://github.com/soypat/rebed">rebed</a>; a library that enables us to read and write the contents of the <code>embed.FS</code>. We can use <code>rebed</code> to write the embedded files to a directory and let Fiber serve them straight from that directory. This gives us the benefit of embedding while also enabling us to have the files on the file system where they can be served via an another web-server like NGINX.</p><p>Here is how we could go about that:</p><pre><code class="lang-go"><span class="hljs-keyword">package</span> main<span class="hljs-keyword">import</span> (    <span class="hljs-string">"embed"</span>    <span class="hljs-string">"github.com/gofiber/fiber/v2"</span>    <span class="hljs-string">"github.com/soypat/rebed"</span>)<span class="hljs-comment">// Let's assume you an app created by the likes of create-react-app</span><span class="hljs-comment">// which leaves it's built files in frontend/dist</span><span class="hljs-comment">//go:embed frontend/dist</span><span class="hljs-keyword">var</span> assets embed.FS<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {    app := fiber.New()    <span class="hljs-comment">// I prefer this form with www parent directory</span>    <span class="hljs-comment">// as it avoids overwriting the existing frontend/dist directory</span>    <span class="hljs-comment">// if you happen to run the program in the same working directory</span>    rebed.Write(assets, <span class="hljs-string">"www"</span>)    app.Static(<span class="hljs-string">"/"</span>, <span class="hljs-string">"www/frontend/dist"</span>)    err := app.Listen(<span class="hljs-string">":3000"</span>)    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {        <span class="hljs-built_in">panic</span>(err)    }}</code></pre><p>I feel the only icky part of this is that in your <code>app.Static</code> call you have to pass the full path of the embedded file system, e.g. note <code>www/frontend/dist</code> in the example, as well as the fact that you may have to clean up at some point if you don't want that directory to be on the server during re-deploy or something.</p><h3 id="heading-can-we-have-this-another-way">Can we have this another way?</h3><p>~The other option I considered is to send a PR to the Fiber project to modify how the <code>Static</code> function handles files but I don't think it will get accepted as that may also require changing how <code>fasthttp</code> (the http library beneath Fiber) serves Static files.~</p><p>~So for now, you can either copy the files you want to serve manually or embed them and let rebed recreate them for you when the program is running or find another clever way maybe using Fiber's <a target="_blank" href="https://github.com/gofiber/adaptor">adaptor package</a> somehow.~</p><p>UPDATE: Use <a target="_blank" href="https://github.com/gofiber/fiber/tree/master/middleware/filesystem#embed">the right way</a></p><p>Thanks for reading. </p>]]></description><link>https://zikani.hashnode.dev/go-embed-fs-with-rebed-fiber-web-framework</link><guid isPermaLink="true">https://zikani.hashnode.dev/go-embed-fs-with-rebed-fiber-web-framework</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Fri, 06 May 2022 08:21:38 GMT</pubDate></item><item><title><![CDATA[Using Scriggo Templates with the Go Fiber web framework]]></title><description><![CDATA[<p><a target="_blank" href="https://docs.gofiber.io/">Fiber</a> is an express-inspired framework for Go. It uses fasthttp under the hood and has support for many features like middleware, JSON, and template engines among others.</p><p>The Fiber project has support for a lot of <a target="_blank" href="https://docs.gofiber.io/guide/templates">template engines</a> but they don't have one for <a target="_blank" href="https://scriggo.com/templates">Scriggo</a>. I like Scriggo because it looks a bit like Jinja (Python), Twig (PHP) and Pebble (Java) template engines which I have used extensively recently.</p><p>Fortunately, Fiber allows you to implement your own Template Engine without much hassle. In this article I will share a simple implementation of a simple Scriggo template engine that you can use with Fiber.</p><h2 id="heading-a-simple-server-side-rendered-site-in-go">A Simple Server side rendered site in Go</h2><p>We are going to create a simple server side rendered site with Go using the Scriggo engine. So first of all create a new project, e.g.:</p><pre><code class="lang-sh">$ mkdir example-site$ <span class="hljs-built_in">cd</span> example-site$ go mod init example.com/site</code></pre><p>Copy the code for the engine (find it at the bottom of this article) into a file named <code>scriggo_engine/scriggo_engine.go</code> so you can import the Engine.</p><p>Now, place the following server code in a file named <code>main.go</code> in your module directory:</p><pre><code class="lang-go"><span class="hljs-keyword">package</span> main<span class="hljs-keyword">import</span> (    <span class="hljs-string">"log"</span>    <span class="hljs-string">"github.com/gofiber/fiber/v2"</span>    <span class="hljs-string">"example.com/site/scriggo_engine"</span>)<span class="hljs-keyword">type</span> Server <span class="hljs-keyword">struct</span> {    app *fiber.App}<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s *Server)</span> <span class="hljs-title">NotImplemented</span><span class="hljs-params">(ctx *fiber.Ctx)</span> <span class="hljs-title">error</span></span> {    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>}<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">NewServer</span><span class="hljs-params">()</span> *<span class="hljs-title">Server</span></span> {    engine := scriggo_engine.New(<span class="hljs-string">"templates"</span>, <span class="hljs-string">".html"</span>)    s := &amp;Server{        app: fiber.New(fiber.Config{            Views: engine,        }),    }    s.app.Get(<span class="hljs-string">"/"</span>, s.indexPage)    <span class="hljs-keyword">return</span> s}<span class="hljs-keyword">type</span> Contact <span class="hljs-keyword">struct</span> {    Name   <span class="hljs-keyword">string</span>    Phone  <span class="hljs-keyword">string</span>    Email    <span class="hljs-keyword">string</span>}<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s *Server)</span> <span class="hljs-title">indexPage</span><span class="hljs-params">(ctx *fiber.Ctx)</span> <span class="hljs-title">error</span></span> {    contacts := []Contact{        {Name: <span class="hljs-string">"John Phiri"</span>, Email: <span class="hljs-string">"john@example.com"</span>, Phone: <span class="hljs-string">"(+265) 999 123 456"</span>},        {Name: <span class="hljs-string">"Mary Phiri"</span>, Email: <span class="hljs-string">"mary@example.com"</span>, Phone: <span class="hljs-string">"(+265) 999 123 456"</span>},        {Name: <span class="hljs-string">"Jane Phiri"</span>, Email: <span class="hljs-string">"jane@example.com"</span>, Phone: <span class="hljs-string">"(+265) 999 123 456"</span>},    }    <span class="hljs-keyword">return</span> ctx.Render(<span class="hljs-string">"index"</span>, fiber.Map{        <span class="hljs-string">"contacts"</span>: &amp;contacts,    })}<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(s *Server)</span> <span class="hljs-title">Start</span><span class="hljs-params">(bind <span class="hljs-keyword">string</span>)</span> <span class="hljs-title">error</span></span> {    <span class="hljs-keyword">return</span> s.app.Listen(bind)}<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {    server := NewServer()    err := server.Start(<span class="hljs-string">"localhost:3000"</span>)    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {        log.Fatalf(<span class="hljs-string">"Failed to run, got error %v"</span>, err)    }}</code></pre><p>In a file named <code>templates/base.html</code></p><pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"X-UA-Compatible"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"IE=edge"</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>{{ Title() }} - My Website<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">nav</span>&gt;</span>        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>My Website<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>        <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>            <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/home"</span>&gt;</span>Home<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>            <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/about"</span>&gt;</span>About<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>            <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/contact"</span>&gt;</span>Contact<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>        <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>    <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>    {{ Content() }}    {{ Footer() }}<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></code></pre><p>In a file named <code>templates/index.html</code></p><pre><code class="lang-html">{% extends "base.html" %}{% macro Title %}Home{% end %}{% macro Content %}<span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"app-content"</span>&gt;</span>    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"contacts"</span>&gt;</span>        {% for contact in contacts %}        <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/contact/{{ contact.Name }}"</span>&gt;</span>{{ contact.Phone}}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> ({{ contact.Email }})<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>        {% end %}    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>{% end %}{% macro Footer %} <span class="hljs-tag">&lt;<span class="hljs-name">footer</span>&gt;</span>(c) My Website<span class="hljs-tag">&lt;/<span class="hljs-name">footer</span>&gt;</span>{% end %}</code></pre><h2 id="heading-project-structure">Project Structure</h2><p>Your directory should look something like this</p><pre><code>example<span class="hljs-operator">-</span>sitemain.gogo.modgo.sumscriggo_engine<span class="hljs-operator">|</span>    scriggo_engine.gotemplates    base.html    index.html</code></pre><h2 id="heading-running-the-code">Running the code</h2><p>First we are going to make sure go modules are tidied and downloaded via <code>go mod tidy</code> and then we can run the main.go</p><pre><code class="lang-sh">$ go mod tidy$ go run main.go</code></pre><p>Now go to <code>http://localhost:3000</code> you should see a server-side rendered HTML page!  </p><h2 id="heading-registering-scriggo-in-built-functions">Registering Scriggo in-built functions</h2><p>Typically in template engines we may want to call some functions on our data. Scriggo comes with a lot of built-in functions and using them is relatively straight-foward.</p><p>Register the function via <code>addFunc</code></p><pre><code class="lang-go"><span class="hljs-keyword">import</span> <span class="hljs-string">"github.com/open2b/scriggo/builtin"</span><span class="hljs-comment">// ..</span>engine.AddFunc(<span class="hljs-string">"base64"</span>, builtin.Base64 )<span class="hljs-comment">// ..</span></code></pre><p>Use the function in your templates</p><pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>{{ base64("Hello") }}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></code></pre><h2 id="heading-scriggo-template-engine-for-fiber">Scriggo Template Engine for Fiber</h2><p>Here is the implementation of the engine code, it has not been battle-tested so if there are bugs or ways to improve it, feel free to reach out on Twitter.</p><pre><code class="lang-go"><span class="hljs-keyword">package</span> scriggo_engine<span class="hljs-keyword">import</span> (    <span class="hljs-string">"fmt"</span>    <span class="hljs-string">"io"</span>    <span class="hljs-string">"net/http"</span>    <span class="hljs-string">"os"</span>    <span class="hljs-string">"path/filepath"</span>    <span class="hljs-string">"strings"</span>    <span class="hljs-string">"sync"</span>    <span class="hljs-string">"github.com/gofiber/fiber/v2"</span>    <span class="hljs-string">"github.com/gofiber/template/utils"</span>    <span class="hljs-string">"github.com/open2b/scriggo"</span>    <span class="hljs-string">"github.com/open2b/scriggo/native"</span>)<span class="hljs-comment">// Engine struct</span><span class="hljs-keyword">type</span> Engine <span class="hljs-keyword">struct</span> {    <span class="hljs-comment">// views folder</span>    directory <span class="hljs-keyword">string</span>    <span class="hljs-comment">// http.FileSystem supports embedded files</span>    fileSystem http.FileSystem    <span class="hljs-comment">// views extension</span>    extension <span class="hljs-keyword">string</span>    <span class="hljs-comment">// layout variable name that incapsulates the template</span>    layout <span class="hljs-keyword">string</span>    <span class="hljs-comment">// determines if the engine parsed all templates</span>    loaded <span class="hljs-keyword">bool</span>    <span class="hljs-comment">// reload on each render</span>    reload <span class="hljs-keyword">bool</span>    <span class="hljs-comment">// debug prints the parsed templates</span>    debug <span class="hljs-keyword">bool</span>    <span class="hljs-comment">// lock for funcmap and templates</span>    mutex sync.RWMutex    <span class="hljs-comment">// template funcmap</span>    funcmap native.Declarations    <span class="hljs-comment">// templates</span>    templates <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>    <span class="hljs-comment">// scriggo filesystem</span>    fsys scriggo.Files}<span class="hljs-comment">// New returns a Scriggo render engine for Fiber</span><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">New</span><span class="hljs-params">(directory, extension <span class="hljs-keyword">string</span>)</span> *<span class="hljs-title">Engine</span></span> {    engine := &amp;Engine{        directory: directory,        extension: extension,        layout:    <span class="hljs-string">"embed"</span>,        funcmap:   <span class="hljs-built_in">make</span>(native.Declarations),    }    engine.AddFunc(engine.layout, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span> <span class="hljs-title">error</span></span> {        <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"layout called unexpectedly."</span>)    })    <span class="hljs-keyword">return</span> engine}<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">NewFileSystem</span><span class="hljs-params">(fs http.FileSystem, extension <span class="hljs-keyword">string</span>)</span> *<span class="hljs-title">Engine</span></span> {    engine := &amp;Engine{        directory:  <span class="hljs-string">"/"</span>,        fileSystem: fs,        extension:  extension,        layout:     <span class="hljs-string">"embed"</span>,        funcmap:    <span class="hljs-built_in">make</span>(native.Declarations),    }    engine.AddFunc(engine.layout, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">()</span> <span class="hljs-title">error</span></span> {        <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"layout called unexpectedly."</span>)    })    <span class="hljs-keyword">return</span> engine}<span class="hljs-comment">// Layout defines the variable name that will incapsulate the template</span><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(e *Engine)</span> <span class="hljs-title">Layout</span><span class="hljs-params">(key <span class="hljs-keyword">string</span>)</span> *<span class="hljs-title">Engine</span></span> {    e.layout = key    <span class="hljs-keyword">return</span> e}<span class="hljs-comment">// Delims sets the action delimiters to the specified strings, to be used in</span><span class="hljs-comment">// templates. An empty delimiter stands for the</span><span class="hljs-comment">// corresponding default: {{ or }}.</span><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(e *Engine)</span> <span class="hljs-title">Delims</span><span class="hljs-params">(left, right <span class="hljs-keyword">string</span>)</span> *<span class="hljs-title">Engine</span></span> {    fmt.Println(<span class="hljs-string">"delims: this method is not supported for scriggo"</span>)    <span class="hljs-keyword">return</span> e}<span class="hljs-comment">// AddFunc adds the function to the template's function map.</span><span class="hljs-comment">// It is legal to overwrite elements of the default actions</span><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(e *Engine)</span> <span class="hljs-title">AddFunc</span><span class="hljs-params">(name <span class="hljs-keyword">string</span>, fn native.Declaration)</span> *<span class="hljs-title">Engine</span></span> {    e.mutex.Lock()    e.funcmap[name] = fn    e.mutex.Unlock()    <span class="hljs-keyword">return</span> e}<span class="hljs-comment">// Reload if set to true the templates are reloading on each render,</span><span class="hljs-comment">// use it when you're in development and you don't want to restart</span><span class="hljs-comment">// the application when you edit a template file.</span><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(e *Engine)</span> <span class="hljs-title">Reload</span><span class="hljs-params">(enabled <span class="hljs-keyword">bool</span>)</span> *<span class="hljs-title">Engine</span></span> {    e.reload = enabled    <span class="hljs-keyword">return</span> e}<span class="hljs-comment">// Debug will print the parsed templates when Load is triggered.</span><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(e *Engine)</span> <span class="hljs-title">Debug</span><span class="hljs-params">(enabled <span class="hljs-keyword">bool</span>)</span> *<span class="hljs-title">Engine</span></span> {    e.debug = enabled    <span class="hljs-keyword">return</span> e}<span class="hljs-comment">// Load parses the templates to the engine.</span><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(e *Engine)</span> <span class="hljs-title">Load</span><span class="hljs-params">()</span> <span class="hljs-title">error</span></span> {    <span class="hljs-comment">// race safe</span>    e.mutex.Lock()    <span class="hljs-keyword">defer</span> e.mutex.Unlock()    e.templates = <span class="hljs-built_in">make</span>(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>)    e.fsys = scriggo.Files{}    <span class="hljs-comment">// Loop trough each directory and register template files</span>    walkFn := <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(path <span class="hljs-keyword">string</span>, info os.FileInfo, err error)</span> <span class="hljs-title">error</span></span> {        <span class="hljs-comment">// Return error if exist</span>        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {            <span class="hljs-keyword">return</span> err        }        <span class="hljs-comment">// Skip file if it's a directory or has no file info</span>        <span class="hljs-keyword">if</span> info == <span class="hljs-literal">nil</span> || info.IsDir() {            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>        }        <span class="hljs-comment">// Skip file if it does not equal the given template extension</span>        <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(e.extension) &gt;= <span class="hljs-built_in">len</span>(path) || path[<span class="hljs-built_in">len</span>(path)-<span class="hljs-built_in">len</span>(e.extension):] != e.extension {            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>        }        <span class="hljs-comment">// Get the relative file path</span>        <span class="hljs-comment">// ./views/html/index.tmpl -&gt; index.tmpl</span>        rel, err := filepath.Rel(e.directory, path)        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {            <span class="hljs-keyword">return</span> err        }        <span class="hljs-comment">// Reverse slashes '\' -&gt; '/' and</span>        <span class="hljs-comment">// partials\footer.tmpl -&gt; partials/footer.tmpl</span>        name := filepath.ToSlash(rel)        <span class="hljs-comment">// Remove ext from name 'index.tmpl' -&gt; 'index'</span>        name = strings.TrimSuffix(name, e.extension)        <span class="hljs-comment">// name = strings.Replace(name, e.extension, "", -1)</span>        <span class="hljs-comment">// Read the file</span>        <span class="hljs-comment">// #gosec G304</span>        buf, err := utils.ReadFile(path, e.fileSystem)        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {            <span class="hljs-keyword">return</span> err        }        e.fsys[filepath.ToSlash(rel)] = buf        e.templates[name] = filepath.ToSlash(rel)        <span class="hljs-comment">// Debugging</span>        <span class="hljs-keyword">if</span> e.debug {            fmt.Printf(<span class="hljs-string">"views: parsed template: %s\n"</span>, name)        }        <span class="hljs-keyword">return</span> err    }    <span class="hljs-comment">// notify engine that we parsed all templates</span>    e.loaded = <span class="hljs-literal">true</span>    <span class="hljs-keyword">if</span> e.fileSystem != <span class="hljs-literal">nil</span> {        <span class="hljs-keyword">return</span> utils.Walk(e.fileSystem, e.directory, walkFn)    }    <span class="hljs-keyword">return</span> filepath.Walk(e.directory, walkFn)}<span class="hljs-comment">// Render will execute the template name along with the given values.</span><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(e *Engine)</span> <span class="hljs-title">Render</span><span class="hljs-params">(out io.Writer, template <span class="hljs-keyword">string</span>, binding <span class="hljs-keyword">interface</span>{}, layout ...<span class="hljs-keyword">string</span>)</span> <span class="hljs-title">error</span></span> {    <span class="hljs-keyword">if</span> !e.loaded || e.reload {        <span class="hljs-keyword">if</span> e.reload {            e.loaded = <span class="hljs-literal">false</span>        }        <span class="hljs-keyword">if</span> err := e.Load(); err != <span class="hljs-literal">nil</span> {            <span class="hljs-keyword">return</span> err        }    }    templatePath, ok := e.templates[template]    <span class="hljs-keyword">if</span> !ok {        <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"render: template %s does not exist"</span>, template)    }    opts := &amp;scriggo.BuildOptions{        Globals: e.funcmap,    }    <span class="hljs-comment">// Register the variables as Globals for Scriggo's type checking to work</span>    <span class="hljs-keyword">for</span> key, value := <span class="hljs-keyword">range</span> binding.(fiber.Map) {        opts.Globals[key] = value    }    <span class="hljs-comment">// Build the template.</span>    tmpl, err := scriggo.BuildTemplate(e.fsys, templatePath, opts)    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {        <span class="hljs-keyword">return</span> err    }    <span class="hljs-keyword">return</span> tmpl.Run(out, <span class="hljs-literal">nil</span>, <span class="hljs-literal">nil</span>)}</code></pre><h2 id="heading-whats-next">What's next?</h2><ol><li><p>Support for embedded filesytems (<code>embed.FS</code>) - I suspect it's already possible but the API doesn't look good for doing so yet.</p></li><li><p>Releasing to the public - I am thinking of either releasing this as a module that others can consume or  sending a Pull Request to the Fiber team if they would be interested, or I may just make it a Gist. We will see. Right now the proof of concept works well enough for me to play with on toy projects.</p></li></ol><p>I may update this article later on</p>]]></description><link>https://zikani.hashnode.dev/using-scriggo-templates-with-the-go-fiber-web-framework</link><guid isPermaLink="true">https://zikani.hashnode.dev/using-scriggo-templates-with-the-go-fiber-web-framework</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Fri, 11 Feb 2022 10:02:36 GMT</pubDate></item><item><title><![CDATA[Using `in` to run a command in multiple directories]]></title><description><![CDATA[<p>Recently, I found a cool Go utility named <a target="_blank" href="https://github.com/xyproto/in"><code>in</code></a> that allows you to run a command in a directory you specify. When you run the <code>in</code> command with a directory that doesn't exist it first creates the directory and runs the command.So this command is cool because it allows you to run a command in a specific directory on the command-line without first navigating to it (via <code>cd</code>). </p><p>The idea behind the tool is to allow you to do this:</p><pre><code class="lang-sh">$ <span class="hljs-keyword">in</span> build cmake ..</code></pre><p>Instead of </p><pre><code class="lang-sh">$ mkdir -p build$ <span class="hljs-built_in">cd</span> build$ cmake ..</code></pre><h2 id="heading-adding-a-feature-to-in">Adding a feature to <code>in</code></h2><p>When I found this tool I knew I had to add another feature to it to accomplish something I have needed for a while; a command-line tool to run the same command in multiple project directories at once. </p><p>I sent in a <a target="_blank" href="https://github.com/xyproto/in/pull/1">Pull-Request</a> to add glob support to enable <code>in</code> to be used to run a command in multiple directories. I was pleased to see the PR merged 🎉 So this functionality is available to anyone else that comes across <code>in</code>.</p><p>So, let's see what we can do with this new feature:</p><p><strong>Compile all maven projects</strong></p><pre><code class="lang-sh">$ <span class="hljs-keyword">in</span> <span class="hljs-string">"./**/*pom.xml"</span> mvn clean compile</code></pre><p><strong>View last N commits for all projects in a directory</strong></p><p>This works if the directory has subdirectories that are Git repositories.</p><pre><code class="lang-sh">$ <span class="hljs-keyword">in</span> <span class="hljs-string">"./*"</span> git <span class="hljs-built_in">log</span> --oneline -n 3</code></pre><p><strong>Pull refs for all git repos in a directory</strong></p><p>Like the previous example, this works if the directory has subdirectories that are Git repositories.</p><pre><code class="lang-sh">$ <span class="hljs-keyword">in</span> <span class="hljs-string">"./*"</span> git pull --all</code></pre><p><strong>Run go fmt on all directories containing Go files</strong></p><pre><code class="lang-sh">$ <span class="hljs-keyword">in</span> <span class="hljs-string">"./**/*.go"</span> go fmt</code></pre><p><strong>Creating GitHub actions workflow directories in multiple directories</strong></p><p><strong>With Powershell</strong></p><pre><code class="lang-sh">$ <span class="hljs-keyword">in</span> <span class="hljs-string">".\oss\*"</span> powershell <span class="hljs-string">"mkdir .github/workflow"</span></code></pre><p><strong>With Bash</strong></p><pre><code class="lang-sh">$ <span class="hljs-keyword">in</span> <span class="hljs-string">"./oss/*"</span> mkdir -p .github/workflow</code></pre><h2 id="heading-installing-and-using-in">Installing and using <code>in</code></h2><p>In order to use <code>in</code> you will currently need to build it locally. Fortunately, the command does not have external dependencies, at the time of writing, so all you will need is the Go toolchain. <a target="_blank" href="https://go.dev/dl">Download Go here</a></p><pre><code class="lang-sh">$ git <span class="hljs-built_in">clone</span> https://github.com/xyproto/<span class="hljs-keyword">in</span>$ <span class="hljs-built_in">cd</span> <span class="hljs-keyword">in</span>$ go build</code></pre><p>Personally, I build the binary as <code>indir</code> as I think that makes more sense despite being more characters and also works much better with Powershell :). If you want to use that similar approach you can build it like so:</p><pre><code class="lang-sh">$ go build -o indir main.go</code></pre><p>Or like this on Windows</p><pre><code class="lang-sh">$ go build -o indir.exe main.go</code></pre><p>When you build it like that you can place the command on your <code>$PATH</code> and call the command as follows:</p><pre><code class="lang-sh">$ indir <span class="hljs-string">"./**/*pom.xml"</span> mvn clean compile</code></pre><h2 id="heading-conclusion">Conclusion</h2><p><code>in</code> is a great tool which has saved me the trouble of writing multiple single-use case commands. Open-Source software is awesome and contributing to something that I needed and use feels good. Moving on, I would like to improve the documentation to make sure people understand the behavior of the tool especially with the glob feature and add some notes on using the tool in a Windows environment.</p>]]></description><link>https://zikani.hashnode.dev/using-in-to-run-a-command-in-multiple-directories</link><guid isPermaLink="true">https://zikani.hashnode.dev/using-in-to-run-a-command-in-multiple-directories</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Thu, 23 Dec 2021 20:16:17 GMT</pubDate></item><item><title><![CDATA[Can I `git clone` into an in-memory file system?]]></title><description><![CDATA[<p>Recently, I wanted to find ways to clone a Git repository into an in-memory file system for a toy project, as it seems I need a <a target="_blank" href="https://github.com/zikani03/multiclone">lot of</a> <a target="_blank" href="https://gist.github.com/zikani03/95b9c191893547f9ccb70ab7dc152aba">git</a> <a target="_blank" href="https://github.com/zikani03/git-down">tools</a> in my life.</p><p>Well the answer to the question in the title is simply: <strong>YES</strong>, you can clone a Git repository to an in-memory file system.  Let us see how.</p><p>In this post I will present two ways we can accomplish this; using a temporary filesystem using facilities provided by the OS (Linux in this case) and using a Git library which has capability to clone and work with repositories in-memory (using Go). <em>Maybe we can even combine the approaches?</em></p><h3 id="clone-to-a-ram-storage-on-linux">Clone to a RAM storage (on Linux)</h3><p>The first approach involves using the OS and relevant programs to create a temporary filesystem that runs in-memory and does not persist across reboot. Linux supports creating file system which keeps files in virtual memory.  In order to accomplish our particular task, we create a <a target="_blank" href="https://www.kernel.org/doc/html/latest/filesystems/tmpfs.html">tmpfs</a> filesystem, mount it to a directory then navigate to that directory and perform the clone operation there.The basic idea would work as follows:</p><pre><code>$ sudo mkdir /mnt/tempgit$ sudo mount -t tmpfs -o size=<span class="hljs-number">50</span>m tmpfs /mnt/tempgit$ cd /mnt/tempgit$ git clone <span class="hljs-symbol">https:</span>/<span class="hljs-regexp">/github.com/zikani</span>03/multiclone.git$ lsmulticlone</code></pre><p>Just take note that <em>"tmpfs may use SWAP space. If your system runs out of physical RAM, files in your tmpfs partitions may be written to disk based SWAP partitions and will have to be read from disk when the file is next accessed"</em> (<a target="_blank" href="https://www.jamescoyle.net/knowledge/951-the-difference-between-a-tmpfs-and-ramfs-ram-disk">source</a>)</p><h3 id="clone-using-go-git-library">Clone using go-git library</h3><p>This approach requires a fairly recent installation of <a target="_blank" href="https://go.dev">Go</a> in-order to use the go-git library. The <a target="_blank" href="https://pkg.go.dev/github.com/go-git/go-git/v5">go-git</a> library is</p><blockquote><p>... a highly extensible git implementation library written in pure Go.</p></blockquote><p>The library has a Clone function which we can use with a storage type. Fortunately for us, one of the storage types that comes out of the box in the library is the memory storage. We can use that to perform an in-memory clone easily:</p><pre><code class="lang-go"><span class="hljs-keyword">package</span> main<span class="hljs-keyword">import</span> (    git <span class="hljs-string">"github.com/go-git/go-git/v5"</span>    <span class="hljs-string">"github.com/go-git/go-git/v5/storage/memory"</span>)<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {    _, err := git.Clone(memory.NewStorage(), <span class="hljs-literal">nil</span>, &amp;git.CloneOptions{        URL: <span class="hljs-string">"https://github.com/zikani03/multiclone.git"</span>,    })    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {        <span class="hljs-built_in">panic</span>(err)    }    <span class="hljs-comment">// ...</span>}</code></pre><p>Obviously, this approach will be easier if you are comfortable working with Go. </p><h2 id="why-would-you-wanna-do-this">Why would you wanna do this?</h2><p>🚅. Using an in-memory storage should improve performance of git operations as memory is generally faster than disk. As mentioned earlier, I am working on something that might need this behavior. If you are curious about the tool, I spoke a bit about it in response to another Tweet:</p><div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/zikani03/status/1456280072646578188?s=20">https://twitter.com/zikani03/status/1456280072646578188?s=20</a></div><p>Well, that's all folks.</p>]]></description><link>https://zikani.hashnode.dev/git-clone-inmemory</link><guid isPermaLink="true">https://zikani.hashnode.dev/git-clone-inmemory</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Sun, 07 Nov 2021 22:00:00 GMT</pubDate><cover_image>https://cdn.hashnode.com/res/hashnode/image/upload/v1636414355475/thuQ4YBAm.jpeg</cover_image></item><item><title><![CDATA[Seeding Elasticsearch with test data using zefaker and esbulk]]></title><description><![CDATA[<p>In this short article, we will see how you can use zefaker to generate 5 million records of random data which can easily be indexed in Elasticsearch using <code>esbulk</code>.</p><p><strong>Prerequisites</strong>:</p><ul><li><a target="_blank" href="https://github.com/creditdatamw/zefaker/releases/">zefaker</a> (version 0.6 at the time of writing)</li><li><a target="_blank" href="https://www.azul.com/downloads/zulu-community/?architecture=x86-64-bit&amp;package=jdk">JDK 15+</a></li><li><a target="_blank" href="https://golang.org">Go 1.16+</a></li><li><a target="_blank" href="https://github.com/miku/esbulk">esbulk</a></li><li><a target="_blank" href="https://www.elastic.co/downloads/elasticsearch">Elasticsearch 7+ / OpenSearch 1.0.0</a></li><li><a target="_blank" href="https://curl.se/download.html">curl</a> (optional, but really you should have this already)</li></ul><h2 id="creating-the-zefaker-file">Creating the <code>zefaker</code> file</h2><p>Firstly, we need to create a Groovy script to use with the zefaker to specify the form of our random data.You can copy the code snippet below and place it in a file named <code>data.groovy</code> which we will pass to zefaker to generate our data.</p><pre><code class="lang-groovy">// in data.groovyimport com.google.gson.JsonObjectfirstName = column(index= 0, name= "firstName")lastName  = column(index= 1, name= "lastName")age       = column(index= 2, name= "age")accountStatus = column(index=3, name="accountStatus")accountMeta   = column(index=4, name="accountMeta")generateFrom([    (firstName): { faker -&gt; faker.name().firstName() },    (lastName): { faker -&gt; faker.name().lastName() },    (age): { faker -&gt; faker.number().numberBetween(18, 70) },    (accountStatus): { faker -&gt; faker.options().option("Open", "Closed") },    // You can nest objects like this    (accountMeta): { faker -&gt;         def meta = new JsonObject()        meta.addProperty("totalTokens", faker.number().numberBetween(5000, 10000))        meta.addProperty("activityStatus", faker.options().option("Active", "Dormant"))        return meta    }])</code></pre><h2 id="generating-the-data">Generating the data</h2><p>zefaker requires Java to be installed to run. I'm assuming you have the <code>java</code> command in your PATH.With that we can run the following to generate 5 million rows of random data exported into a JSON Lines format (basically a plain text file where each line is a JSON Object).</p><pre><code class="lang-sh">$ java -jar zefaker-all.jar -f data.groovy -jsonl -output elasticdata.jsonl -rows 5000000</code></pre><p>You can also try the <code>zefaker</code> web instance I have running <a target="_blank" href="http://zefaker.labs.zikani.me">here</a>, this will save you from having to install Java or zefaker on your machine. Make sure you select JSON Lines as the export option</p><h2 id="getting-esbulk-utility">Getting <code>esbulk</code> utility</h2><p>We will use <code>esbulk</code>, a nifty small command-line program written in Go, to perform the indexing. We will have to build it first.</p><pre><code class="lang-sh">$ git <span class="hljs-built_in">clone</span> https://github.com/miku/esbulk$ <span class="hljs-built_in">cd</span> esbulk$ go build</code></pre><p>This will create an executable named esbulk (esbulk.exe on Windows). You can add it on your <code>PATH</code></p><h2 id="indexing-the-data-in-elasticsearch">Indexing the data in Elasticsearch</h2><p>Again, it is assumed that you have installed Elasticsearch or OpenSearch and have it running.  We can use the following command to index our data:</p><pre><code class="lang-sh">$ esbulk -index <span class="hljs-string">"people-2021.07.07"</span> -optype create -server http://localhost:9200 &lt; elasticdata.jsonl</code></pre><p>After esbulk completes (silently) you can check that the operation was successful by visiting http://localhost:9200/people-2021.07.07/_search in your browser or using curl like so:</p><pre><code class="lang-sh">$ curl -G http://localhost:9200/people-2021.07.07/_search</code></pre><p>And, that's all folks. Hope you found this useful. </p><p>Show some love and star <a target="_blank" href="https://github.com/creditdatamw/zefaker">zefaker</a> :)</p>]]></description><link>https://zikani.hashnode.dev/seeding-elasticsearch-with-test-data-using-zefaker-and-esbulk</link><guid isPermaLink="true">https://zikani.hashnode.dev/seeding-elasticsearch-with-test-data-using-zefaker-and-esbulk</guid><dc:creator><![CDATA[Zikani Nyirenda Mwase]]></dc:creator><pubDate>Wed, 29 Sep 2021 10:58:22 GMT</pubDate></item></channel></rss>