<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Miguel Ventura's blog</title><link href="https://miguelventura.pt/" rel="alternate"></link><link href="https://miguelventura.pt/feeds/atom.xml" rel="self"></link><id>https://miguelventura.pt/</id><updated>2026-07-12T00:00:00+01:00</updated><entry><title>A multiplexer for the ever-growing pile of projects</title><link href="https://miguelventura.pt/2026/a-multiplexer-for-the-ever-growing-pile-of-projects.html" rel="alternate"></link><published>2026-07-12T00:00:00+01:00</published><updated>2026-07-12T00:00:00+01:00</updated><author><name>Miguel Ventura</name></author><id>tag:miguelventura.pt,2026-07-12:/2026/a-multiplexer-for-the-ever-growing-pile-of-projects.html</id><summary type="html">&lt;p&gt;My &lt;code&gt;~/work&lt;/code&gt; directory only ever grows. Big projects, medium projects, one-afternoon
experiments that never got deleted, clones of other people's code kept around for
reference. Twenty-six directories at last count, and I had stopped being able to answer
basic questions about my own machine: &lt;em&gt;where did I put that script …&lt;/em&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;My &lt;code&gt;~/work&lt;/code&gt; directory only ever grows. Big projects, medium projects, one-afternoon
experiments that never got deleted, clones of other people's code kept around for
reference. Twenty-six directories at last count, and I had stopped being able to answer
basic questions about my own machine: &lt;em&gt;where did I put that script? What did I even call
it? Is it git-tracked, or one &lt;code&gt;rm -rf&lt;/code&gt; away from oblivion?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For the rest of this post, let three made-up projects stand in for the whole zoo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;superfoo&lt;/strong&gt; — the big website. Gets attention almost every day.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;midbar&lt;/strong&gt; — a log-analysis system I had to build once and never cared about again.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;tinybaz&lt;/strong&gt; — a script that parses metadata out of a pile of files. Tiny, but I still
  reach for it regularly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I use &lt;a href="https://github.com/rupa/z"&gt;z&lt;/a&gt; (via the oh-my-zsh plugin), and jumping around was
never the problem — &lt;code&gt;z sfoo&lt;/code&gt; lands me in &lt;code&gt;superfoo&lt;/code&gt; just fine. The problems were
everything around the jump:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;z&lt;/code&gt; can only &lt;code&gt;cd&lt;/code&gt;. You can't say &lt;code&gt;cp {z baz}/parse.py {z sfoo}&lt;/code&gt; — the moment you need
   a project's path &lt;em&gt;inside&lt;/em&gt; a command, you're back to typing it out.&lt;/li&gt;
&lt;li&gt;Frecency only helps with projects you visit. The ones you &lt;em&gt;forgot about&lt;/em&gt; — &lt;code&gt;midbar&lt;/code&gt;
   and friends — are exactly the ones with no recent rank, and the ones you most need
   help finding.&lt;/li&gt;
&lt;li&gt;Nothing nags you about hygiene, so half the projects end up without git, without a
   README, or with months of unpushed commits.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I was tempted to build A System™ — a project database, metadata files, an index. I'm
glad I didn't, because the whole thing came down to about a hundred lines of zsh built
on one principle: &lt;strong&gt;the filesystem is the database&lt;/strong&gt;. Every listing is derived by
scanning &lt;code&gt;~/work&lt;/code&gt; at call time, so nothing can ever go stale. The only convention I had
to adopt: each project's README starts with a one-line description.&lt;/p&gt;
&lt;h2&gt;The cake first&lt;/h2&gt;
&lt;p&gt;Before the recipe, here's what day-to-day feels like with the whole thing in place:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gp"&gt;# &lt;/span&gt;what&lt;span class="w"&gt; &lt;/span&gt;did&lt;span class="w"&gt; &lt;/span&gt;I&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;that&lt;span class="w"&gt; &lt;/span&gt;log-analysis&lt;span class="w"&gt; &lt;/span&gt;thing,&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;where&lt;span class="w"&gt; &lt;/span&gt;did&lt;span class="w"&gt; &lt;/span&gt;I&lt;span class="w"&gt; &lt;/span&gt;put&lt;span class="w"&gt; &lt;/span&gt;it?
&lt;span class="gp"&gt;$ &lt;/span&gt;work&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;logs
&lt;span class="go"&gt;midbar                 One-off log analysis for the 2024 gateway migration.&lt;/span&gt;

&lt;span class="gp"&gt;# &lt;/span&gt;jump&lt;span class="w"&gt; &lt;/span&gt;around&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;fuzzy&lt;span class="w"&gt; &lt;/span&gt;names,&lt;span class="w"&gt; &lt;/span&gt;as&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;z&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;always&lt;span class="w"&gt; &lt;/span&gt;allowed...
&lt;span class="gp"&gt;$ &lt;/span&gt;z&lt;span class="w"&gt; &lt;/span&gt;sfoo

&lt;span class="gp"&gt;# &lt;/span&gt;...but&lt;span class="w"&gt; &lt;/span&gt;project&lt;span class="w"&gt; &lt;/span&gt;names&lt;span class="w"&gt; &lt;/span&gt;now&lt;span class="w"&gt; &lt;/span&gt;also&lt;span class="w"&gt; &lt;/span&gt;work&lt;span class="w"&gt; &lt;/span&gt;*inside*&lt;span class="w"&gt; &lt;/span&gt;commands
&lt;span class="gp"&gt;$ &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;[&lt;/span&gt;baz&lt;span class="o"&gt;]&lt;/span&gt;/parse_meta.py&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;[&lt;/span&gt;sfoo&lt;span class="o"&gt;]&lt;/span&gt;/scripts/

&lt;span class="gp"&gt;# &lt;/span&gt;what&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;s&lt;span class="w"&gt; &lt;/span&gt;rotting?
&lt;span class="gp"&gt;$ &lt;/span&gt;work&lt;span class="w"&gt; &lt;/span&gt;doctor
&lt;span class="go"&gt;midbar                 no git&lt;/span&gt;
&lt;span class="go"&gt;superfoo               unpushed 3&lt;/span&gt;
&lt;span class="go"&gt;tinybaz                no remote&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(23 projects clean)&lt;/span&gt;

&lt;span class="gp"&gt;# &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;projects&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;life&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;git,&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;README&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;jump&lt;span class="w"&gt; &lt;/span&gt;entry
&lt;span class="gp"&gt;$ &lt;/span&gt;work&lt;span class="w"&gt; &lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;quuxwatch&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Alerting for the quux fleet&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;/home/mmv/work/quuxwatch&lt;/span&gt;

&lt;span class="gp"&gt;# &lt;/span&gt;retiring&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;project&lt;span class="w"&gt; &lt;/span&gt;doesn&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;your&lt;span class="w"&gt; &lt;/span&gt;jump&lt;span class="w"&gt; &lt;/span&gt;muscle&lt;span class="w"&gt; &lt;/span&gt;memory
&lt;span class="gp"&gt;$ &lt;/span&gt;work&lt;span class="w"&gt; &lt;/span&gt;archive&lt;span class="w"&gt; &lt;/span&gt;midbar
&lt;span class="go"&gt;/home/mmv/work/archive/midbar&lt;/span&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;z&lt;span class="w"&gt; &lt;/span&gt;midb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;
&lt;span class="go"&gt;/home/mmv/work/archive/midbar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Everything below is the recipe for the above, piece by piece.&lt;/p&gt;
&lt;h2&gt;zoxide instead of z&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/ajeetdsouza/zoxide"&gt;zoxide&lt;/a&gt; is the actively maintained successor of
&lt;code&gt;z&lt;/code&gt;, with the same muscle memory. The killer difference is that it's &lt;em&gt;composable&lt;/em&gt;:
&lt;code&gt;zoxide query sfoo&lt;/code&gt; prints the resolved path instead of cd-ing into it. Migrating took
two commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;zoxide
zoxide&lt;span class="w"&gt; &lt;/span&gt;import&lt;span class="w"&gt; &lt;/span&gt;--from&lt;span class="o"&gt;=&lt;/span&gt;z&lt;span class="w"&gt; &lt;/span&gt;~/.z&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# keep years of frecency habits&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then in &lt;code&gt;.zshrc&lt;/code&gt;, replace the &lt;code&gt;z&lt;/code&gt; plugin with &lt;code&gt;eval "$(zoxide init zsh)"&lt;/code&gt;. A trivial
wrapper gives you paths in command substitutions:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;zq&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;zoxide&lt;span class="w"&gt; &lt;/span&gt;query&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;zq&lt;span class="w"&gt; &lt;/span&gt;baz&lt;span class="k"&gt;)&lt;/span&gt;/parse_meta.py&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;zq&lt;span class="w"&gt; &lt;/span&gt;sfoo&lt;span class="k"&gt;)&lt;/span&gt;/scripts/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;&lt;code&gt;~[name]&lt;/code&gt;: the zsh feature nobody told you about&lt;/h2&gt;
&lt;p&gt;zsh has &lt;a href="https://zsh.sourceforge.io/Doc/Release/Expansion.html#Dynamic-named-directories"&gt;dynamic named directories&lt;/a&gt;:
if you define a &lt;code&gt;zsh_directory_name&lt;/code&gt; function, then &lt;code&gt;~[whatever]&lt;/code&gt; anywhere on a command
line expands through it. Wire it to zoxide and you get fuzzy project names as first-class
path syntax:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;zsh_directory_name&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dir
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;zoxide&lt;span class="w"&gt; &lt;/span&gt;query&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;/dev/null&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;typeset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-ga&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;reply&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cp&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;[&lt;/span&gt;baz&lt;span class="o"&gt;]&lt;/span&gt;/parse_meta.py&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;[&lt;/span&gt;sfoo&lt;span class="o"&gt;]&lt;/span&gt;/scripts/
vim&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="o"&gt;[&lt;/span&gt;midb&lt;span class="o"&gt;]&lt;/span&gt;/README.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It tab-completes, it works in any command, and it reads almost like what I wished for in
the first place. (If something else in your setup also defines &lt;code&gt;zsh_directory_name&lt;/code&gt;,
append to the &lt;code&gt;zsh_directory_name_functions&lt;/code&gt; array instead of clobbering the function.)&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;work&lt;/code&gt;: list, find, doctor, new&lt;/h2&gt;
&lt;p&gt;The forgetting problem and the hygiene problem are handled by one small script with four
verbs, all of them just scans over &lt;code&gt;~/work&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;work new &amp;lt;name&amp;gt; [desc]&lt;/code&gt; — mkdir + &lt;code&gt;git init&lt;/code&gt; + README with the description + first
  commit. New projects start compliant, so the convention maintains itself.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;work list&lt;/code&gt; — every project with the first non-heading line of its README next to it.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;work find &amp;lt;pattern&amp;gt;&lt;/code&gt; — matches project names and greps all the READMEs. This is the
  "I know I analyzed those logs &lt;em&gt;somewhere&lt;/em&gt;" command that digs up &lt;code&gt;midbar&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;work doctor&lt;/code&gt; — flags projects with no git, no README, dirty worktrees, unpushed
  commits. Running it the first time was humbling: 12 of 26 projects weren't
  git-tracked at all, and one had 506 unpushed commits.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these keep any state. Deleting a project directory &lt;em&gt;is&lt;/em&gt; removing it from the
