Ex commito [open source]2024-03-19T08:13:43+00:00http://kray.meFabrice LaporteSublimeText PythonAutoImport plugin2023-09-07T00:00:00+00:00http://kray.me/2023/09/st3_auto_imports<p>Six months ago, I forked a 10 years old <a href="https://github.com/jasonmyers/PythonAutoImport">plugin</a> that do auto-imports and contributed <a href="https://github.com/jasonmyers/PythonAutoImport/commit/fc7c7551a20a717576b85b54e12ff8683d140e45">a fix</a> so it works gracefully now with multi-lines import.</p>
<p>After months using it I can safely says it improved my use of ST3 and so share it here for those that spend a hell lot of time in ST3 to write import statements.</p>
<p><a href="https://github.com/Kraymer/PythonAutoImport">https://github.com/Kraymer/PythonAutoImport</a></p>
Blog last publish date in favicon2018-01-21T00:00:00+00:00http://kray.me/2018/01/jekyll-favicon-publish-date<p>As for every details of <a href="https://github.com/Kraymer/kraymer.github.com"><em>Ex commito</em> theme</a>, I wanted the favicon to convey the maximum of information.
Basing your favicon on a thumbnail of your homepage sounds like a pretty dumb idea at first.<br />
Obviously abstracting parts of the page details is essential to make the 128x128 square legible : I kept just the vertical split with artsy colourful sidebar and creamy right side.</p>
<p class="center"><img src="/public/img/favicon.png" alt="empty favicon" /><br />
<em>Favicon empty background</em></p>
<p>That’s a gross preview of how the blog looks, but that does not bring any useful information and makes a shitty favicon for sure.<br />
But put the date of your last post in the empty right part of the image and this icon becomes a thing of beauty, <em>erm</em>. The last publishing date identifies the state of the website like no other (metric).</p>
<p>Because we don’t have that much space available we use the short <code class="language-plaintext highlighter-rouge">MMDD</code> format to print the date.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">from</span> <span class="nn">PIL</span> <span class="kn">import</span> <span class="n">Image</span><span class="p">,</span> <span class="n">ImageDraw</span><span class="p">,</span> <span class="n">ImageFont</span>
<span class="c1"># Generates timestamped favicon images for every day of the year, store images in 12 folders (one per month).
</span>
<span class="n">FONT</span> <span class="o">=</span> <span class="n">ImageFont</span><span class="p">.</span><span class="n">truetype</span><span class="p">(</span><span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="n">abspath</span><span class="p">(</span><span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="s">'static'</span><span class="p">,</span> <span class="s">'fonts'</span><span class="p">,</span> <span class="s">'Avenir Next Condensed.ttc'</span><span class="p">)),</span> <span class="mi">60</span><span class="p">)</span>
<span class="k">for</span> <span class="n">month</span> <span class="ow">in</span> <span class="p">[</span><span class="nb">str</span><span class="p">(</span><span class="n">x</span><span class="p">).</span><span class="n">zfill</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">13</span><span class="p">)]:</span>
<span class="n">os</span><span class="p">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">month</span><span class="p">)</span>
<span class="k">for</span> <span class="n">day</span> <span class="ow">in</span> <span class="p">[</span><span class="nb">str</span><span class="p">(</span><span class="n">x</span><span class="p">).</span><span class="n">zfill</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">32</span><span class="p">)]:</span>
<span class="n">img</span> <span class="o">=</span> <span class="n">Image</span><span class="p">.</span><span class="nb">open</span><span class="p">(</span><span class="s">'public/img/favicon.png'</span><span class="p">)</span>
<span class="n">canvas</span> <span class="o">=</span> <span class="n">ImageDraw</span><span class="p">.</span><span class="n">Draw</span><span class="p">(</span><span class="n">img</span><span class="p">)</span>
<span class="n">canvas</span><span class="p">.</span><span class="n">text</span><span class="p">((</span><span class="mi">65</span><span class="p">,</span> <span class="mi">50</span><span class="p">),</span> <span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">day</span><span class="p">).</span><span class="n">zfill</span><span class="p">(</span><span class="mi">2</span><span class="p">)),</span> <span class="n">font</span><span class="o">=</span><span class="n">FONT</span><span class="p">,</span> <span class="n">fill</span><span class="o">=</span><span class="p">(</span><span class="mi">165</span><span class="p">,</span> <span class="mi">85</span><span class="p">,</span> <span class="mi">64</span><span class="p">))</span>
<span class="n">canvas</span><span class="p">.</span><span class="n">text</span><span class="p">((</span><span class="mi">65</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">month</span><span class="p">).</span><span class="n">zfill</span><span class="p">(</span><span class="mi">2</span><span class="p">)),</span> <span class="n">font</span><span class="o">=</span><span class="n">FONT</span><span class="p">,</span> <span class="n">fill</span><span class="o">=</span><span class="p">(</span><span class="mi">165</span><span class="p">,</span> <span class="mi">85</span><span class="p">,</span> <span class="mi">64</span><span class="p">))</span>
<span class="n">img</span><span class="p">.</span><span class="n">save</span><span class="p">(</span><span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="s">'%s/favicon_%s.png'</span> <span class="o">%</span> <span class="p">(</span><span class="n">month</span><span class="p">,</span> <span class="n">day</span><span class="p">)))</span>
</code></pre></div></div>
<p>This script generates the <a href="https://github.com/Kraymer/bulkdata/tree/master/excommito">365+ different versions</a> of the icon to cover the whole year, for a total size of 4,3M.</p>
<p>And to use the correct version of the favicon, just use these lines in <code class="language-plaintext highlighter-rouge">head.html</code> :</p>
<div class="language-liquid highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{%</span><span class="w"> </span><span class="nt">capture</span><span class="w"> </span><span class="nv">last_date</span><span class="w"> </span><span class="p">%}{{</span><span class="w"> </span><span class="nv">site</span><span class="p">.</span><span class="nv">posts</span><span class="p">.</span><span class="nf">first</span><span class="p">.</span><span class="nv">date</span><span class="w"> </span><span class="p">}}{%</span><span class="w"> </span><span class="nt">endcapture</span><span class="w"> </span><span class="p">%}</span>
<link rel="shortcut icon" type="image/png"
href="https://raw.githubusercontent.com/Kraymer/bulkdata/master/excommito/<span class="p">{{</span><span class="w"> </span><span class="nv">last_date</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nf">slice</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="mi">2</span><span class="w"> </span><span class="p">}}</span>/favicon_<span class="p">{{</span><span class="w"> </span><span class="nv">last_date</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nf">slice</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="mi">2</span><span class="w"> </span><span class="p">}}</span>.png">
</code></pre></div></div>
<p>Have a look in your browser tab for the result! :sparkles:</p>
Deep in F-dotfiles : zsh package2017-06-26T00:00:00+00:00http://kray.me/2017/06/fdotfiles-zsh<p>This post is the first of a serie focused on some cool things stored in
<a href="https://github.com/Kraymer/F-dotfiles">my dotfiles</a>.
The topical organization of my dotfiles is such that each blog post will be dedicated to a given
package. Today we will start with the basics, setup of the shell with the <code class="language-plaintext highlighter-rouge">zsh</code> package.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>├── .oh_my.zsh Zsh theme and plugins
├── .zsh
│ ├── aliases.zsh
│ ├── config.zsh Configure shell behavior
│ ├── dircolors.256dark Colored ls
│ └── functions.zsh Custom shell functions
└── .zshrc Routine loading all .zsh files
</code></pre></div></div>
<h3 id="oh_myzsh"><a href="https://github.com/Kraymer/F-dotfiles/blob/master/zsh/.oh_my.zsh">.oh_my.zsh</a></h3>
<p><img src="/public/img/posts/zshprompt.png" alt="zsh prompt" /></p>
<p>I use <a href="https://github.com/robbyrussell/oh-my-zsh">Oh My Zsh</a> for its plugins coupled with
<a href="https://github.com/bhilburn/powerlevel9k">Powerlevel9k</a> theme that makes the <em>pimpization</em> of <code class="language-plaintext highlighter-rouge">PS1</code>
a treat.</p>
<p>The setup file weights a dozen lines (comments excluded) but has a tremendous impact on my
productivity, which is no surprise given that I spend most of my work day in a shell session.
I can live without the badass prompt, but I feel the pain when I ssh onto servers and lose my
<code class="language-plaintext highlighter-rouge">autojump</code> and <code class="language-plaintext highlighter-rouge">history-substring-search</code> plugins superpowers.</p>
<p>I don’t use many plugins as I want to keep my prompt snappy, but core zsh is shipped with <a href="https://code.joejag.com/2014/why-zsh.html">a bunch
of powerful features</a> already.</p>
<h3 id="aliaseszsh"><a href="https://github.com/Kraymer/F-dotfiles/blob/master/zsh/.zsh/aliases.zsh">aliases.zsh</a></h3>
<p>I use custom bash aliases parsimoniously, I prefer to do things the canonical way.
My primary use for aliases is to add default flags that i consider essential to commands like <code class="language-plaintext highlighter-rouge">-c</code> for <code class="language-plaintext highlighter-rouge">nano</code>.</p>
<p>But nothing fancy here.</p>
<h3 id="configzsh"><a href="https://github.com/Kraymer/F-dotfiles/blob/master/zsh/.zsh/config.zsh">config.zsh</a></h3>
<p>That’s where I put settings relative to shell core functions.
In particular the <code class="language-plaintext highlighter-rouge">history</code> config is here : the filesize is boosted and the <code class="language-plaintext highlighter-rouge">SHARE_HISTORY</code> attribute
makes the history work ok with multiple iTerm opened tabs.</p>
<p>I put <code class="language-plaintext highlighter-rouge">TMOUT</code> setting to <code class="language-plaintext highlighter-rouge">60</code> seconds so that the hour in my prompt get updated : doing it one per
minute does not distract the eye and means you always have current time in prompt even when you’re
idle.</p>
<h3 id="functionszsh"><a href="https://github.com/Kraymer/F-dotfiles/blob/master/zsh/.zsh/functions.zsh">functions.zsh</a></h3>
<p>Where things get interesting.
We have <code class="language-plaintext highlighter-rouge">which</code> and <code class="language-plaintext highlighter-rouge">who</code> commands but the need for <code class="language-plaintext highlighter-rouge">where</code> arise much more frequently :</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>where<span class="o">()</span> <span class="o">{</span>
find <span class="nb">.</span> <span class="nt">-name</span> <span class="se">\*</span><span class="nv">$1</span><span class="se">\*</span>
<span class="o">}</span>
</code></pre></div></div>
<p>But the one function I would bring with me on a desert island is the one wrapping <code class="language-plaintext highlighter-rouge">ssh</code> command
that attaches me to an existing tmux session once connected.</p>
<h3 id="zshrc"><a href="https://github.com/Kraymer/F-dotfiles/blob/master/zsh/.zshrc">.zshrc</a></h3>
<p>This file is responsible to source all the .zsh files located in my <code class="language-plaintext highlighter-rouge">~/.zsh</code> directory.
It’s useful when you want a dotfiles package to alter an environment variable eg <code class="language-plaintext highlighter-rouge">$PATH</code>, you just
write a <code class="language-plaintext highlighter-rouge"><package>/.zsh/<package>.env</code> and reinstall the package via <code class="language-plaintext highlighter-rouge">stow</code>.</p>
<p>This dynamic sourcing step is what makes F-dotfiles packages installs so seamless.</p>
<hr />
<p><em>Any question or insights to share regarding zsh setup ? Please leave a <a href="https://github.com/Kraymer/kraymer.github.com/issues/11">comment</a>.</em></p>
RSS subscribe badge for Github projects2017-06-13T00:00:00+00:00http://kray.me/2017/06/rss-badge-github<p>One not-so-known feature of github is its atom feeds to track projects releases deliveries.<br />
You can subscribe to it at :</p>
<p><code class="language-plaintext highlighter-rouge">https://github.com/<user>/<project>/releases.atom</code></p>
<p>I like that so much that I now add a <img src="/public/img/posts/rss-badge.svg" alt="releases badge" />
badge to all my projects to make that more visible.</p>
Get <i>high</i> to reach your github fans2017-04-12T00:00:00+00:00http://kray.me/2017/04/high-get-github-repo-mails<p>As I intend to release <a href="https://github.com/Kraymer/qifqif">qifqif</a> 1.0.0 sooner than later, <strong>I
wanted to email the project users</strong> for a last minute roundtable about features I could possible squeeze
in it.</p>
<p>The Github API makes getting stargazers public emails easy enough, but there is more …
When you comment on an issue by replying to the github notification
emails, your email address may leak in the comment body like so :</p>
<blockquote>
<p>On Wed, Oct 28, 2015 at 12:37 PM, kraymer kraymer+github@gmail.com wrote:</p>
</blockquote>
<p>Interested to grab these pesky email addresses hidden in issue comments data ? Not a big fan of
node.js for cli tools <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> ?
Give <a href="https://github.com/Kraymer/high">high</a> a try.</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p>the two top results when googling for <em>“github get stargazers emails”</em> are nodejs repositories <a href="https://github.com/neoziro/stargazer">neoziro/stargazer</a> and <a href="https://github.com/lambtron/get-stargazers-emails">lambtron/get-stargazers-emails</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>
<i>ezkill</i>, selective process killing made easy2017-03-29T00:00:00+00:00http://kray.me/2017/03/ezkill-kill-process<p><code class="language-plaintext highlighter-rouge">killall</code> and <code class="language-plaintext highlighter-rouge">pkill -f</code> are the two most popular tools to send SIGTERM to all
processes running a specified command.
With <a href="https://github.com/Kraymer/ezkill"><strong><code class="language-plaintext highlighter-rouge">ezkill</code></strong></a> I want to provide more
flexibility in the choice of processes to terminate while honoring the terse
efficiency of aforementioned commands.</p>
<p><strong><code class="language-plaintext highlighter-rouge">ezkill</code></strong> list processes matching input pattern, <code class="language-plaintext highlighter-rouge">ps</code> style,
and add a letter prefix before each line.
This single letter plays the role of pid, you designate which processes to kill
by entering their letters at the prompt.</p>
<script type="text/javascript" src="https://asciinema.org/a/5dxi20xzerjxhw2fn1tdhqi47.js" id="asciicast-5dxi20xzerjxhw2fn1tdhqi47" async=""></script>
<p>So that’s the trade-off: only the 26 first matching processes are displayed,
only one keystroke per process to kill is required.</p>
Pruning beets genre canonicalization tree2012-10-13T00:00:00+00:00http://kray.me/2012/10/customizing-beet-genres-canonicalization-tree<p>Unlike most tags, ‘genre’ accept some latitude and is open to interpretation. In others words: a music management nightmare.
The <a href="https://gist.github.com/1241307">hundreds variations of genres</a> in the nature are too numerous to be exploited for classification purposes. You gotta restrict yourself!</p>
<p><strong>Figure a limited set of genres that fits your music tastes</strong>. Focus on genres that don’t overlap and partition your library into sets of comparable sizes.</p>
<p>Done right, genres are a great asset that enable to browse through files faster and to create better playlists based on genre filtering.</p>
<h3 id="genres-entrance-policy">Genres entrance policy</h3>
<p>Once you have handpicked your genres list, the easier way to keep it intact consist in ensuring that new additions to your library don’t introduce alien genres.</p>
<p>I use <a href="http://beets.radbox.org/">beets</a> to inject newcomers in my library and bring them into line.</p>
<p>You will have to activate the <code class="language-plaintext highlighter-rouge">lastgenre</code> plugin : it uses <a href="http://www.lastfm.com">lastfm</a> crowdsourced data to fetch the genre information. I encourage you to read the <a href="http://beets.readthedocs.org/en/latest/plugins/lastgenre.html">plugin documentation page</a> first. The plugin works great by default, but you must tweak its settings if you want to enforce a strict set of genres.</p>
<p><code class="language-plaintext highlighter-rouge">beetsplug/lastgenre/genres.txt</code> is a whitelist of all accepted genres. Replace the default one by
your custom list (all genres lowercase, one genre per line)</p>
<p><code class="language-plaintext highlighter-rouge">beetsplug/lastgenre/genres-tree.yaml</code> is the genres canonicalization tree : it stores the genres hierarchically relying on outline indentation for structure. It allows to find multiples candidates from a given genre thus improving the chance to have one present in user whitelist.</p>
<p><img src="/public/img/posts/lastgenre_c14n.png" alt="lastgenre plugin canonicalization steps" />
<em>An example of lastgenre plugin canonicalization: resolving ‘crunk’ to ‘hip-hop’ : lastfm tag tree branch is parsed backward until a tag present in user whitelist is found.</em></p>
<p>Edit the yaml file to introduce your genres at strategic places : placing them near the tree root help you maximize the number of suggested genres that are funneled to your accepted tags.</p>
<p><img src="/public/img/posts/c14nedit.jpg" alt="editing canonicalization tree" />
<em>Diff view of genres-tree.yaml showing changes done to redirect genres to ‘hip-hop’.</em></p>
Introducing replica, the id3 cloner CLI2012-04-30T00:00:00+00:00http://kray.me/2012/04/introducing-replica-the-id3-cloner-cli<p>An mp3 file is made up of a metadata header prepended to the audio data blocks. While the id3 header can be edited to enrich the file description, the audio data is a result of a lossy compression and cannot be enhanced <em>a posteriori</em>.<br />
So, to upgrade the audio quality of a mp3 file, you have to acquire a new – better – copy of it.</p>
<p>How do you cope then with the migration of the id3 <em>intel</em> (such as <a href="/blog/resilient-id3-embedded-ratings">ratings</a> information) contained in the original files?
<a href="http://www.softpointer.com/tr.htm">Tag&Rename</a> for mac is yet to come (<em>sigh</em>) and this specific problem looked simple enough that I decided to code a tool to solve it.</p>
<p>Replica is a python script that enables you to copy id3 metadata between files (and more). <br />
Go to the <strong><a href="https://github.com/KraYmer/replica">project homepage</a></strong> for more infos, or just start using it by typing <code>pip install replica</code>.</p>