"database".&lt;/p&gt;
&lt;h2&gt;Bonus: an archive that zoxide follows&lt;/h2&gt;
&lt;p&gt;Retiring old projects to &lt;code&gt;~/work/archive/&lt;/code&gt; keeps the active listing short, but a plain
&lt;code&gt;mv&lt;/code&gt; breaks your jump history — zoxide still points at the old path. So &lt;code&gt;work archive&lt;/code&gt;
moves the project and remaps every zoxide entry beneath it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;work-archive&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$WORK_DIR&lt;/span&gt;/&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;dest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$WORK_DIR&lt;/span&gt;/archive/&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$WORK_DIR&lt;/span&gt;/archive
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# capture the list BEFORE the move: zoxide query --list&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# silently omits paths that no longer exist&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;zpaths&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="p"&gt;(f)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;zoxide&lt;span class="w"&gt; &lt;/span&gt;query&lt;span class="w"&gt; &lt;/span&gt;--list&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;mv&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$dest&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;p
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$zpaths&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;/*&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;zoxide&lt;span class="w"&gt; &lt;/span&gt;remove&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;zoxide&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$dest&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;p&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The comment marks the one real gotcha: query the database &lt;em&gt;before&lt;/em&gt; the &lt;code&gt;mv&lt;/code&gt;. zoxide
hides entries whose directories no longer exist, so if you move first there is nothing
left to remap — I found out the hard way. The remap resets frecency scores (there's no
"edit" operation, only remove + add), which for archived projects is arguably a feature.&lt;/p&gt;
&lt;p&gt;After archiving, &lt;code&gt;z oldproject&lt;/code&gt; still lands you in the right place — it's just under
&lt;code&gt;archive/&lt;/code&gt; now, and &lt;code&gt;work list&lt;/code&gt; no longer mentions it.&lt;/p&gt;
&lt;h2&gt;Takeaway&lt;/h2&gt;
&lt;p&gt;I expected this to become a real tool with a real name (it's &lt;code&gt;wmplex&lt;/code&gt; in my &lt;code&gt;~/work&lt;/code&gt;,
because of course the tool for managing projects is itself a project). It turned out to
be a shell script and two config stanzas. If your project directory is drifting out of
control, you probably don't need A System either — you need &lt;code&gt;zoxide&lt;/code&gt;, one README
convention, and a hundred lines of zsh.&lt;/p&gt;</content><category term="misc"></category><category term="zsh"></category><category term="shell"></category></entry><entry><title>GitVersion on NixOS</title><link href="https://miguelventura.pt/2026/gitversion-on-nixos.html" rel="alternate"></link><published>2026-03-20T00:00:00+00:00</published><updated>2026-03-20T00:00:00+00:00</updated><author><name>Miguel Ventura</name></author><id>tag:miguelventura.pt,2026-03-20:/2026/gitversion-on-nixos.html</id><summary type="html">&lt;p&gt;I was recently working with a dotnet project that used &lt;a href="https://gitversion.net/"&gt;GitVersion&lt;/a&gt; to stamp builds with version numbers derived from the git history:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;&amp;lt;!-- Directory.Packages.props --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;PackageVersion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;GitVersion.MsBuild&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;6.2.0&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The project also had a few other unusual build dependencies for a dotnet project — nodejs, Java, and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was recently working with a dotnet project that used &lt;a href="https://gitversion.net/"&gt;GitVersion&lt;/a&gt; to stamp builds with version numbers derived from the git history:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;&amp;lt;!-- Directory.Packages.props --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;PackageVersion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;Include=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;GitVersion.MsBuild&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;Version=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;6.2.0&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The project also had a few other unusual build dependencies for a dotnet project — nodejs, Java, and others — so I decided to create a &lt;code&gt;flake.nix&lt;/code&gt; to be able to quickly spin up a development environment with everything needed.&lt;/p&gt;
&lt;p&gt;The problem was that the build started to fail:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nuget&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gitversion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;msbuild&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;6.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;GitVersion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MsBuild&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MSB3073&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dotnet --roll-forward Major &amp;quot;&lt;/span&gt;&lt;span class="o"&gt;~/.&lt;/span&gt;&lt;span class="n"&gt;nuget&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gitversion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;msbuild&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;6.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;net8&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gitversion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dll&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exited&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;139.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Exit code 139 means a segmentation fault. We don't need the full dotnet build to reproduce this — we can run the GitVersion tool directly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;dotnet&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;~/.nuget/packages/gitversion.msbuild/6.2.0/tools/net8.0/gitversion.dll&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;1256082&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;segmentation&lt;span class="w"&gt; &lt;/span&gt;fault&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;core&lt;span class="w"&gt; &lt;/span&gt;dumped&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;dotnet
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With help of &lt;code&gt;strace&lt;/code&gt; we can see that there's an attempt to load &lt;code&gt;libssl&lt;/code&gt; (various versions), and every one of them fails:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;strace&lt;span class="w"&gt; &lt;/span&gt;dotnet&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;~/.nuget/packages/gitversion.msbuild/6.2.0/tools/net8.0/gitversion.dll&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;ssl
openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/nix/store/l0l2ll1lmylczj1ihqn351af2kyp5x19-glibc-2.42-51/lib/libssl.so.1.1&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;O_RDONLY&lt;span class="p"&gt;|&lt;/span&gt;O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-1&lt;span class="w"&gt; &lt;/span&gt;ENOENT&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;No&lt;span class="w"&gt; &lt;/span&gt;such&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;directory&lt;span class="o"&gt;)&lt;/span&gt;
openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/nix/store/r7vdyf8pwlqsgd5ydak9fmq3q1i5nm3m-xgcc-15.2.0-libgcc/lib/glibc-hwcaps/x86-64-v3/libssl.so.1.1&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;O_RDONLY&lt;span class="p"&gt;|&lt;/span&gt;O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-1&lt;span class="w"&gt; &lt;/span&gt;ENOENT&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;No&lt;span class="w"&gt; &lt;/span&gt;such&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;directory&lt;span class="o"&gt;)&lt;/span&gt;
openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/nix/store/r7vdyf8pwlqsgd5ydak9fmq3q1i5nm3m-xgcc-15.2.0-libgcc/lib/glibc-hwcaps/x86-64-v2/libssl.so.1.1&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;O_RDONLY&lt;span class="p"&gt;|&lt;/span&gt;O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-1&lt;span class="w"&gt; &lt;/span&gt;ENOENT&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;No&lt;span class="w"&gt; &lt;/span&gt;such&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;directory&lt;span class="o"&gt;)&lt;/span&gt;
openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/nix/store/r7vdyf8pwlqsgd5ydak9fmq3q1i5nm3m-xgcc-15.2.0-libgcc/lib/libssl.so.1.1&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;O_RDONLY&lt;span class="p"&gt;|&lt;/span&gt;O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-1&lt;span class="w"&gt; &lt;/span&gt;ENOENT&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;No&lt;span class="w"&gt; &lt;/span&gt;such&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;directory&lt;span class="o"&gt;)&lt;/span&gt;
openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/nix/store/l0l2ll1lmylczj1ihqn351af2kyp5x19-glibc-2.42-51/lib/libssl.so.3&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;O_RDONLY&lt;span class="p"&gt;|&lt;/span&gt;O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-1&lt;span class="w"&gt; &lt;/span&gt;ENOENT&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;No&lt;span class="w"&gt; &lt;/span&gt;such&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;directory&lt;span class="o"&gt;)&lt;/span&gt;
openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/nix/store/r7vdyf8pwlqsgd5ydak9fmq3q1i5nm3m-xgcc-15.2.0-libgcc/lib/libssl.so.3&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;O_RDONLY&lt;span class="p"&gt;|&lt;/span&gt;O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-1&lt;span class="w"&gt; &lt;/span&gt;ENOENT&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;No&lt;span class="w"&gt; &lt;/span&gt;such&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;directory&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;dlopen&lt;/code&gt; calls are searching in many places, just not the right ones. GitVersion bundles a native library (&lt;code&gt;git2&lt;/code&gt;) that uses &lt;code&gt;dlopen()&lt;/code&gt; to load &lt;code&gt;libssl&lt;/code&gt; at runtime. On NixOS, libraries aren't in the usual &lt;code&gt;/usr/lib&lt;/code&gt; paths, and &lt;code&gt;dlopen&lt;/code&gt; doesn't respect &lt;code&gt;RPATH&lt;/code&gt; — so the library simply can't find OpenSSL.&lt;/p&gt;
&lt;p&gt;The fix is to add &lt;code&gt;openssl&lt;/code&gt; to the dev shell and expose it via &lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt; in the &lt;code&gt;flake.nix&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  devShells&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; pkgs&lt;span class="o"&gt;.&lt;/span&gt;mkShell &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;

    &lt;span class="ss"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; pkgs&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;
      openssl
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="ss"&gt;shellHook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s s-Multiline"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      # git2-a418d9d.so (bundled with GitVersion) uses dlopen() to find libssl at runtime.&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      # Since dlopen doesn&amp;#39;t use RPATH, we must expose openssl via LD_LIBRARY_PATH.&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;      export LD_LIBRARY_PATH=&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;pkgs&lt;span class="o"&gt;.&lt;/span&gt;openssl&lt;span class="o"&gt;.&lt;/span&gt;out&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s s-Multiline"&gt;/lib:$LD_LIBRARY_PATH&amp;quot;&lt;/span&gt;
&lt;span class="s s-Multiline"&gt;    &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is a recurring pattern on NixOS: any native dependency that's loaded via &lt;code&gt;dlopen&lt;/code&gt; at runtime — rather than linked at build time — won't be found unless you explicitly set &lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt;. It's one of those things that "just works" on other distros but requires a bit of manual wiring on Nix.&lt;/p&gt;</content><category term="misc"></category><category term="nix"></category><category term="dotnet"></category></entry><entry><title>Dotnet core debugging in Apple Silicon (M1)</title><link href="https://miguelventura.pt/2021/dotnet-core-debugging-in-apple-silicon-m1.html" rel="alternate"></link><published>2021-08-09T00:00:00+01:00</published><updated>2021-08-09T00:00:00+01:00</updated><author><name>Miguel Ventura</name></author><id>tag:miguelventura.pt,2021-08-09:/2021/dotnet-core-debugging-in-apple-silicon-m1.html</id><summary type="html">&lt;p&gt;Apple is transitioning its processors' architecture from Intel (x86_64) to ARM (arm64e)
for its line of laptop and desktop computers. The M1 is the first processor in this
transition. Programs built for one architecture don't run on the other, but to make this
transition smooth, Apple is using a few …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Apple is transitioning its processors' architecture from Intel (x86_64) to ARM (arm64e)
for its line of laptop and desktop computers. The M1 is the first processor in this
transition. Programs built for one architecture don't run on the other, but to make this
transition smooth, Apple is using a few tricks it already used in past transitions. One
of these tricks is Rosetta, which effectively acts as an emulator, allowing x86_64
applications to run in arm64e as if they were running in x86_64. The other trick is fat
binaries, which Apple calls universal binaries. These are binaries that contain multiple
copies of the code, one for each target architecture. When the time comes to run the
program, the system selects the copy of the code that matches the architecture where
it's running. The disadvantage is that the binaries get a lot larger, but it's great for
distribution as users don't need to worry about downloading the right thing for the
architecture they have.&lt;/p&gt;
&lt;p&gt;During this transition phase, there are a few apps already using fat binaries, but a lot
of apps are still running only on x86_64. At the time this text is being written, this
is the case for dotnet core, and for all dotnet core based applications.&lt;/p&gt;
&lt;p&gt;Dotnet runs well on arm64 architecture, and has been running on Raspberry Pi,
smartphones, AWS Graviton, etc. But on the M1, being macOS, the runtime installation
defaults to x86_64.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;m1mac$&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;which&lt;span class="w"&gt; &lt;/span&gt;dotnet&lt;span class="sb"&gt;`&lt;/span&gt;
/usr/local/share/dotnet/dotnet:&lt;span class="w"&gt; &lt;/span&gt;Mach-O&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;-bit&lt;span class="w"&gt; &lt;/span&gt;executable&lt;span class="w"&gt; &lt;/span&gt;x86_64
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This isn't a problem — Rosetta allows the code to run flawlessly. The problem comes if
we attempt to debug this process using LLDB and dotnet-sos. The problems start right
after running &lt;code&gt;dotnet sos install&lt;/code&gt;. After that command, attempt to run lldb will result
in the following errors before the prompt&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;error:&lt;span class="w"&gt; &lt;/span&gt;this&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;does&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;represent&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;loadable&lt;span class="w"&gt; &lt;/span&gt;dylib
error:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;setsymbolserver&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;valid&lt;span class="w"&gt; &lt;/span&gt;command.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This means that LLDB is trying to load the SOS plugin, but there's an architecture
mismatch — LLDB is running in arm64 and attempting to load a library built for x86_64.
Rosetta doesn't help here, because Rosetta only applies to programs being run, not to
libraries being loaded into programs that are already running.&lt;/p&gt;
&lt;p&gt;We can fix the architecture of the SOS library being loaded, by running &lt;code&gt;dotnet sos
install --architecture arm64&lt;/code&gt;. This will apparently work (the errors at the beginning of
the session will be gone), but no SOS command will work.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;lldb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dbgout
Debug&lt;span class="w"&gt; &lt;/span&gt;output&lt;span class="w"&gt; &lt;/span&gt;logging&lt;span class="w"&gt; &lt;/span&gt;enabled

&lt;span class="o"&gt;(&lt;/span&gt;lldb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sos&lt;span class="w"&gt; &lt;/span&gt;ClrStack
LoadLibrary&lt;span class="o"&gt;(&lt;/span&gt;/usr/local/share/dotnet/shared/Microsoft.NETCore.App/5.0.8/libmscordaccore.dylib&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;FAILED&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;00000000&lt;/span&gt;
Failed&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;load&lt;span class="w"&gt; &lt;/span&gt;data&lt;span class="w"&gt; &lt;/span&gt;access&lt;span class="w"&gt; &lt;/span&gt;module,&lt;span class="w"&gt; &lt;/span&gt;0x80131c4f
You&lt;span class="w"&gt; &lt;/span&gt;can&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;debugger&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;setclrpath &amp;lt;directory&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;control&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;load&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;libmscordaccore.dylib.
If&lt;span class="w"&gt; &lt;/span&gt;that&lt;span class="w"&gt; &lt;/span&gt;succeeds,&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;SOS&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;work&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;retry.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;From the complaint, it looks like SOS can't locate the CLR runtime libraries, but the
problem is actually that we're running LLDB+SOS in arm64 and those CLR libraries are …
in x86_64.&lt;/p&gt;
&lt;p&gt;Fortunately, there's another workaround. LLDB is installed as a fat binary, which we can
easily validate by running&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;m1mac$&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;which&lt;span class="w"&gt; &lt;/span&gt;lldb&lt;span class="sb"&gt;`&lt;/span&gt;
/usr/bin/lldb:&lt;span class="w"&gt; &lt;/span&gt;Mach-O&lt;span class="w"&gt; &lt;/span&gt;universal&lt;span class="w"&gt; &lt;/span&gt;binary&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;architectures:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;x86_64:Mach-O&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;-bit&lt;span class="w"&gt; &lt;/span&gt;executable&lt;span class="w"&gt; &lt;/span&gt;x86_64&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;arm64e:Mach-O&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;-bit&lt;span class="w"&gt; &lt;/span&gt;executable&lt;span class="w"&gt; &lt;/span&gt;arm64e&lt;span class="o"&gt;]&lt;/span&gt;
/usr/bin/lldb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;architecture&lt;span class="w"&gt; &lt;/span&gt;x86_64&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt;    &lt;/span&gt;Mach-O&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;-bit&lt;span class="w"&gt; &lt;/span&gt;executable&lt;span class="w"&gt; &lt;/span&gt;x86_64
/usr/bin/lldb&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;architecture&lt;span class="w"&gt; &lt;/span&gt;arm64e&lt;span class="o"&gt;)&lt;/span&gt;:&lt;span class="w"&gt;    &lt;/span&gt;Mach-O&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;-bit&lt;span class="w"&gt; &lt;/span&gt;executable&lt;span class="w"&gt; &lt;/span&gt;arm64e
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Since we'll be debugging a dotnet process (x86_64) and we'll need to load the dotnet
libraries (x86_64), forcing LLDB to start through x86_64 with Rosetta should fix all our
issues. This can be done by passing the command through &lt;code&gt;/usr/bin/arch -x86_64&lt;/code&gt;, so
instead of &lt;code&gt;lldb&lt;/code&gt; you would run &lt;code&gt;/usr/bin/arch -x86_64 lldb&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;m1mac$&lt;span class="w"&gt; &lt;/span&gt;/usr/bin/arch&lt;span class="w"&gt; &lt;/span&gt;-x86_64&lt;span class="w"&gt; &lt;/span&gt;lldb&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;21787&lt;/span&gt;
Added&lt;span class="w"&gt; &lt;/span&gt;Microsoft&lt;span class="w"&gt; &lt;/span&gt;public&lt;span class="w"&gt; &lt;/span&gt;symbol&lt;span class="w"&gt; &lt;/span&gt;server
&lt;span class="o"&gt;(&lt;/span&gt;lldb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;process&lt;span class="w"&gt; &lt;/span&gt;attach&lt;span class="w"&gt; &lt;/span&gt;--pid&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;21787&lt;/span&gt;
Process&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;21787&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;stopped
*&lt;span class="w"&gt; &lt;/span&gt;thread&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#1, queue = &amp;#39;com.apple.main-thread&amp;#39;, stop reason = signal SIGSTOP&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;frame&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#0: 0x00007fff2031bcde libsystem_kernel.dylib`__psynch_cvwait + 10&lt;/span&gt;
libsystem_kernel.dylib&lt;span class="sb"&gt;`&lt;/span&gt;__psynch_cvwait:
-&amp;gt;&lt;span class="w"&gt;  &lt;/span&gt;0x7fff2031bcde&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;+10&amp;gt;:&lt;span class="w"&gt; &lt;/span&gt;jae&lt;span class="w"&gt;    &lt;/span&gt;0x7fff2031bce8&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;+20&amp;gt;
&lt;span class="w"&gt;    &lt;/span&gt;0x7fff2031bce0&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;+12&amp;gt;:&lt;span class="w"&gt; &lt;/span&gt;movq&lt;span class="w"&gt;   &lt;/span&gt;%rax,&lt;span class="w"&gt; &lt;/span&gt;%rdi
&lt;span class="w"&gt;    &lt;/span&gt;0x7fff2031bce3&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;+15&amp;gt;:&lt;span class="w"&gt; &lt;/span&gt;jmp&lt;span class="w"&gt;    &lt;/span&gt;0x7fff20319ad9&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cerror_nocancel
&lt;span class="w"&gt;    &lt;/span&gt;0x7fff2031bce8&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;+20&amp;gt;:&lt;span class="w"&gt; &lt;/span&gt;retq
Target&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;dotnet&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;stopped.

Executable&lt;span class="w"&gt; &lt;/span&gt;module&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/usr/local/share/dotnet/dotnet&amp;quot;&lt;/span&gt;.
Architecture&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to:&lt;span class="w"&gt; &lt;/span&gt;x86_64-apple-macosx-.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We finally have a working debugger, which we can easily validate by issuing some SOS commands.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;lldb&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sos&lt;span class="w"&gt; &lt;/span&gt;ClrStack
OS&lt;span class="w"&gt; &lt;/span&gt;Thread&lt;span class="w"&gt; &lt;/span&gt;Id:&lt;span class="w"&gt; &lt;/span&gt;0x358c9&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;Child&lt;span class="w"&gt; &lt;/span&gt;SP&lt;span class="w"&gt;               &lt;/span&gt;IP&lt;span class="w"&gt; &lt;/span&gt;Call&lt;span class="w"&gt; &lt;/span&gt;Site
0000000304A6B6D8&lt;span class="w"&gt; &lt;/span&gt;00007fff2031bcde&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;HelperMethodFrame:&lt;span class="w"&gt; &lt;/span&gt;0000000304a6b6d8&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;System.Threading.Thread.SleepInternal&lt;span class="o"&gt;(&lt;/span&gt;Int32&lt;span class="o"&gt;)&lt;/span&gt;
0000000304A6B820&lt;span class="w"&gt; &lt;/span&gt;000000010F3808B7&lt;span class="w"&gt; &lt;/span&gt;System.Threading.Thread.Sleep&lt;span class="o"&gt;(&lt;/span&gt;Int32&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;/_/src/coreclr/src/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs&lt;span class="w"&gt; &lt;/span&gt;@&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;257&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
0000000304A6B830&lt;span class="w"&gt; &lt;/span&gt;000000010F81F443&lt;span class="w"&gt; &lt;/span&gt;TestProj.Program.D&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;~/TestProj/Program.cs&lt;span class="w"&gt; &lt;/span&gt;@&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;32&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
0000000304A6B840&lt;span class="w"&gt; &lt;/span&gt;000000010F81F3FE&lt;span class="w"&gt; &lt;/span&gt;TestProj.Program.C&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;~/TestProj/Program.cs&lt;span class="w"&gt; &lt;/span&gt;@&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;27&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
0000000304A6B850&lt;span class="w"&gt; &lt;/span&gt;000000010F81F3BE&lt;span class="w"&gt; &lt;/span&gt;TestProj.Program.B&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;~/TestProj/Program.cs&lt;span class="w"&gt; &lt;/span&gt;@&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
0000000304A6B860&lt;span class="w"&gt; &lt;/span&gt;000000010F81F37E&lt;span class="w"&gt; &lt;/span&gt;TestProj.Program.A&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;~/TestProj/Program.cs&lt;span class="w"&gt; &lt;/span&gt;@&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
0000000304A6B870&lt;span class="w"&gt; &lt;/span&gt;000000010F815AFB&lt;span class="w"&gt; &lt;/span&gt;TestProj.Program.Main&lt;span class="o"&gt;(&lt;/span&gt;System.String&lt;span class="o"&gt;[])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;~/TestProj/Program.cs&lt;span class="w"&gt; &lt;/span&gt;@&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It took me a lot more than I'm proud to admit to get to this point, and it was
disappointing that throughout the journey, Googling for most error messages yielded no
useful results. Hopefully this post may be found by someone hitting the same problems.
At the pace dotnet is evolves, it's likely that this information will soon be obsoleted
by a native dotnet arm64e version for macOS.&lt;/p&gt;</content><category term="misc"></category><category term="debug"></category><category term="dotnet"></category></entry><entry><title>Advent of Code 2020</title><link href="https://miguelventura.pt/2021/advent-of-code-2020.html" rel="alternate"></link><published>2021-01-05T00:00:00+00:00</published><updated>2021-01-05T00:00:00+00:00</updated><author><name>Miguel Ventura</name></author><id>tag:miguelventura.pt,2021-01-05:/2021/advent-of-code-2020.html</id><summary type="html">&lt;p&gt;Another year, another December, another &lt;a href="https://adventofcode.com/"&gt;Advent of Code&lt;/a&gt; — an advent
calendar where each day you get a problem to solve through programming, all
wrapped in a surreal Christmas-themed story. &lt;a href="https://twitter.com/ericwastl"&gt;@ericwastl&lt;/a&gt; does a
phenomenal job of creating very varied problems where several types of computer
science knowledge can come in handy …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Another year, another December, another &lt;a href="https://adventofcode.com/"&gt;Advent of Code&lt;/a&gt; — an advent
calendar where each day you get a problem to solve through programming, all
wrapped in a surreal Christmas-themed story. &lt;a href="https://twitter.com/ericwastl"&gt;@ericwastl&lt;/a&gt; does a
phenomenal job of creating very varied problems where several types of computer
science knowledge can come in handy, but (especially in the age of Google) none
are hard requirements to get through the challenges.&lt;/p&gt;
&lt;p&gt;There are points for those who finish faster with a public leaderboard to show
off your skill. I never competed for time to completion, because my timezone is
not ideal (problems are released at 5 AM, when I'm definitely not at the top of
my coding game) and because life tends to get in the way of being able to
allocate the same time slot every day for the challenges. Not racing for time
doesn't mean I don't get a lot of fun. And this year was no exception.&lt;/p&gt;
&lt;h1&gt;Using fun technology&lt;/h1&gt;
&lt;p&gt;I write computer code for a living. It's serious business, which means that on
the job I often &lt;a href="https://panelbear.com/blog/boring-tech/"&gt;choose boring technology&lt;/a&gt;, and you should too. But in
&lt;abbr title="Advent of Code"&gt;AoC&lt;/abbr&gt; you're solving problems with limited scope and you won't have to maintain
that code running in customers for years to come. You can chose whatever new
technology you heard of yesterday and you can try stuff you have no experience
with. Unless you're competing for time (in which case you should use whatever
you're most proficient with), it's a great opportunity to gain experience in a
new programming language and with new tools and libraries. I've been using the
Advent of Code challenges to either learn new programming languages or sharpen
my skills in those I use the least.&lt;/p&gt;
&lt;p&gt;This year the challenge for me was attempting to &lt;strong&gt;solve all problems using
purely functional programming with F#&lt;/strong&gt;. This meant reaching solutions where I
didn't mutate any variable, and for that using a functional-friendly but at the
same time pragmatic programming langauge. This was the second time I tried
solving all problems without mutating a single variable. And this year it felt a
lot easier. The only problem where I really felt the need to do mutations was
one where the obvious efficient solution for me was using (and mutating) a
linked list. I was ready to give up on my goal and started writing a rant for
some friends who were also participating. By the time I wrote about it, I
remembered an alternative solution&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;. A great &lt;a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging"&gt;rubber ducking&lt;/a&gt;
moment.&lt;/p&gt;
&lt;h1&gt;Learn with everyone's mistakes (including your own)&lt;/h1&gt;
&lt;p&gt;This year, a few folks at OutSystems also decided to participate and tried their
best to keep the daily pace with the challenges. This wasn't the first time it
happened. In previous edition, we'd have a whiteboard at the office with the
private leaderboard. Due to the COVID situation none of us is at the office now,
which strangely made everything &lt;em&gt;better&lt;/em&gt;. Here's how it went:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;we created a slack channel specific to advent of code&lt;/li&gt;
&lt;li&gt;we pinned a thread with everyone's git repositories&lt;/li&gt;
&lt;li&gt;we created a thread for discussions on each particular day&lt;/li&gt;
&lt;li&gt;we kept a private leaderboard at &lt;abbr title="Advent of Code"&gt;AoC&lt;/abbr&gt; site&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This meant that like the previous year, we still had the private leaderboard to
compare our progress. But the discussion threads and the ability to easily see
everyone else's code was the most interesting aspect. In programming there's
often no "single right answer" and looking at other people's solutions,
especially when they were thinking very differently than you is a great way to
expand your way of thinking. In fact this was the way I got to know about &lt;abbr title="Advent of Code"&gt;AoC&lt;/abbr&gt; in
the first place — I was browing &lt;a href="https://github.com/norvig/pytudes/tree/master/ipynb"&gt;Peter Norvig's notebooks&lt;/a&gt; when I
found a reference to &lt;abbr title="Advent of Code"&gt;AoC&lt;/abbr&gt; and I got hooked since then.&lt;/p&gt;
&lt;p&gt;The daily discussion threads were also great. They enabled us to help eachother
keep motivated and they provided a great rubber ducking experince.&lt;/p&gt;
&lt;h1&gt;Wrap-up&lt;/h1&gt;
&lt;p&gt;This was probably the year I enjoyed &lt;abbr title="Advent of Code"&gt;AoC&lt;/abbr&gt; the most. I don't think it was the one
with the best problems, but it definitely was the one when I had the most fun.
The adding of an additional constraint made it more challenging, but the social
aspect was the best. A great &lt;em&gt;thank you&lt;/em&gt; to &lt;a href="https://twitter.com/ericwastl"&gt;Eric Wastl&lt;/a&gt; and to all
the friends that walked this journey with me. And in case you're curious, you
can look up &lt;a href="https://github.com/mmv/adventofcode2020"&gt;my solutions here&lt;/a&gt;.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;A linked list can be implemented with a Dictionary/Map where the value for each key is the &lt;em&gt;next node&lt;/em&gt; key. So Immutable Maps can be used for an algorithm that would rely in mutations to a linked list as a substitute data structure.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content><category term="misc"></category><category term="code"></category></entry><entry><title>Three Years of OnePlus One</title><link href="https://miguelventura.pt/2017/three-years-of-oneplus-one.html" rel="alternate"></link><published>2017-04-16T00:00:00+01:00</published><updated>2017-04-16T00:00:00+01:00</updated><author><name>Miguel Ventura</name></author><id>tag:miguelventura.pt,2017-04-16:/2017/three-years-of-oneplus-one.html</id><summary type="html">&lt;h1&gt;Three Years of OnePlus One&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;This is the phone everyone wants&lt;br/&gt;
but only a few can get.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure&gt;
&lt;img src="images/oneplus/opo-original.jpg" alt="OnePlus One" /&gt;
&lt;figcaption&gt;OnePlus One, the original &lt;em&gt;flagship killer&lt;/em&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I used to play Ingress, a geocaching kind of game. It was the predecessor of Pokémon Go. While on vacations, away from my usual territory, I met …&lt;/p&gt;</summary><content type="html">&lt;h1&gt;Three Years of OnePlus One&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;This is the phone everyone wants&lt;br/&gt;
but only a few can get.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure&gt;
&lt;img src="images/oneplus/opo-original.jpg" alt="OnePlus One" /&gt;
&lt;figcaption&gt;OnePlus One, the original &lt;em&gt;flagship killer&lt;/em&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I used to play Ingress, a geocaching kind of game. It was the predecessor of Pokémon Go. While on vacations, away from my usual territory, I met with a fellow player. We talked, shared stories and exchanged game items. When I got to see the phone he was running the game on, I was impressed. By then I had my second Nexus phone which was signaling the need of retirement. When it was under load, the screen would flicker like old TVs used to do. The performance was already poor for a few apps. The phone would drain its battery fast, even while turned off. This was all quite disappointing, as the phone had about 2 years. Especially disappointing considering the lifespan of the cellphones I had before the smartphone era, and especially disappointing considering that Nexus phones were supposed to be representative of the best Android had to offer.&lt;/p&gt;
&lt;p&gt;When I asked my fellow player what phone was he using he introduced me to the OnePlus One. I had no idea what that was about, so he proceeded to explain to me how OnePlus was a recent brand in China, how they were making an awesome device, comparable to flagship products from other brands but much cheaper due to different marketing and distribution techniques. &lt;em&gt;This is the phone everyone wants but only a few can get&lt;/em&gt;, he told me. It's invite only. I can hook you up with some folks who might be able to get you an invite. I asked if it was that good and he went on telling me how awesome everything worked in that device. I was certainly impressed. The &lt;em&gt;Net Promoter Score&lt;/em&gt; here was clearly over the roof.&lt;/p&gt;
&lt;p&gt;Back from vacations I got my hands on an invite and proceeded to order the OnePlus One, also known as OPO, the only device OnePlus had delivered so far.&lt;/p&gt;
&lt;p&gt;Along with my device I also got an email message. &lt;em&gt;You are now an ambassador of the OnePlus brand&lt;/em&gt;. That's not the same as saying thank you for buying our product. This is a statement that these folks take word of mouth very seriously. This usually means that they put a great deal of effort in customer satisfaction. And the best way to get customers satisfied is by delivering a great product.&lt;/p&gt;
&lt;p&gt;Being used to the Nexus devices, and having seen many other Android, iPhone and Lumia devices, the OPO was an incredible surprise. This phone cost about half of other flagship phones. Its camera was incredible. Its performance was incredible. I was used for GPS to take at least 1 or 2 minutes to lock on the Nexus, but on the OPO it was incredibly fast. &lt;/p&gt;
&lt;p&gt;But it's not fair to compare a phone that just got into the market with the ones that are already there. Because before installing many apps and filling the storage with lots of photos, many phones feel fast. It's more interesting to compare it after 3 years of usage. And after all this time I can say that the OnePlus One is a truly remarkable device. The apps and games being delivered today on the app store work flawlessly, with great performance. The battery life is still acceptable with nearly 5 days of standby on 2G without internet, or 2 days with WIFI always on, or 1 whole day with WIFI with 4G, which is the same I see recently bought phones performing.&lt;/p&gt;
&lt;figure&gt;
&lt;img src="images/oneplus/opo-multiple-falls.jpg" alt="OnePlus' glass after a few falls" /&gt;
&lt;figcaption&gt;OnePlus One after many falls marking the protective glass&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This phone has been with me through hail and heat, snowy mountains and desert sand, climbing and camping. It has seen Europe and Japan. It withstood a few falls which left heavy scars in the screen protector and the case, both bought directly from OnePlus. The phone itself, however, got away without any noticeable scratches, except at the bottom where a tiny scratch can be seen in the metal junction.&lt;/p&gt;
&lt;h1&gt;With great products come… great problems?&lt;/h1&gt;
&lt;p&gt;Overall I think the biggest problem that OnePlus might face is &lt;em&gt;having a product that's just too good&lt;/em&gt;. It's over 3 years now and I feel no pressure for upgrading. The same happens with other people I know who bought OnePlus One or the newer OnePlus 2 models. Like me, they seem excited to know about what's coming up new with OnePlus devices, but they feel so good with their current device that there's just reason to change. Good, durable quality means OnePlus will have some trouble selling new products to its loyal customers.&lt;/p&gt;
&lt;p&gt;Will this be a problem for OnePlus' growth as a company? It's might be, but it's the best kind of problem to have. With this feeling of satisfaction with the product comes a great Net Promoter Score, and that is hugely valuable. How huge? I might not feel the need to upgrade my OnePlus, but since I got mine I influenced 4 persons in my family to switch to OnePlus.&lt;/p&gt;
&lt;p&gt;What a &lt;em&gt;great&lt;/em&gt; problem to have!&lt;/p&gt;
&lt;hr/&gt;

&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; after far too many drops, the bottom of the digitizer finally
stopped working correctly in July 2020. The rest of the phone is still working.
Six years of flawless lifetime is actually way more than the Nokia 3210 I had
(a model famed for being indestructible) resisted on my hands. Can you guess the
brand of the new phone I got to replace my One?&lt;/p&gt;</content><category term="Product Reviews"></category><category term="Reviews"></category></entry><entry><title>Controlled Output through Rx Streams</title><link href="https://miguelventura.pt/2016/controlled-output-through-rx-streams.html" rel="alternate"></link><published>2016-07-18T00:00:00+01:00</published><updated>2016-07-18T00:00:00+01:00</updated><author><name>Miguel Ventura</name></author><id>tag:miguelventura.pt,2016-07-18:/2016/controlled-output-through-rx-streams.html</id><summary type="html">&lt;p&gt;Reactive Extensions make it very easy to create a processing pipeline, 
especially when some stages on that pipeline are naturally asynchronous operations.
This is because just like a &lt;em&gt;Promise&lt;/em&gt; (or &lt;em&gt;Task&lt;/em&gt; in .NET) can be seen as the asynchronous
version of anything that produces a value, the &lt;em&gt;Observable&lt;/em&gt; pattern can …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Reactive Extensions make it very easy to create a processing pipeline, 
especially when some stages on that pipeline are naturally asynchronous operations.
This is because just like a &lt;em&gt;Promise&lt;/em&gt; (or &lt;em&gt;Task&lt;/em&gt; in .NET) can be seen as the asynchronous
version of anything that produces a value, the &lt;em&gt;Observable&lt;/em&gt; pattern can be seen as the asynchronous
version of the &lt;em&gt;Enumerable&lt;/em&gt; pattern.&lt;/p&gt;
&lt;p&gt;So the following code seems pretty straightforward and easy with Rx:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;getListOfLocations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flatMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unzip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bigText&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;heavyProcessing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bigText&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;registerResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We're essentially fetching resources from the internet, unzipping and processing them. The fact that download is asynchronous fits nicely with Rx. But this simple chain might yield a problem: what happens if &lt;code&gt;heavyProcessing&lt;/code&gt; becomes the bottleneck?&lt;/p&gt;
&lt;p&gt;&lt;img alt="bottleneck on Rx" src="images/rx/flow-over.png"
    style="max-width: 100%;"
    srcset="images/rx/flow-over@2x.png 2x" /&gt;&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;heavyProcessing&lt;/code&gt; becomes the bottleneck, we'll still be downloading and unzipping content (into memory) but we'll have to queue it up because the processing stage will only observe one item at a time. This means we'll probably run out of memory fast, which makes no sense because we should have full control on the rhythm under which we download the resources or unzip them.&lt;/p&gt;
&lt;p&gt;There are some Rx operators that aim at throughput control, like &lt;em&gt;throttle&lt;/em&gt; or &lt;em&gt;debounce&lt;/em&gt;, but none of them is very useful here, as both end up discarding items when they're observed too fast. In this example we're not interested in discarding any item. We just want to queue up work where we can handle it and then control the rhythm at which we emit items to make sure we won't drown the system under heavy load.&lt;/p&gt;
&lt;p&gt;It's still possible to achieve such a solution with Rx by using the following pattern:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create an observable to represent work tokens.&lt;/li&gt;
&lt;li&gt;Use these items to control throughput by either:&lt;ul&gt;
&lt;li&gt;Mapping work tokens into work like&lt;br/&gt;
  &lt;code&gt;tokens.map(t =&amp;gt; /* disregard t and get next location */)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Using tokens to buffer up work items where you can&lt;br/&gt;
  &lt;code&gt;getListOfLocations().zip(tokens).map(pair =&amp;gt; pair.first)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Put a step down the Rx operations chain to emit tokens when resources become available (eg: for each item you process)&lt;/li&gt;
&lt;li&gt;Start the whole chain by feeding a few tokens into it (or just one if you're processing one item at a time)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt="bottleneck on Rx" src="images/rx/flow-control.png"
    style="max-width: 100%;"
    srcset="images/rx/flow-control@2x.png 2x" /&gt;&lt;/p&gt;
&lt;p&gt;This strategy will only work very well for scenarios where you can control the initial feeding of items into the observable stream, such as controlling the rate at which you read lines from a file you already have in disk. For other scenarios where you have no control over the events that trigger the stream (such as a server taking requests from clients or a user clicking or typing) this strategy can still apply as it enables you to queue items at a stage of the pipeline where they don't need big resources (before turning URLs into downloaded content, or before unzipping that downloaded content into something bigger). Of course, in this latter case, the queue can still grow up too much. But then there's no miraculous solution. To solve such a case, you'll have to consider either discarding work items (through &lt;em&gt;throttling&lt;/em&gt; or &lt;em&gt;debouncing&lt;/em&gt;) or adding more horsepower into the system.&lt;/p&gt;
&lt;p&gt;Note that these &lt;em&gt;controllable observables&lt;/em&gt; (for the lack of a better name) are
neither &lt;em&gt;hot observables&lt;/em&gt; (which emit events despite being subscribed or not)
nor &lt;em&gt;cold observables&lt;/em&gt; (which only start emitting events once subscribed).&lt;/p&gt;
&lt;h2&gt;See it in action!&lt;/h2&gt;
&lt;p&gt;&lt;a class="jsbin-embed" href="https://jsbin.com/rayuneneba/embed?js,console"&gt;Try in a JS Bin&lt;/a&gt;&lt;/p&gt;
&lt;script src="https://gist.github.com/mmv/8c3ef5eb9d157c1a2923f3060dfac954.js"&gt;&lt;/script&gt;

&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Reactive Extensions provide a very good way of turning asynchronous processing
pipelines into readable code that's easy to reason about. However, when dealing
with asynchronous processing, it's always good to take care of understanding
how the system's we're building will queue up tasks or attempt to do them all
in parallel, and what that will mean regarding system resources.
A library such as Rx can be a great help to design a good system, but it isn't
in itself an all-in-one solution nor a replacement for thinking about a good
architecture.&lt;/p&gt;</content><category term="misc"></category></entry><entry><title>Going Mechanical: 60 days of Pok3r</title><link href="https://miguelventura.pt/2015/going-mechanical-60-days-of-pok3r.html" rel="alternate"></link><published>2015-10-30T00:00:00+00:00</published><updated>2015-10-30T00:00:00+00:00</updated><author><name>Miguel Ventura</name></author><id>tag:miguelventura.pt,2015-10-30:/2015/going-mechanical-60-days-of-pok3r.html</id><summary type="html">&lt;h1&gt;The case for the Mechanical Keyboard&lt;/h1&gt;
&lt;p&gt;If you take your craft seriously enough, there's a point in time where the tool
becomes the bottleneck. The warriors' sword, the musician's instrument, the
writers' pen. As a software engineer, I spend more time thinking than typing,
but I still kind of end …&lt;/p&gt;</summary><content type="html">&lt;h1&gt;The case for the Mechanical Keyboard&lt;/h1&gt;
&lt;p&gt;If you take your craft seriously enough, there's a point in time where the tool
becomes the bottleneck. The warriors' sword, the musician's instrument, the
writers' pen. As a software engineer, I spend more time thinking than typing,
but I still kind of end up using the keyboard all day, every day. However, I never
considered the quality of the keyboards I used as a limiting factor to my
productivity, and effectively it wasn't... not until I went through two weeks of
intense typing durinch which I'd get to the end of the day with aching fingers.&lt;/p&gt;
&lt;p&gt;The keyboard I was using was to blame. 
Cheap, non-mechanical keyboards, have a single membrane over a
circuit board that needs to be activated to register the keypress. This usually
means that the key has to go all the way down to register. If instead of
delivering keypresses you were delivering punches, this would be the equivelent
of punching a wall. It would hurt your hand. As-is, it hurts your finger.
Probably not enough for you to notice, except after some serious repetition.
A mechanical keyboard, on the other hand, has individual switches on every key.
By having an individual switch for each key, mechanical
keyboards usually register the keypress half-way during the key travel, which
means (getting back to the punch metaphor) that you can slow down before hitting
the wall and with some training you stop hitting the bottom at all.
Trust me, it causes much less strain on you and your fingers.&lt;/p&gt;
&lt;p&gt;Mechanical keyboards may be much more expensive than their non-mechanical
counterparts, but the investment is totally worth it if you spend a lot of time
using your keyboard. In my case, I immediately felt the difference the day I
switched keyboards &amp;mdash; the aching disappeared and hasn't come back ever
since.&lt;/p&gt;
&lt;p&gt;There are lots of sites explaining why a mechanical keyboard is worth the price,
but I think &lt;a href="https://mechanicalkeyboards.com/faqs.php?faq=worth_the_price"&gt;MechanicalKeyboards.com&lt;/a&gt;
sums it up pretty well:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Stop and consider how much time you actually spend sitting down (or standing)
and typing. Odds are it's a considerable amount. Do you want to spend such a
significant portion of your life interacting with an inferior product?&lt;/p&gt;
&lt;p&gt;For the price of a nice dinner out, you can purchase an amazing piece of
hardware you will use nearly everyday of your life.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Finding &lt;em&gt;your&lt;/em&gt; keyboard&lt;/h1&gt;
&lt;p&gt;Mechanical keyboards are usually much more expensive than their non-mechanical
cousins. You can find a pretty good keyboard by 10€ but in the mechanical realm
your starting point is much higher, so you should set your starting point
expectation at 80€.&lt;/p&gt;
&lt;p&gt;There's also a &lt;em&gt;great deal of options&lt;/em&gt; regarding mechanical keyboards. The &lt;em&gt;kind of
switches&lt;/em&gt; you chose, for instance, will influence the kind of noise and key
stiffness you'll get, so it'll influence a lot the typing experience. If you're
spending 100€+ on a keyboard (80€ was just a reference starting point; chances
are you'll spend much more) you want to make sure you're buying something you'll
actually like. If you don't have any way of trying a keyboard before buying it,
your second best option is to get a switch tester. I got mine from &lt;a href="http://www.wasdkeyboards.com/index.php/products/sampler-kit.html"&gt;WASD
keyboards&lt;/a&gt; but UK Keycaps also has &lt;a href="http://ukkeycaps.bigcartel.com/category/switch-testers"&gt;a few options&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Pok3r Keyboard"
    src="images/going-mechanical/wasd-2.jpg" /&gt;
&lt;figcaption&gt;The switch sampler I bought from WASD Keyboards&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
&lt;img alt="Pok3r Keyboard"
    src="images/going-mechanical/wasd-1.jpg" /&gt;
&lt;figcaption&gt;The switches in this tester: MX Blue, Brown, Black, Red, Clear and Green&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;In my case, from looking at the &lt;a href="http://www.wasdkeyboards.com/index.php/mechanical-keyboard-guide#best"&gt;characteristics&lt;/a&gt;
I had decided on MX Brown
switches (no clicky sound, tactile bump on the actuation point, low stiffness),
but upon experimenting the switches I ended up deciding on MX Clear because it
&lt;em&gt;felt better&lt;/em&gt; for me. Only after getting my keyboard with the Clear switches,
I had a chance to try another one with Brown switches and verified that I had done
the right choice &lt;em&gt;for me&lt;/em&gt;. The real point here is that the switch type that
feels better is a personal choice, so you should really try them out to see
what's best for you.&lt;/p&gt;
&lt;p&gt;Another good idea to get acquainted with the world of the mechanical keyboards
is to check out the online communities around this matter. I spent some time
lurking in &lt;a href="http://www.reddit.com/r/mechanicalkeyboards"&gt;/r/mk&lt;/a&gt; and checking their &lt;a href="https://www.reddit.com/r/MechanicalKeyboards/wiki/index"&gt;wiki&lt;/a&gt;. There, besides
learning a lot about keyboards, I also got to learn about keyboard enthusiasts.
Initially I thought that a keyboard enthusiast would be someone doing intense
use of keyboard that would probably have to fit into the category of gamer or
programmer. It turns out that keyboard enthusiasts come &lt;a href="https://wlhlm.github.io/rmk-march-2015-survey/"&gt;from all kinds of
backgrounds&lt;/a&gt; and the only thing in common is really the interest in keyboards and
in a superior keyboard usage experience.&lt;/p&gt;
&lt;h1&gt;Size Matters&lt;/h1&gt;
&lt;p&gt;Since I was getting a different and rather expensive &amp;mdash; for my standards, but
not expensive in the mechanical price range &amp;mdash; new keyboard, I also wanted to
try ditching the numpad. Although the numpad helps a lot when introducing a fair
amount of numbers, I actually use it very rarely but I switch a lot between
typing and using the mouse, and the numpad is just in the way, forcing a bigger
distance for this switch and usually leading to a worse position for the
keyboard and a worse posture overall.&lt;/p&gt;
&lt;p&gt;However after getting to know more about mechanical keyboards (and keyboards in
general) I found out that besides keyboards without numpads (also called
&lt;em&gt;tenkeyless&lt;/em&gt; or TKL) there are lots of other sizes, and I got especially
interested in the 60% size, which are the size of a laptop keyboard.
Besides discarding the numpad, 60% keyboards also
don't have the arrow keys cluster, further shortening the distance a right hand
has to go from a normal writing position to a mouse placed at the right of the
keyboard. As a software engineer, I spend quite some time reading code and
navigating around, so I use the navigation keys a lot and discarding them
completely wouldn't do.&lt;/p&gt;
&lt;p&gt;Some 60% keyboards, however, allow you to access those keys either by using a
&lt;em&gt;Fn&lt;/em&gt; key or by packing them without a cluster of their own (like laptops do). I
thought that could be enough for me and decided for the 60%. I regretted this
decision every day for the first week of using the keyboard. By the 3rd week,
however, I had no regrets. Now I find it strange to use those big, &lt;em&gt;normal&lt;/em&gt;
keyboards.&lt;/p&gt;
&lt;h1&gt;The Pok3r&lt;/h1&gt;
&lt;figure&gt;
&lt;img alt="Pok3r Keyboard"
    src="images/going-mechanical/pok3r-1.jpg" /&gt;
&lt;figcaption&gt;My Pok3r keyboard &amp;mdash; MX Clears, White version, UK ISO
Layout&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The keyboard I settled on buying was a Vortex Pok3r with UK ISO Layout. I would
prefer a PT layout but I don't mind what's written in the keys as long as the
layout is ISO.&lt;/p&gt;
&lt;p&gt;My very first feeling about the Pok3r, right while unboxing, even before trying
it, was that &lt;em&gt;this is a very robust keyboard&lt;/em&gt;! This was because I was used to
plastic keyboards which are quite light, and suddenly I'm holding a keyboard
with a solid metal case which feels a lot heavier in comparison.
Aluminium is supposed to be light, but this is thick aluminium and it gives a
great feeling of robustness.&lt;/p&gt;
&lt;p&gt;The typing experience is also great. This is not exclusive from the Pok3r, it
has much to do with my choice of switches (the MX Clears feel great for me) and
with their choice of keycap material &amp;mdash; the slightly rugged surface of the
&lt;a href="https://en.wikipedia.org/wiki/Polybutylene_terephthalate"&gt;PBT&lt;/a&gt; plastic of the keycaps was a pleasant surprise for me;
I'm pretty sure that all
other keyboards I've used in the last 15 years had &lt;a href="https://en.wikipedia.org/wiki/Acrylonitrile_butadiene_styrene"&gt;ABS&lt;/a&gt; keycaps.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Pok3r Keyboard"
    src="images/going-mechanical/pok3r-2.jpg" /&gt;
&lt;figcaption&gt;The keycaps that come with the Pok3r with their legends for the
default behavior and &lt;em&gt;Fn&lt;/em&gt; behavior&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The Pok3r also has programmable layers that can be switched into. This means
that despite not having the F1-F12 keys that are very useful in a lot of
programs (most visual debuggers use &lt;em&gt;F8&lt;/em&gt;-&lt;em&gt;F12&lt;/em&gt;) I could easily program a layer where
I replaced the top row keys from &lt;em&gt;5&lt;/em&gt; all the way to the backspace key with &lt;em&gt;F5-F12&lt;/em&gt;.
This means I can debug by switching to a layer and pressing single keys instead
of having to press combinations like &lt;em&gt;Fn+9&lt;/em&gt; to produce &lt;em&gt;F9&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Besides the great typing experience and its robustness, the Pok3r also &lt;em&gt;looks
good&lt;/em&gt;. It's so photogenic that it's impossible not to see it while browsing
mechanical keyboard communities such as &lt;a href="http://www.reddit.com/r/mechanicalkeyboards"&gt;/r/mk&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;In Conclusion&lt;/h1&gt;
&lt;p&gt;Mechanical keyboards may be more expensive, but the extra confort and superior
feeling make them totally worth the investment.&lt;/p&gt;
&lt;p&gt;Going to a smaller size (like 60%) is not for everyone and might call for an
adaptation period if you use a lot the keys you are discarding (probably a
non-issue for most laptop users).&lt;/p&gt;
&lt;p&gt;The Pok3r is a great product. Looks good and feels good. 100% recommended! Even
if it's not the right product for you (due to the layout), if you're a regular
keyboard user you should consider other options for mechanical keyboards.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Pok3r Keyboard"
    src="images/going-mechanical/pok3r-3.jpg" /&gt;
&lt;figcaption&gt;Profile View &amp;mdash; you can see the height and slope, as well as
a bit of the switches underneath the keycaps&lt;/figcaption&gt;
&lt;/figure&gt;</content><category term="Product Reviews"></category><category term="Reviews"></category></entry><entry><title>Winning the Hackathon</title><link href="https://miguelventura.pt/2015/winning-the-hackathon.html" rel="alternate"></link><published>2015-08-30T00:00:00+01:00</published><updated>2015-08-30T00:00:00+01:00</updated><author><name>Miguel Ventura</name></author><id>tag:miguelventura.pt,2015-08-30:/2015/winning-the-hackathon.html</id><summary type="html">&lt;p&gt;&lt;em&gt;How our mobile app, built in 24 hours, got a 2nd place and what we could've
done even better.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In 2013 a bank in Portugal named BPI organized a contest called &lt;em&gt;BPI Appy
Day&lt;/em&gt; where 400 teams of up to 4 players tried to create a mobile app
in 24 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;How our mobile app, built in 24 hours, got a 2nd place and what we could've
done even better.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In 2013 a bank in Portugal named BPI organized a contest called &lt;em&gt;BPI Appy
Day&lt;/em&gt; where 400 teams of up to 4 players tried to create a mobile app
in 24 hours.
The team I ran with produced an app got a nice 2nd
place.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Team MobiLX presenting"
 src="images/winning-appy-day/team-fotura.jpg" /&gt;
&lt;figcaption&gt;My team, MobiLx, presenting our project Fotura&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;However, at the time I felt like a lot of the
teams that were participating were following a bad strategy for this kind of
contests. This post is due since then.&lt;/p&gt;
&lt;p&gt;Hackathons are seen as programming contents, but some couldn't be further
away from that. Programming contests usually have a very limited scope that
requires quite a bit of technical ability and the entries are usually code.
In a lot of hackathons nowdays, the entries are apps, which is quite
different. Sure that there are hackathons very technical in scope, but many
of them nowdays aren't much about tech as they are about &lt;em&gt;enterpreneurship&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Winning such competitions isn't as much about devising the most technically
awesome solution as it is about selling your app's main idea. Note that this
isn't even selling the app but rather selling the idea that it addresses.
This isn't much different from pitching any other product in Shark Tank
&amp;mdash; the details of how the product was made aren't nearly as important as
if the opportunity that the product addresses is real.&lt;/p&gt;
&lt;p&gt;The rules for selling ideas have a lot of literature of their own, but here's
my own summary of rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Focus on the idea&lt;/strong&gt; &amp;mdash; remember that's the use-case, not the
   technical details;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Focus on a single idea&lt;/strong&gt; &amp;mdash; the more you bundle in your pitch, the
   more confusing everything will get, the harder it will be to sell it;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make the idea resonate emotionally with the target audience&lt;/strong&gt; &amp;mdash;
   make sure it's a problem they not only understand but they already felt,
   so they know it's real.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Selling your work, your ideas and yourself, is probably the most import skill
that's absent from the curriculum of most engineering courses. Because we're
mostly used to having evaluations focusing exclusively on the technical
aspects (at least throughout our academic life), we're quite unprepared for
this kind of contests which resemble a lot better the real life.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="24 hours"
 src="images/winning-appy-day/24h-counter.jpg" /&gt;
&lt;figcaption&gt;The timer on the wall &amp;mdash; with a tight schedule it's paramount
to focus on a polished main use-case instead of a mess of
features.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Our project landed in the 2nd place only because in our team of 4 there was 1
element dedicated exclusively to working out the pitch and the presentation
of the app, while the other 3 elements were hitting the code as fast and hard
as they could. In the end, we had a working app and a pitch that was good,
if I may say so. Our app used OCR technology and integration with tax ID
databases to ease a lot the work of tracking personal finances and filling
tax forms by taking pictures of recipts instead of having to fill in all the
info by hand. Our pitch was about how we all dreamed of being rich, and how
we envied people who took personal finances seriously and that actually had
the self-discipline to go through the work of tracking their expenses and
keeping their records tidy. The team that got the 1st place had a far better
pitch &amp;mdash; their app was about tracking the taking of pills and their
pitch started with &lt;em&gt;"I love my grandparents"&lt;/em&gt;. Bang! Emotional
resonance in the jury over the roof.&lt;/p&gt;
&lt;p&gt;Is this fair? Shouldn't people who invest a lot in the making of the product
(the code) rather than on the pitch and the selling be rewarded by their hard
work all the same? Can ideas be worth more than products?&lt;/p&gt;
&lt;p&gt;Well, you may still be too used to evaluations exclusively over the technical
aspects to accept that it's not the way the real world works. And don't take
my word for it or the word of the jury of BPI Appy Day, because in their case
the &lt;em&gt;best app as voted by all participants&lt;/em&gt; was the very same that got the
1st place from the jury. Even the audience that was made primarily by geeks
(who would be more prone to evaluating the technical aspects rather than the
pitch) was far from imune to the pitch's seduction.&lt;/p&gt;
&lt;p&gt;So before learning the next programming language or framework, how about
investing a bit in sharpening that salesman in you?&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="celebrating the victory"
 src="images/winning-appy-day/celebrating-victory.jpg" /&gt;
&lt;figcaption&gt;Celebrating the victory. Might not seem much but it was as much
as possible after half a day of
work plus 24 hours straight of non-stop development.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;em&gt;Getting to the second place wouldn't be possible without
the incredible team I had the priviledge to work with &amp;mdash; João Neves,
Tiago Simões and Vasco Pessanha&lt;/em&gt;&lt;/p&gt;</content><category term="misc"></category></entry><entry><title>From MySQL to PostgreSQL</title><link href="https://miguelventura.pt/2015/from-mysql-to-postgresql.html" rel="alternate"></link><published>2015-05-12T00:00:00+01:00</published><updated>2015-05-12T00:00:00+01:00</updated><author><name>Miguel Ventura</name></author><id>tag:miguelventura.pt,2015-05-12:/2015/from-mysql-to-postgresql.html</id><content type="html">&lt;p&gt;I recently migrated all my projects from MySQL to Postgresql.
These are just some notes to serve as memory aids for me.&lt;/p&gt;
&lt;p&gt;I'll keep adding things as I need to recall them.&lt;/p&gt;
&lt;table border="1" cellpadding="3" cellspacing="0"&gt;
&lt;thead&gt;
&lt;tr&gt;
  &lt;th&gt;MySQL&lt;/th&gt;
  &lt;th&gt;PostgreSQL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
  &lt;td&gt;show tables&lt;/td&gt;
  &lt;td&gt;\dt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;use *db*&lt;/td&gt;
  &lt;td&gt;\c *db*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;show columns from *table*&lt;/td&gt;
  &lt;td&gt;\d *table*&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</content><category term="misc"></category><category term="PostgreSQL"></category><category term="MySQL"></category></entry><entry><title>The Best Programming Language</title><link href="https://miguelventura.pt/2013/the-best-programming-language.html" rel="alternate"></link><published>2013-09-01T00:00:00+01:00</published><updated>2013-09-01T00:00:00+01:00</updated><author><name>Miguel Ventura</name></author><id>tag:miguelventura.pt,2013-09-01:/2013/the-best-programming-language.html</id><summary type="html">&lt;p&gt;&lt;em&gt;To the kid who asked me about programming languages...&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Yesterday, at a barbecue party I met a kid who was very interested in
programming. He had been learning about programming languages through the
internet and was very interested in the subject and about good practices of
programming, the different programming …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;To the kid who asked me about programming languages...&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Yesterday, at a barbecue party I met a kid who was very interested in
programming. He had been learning about programming languages through the
internet and was very interested in the subject and about good practices of
programming, the different programming paradigms, etc.. We had a good chat,
where his confusion about the amount of programming languages became very obvious
&amp;mdash; a natural incarnation of the &lt;em&gt;Paradox of Choice&lt;/em&gt;. Some of the
questions he asked were like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Which programming language should I learn next?&lt;/li&gt;
&lt;li&gt;Should I focus on learning as many languages as possible or just focus on a
   single language?&lt;/li&gt;
&lt;li&gt;Which programming language is the best?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These questions are very interesting to me because they are something that I 
end up asking myself somewhat often, and I'm pretty confident that they'll
appear in the minds of all those who constantly aim at becoming better
programmers.&lt;/p&gt;
&lt;p&gt;After our long chat, however, I had this epiphany that I want to share with you,
kid.&lt;/p&gt;
&lt;p&gt;Learning programming languages shouldn't be an end in itself, but rather a means 
to an end. Just like natural languages: it's rather worthless to learn esperanto 
if you're not going to use it to communicate something. Communication is the end 
for each languages serve as the means. Therefore the most important programming 
language for you should be the one where your ideas can be more easily and 
elegantly expressed, either to machines or to others. Sometimes this means the 
&lt;em&gt;better language&lt;/em&gt;, other times it means the most commonly known one.&lt;/p&gt;</content><category term="misc"></category></entry><entry><title>60 days of Kindle Paperwhite</title><link href="https://miguelventura.pt/2013/60-days-of-kindle-paperwhite.html" rel="alternate"></link><published>2013-03-04T00:00:00+00:00</published><updated>2013-03-04T00:00:00+00:00</updated><author><name>Miguel Ventura</name></author><id>tag:miguelventura.pt,2013-03-04:/2013/60-days-of-kindle-paperwhite.html</id><summary type="html">&lt;p&gt;TL;DR: It's awesome and I love it!&lt;/p&gt;
&lt;p&gt;Amazon had been making fun of me. For a while they sent me promotional emails
about the new Kindle Paperwhite. They would let me add it to the shopping
cart just to end up telling me that unfortunely &lt;em&gt;some items can't be …&lt;/em&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;TL;DR: It's awesome and I love it!&lt;/p&gt;
&lt;p&gt;Amazon had been making fun of me. For a while they sent me promotional emails
about the new Kindle Paperwhite. They would let me add it to the shopping
cart just to end up telling me that unfortunely &lt;em&gt;some items can't be sent to
the shipping address&lt;/em&gt;. They knew where I live, so why bother trying to sell
me something that they say they can't?&lt;/p&gt;
&lt;p&gt;I had waited for the Paperwhite for quite a while. Not because of the
back-light, because that actually goes against what I expect the kindle to
be: a device that's pretty much book-like. I wanted Paperwhite because I
wanted a touch interface instead of bulky keyboards.&lt;/p&gt;
&lt;h1&gt;The bad&lt;/h1&gt;
&lt;p&gt;The thing I like the least about the device is that it doesn't allow for the
backlight to be completely turned off. There's always a residual light that,
at least for me, is always noticeable. The e-ink display technology is
awesome because it makes use of environment light. Forcing me to have the
backlight always on just doesn't make sense. It also gives a blueish glow to
the pages. Fortunately at least this isn't as much of a battery drain as I'd
expect: even with the light always on, a single battery charge seems to last forever!&lt;/p&gt;
&lt;h1&gt;The meh&lt;/h1&gt;
&lt;p&gt;I've seen people praising the newly added fonts. I've seen people complaining
about them not being the best fonts in the world. I didn't even care about
changing the font. Did you ever wanted to buy a book but didn't because the
font wasn't &lt;em&gt;Scala&lt;/em&gt; for serif and Myriad Pro for sans-serif? Of course this
is a poor argument: it could still be applied if the device only supported
fixed-width fonts. But then, if a book was written in a fixed width font,
that could've prevented me from buying it. Well, these fonts don't hurt and
are readable enough not to make me care.&lt;/p&gt;
&lt;p&gt;Whenever the contents of the screen change, it is possible that remains of
the previous contents remain visible on the e-ink displays. A way to get
around this is to make the software controlling the screen to do a full
refresh (setting the screen all black and all white and then finally printing
the new content). Would I make a big deal out of this? No. In almost every
book I can see visual evidence of whatever's written in the back of the page
that I'm currently reading, so this misbehavior makes the device seem even
more book-like to me!&lt;/p&gt;
&lt;h1&gt;The awesome&lt;/h1&gt;
&lt;p&gt;Best feature ever: &lt;em&gt;Time until end of chapter&lt;/em&gt;. I simply can not overstate
how awesome this little estimate is. Until now the estimates were never
completely off, and this helps tremendously with scheduling the reading (or
the sleeping or whatever else comes next to reading in your agenda).&lt;/p&gt;
&lt;h2&gt;The &lt;em&gt;Read Later&lt;/em&gt; that really works&lt;/h2&gt;
&lt;p&gt;If you own a Kindle then you know you can email mostly any document to 
Amazon, to get it delivered on your device. If you use Instapaper and go on
browsing the web and marking stuff to &lt;em&gt;read later&lt;/em&gt;, you can tell Instapaper
to send a bundle of those unread articles to your Kindle. This workflow can
be a real productivity booster because it allows you to read the long
insightful articles when you have &lt;em&gt;thought time&lt;/em&gt; for them, or speed reading
(because e-ink displays are awesome for that) the less insightful stuff.&lt;/p&gt;
&lt;h1&gt;Veredict&lt;/h1&gt;
&lt;p&gt;Ever since I got myself the Kindle Paperwhite that I've been not only reading
more but actually organizing my browsing habits differently, and I really
feel more productive with it. Even though 60 days have passed, I'm totally
confident that this device will keep bringing me value for quite some time.&lt;/p&gt;</content><category term="Product Reviews"></category><category term="Reviews"></category></entry><entry><title>Ingress Portal Bursting</title><link href="https://miguelventura.pt/2013/ingress-portal-bursting.html" rel="alternate"></link><published>2013-01-16T00:00:00+00:00</published><updated>2013-01-16T00:00:00+00:00</updated><author><name>Miguel Ventura</name></author><id>tag:miguelventura.pt,2013-01-16:/2013/ingress-portal-bursting.html</id><summary type="html">&lt;h1&gt;The world around you is not what it seems.&lt;/h1&gt;
&lt;p&gt;Or at lease that's how &lt;a href="http://www.ingress.com/"&gt;Ingress&lt;/a&gt; is presented to the newcommers. The
game was created by Google which is by itself quite interesting, and like
many Google products, it's been released under an invite-only beta program.
It's an &lt;a href="http://en.wikipedia.org/wiki/Alternate_reality_game"&gt;ARG&lt;/a&gt; where
you …&lt;/p&gt;</summary><content type="html">&lt;h1&gt;The world around you is not what it seems.&lt;/h1&gt;
&lt;p&gt;Or at lease that's how &lt;a href="http://www.ingress.com/"&gt;Ingress&lt;/a&gt; is presented to the newcommers. The
game was created by Google which is by itself quite interesting, and like
many Google products, it's been released under an invite-only beta program.
It's an &lt;a href="http://en.wikipedia.org/wiki/Alternate_reality_game"&gt;ARG&lt;/a&gt; where
you have to (or at least you're supposed to) actually go to places to perform
game actions. Yes, it's a bit like geocaching.&lt;/p&gt;
&lt;p&gt;Being a Google closed beta, the game has attracted the attention of a lot of 
geeks that have been using it as an excuse to go outside and walk. A lot.&lt;/p&gt;
&lt;h1&gt;Portal attack dynamics&lt;/h1&gt;
&lt;p&gt;There are many artifacts in the game, but for now let's focus on &lt;em&gt;portals&lt;/em&gt; and
&lt;em&gt;resonators&lt;/em&gt;. Portals are what you want to &lt;em&gt;take over&lt;/em&gt;. Resonators are what you
place around a portal (up to 8 per portal, one per octant) to make it yours.
Conquering a portal usually means destroying the resonators.&lt;/p&gt;
&lt;p&gt;Resonators are destroyed by consuming another artifact called a &lt;em&gt;XMP
Burster&lt;/em&gt;, which come in levels, ranging from 1 to 8. Here's a nice &lt;a href="http://ingressfieldguide.com/content/xmp-bursters-attacking-enemy-portals"&gt;table on
damage and range&lt;/a&gt;. The amount of damage inflicted by
firing a burster varies with the distance to the in-range resonators. Initial
research shows a &lt;a href="http://ingressportal.com/research/attacking/"&gt;linear decrease&lt;/a&gt;
of damage with distance, but many say defend that the decrease is rather
&lt;a href="http://www.reddit.com/r/Ingress/comments/14sv1i/optimal_xmp_blast_locations_aim_for_the_middle/c7qtjk0"&gt;quadratic&lt;/a&gt; or even that it has to do with where you're [pointing
at].&lt;/p&gt;
&lt;p&gt;Because of this, it's been &lt;em&gt;common knowledge&lt;/em&gt; that you should fire the
burster while standing on top of the resonator you're trying to fry.
Reddit user &lt;a href="http://www.reddit.com/user/sikosmurf"&gt;sikosmurf&lt;/a&gt; disagrees with
the trend and suggested that the most effective way to take down all
resonators on a portal would be to
&lt;a href="http://redd.it/14sv1i"&gt;stand in the middle&lt;/a&gt;. I decided to do the math
myself.&lt;/p&gt;
&lt;h2&gt;Total damage per position&lt;/h2&gt;
&lt;p&gt;Let's start by assuming an optimal configuration (all 8 resonators at 35
meters from the portal).&lt;/p&gt;
&lt;p&gt;With a linear decrease, the optimal position would effectively be at the
center of the portal, for that would be where the sum of the damage over all
resonators would be the highest.&lt;/p&gt;
&lt;p&gt;&lt;img alt="L1 XMP, linear decay" src="https://miguelventura.pt/images/ingress/b1x8xlin.png"&gt;&lt;/p&gt;
&lt;p&gt;If we use a quadratic decrease, the picture changes immediately&lt;/p&gt;
&lt;p&gt;&lt;img alt="L1 XMP, quadratic decay" src="https://miguelventura.pt/images/ingress/b1x8.png"&gt;&lt;/p&gt;
&lt;p&gt;However, using a L2 XMP will yield an attack range that makes centering the
attack the best option again, even considering the quadratic decay.&lt;/p&gt;
&lt;p&gt;&lt;img alt="L2 XMP, quad" src="https://miguelventura.pt/images/ingress/b2x8.png"&gt;&lt;/p&gt;
&lt;p&gt;Increasing the level of the XMP will only make the pattern more obvious&lt;/p&gt;
&lt;p&gt;&lt;img alt="L3 XMP, quad" src="https://miguelventura.pt/images/ingress/b3x8.png"&gt;&lt;/p&gt;
&lt;p&gt;What if the resonators aren't placed as far from the portal center as they
could? In that case, it might make sense to fire even L1 bursters from the
portal's center.&lt;/p&gt;
&lt;p&gt;&lt;img alt="L1 XMP, 25m" src="https://miguelventura.pt/images/ingress/b3x8d25m.png"&gt;&lt;/p&gt;
&lt;p&gt;L1 against resonators placed in a 25m radius (instead of 35m)&lt;/p&gt;
&lt;p&gt;&lt;img alt="L1 XMP, some closer" src="https://miguelventura.pt/images/ingress/b3x8d7m.png"&gt;&lt;/p&gt;
&lt;p&gt;L1 against resonators placed at several distances from the portal, the
closest ones at 7m.&lt;/p&gt;
&lt;p&gt;These graphs were made using &lt;a href="http://python.org/"&gt;python&lt;/a&gt; and
&lt;a href="http://matplotlib.sourceforge.net/"&gt;matplotlib&lt;/a&gt;. You can find the
&lt;a href="https://gist.github.com/4552415"&gt;source code here&lt;/a&gt;. Any improvement
suggestions are welcome.&lt;/p&gt;</content><category term="misc"></category></entry><entry><title>Image Manipulation with HSL</title><link href="https://miguelventura.pt/2012/image-manipulation-with-hsl.html" rel="alternate"></link><published>2012-11-04T00:00:00+00:00</published><updated>2012-11-04T00:00:00+00:00</updated><author><name>Miguel Ventura</name></author><id>tag:miguelventura.pt,2012-11-04:/2012/image-manipulation-with-hsl.html</id><summary type="html">&lt;style type="text/css"&gt;
.cdisplay {
    display: inline-block;
    width: 1em;
    height: .8em;
    border: 1px solid black;
    position: relative;
    top: .15em;
    font-size: 0.5em;
}
&lt;/style&gt;

&lt;p&gt;Image manipulation is cool: you can &lt;em&gt;see&lt;/em&gt; the result. And people generally like
to somehow &lt;em&gt;enhance&lt;/em&gt; their pictures. &lt;a href="http://instagram.com/"&gt;Instagram&lt;/a&gt; is now a standard,
but before it people were already altering photos to …&lt;/p&gt;</summary><content type="html">&lt;style type="text/css"&gt;
.cdisplay {
    display: inline-block;
    width: 1em;
    height: .8em;
    border: 1px solid black;
    position: relative;
    top: .15em;
    font-size: 0.5em;
}
&lt;/style&gt;

&lt;p&gt;Image manipulation is cool: you can &lt;em&gt;see&lt;/em&gt; the result. And people generally like
to somehow &lt;em&gt;enhance&lt;/em&gt; their pictures. &lt;a href="http://instagram.com/"&gt;Instagram&lt;/a&gt; is now a standard,
but before it people were already altering photos to create pop-art like
pictures or to enhance their colors. Go check &lt;a href="http://500px.com/"&gt;500px&lt;/a&gt; and you'll see that
the amount of pictures that were digitally altered is huge.&lt;/p&gt;
&lt;h1&gt;What's with HSL?&lt;/h1&gt;
&lt;p&gt;If you've been working with computer software, you must have seen colors being
described in terms of &lt;a href="http://en.wikipedia.org/wiki/RGB"&gt;RGB&lt;/a&gt;
&lt;span class="cdisplay" style="background-color: red"&gt;   &lt;/span&gt;
&lt;span class="cdisplay" style="background-color: green"&gt;   &lt;/span&gt;
&lt;span class="cdisplay" style="background-color: blue"&gt;   &lt;/span&gt;
values because the
&lt;a href="http://en.wikipedia.org/wiki/Primary_color#Additive_primaries"&gt;additive primary color&lt;/a&gt; model is just like what the computer
screen ends up using.&lt;/p&gt;
&lt;p&gt;But what's a nice model to use for display technology purposes is hardly the
most intuitive model for playing around with pictures. Sure that if you've been
using image editing software or doing some kind of web development, you've
probably grown accustomed to the RGB model and you probably read &lt;code&gt;#FF0000&lt;/code&gt; as
"solid red". That's OK.&lt;/p&gt;
&lt;p&gt;However, the &lt;a href="http://en.wikipedia.org/wiki/HSL_and_HSV"&gt;HSL&lt;/a&gt; model is a lot nicer for some image operations. Instead of
expressing the colors in 3 dimensions that are about the &lt;em&gt;amount of a primary
color&lt;/em&gt;, this model represents colors in terms of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hue (what you may think of as a &lt;em&gt;pure color&lt;/em&gt;)
   &lt;span style="white-space: nowrap;"&gt;
   &lt;span class="cdisplay" style="background-color: hsl(0,100%,50%)"&gt;&lt;/span&gt;
   &lt;span class="cdisplay" style="background-color: hsl(60,100%,50%)"&gt;&lt;/span&gt;
   &lt;span class="cdisplay" style="background-color: hsl(120,100%,50%)"&gt;&lt;/span&gt;
   &lt;span class="cdisplay" style="background-color: hsl(180,100%,50%)"&gt;&lt;/span&gt;
   &lt;span class="cdisplay" style="background-color: hsl(240,100%,50%)"&gt;&lt;/span&gt;
   &lt;span class="cdisplay" style="background-color: hsl(300,100%,50%)"&gt;&lt;/span&gt;
   &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Saturation (you may think of it as the &lt;em&gt;amount of color&lt;/em&gt;)
   &lt;span style="white-space: nowrap;"&gt;
   &lt;small&gt;100%&lt;/small&gt;
   &lt;span class="cdisplay" style="background-color: hsl(120,100%,50%)"&gt;&lt;/span&gt;
   &lt;span class="cdisplay" style="background-color: hsl(120,75%,50%)"&gt;&lt;/span&gt;
   &lt;span class="cdisplay" style="background-color: hsl(120,50%,50%)"&gt;&lt;/span&gt;
   &lt;span class="cdisplay" style="background-color: hsl(120,25%,50%)"&gt;&lt;/span&gt;
   &lt;span class="cdisplay" style="background-color: hsl(120,0%,50%)"&gt;&lt;/span&gt;
   &lt;small&gt;0%&lt;/small&gt;
   &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;Lightness (what makes your color closer to black or white)
   &lt;span style="white-space: nowrap;"&gt;
   &lt;small&gt;5%&lt;/small&gt;
   &lt;span class="cdisplay" style="background-color: hsl(120,100%,5%)"&gt;&lt;/span&gt;
   &lt;span class="cdisplay" style="background-color: hsl(120,100%,25%)"&gt;&lt;/span&gt;
   &lt;span class="cdisplay" style="background-color: hsl(120,100%,50%)"&gt;&lt;/span&gt;
   &lt;span class="cdisplay" style="background-color: hsl(120,100%,75%)"&gt;&lt;/span&gt;
   &lt;span class="cdisplay" style="background-color: hsl(120,100%,95%)"&gt;&lt;/span&gt;
   &lt;small&gt;95%&lt;/small&gt;
   &lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How do these dimensions help us? Depends on what you're doing. Making a picture
gray scale? Just set &lt;em&gt;saturation&lt;/em&gt; to 0 everywhere. Want to make it feel lighter or
darker? Just play with &lt;em&gt;lightness&lt;/em&gt; and leave everything else as is. You can do
all that in the RGB model, but it's usually less intuitive.&lt;/p&gt;
&lt;p&gt;Even if you feel
like you've spent too much time understanding one color model to get interested
on another, note that HSL is much easier to learn because it's more related with
the way that we, humans, perceive colors.&lt;/p&gt;
&lt;h1&gt;Image effects that become easy with HSL&lt;/h1&gt;
&lt;p&gt;To show these effects, let's start with &lt;a href="http://en.wikipedia.org/wiki/Lenna"&gt;Lenna, because she's a standard&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Lenna" src="https://miguelventura.pt/images/hsl/lenna250.png"&gt;&lt;/p&gt;
&lt;p&gt;I'll describe how to use these algorithms using Python with the &lt;a href="http://www.pythonware.com/library/pil/handbook/index.htm"&gt;Python Image
Library (PIL)&lt;/a&gt; and the &lt;a href="http://docs.python.org/2/library/colorsys.html"&gt;colorsys&lt;/a&gt; package (which is part
of the standard library).&lt;/p&gt;
&lt;p&gt;We'll also need a piece of boilerplate code to help us with loading images and
storing some relevant attributes (such as dimensions), some functions to help
with the conversion from RGB to HSL and back to RGB (because we'll be
manipulating the image in HSL but PIL only talks RGB).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageColor&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;colorsys&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;rgb_to_hls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hls_to_rgb&lt;/span&gt;

&lt;span class="n"&gt;IMAGE_SRC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;lenna250.png&amp;#39;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;showme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; use PIL standard display &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;h_dist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; distance between color hues in angular space,&lt;/span&gt;
&lt;span class="sd"&gt;    where 1.0 == 0.0 (so distance must wrap around if &amp;gt; 1)&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rgb2hls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; convert PIL-like RGB tuple (0 .. 255) to colorsys-like&lt;/span&gt;
&lt;span class="sd"&gt;    HSL tuple (0.0 .. 1.0) &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;/=&lt;/span&gt; &lt;span class="mf"&gt;255.0&lt;/span&gt;
    &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;/=&lt;/span&gt; &lt;span class="mf"&gt;255.0&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;/=&lt;/span&gt; &lt;span class="mf"&gt;255.0&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rgb_to_hls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hls2rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; convert a colorsys-like HSL tuple (0.0 .. 1.0) to a&lt;/span&gt;
&lt;span class="sd"&gt;    PIL-like RGB tuple (0 .. 255) &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hls_to_rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;
    &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reload_img&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sizew&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sizeh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxsize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;imgdata&lt;/span&gt;
    &lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IMAGE_SRC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sizew&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sizeh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;
    &lt;span class="n"&gt;maxsize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;sizew&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sizeh&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;
    &lt;span class="n"&gt;imgdata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getdata&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now it becomes trivial to do things such as...&lt;/p&gt;
&lt;h2&gt;Converting Image Color&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# change image to single color (hue)&lt;/span&gt;
&lt;span class="c1"&gt;# while keeping saturation and lightness&lt;/span&gt;
&lt;span class="n"&gt;reload_img&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;hue&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgdata&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rgb2hls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgdata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hue&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;
        &lt;span class="n"&gt;imgdata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hls2rgb&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;putdata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgdata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;showme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And the output is similar to the color palette of a CGA display. &lt;/p&gt;
&lt;p&gt;&lt;img alt="red" src="https://miguelventura.pt/images/hsl/out-1.png"&gt;
&lt;img alt="green" src="https://miguelventura.pt/images/hsl/out-2.png"&gt;
&lt;img alt="cyan" src="https://miguelventura.pt/images/hsl/out-3.png"&gt;
&lt;img alt="magenta" src="https://miguelventura.pt/images/hsl/out-4.png"&gt;&lt;/p&gt;
&lt;p&gt;Here we kept the saturation and lightness of colors but based everything into 4
distinct hues in our [0.0 .. 1.0] scale (&lt;em&gt;h&lt;/em&gt; =
0.00 &lt;span class="cdisplay" style="background-color: hsl(0,100%,50%)"&gt;   &lt;/span&gt;,
0.25 &lt;span class="cdisplay" style="background-color: hsl(90,100%,50%)"&gt;  &lt;/span&gt;,
0.50 &lt;span class="cdisplay" style="background-color: hsl(180,100%,50%)"&gt; &lt;/span&gt;,
0.75 &lt;span class="cdisplay" style="background-color: hsl(270,100%,50%)"&gt; &lt;/span&gt;).&lt;/p&gt;
&lt;p&gt;For extra impact, we may also increase saturation.&lt;/p&gt;
&lt;p&gt;&lt;img alt="blue" src="https://miguelventura.pt/images/hsl/out-4b.png"&gt;&lt;/p&gt;
&lt;p&gt;Same as the above (&lt;em&gt;h&lt;/em&gt; = 
0.75 &lt;span class="cdisplay" style="background-color: hsl(270,100%,50%)"&gt; &lt;/span&gt;)
but with &lt;em&gt;s&lt;/em&gt; = 1.&lt;/p&gt;
&lt;h2&gt;Increasing relevance of a single color&lt;/h2&gt;
&lt;p&gt;We can increase the relevant of a single color by dimming all other colors, and
we can easily do that by reducing their saturation. The formula is simple: the
more distant a pixel's color hue from our target color hue, the more we'll
reduce the saturation.&lt;/p&gt;
&lt;p&gt;We can use the above function &lt;code&gt;hdist&lt;/code&gt; to get how distant the pixel hue is from
ours. Since the hue space goes from 0 to 1 and &lt;em&gt;wraps around&lt;/em&gt;, the maximum
distance is 0.5. If we double the result from &lt;code&gt;hdist&lt;/code&gt; we'll
have a number ranging from 0 to 1 that represents the distance to our target
color. Having a number between 0 and 1 is great because it means that we can use
this number directly as the saturation value or use it as a correction
coefficient by multiplying it with the current saturation value.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;reload_img&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgdata&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rgb2hls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgdata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="c1"&gt;## Here we chose ONE of the following formulas:&lt;/span&gt;
    &lt;span class="c1"&gt;# dim all but blues&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;h_dist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="c1"&gt;# dim all but blues (quadratic makes our &amp;quot;close to blue&amp;quot; mean closer)&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;h_dist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="c1"&gt;## don&amp;#39;t correct, replace&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;h_dist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="n"&gt;imgdata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hls2rgb&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;putdata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgdata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;showme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="linear saturation correction" src="https://miguelventura.pt/images/hsl/rel-norm.png"&gt;
&lt;img alt="quadratic saturation correction" src="https://miguelventura.pt/images/hsl/rel-quad.png"&gt; 
&lt;img alt="linear saturation replacement" src="https://miguelventura.pt/images/hsl/rel-over.png"&gt;&lt;/p&gt;
&lt;p&gt;Linear correction, Quadratic correction and Linear replacement respectively. You
should be able to see the original image by hovering the mouse or tapping on any
of the images.&lt;/p&gt;
&lt;p&gt;As you can see, blues are mostly kept while all other colors fade. This makes
them more noticeable. &lt;/p&gt;
&lt;h2&gt;Movie like effects&lt;/h2&gt;
&lt;p&gt;We can easily get a &lt;strong&gt;Predator&lt;/strong&gt;-like vision by using lightness directly as hue,
with full saturation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# the predator vision&lt;/span&gt;
&lt;span class="c1"&gt;# full saturation, lightness-based hue, constant lightness&lt;/span&gt;
&lt;span class="n"&gt;reload_img&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgdata&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rgb2hls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgdata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;# one&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="c1"&gt;# L (lowercase-L, probably the worst variable name)&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;
    &lt;span class="n"&gt;imgdata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hls2rgb&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;putdata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgdata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;showme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Lenna as seen by the Predator" src="https://miguelventura.pt/images/hsl/predator.png" title="Lenna as seen by the Predator"&gt;&lt;/p&gt;
&lt;p&gt;We can also simulate a &lt;strong&gt;Terminator&lt;/strong&gt;-like vision by switching everything to red
hue, using full saturation, and putting lightness to its extremes &amp;mdash; making
everything light lighter and everything dark darker.&lt;/p&gt;
&lt;p&gt;The trick here is to always use smooth corrections for lightness. If we just did
something like&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;the resulting image would clearly reveal the lack of smoothness of our approach
in areas that should smoothly pass through the threshold&lt;/p&gt;
&lt;p&gt;&lt;img alt="Lenna as seen by a defective Terminator" src="https://miguelventura.pt/images/hsl/terminator-nonsmooth.png" title="Lenna as seen by a defective Terminator"&gt;&lt;/p&gt;
&lt;p&gt;In order to overburn lightness smoothly, we can use a S-curve function such as
an adaptation of the &lt;a href="http://en.wikipedia.org/wiki/Logistic_function"&gt;Logistic function&lt;/a&gt;. Our resulting code
could then be something like&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# the terminator vision&lt;/span&gt;
&lt;span class="c1"&gt;# red hue, full saturation, lightness based on original lightness&lt;/span&gt;
&lt;span class="c1"&gt;# but S-curved (more gravity at the extremes of the scale)&lt;/span&gt;
&lt;span class="n"&gt;reload_img&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# we&amp;#39;ll use Euler&amp;#39;s constant in the Logistic function&amp;#39;s formula&lt;/span&gt;
&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.718281828459045&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgdata&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rgb2hls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgdata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="c1"&gt;# Adaptation of the Logistic Function&lt;/span&gt;
    &lt;span class="c1"&gt;# A factor of 4+ is OK but the bigger the number, the bigger the gravity&lt;/span&gt;
    &lt;span class="c1"&gt;# towards extremes of the scale.&lt;/span&gt;
    &lt;span class="n"&gt;s_curve_factor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s_curve_factor&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;s_curve_factor&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;imgdata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hls2rgb&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;putdata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgdata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;showme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Logistic Function" src="https://miguelventura.pt/images/hsl/logistic-f.png" title="Logistic function used for lightness correction (gravitating the lightness values toward the extremes 0.0 and 1.0&amp;#x29;."&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Lenna as seen by the Terminator" src="https://miguelventura.pt/images/hsl/terminator.png" title="Lenna as seen smoothly by the Terminator"&gt;&lt;/p&gt;
&lt;h2&gt;Rainbows&lt;/h2&gt;
&lt;p&gt;By rotating the hue value, it's trivial to create a rainbow.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# hue according to pixel position, increasing from&lt;/span&gt;
&lt;span class="c1"&gt;# top to bottom&lt;/span&gt;
&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgdata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Lenna horizontal rainbow" src="https://miguelventura.pt/images/hsl/rainbow-linear.png" title="Lenna with a linear top-to-bottom rainbow"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# hue according to pixel position, in a radial progression from&lt;/span&gt;
&lt;span class="c1"&gt;# image center towards corners&lt;/span&gt;
&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sizew&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sizeh&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;maxsize&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Lenna radial rainbow" src="https://miguelventura.pt/images/hsl/rainbow-radial.png" title="With a radial rainbow centered on the middle of the image"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# radial rainbow&lt;/span&gt;
&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sizew&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;sizeh&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;maxsize&lt;/span&gt;
&lt;span class="c1"&gt;# but instead of replacing original data, we average it with the original&lt;/span&gt;
&lt;span class="c1"&gt;# data with weights (3/4 original, 1/4 rainbow)&lt;/span&gt;
&lt;span class="n"&gt;imgdata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imgdata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="n"&gt;hls2rgb&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Lenna radial averaged rainbow" src="https://miguelventura.pt/images/hsl/rainbow-avg.png" title="Same radial rainbow, only merged with the original image"&gt;&lt;/p&gt;
&lt;p&gt;By merging the radial rainbow with the original colors, in this particular case,
we end up with both a subtle aura and &lt;a href="http://en.wikipedia.org/wiki/Heterochromia_Iridum"&gt;&lt;em&gt;heterochromia iridum&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;In Summary&lt;/h1&gt;
&lt;p&gt;Any of the presented effects is more intuitive to create when operating in an
HSL space than when operating in the RGB one. Unfortunately due to the
popularity of the RGB notation, especially in web development, people seem to
forget that other color systems exist, including ones that may be more
appropriate to create whatever effect they need.&lt;/p&gt;
&lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript"&gt;
$(function() {
    var default_src = function(src) {
        src = src.split("/");
        src[src.length-1] = 'lenna250.png';
        return src.join("/");
    }
    var enterFunc = function () {
        var $this = $(this);

        if ($this.width() == 256 &amp;&amp; $this.height() == 256) {
            $this.data('original-src', $this.attr('src'))
                .attr('src', default_src($this.attr('src')));
        }
    },
        leaveFunc = function() {
        var $this = $(this);
        var osrc = $this.data('original-src')
        if (osrc) {
            $this.data('original-src', null).attr('src', osrc);
        }    
    };
    $(document.body)
        .on('mouseenter', 'img', enterFunc)
        .on('mouseleave', 'img', leaveFunc)
        .on('click', 'img', function() {
            if ($(this).data('original-src')) {
                leaveFunc.apply(this, []);
            } else {
                enterFunc.apply(this, []);
            }
        });

    // Every image referenced from a Markdown document
    $("article img").each(function() {
        // Let's put a caption if there is one
        if($(this).attr("title"))
            $(this).wrap('&lt;figure class="image"&gt;&lt;/figure&gt;')
                .after('&lt;figcaption&gt;'+$(this).attr("title")+'&lt;/figcaption&gt;');
        });
});
&lt;/script&gt;</content><category term="misc"></category></entry><entry><title>Scripting WinDBG with PyKd</title><link href="https://miguelventura.pt/2012/scripting-windbg-with-pykd.html" rel="alternate"></link><published>2012-09-02T00:00:00+01:00</published><updated>2012-09-02T00:00:00+01:00</updated><author><name>Miguel Ventura</name></author><id>tag:miguelventura.pt,2012-09-02:/2012/scripting-windbg-with-pykd.html</id><summary type="html">&lt;p&gt;&lt;img alt="WinDBG + Python = Love" src="https://miguelventura.pt/images/windbg-pykd/windbg-python-luv.png"&gt;&lt;/p&gt;
&lt;h1&gt;Why WinDBG?&lt;/h1&gt;
&lt;p&gt;&lt;a href="http://msdn.microsoft.com/en-us/windows/hardware/gg463009.aspx"&gt;WinDBG&lt;/a&gt; is &lt;em&gt;the debugger&lt;/em&gt; for Microsoft Windows. If you don't believe me, just
ask any of Microsoft's support escalation engineers. It is extremely powerful
but has a very steep learning curve, so usually it's used only when Visual
Studio isn't up for the job.&lt;/p&gt;
&lt;h1&gt;Why Automation?&lt;/h1&gt;
&lt;p&gt;Often you …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;img alt="WinDBG + Python = Love" src="https://miguelventura.pt/images/windbg-pykd/windbg-python-luv.png"&gt;&lt;/p&gt;
&lt;h1&gt;Why WinDBG?&lt;/h1&gt;
&lt;p&gt;&lt;a href="http://msdn.microsoft.com/en-us/windows/hardware/gg463009.aspx"&gt;WinDBG&lt;/a&gt; is &lt;em&gt;the debugger&lt;/em&gt; for Microsoft Windows. If you don't believe me, just
ask any of Microsoft's support escalation engineers. It is extremely powerful
but has a very steep learning curve, so usually it's used only when Visual
Studio isn't up for the job.&lt;/p&gt;
&lt;h1&gt;Why Automation?&lt;/h1&gt;
&lt;p&gt;Often you might find yourself doing repetitive work in the debugger console. For
instance iterating over items of a linked list or printing some property of all
.NET objects of type X or just chewing up data from some memory region.
Sometimes you just want to follow a lot of pointers until you get to the stuff
that matters, and that means you'll spend a lot of time doing copy-paste of
memory addresses and playing Where's Waldo with cryptic data.&lt;/p&gt;
&lt;p&gt;The problem is that most WinDBG commands weren't designed with automation in
mind. There are some commands to ease automation... it is easy to iterate over a
linked list and there's a &lt;code&gt;.foreach&lt;/code&gt; command that's able to parse the output of
some other command in a somewhat limited fashion. However the mechanisms that
exist are neither easy to use, easy to learn, or even sufficient.&lt;/p&gt;
&lt;p&gt;It would be awesome if WinDBG provided some well structured scripting language
for aiding with this kind of tasks.&lt;/p&gt;
&lt;h1&gt;PyKd to the rescue!&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://pykd.codeplex.com/"&gt;PyKd&lt;/a&gt; is a WinDBG extension that allows you to integrate &lt;a href="http://www.python.org/"&gt;Python&lt;/a&gt;
scripts with WinDBG. Python is awesome for parsing strings and lists, and it's
extremely easy to learn and to write. The idea of running Python in WinDBG is so
good that I believe Microsoft should integrate this extension with the debugger!
(this will probably never happen, but it would be awesome if it did).&lt;/p&gt;
&lt;p&gt;Unfortunately PyKd's documentation is at the time mostly in Russian only, but
this may very well change in a near future.&lt;/p&gt;
&lt;h2&gt;Installing and Using&lt;/h2&gt;
&lt;p&gt;PyKd installation is very straightforward if you use the installer. However I
already had Python installed, so I took the zip file only. Due to this I sumbled
upon some problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The pykd.pyd file must be placed in the &lt;code&gt;winext&lt;/code&gt; directory inside your WinDBG
  installation directory.&lt;/li&gt;
&lt;li&gt;PyKd assumes that you have the &lt;code&gt;PYTHONPATH&lt;/code&gt; environment variable defined. If
  you don't, invoking the extension commands will freeze WinDBG (you'll get a
  &lt;code&gt;BUSY&lt;/code&gt; prompt forever).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The usage is very straightforward, especially if all you want to do can be
achieved by issuing debugger commands and parsing their output. You can use
&lt;code&gt;!pycmd&lt;/code&gt; to get a Python shell right in your WinDBG session and
&lt;code&gt;dbgClient.dbgCommand&lt;/code&gt; to issue debugger commands from python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt; &lt;span class="n"&gt;pykd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pyd&lt;/span&gt;
&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="n"&gt;pycmd&lt;/span&gt;
&lt;span class="n"&gt;Python&lt;/span&gt; &lt;span class="mf"&gt;2.6.5&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r265&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;79096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Mar&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt; &lt;span class="mi"&gt;2010&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;02&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MSC&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="mf"&gt;.1500&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt; &lt;span class="n"&gt;bit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AMD64&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;win32&lt;/span&gt;
&lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;help&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;copyright&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;credits&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;license&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;more&lt;/span&gt; &lt;span class="n"&gt;information&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InteractiveConsole&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pykd&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dbgClient&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;loaded_modules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dbgClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dbgCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;lm&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Alternatively you can use &lt;code&gt;!py &amp;lt;file&amp;gt;&lt;/code&gt; to run a script from &lt;code&gt;&amp;lt;file&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;How cool is that?&lt;/h1&gt;
&lt;p&gt;It's just awesome! Here are some basic examples of how PyKd can save your bacon:&lt;/p&gt;
&lt;h2&gt;Listing all Namespaces of WebServiceMethod&lt;/h2&gt;
&lt;p&gt;You could use &lt;code&gt;!dumpheap -stat WebServiceMethod&lt;/code&gt; to
get the MT of &lt;code&gt;WebServiceMethod&lt;/code&gt; and then &lt;code&gt;!dumpheap -mt &amp;lt;mt&amp;gt; -short&lt;/code&gt; to list
all &lt;code&gt;WebServiceMethod&lt;/code&gt; object addresses. The output of &lt;code&gt;!dumpheap -short&lt;/code&gt; can be
easily parsed by &lt;code&gt;.foreach&lt;/code&gt;, but then you'd have to run &lt;code&gt;!dumpobj&lt;/code&gt; to get the
address for the namespace property and finally &lt;code&gt;!dumpobj&lt;/code&gt; on this address. The
output of &lt;code&gt;!dumpobj&lt;/code&gt; isn't suitable for &lt;code&gt;.foreach&lt;/code&gt;, but it's no trouble for a
python script.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pykd&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dump_soapclientmethod&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# get all SoapClientMethod&amp;#39;s&lt;/span&gt;
    &lt;span class="n"&gt;soapcliaddrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pykd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dbgCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;!dumpheap -mt 0000064283abea38 -short&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;### found &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt; soap client addresses&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;soapcliaddrs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;soapcliaddrs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# dumpobj to get object properties&lt;/span&gt;
        &lt;span class="n"&gt;do&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pykd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dbgCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;!do &lt;/span&gt;&lt;span class="si"&gt;%(addr)s&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;addr&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="c1"&gt;# get the line for &amp;#39;action&amp;#39; property&lt;/span&gt;
        &lt;span class="n"&gt;actionline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;do&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;action&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="c1"&gt;# line ends with &amp;quot;&amp;lt;address&amp;gt; action&amp;quot; and we want the &amp;lt;address&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;actionaddr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;actionline&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="c1"&gt;# get the string in the retrieved &amp;lt;address&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;doaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pykd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dbgCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;!do -nofields &lt;/span&gt;&lt;span class="si"&gt;%(addr)s&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;addr&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;actionaddr&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actionaddr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doaction&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;the code above renders an output like&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="err"&gt;###&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;125&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;soap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;addresses&lt;/span&gt;
&lt;span class="mi"&gt;00000001&lt;/span&gt;&lt;span class="nx"&gt;c1755b48&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//schemas.microsoft.com/sharepoint/soap/List&lt;/span&gt;
&lt;span class="mi"&gt;00000001&lt;/span&gt;&lt;span class="nx"&gt;e2085640&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//schemas.microsoft.com/sharepoint/soap/Copy&lt;/span&gt;
&lt;span class="mi"&gt;0000000240&lt;/span&gt;&lt;span class="nx"&gt;fb35c8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//schemas.microsoft.com/sharepoint/soap/List&lt;/span&gt;
&lt;span class="mi"&gt;00000002419&lt;/span&gt;&lt;span class="nx"&gt;c4158&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//schemas.microsoft.com/sharepoint/soap/Copy&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Listing dynamically generated assemblies for XML Serialization&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dump_assemblies&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;addrs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pykd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dbgCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;!dumpheap -stat -type Microsoft.Xml.Serialization.GeneratedAssembly.ArrayOfObjectSerializer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;### found &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt; objects&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addrs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;laddr&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;addrs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;laddr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;mtdump&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pykd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dbgCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;!dumpmt -md &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;mt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;modaddr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;mtdump&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Module:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;mddump&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pykd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dbgCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;!dumpmodule &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;modaddr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;assembly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;mdump&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Assembly:&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;With &lt;a href="https://pykd.codeplex.com/"&gt;PyKd&lt;/a&gt; you can finally script &lt;a href="http://msdn.microsoft.com/en-us/windows/hardware/gg463009.aspx"&gt;WinDBG&lt;/a&gt; in a decent
programming language and write some code to automate those repetitive
tasks that make you feel you're wasting time rather than debugging.&lt;/p&gt;</content><category term="misc"></category><category term="debug"></category><category term="windbg"></category><category term="python"></category></entry><entry><title>django-imagekit watermarks</title><link href="https://miguelventura.pt/2012/django-imagekit-watermarks.html" rel="alternate"></link><published>2012-07-28T00:00:00+01:00</published><updated>2012-07-28T00:00:00+01:00</updated><author><name>Miguel Ventura</name></author><id>tag:miguelventura.pt,2012-07-28:/2012/django-imagekit-watermarks.html</id><summary type="html">&lt;p&gt;I've been playing around implementing &lt;em&gt;watermark processors&lt;/em&gt; for
&lt;a href="https://github.com/jdriscoll/django-imagekit"&gt;django-imagekit&lt;/a&gt;. There was an open request for an &lt;a href="https://github.com/jdriscoll/django-imagekit/issues/113"&gt;overlay
processor&lt;/a&gt; (which is kind of the same thing), and I wanted to explore a
bit more PIL and imagekit processors.&lt;/p&gt;
&lt;p&gt;So far, I've created processors that take text and images to overlay in …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been playing around implementing &lt;em&gt;watermark processors&lt;/em&gt; for
&lt;a href="https://github.com/jdriscoll/django-imagekit"&gt;django-imagekit&lt;/a&gt;. There was an open request for an &lt;a href="https://github.com/jdriscoll/django-imagekit/issues/113"&gt;overlay
processor&lt;/a&gt; (which is kind of the same thing), and I wanted to explore a
bit more PIL and imagekit processors.&lt;/p&gt;
&lt;p&gt;So far, I've created processors that take text and images to overlay in the base
image. At the time, the code is available in a &lt;a href="https://github.com/mmv/django-imagekit"&gt;fork of imagekit&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Examples&lt;/h1&gt;
&lt;h2&gt;Overlaying text&lt;/h2&gt;
&lt;p&gt;&lt;img alt="Drave" src="https://miguelventura.pt/images/imagekit-watermarks/drave_base.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;TextWatermark&lt;/code&gt; we can overlay some text in the following manner&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;drave_base.jpg&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;title_mark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TextWatermark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Views from Drave&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/Library/Fonts/Arial Bold.ttf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;text_color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;red&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;opacity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;copy_mark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TextWatermark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Photo taken by myself. No copyright.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/Library/Fonts/Arial Bold.ttf&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;text_color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;#ff0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;opacity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;title_mark&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;copy_mark&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And the resulting &lt;code&gt;img&lt;/code&gt; would be&lt;/p&gt;
&lt;p&gt;&lt;img alt="Drave with captions" src="https://miguelventura.pt/images/imagekit-watermarks/drave_captioned.jpg"&gt;&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;TextWatermark&lt;/code&gt;, all configuration is optional, except for the text to be
written.&lt;/p&gt;
&lt;p&gt;You see, you can use names for &lt;code&gt;text_color&lt;/code&gt; or HTML color notation (or any
format supported by &lt;a href="http://www.pythonware.com/library/pil/handbook/imagecolor.htm"&gt;PIL.ImageColor&lt;/a&gt;). &lt;code&gt;opacity&lt;/code&gt; takes a float
ranging from &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt;. The &lt;code&gt;position&lt;/code&gt; parameter
accepts tuples containing horizontal and vertical position values. The values
can be specified as strings (such as &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;left&lt;/code&gt;, &lt;code&gt;bottom&lt;/code&gt;, &lt;code&gt;right&lt;/code&gt; and
&lt;code&gt;center&lt;/code&gt;), strings containing percentages (such as &lt;code&gt;"25%"&lt;/code&gt;) or integers. If the
percentages or integers are negative, they're measured form the bottom-right instad
of the top-left point.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;font&lt;/code&gt; parameter may be a path to a TrueType font file or a tuple with a
path and a size. &lt;strong&gt;Unfortunely there's no font discovery: you have to specify
the full path to the font file.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Overlaying images&lt;/h2&gt;
&lt;p&gt;With &lt;code&gt;ImageWatermark&lt;/code&gt; we can overlay one image on another. We'll use the
landscape image with this star:&lt;/p&gt;
&lt;p&gt;&lt;img alt="star image" src="https://miguelventura.pt/images/imagekit-watermarks/200-star.png"&gt;&lt;/p&gt;
&lt;p&gt;The watermark can be provided to the processor as a &lt;code&gt;str&lt;/code&gt; with the path to
the image, an &lt;code&gt;Image&lt;/code&gt; object or a storage (&lt;code&gt;file&lt;/code&gt;-like object).&lt;/p&gt;
&lt;p&gt;Here's some sample code on how we could use this processor.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;drave_base.jpg&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# any of these could be used&lt;/span&gt;
&lt;span class="n"&gt;watermark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;200-star.png&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;watermark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;200-star.png&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;watermark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;200-star.png&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# if you use a file-like object&lt;/span&gt;
                                 &lt;span class="c1"&gt;# remember to close your file when done&lt;/span&gt;

&lt;span class="n"&gt;scaled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageWatermark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;watermark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;center&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;center&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;opacity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;repeated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageWatermark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;watermark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;right&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;bottom&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;repeat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;opacity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;img_scaled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scaled&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;img_repeated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;repeated&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Would result in &lt;code&gt;img_scaled&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Drave with big star" src="https://miguelventura.pt/images/imagekit-watermarks/drave_star_scale.jpg"&gt;&lt;/p&gt;
&lt;p&gt;And &lt;code&gt;img_repeated&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Drave full of stars" src="https://miguelventura.pt/images/imagekit-watermarks/drave_star_repeat.jpg"&gt;&lt;/p&gt;
&lt;p&gt;For performance tuning, it's better to setup the watermark image to be used by path
or storage object. Using an Image object will require the Image to be kept in
memory forever (which may be a problem depending on the size and amount of images
you'll be using).
By default, the watermark will be cached using a weak
reference, so the image processor doesn't need to constantly fetch it from
storage and the image may be collected in case your process gets low on memory.&lt;/p&gt;
&lt;p&gt;I hope this code can be of use to you. If you have any issues or requests,
please send them &lt;a href="https://github.com/mmv/django-imagekit"&gt;through GitHub&lt;/a&gt;.&lt;/p&gt;</content><category term="misc"></category><category term="django"></category></entry><entry><title>Why is it still Hard to Learn CSS?</title><link href="https://miguelventura.pt/2012/why-is-it-still-hard-to-learn-css.html" rel="alternate"></link><published>2012-07-21T00:00:00+01:00</published><updated>2012-07-21T00:00:00+01:00</updated><author><name>Miguel Ventura</name></author><id>tag:miguelventura.pt,2012-07-21:/2012/why-is-it-still-hard-to-learn-css.html</id><summary type="html">&lt;p&gt;Lately I've been doing presentations on CSS to colleagues.
CSS is continuously evolving, especially with CSS3, but 
this was a crash-course that only covers the very basics of CSS with some exercises in
between, and the whole thing had to fit half a day.
The audience was made of people …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Lately I've been doing presentations on CSS to colleagues.
CSS is continuously evolving, especially with CSS3, but 
this was a crash-course that only covers the very basics of CSS with some exercises in
between, and the whole thing had to fit half a day.
The audience was made of people that have been working with CSS for years now,
some of them on a daily basis. Making real web sites. With a lot of style!
Why would a course of CSS be useful for people that have been using it so
much? Why did almost every attendee give positive feedback on the session?&lt;/p&gt;
&lt;p&gt;The simple answer is &lt;em&gt;because you can
use something for quite some time without really knowing what you're doing&lt;/em&gt;.
And why didn't they learn the basics in the first place? &lt;strong&gt;Because the way CSS
is thaught on the internet is fundamentally broken.&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;CSS Learning with Online Content&lt;/h1&gt;
&lt;p&gt;I've never read a book on CSS, nor attended to a workshop or course or
whatnot. All I've learned about it was by searching and researching within what's
available in the internet. So all the information contained in the presentation was
actually available in the internet.&lt;/p&gt;
&lt;p&gt;CSS information in the internet can mostly be put into one of three
categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;organized reference material&lt;/strong&gt;: W3C Standards, Mozilla's MDN
  documentation, Microsoft's MSDN, ...&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;tutorials&lt;/strong&gt; of all kinds and sizes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;experiments and hacks&lt;/strong&gt; where people explain tricky techniques to achieve a
  specific result or where people compare the behaviors browsers have with the
  behaviors the standards say they should have&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And these categories are accessed by different kinds of audiences&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;organized reference material is used mostly by very experienced users that take
  time and dedication to thoroughly read such documentation&lt;/li&gt;
&lt;li&gt;tutorials are used by everyone else to get starterd and learn everything
  afterwards... at least until they become very experienced users&lt;/li&gt;
&lt;li&gt;experiments and hacks are used by the experienced users as sources of
  learning and inspiration or by the rest of the users as source material for
  a trial-and-error approach to solving a specific problem they're
  experiencing at a particular time&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With so much content available for different audiences, it would be natural to
expect that learning CSS was an easy task. The reason why this isn't true is
because &lt;strong&gt;there's virtually no content dedicated to how to solve the problem you
need solving&lt;/strong&gt;. This may seem like a ridiculous idea because there's no way
the authors will guess exactly what you need, but it's not that stupid either
because there are quite a few problems generic enough for almost everyone to
struggle with them, that could have a little lighter answering.&lt;/p&gt;
&lt;p&gt;Everyone ends up needing to layout things on a page, which means how to
place and position things on a page. But if you look for &lt;em&gt;positioining&lt;/em&gt; you end
up finding &lt;code&gt;static&lt;/code&gt;, &lt;code&gt;absolute&lt;/code&gt;, &lt;code&gt;relative&lt;/code&gt; and &lt;code&gt;fixed&lt;/code&gt;, coordinate systems,
and nothing of relevant for what you're trying to accomplish. Could you have guessed
that &lt;em&gt;floats&lt;/em&gt; were the right section to look for? And even if you could, how
would you then make sense of all the information? Would you, just by reading
&lt;a href="https://developer.mozilla.org/en/CSS/float"&gt;MDN page on floats&lt;/a&gt; be ready to
abuse this property and start splitting your layout into multiple columns
within a centered page?&lt;/p&gt;
&lt;p&gt;So what happens to everyone when they attempt at making their
header-columns-footer layout? They resort to Google or to &lt;code&gt;table&lt;/code&gt;s. If they
get into Google, then they'll reach the clever hacks of people who've been
fighting with the column problem for years, walking paths of such difficulty that
upon reaching the solution they've even called it the
&lt;a href="http://www.alistapart.com/articles/holygrail/"&gt;Holy Grail&lt;/a&gt;. By then it is
already too late: even it they're lucky enough to find a well written article
(such as &lt;em&gt;A List Apart&lt;/em&gt;'s), chances are they're already too tired to
understand it and just want to copy paste the code. If it works, then &lt;strong&gt;the
learned lesson will be that CSS is a kind of a dark magic which can be tamed
by copying and pasting random code from the internet.&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;Keeping the Ultimate Goals in mind&lt;/h1&gt;
&lt;p&gt;People don't usually wake up thinking &lt;em&gt;hey, today I really feel like learning
a lot more about floats!&lt;/em&gt; They have other things in their mind and that's what
we should aim for when doing presentations or writing content on the web, be
it for CSS or for anything else. People want to solve some problem they have
on the back of their heads. You don't want to know how the chemicals mix
themselves to produce the flavor. You just want to have that cake.&lt;/p&gt;
&lt;p&gt;As with everything in life, keeping the ultimate goal in mind will help you
getting into what really matters.
That's why the approach followed to create our little CSS presentation was:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Here's this oversimplified version of things and that's how we want you to see
    the world from now on (setting a common ground for concepts, terminology,
    ...)&lt;/li&gt;
&lt;li&gt;There's this thing you want to achieve (&lt;strong&gt;the goal&lt;/strong&gt;, real world problem, that could be
    as generic as &lt;em&gt;layout things in a webpage&lt;/em&gt; or as specific as &lt;em&gt;make those
    round corners work in IE&lt;/em&gt;)&lt;ul&gt;
&lt;li&gt;These are the tools you have to do it&lt;/li&gt;
&lt;li&gt;These are the problems we know you'll hit when using this approach&lt;/li&gt;
&lt;li&gt;This is how to overcome those problems&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;And now that you know how to do it by hand, here's some info on advanced
    techniques that could help you accelerate that process&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At the end of each topic, people would relate with the goal and with the
pains, and so they'd all be very happy to know about the tools and the
techniques that could make their life a lot easier. They didn't learn what's
the &lt;em&gt;normal flow&lt;/em&gt; or the &lt;em&gt;box formatting context&lt;/em&gt;, but they did learn how to
solve their biggest problems and how CSS wasn't all that magic. Perhaps if we
had more time we could, only after, explain all those technical details which
combined make up the magic.&lt;/p&gt;
&lt;p&gt;To be fair, our task was eased by the fact that we knew a lot about our
audience. We knew what they'd been struggling with, what they felt that they
lacked. We knew what their problems were, and what goals did they have in
mind. But again, I'm pretty sure that laying out things on a page is a goal
generic enough for everyone starting to learn CSS to be interested in that
problem, so there's no excuse for lack of better learning content on the
internet.&lt;/p&gt;
&lt;h1&gt;New Hopes&lt;/h1&gt;
&lt;p&gt;Better and better content on web technologies is appearing every day on the internet.
Initiatives into improving that content and the discoverability of good
content have been sprouting all around. I'm expecting
that content quality on all HTML, CSS and JavaScript will still mature a lot
making it a lot easier to learn these technologies.&lt;/p&gt;
&lt;p&gt;CSS3 has some features that may help tackle some of the problems that where
extremely difficult to solve with the primitives CSS2.1 offered, and that
ended up being the root cause of why solving layouts with CSS wasn't an easy
job. For example, if the &lt;em&gt;CSS Grid Layout&lt;/em&gt; module matures enough to become
the standard of making layouts in HTML, then learning the floating quirks
could be left as an exercise to support the legacy browsers of the future.&lt;/p&gt;</content><category term="misc"></category><category term="learning"></category></entry><entry><title>Beginnings</title><link href="https://miguelventura.pt/2012/beginnings.html" rel="alternate"></link><published>2012-07-07T00:00:00+01:00</published><updated>2012-07-07T00:00:00+01:00</updated><author><name>Miguel Ventura</name></author><id>tag:miguelventura.pt,2012-07-07:/2012/beginnings.html</id><summary type="html">&lt;p&gt;After taking the time to write some articles for the OutSystems' Community, I took some time to evaluate the experiment. The articles that I wrote were about topics I was very familiar with, but even though I forced myself into doing some research to ensure that the quality I was …&lt;/p&gt;</summary><content type="html">&lt;p&gt;After taking the time to write some articles for the OutSystems' Community, I took some time to evaluate the experiment. The articles that I wrote were about topics I was very familiar with, but even though I forced myself into doing some research to ensure that the quality I was producing was good enough for publishing. Producing quality content was very important because those articles, although written on spare time, could be regarded as products of my work, and I sure like doing good work.&lt;/p&gt;
&lt;p&gt;Taking the time to pick your ideas, research them (accepting both refutation and reinforcement) and, last but not least, organizing them in a writeable form, is &lt;em&gt;no news&lt;/em&gt;. You've done it in school, writing reports, and in your job, documenting some research. But in such situations we're so focused on the goal that we often don't think about how much did we actually learned, practiced and improved along the process.&lt;/p&gt;
&lt;p&gt;Organizing your knowledge in a way that it gets easily explainable to a broad audience just because you want to takes a lot of time and effort, but I strongly believe that it pays off. Explaining you something can make us both better, and for that reason, I'm starting this little blogging experiment, which I hope you enjoy.&lt;/p&gt;</content><category term="misc"></category></entry></feed>